【开源】OSharp框架解说系列(4):架构分层及IoC

〇、前言

  前面构造了一个后台管理的界面布局,下面开始讲解整个项目的分层设计。

  关于分层,网上已经存在相当多的讨论了,这也是一个程序员初学架构设计最先会碰到的问题。

  • 该不该分层?
  • 怎样分层?
  • 层与层之间是否需要解耦?是否需要设计接口?接口是否是多余的?

  看完OSharp的分层设计,我想,你应该多少能得到一些启示。

注:OSharp 开发框架的前身是《MVC实体架构设计》系列中讲到的那个架构示例,所以有很多知识点那个系列讲到了,就不会在这个系列再重复了,如果有什么觉得不太明白的可以参考《MVC实体架构设计》系列。

一、目录

〇、前言

一、目录

二、框架整体分层设计

三、分层解耦与依赖注入

四、开源说明

系列导航

二、框架整体分层设计

该不该分层以及分层的好处

  关于该不该分层以及分层的好处,网上已经有相当多的讨论了,这里就不再赘述。这里引述一篇比较有代表性的文章《分层式结构的优缺点》以供参考,全文如下:

分层式结构究竟其优势何在?Martin Fowler在《Patterns of Enterprise Application Architecture》一书中给出了答案:

  1. 开发人员可以只关注整个结构中的其中某一层;
  2. 可以很容易的用新的实现来替换原有层次的实现;
  3. 可以降低层与层之间的依赖;
  4. 有利于标准化;
  5. 利于各层逻辑的复用。

概括来说,分层式设计可以达至如下目的:分散关注、松散耦合、逻辑复用、标准定义。

一个好的分层式结构,可以使得开发人员的分工更加明确。一旦定义好各层次之间的接口,负责不同逻辑设计的开发人员就可以分散关注,齐头并进。例如UI人员只需考虑用户界面的体验与操作,领域的设计人员可以仅关注业务逻辑的设计,而数据库设计人员也不必为繁琐的用户交互而头疼了。每个开发人员的任务得到了确认,开发进度就可以迅速的提高。

松散耦合的好处是显而易见的。如果一个系统没有分层,那么各自的逻辑都紧紧纠缠在一起,彼此间相互依赖,谁都是不可替换的。一旦发生改变,则牵一发而动全身,对项目的影响极为严重。降低层与层间的依赖性,既可以良好地保证未来的可扩展,在复用性上也是优势明显。每个功能模块一旦定义好统一的接口,就可以被各个模块所调用,而不用为相同的功能进行重复地开发。

进行好的分层式结构设计,标准也是必不可少的。只有在一定程度的标准化基础上,这个系统才是可扩展的,可替换的。而层与层之间的通信也必然保证了接口的标准化。

分层式结构同样也具有一些缺陷:

  1. 降低了系统的性能。这是不言而喻的。如果不采用分层式结构,很多业务可以直接造访数据库,以此获取相应的数据,如今却必须通过中间层来完成。
  2. 有时会导致级联的修改。这种修改尤其体现在自上而下的方向。如果在表示层中需要增加一个功能,为保证其设计符合分层式结构,可能需要在相应的业务逻辑层和数据访问层中都增加相应的代码。

分层与面向接口编程

  谈到分层之间的那些接口,大概马上就会有很多同学来吐槽了,不愿意用接口,原因无非如下:

  • 定义接口导致项目分层过细,增加一个模块,要增加很多的类文件及代码量,增加开发人员的工作量
  • 单一实现的接口,实际用处不大,但阅读代码时,接口导致“转到定义”只能转到接口上,实现类还得手动去找。

  以上是通常拒绝使用接口的理由。

  但是,从我个人的看法,从接口使用的利弊来说,利是远远大于弊的:

  • 使用接口的好处之一是解耦各个层次的依赖,让我们能轻松将“具体实现”替换掉(对于大多数项目而言,这个好处并不明显)
  • 接口是项目的骨架,是架构的整体体现,架构师在构建项目的时候,直接定义出各个层次的接口,就把整个项目的主体骨架设计出来了,随后开发人员只需要以“填空”的方式去逐个实现各种方法,能极大的提高开发效率,并保证项目的完整性与合理性
  • 在进行层次的单元测试的时候,由于各个层次是以接口隔离的,上层不依赖于下层的具体实现,测试上层的时候,只要使用Moq、Fakes等Mock框架对下层接口进行模拟,即可非常轻松的测试上层的业务实现。如果上层是与下层紧耦合的,那上层的代码几乎是不可测试的。(如果不了解单元测试,可以看看我写的两篇文章:基于VS2012 Fakes框架的TDD实战

  补充:关于接口是否有必要的讨论,iteye.com上有一个帖子讨论得非常热烈,强烈推荐看一下:

主题:在项目架构中如何进行分层才是最合理的?

OSharp的分层设计

  OSharp开发框架约定的分层方案,依然是传统的三层(数据层 - 业务层 - 展现层)分层方式,但也有自己的特点:

  • 使用了三层分层方式,但并不严格遵守传统三层的“各层职责极其分明”的约定
  • 参考了“领域驱动设计”的一些思想,但为了保证性能,并不使用 DDD 的聚合模型

数据层:

  

  如上图所示,OSharp的数据层的作用是向业务层提供数据实体的数据库持久化操作,向业务层与展现层提供进行数据查询的查询数据集。对外API很简单,主要是 IUnitOfWork 与 IRepository<TEntity> 两个接口,同时定义了支持泛型主键类型的实体模型基类 EntityBase,数据传输对象接口 IAddDto 与 IEditDto。  

业务层:

  

  OSharp的业务层以模块为划分,一个模块是业务内聚的一个或多个实体组成的操作单元,由三个部分组成:

  1. 数据实体模型(Model)的定义,数据模型是业务层与数据层之间的数据交互对象,也是数据层进行持久化的对象,为业务数据的最终承载。
  2. 数据传输对象(Dto)的定义,数据传输对象是展现层向业务层的交互对象,Dto在需要的时候也可以作为视图模型展现到页面上。
  3. 业务处理模块的定义,业务处理模块包括业务契约(IXXXContract)的定义与业务逻辑(IXXXService)的实现。业务处理模块从展现层接受 Dto 形式的数据,经过业务处理后,转化为Model提交给数据层进行持久化操作。业务处理模块同时还向展现层提供相应实体的 IQueryable<TEntity> 类型的查询数据集,作为展现层进行数据查询的数据源。原则上业务处理模块只处理数据的插入、更新、删除操作,不对展现层提供数据查询服务。

  业务层有如下特点:

  1. 业务层接受来自展现层的参数为 Dto 或简单类型参数
  2. 业务层向展现层返回业务操作结果为 OperationResult 类型
  3. 业务层只处理 插入、修改、删除等非查询业务,原则上不提供展现层数据的查询业务
  4. 业务层向展现层开放相应实体的 IQueryable<T> 类型的查询数据集,作为展现层的查询数据源
  5. Dto 与 Model 的转换工作由 Automapper 来完成
  6. Model 的构建工作由业务层来完成,展现层向业务层提供必要的 Dto,不参与 Model 的构建工作

展现层:

  

  MVC 的展现层包含 控制器(Controller)与 视图(View)两个部分。控制器负责使用业务层开放的查询数据集来进行数据查询操作,再把查询结果提交给 视图 进行展示,还负责接收来自 View 提交的数据,转换为 Dto 提交给业务层进行处理,并接收并解析业务层反馈的业务处理结果(OperationResult),交给 视图 展现给用户。视图 接收并解析 控制器 传递过来的数据,解析成 HTML 页面发送到浏览器进行展示,并向 控制器 提交用户的请求数据。

在本篇,只是对分层做一个简要的讲解,从整体上认识 OSharp 的分层架构,在后面进入实际应用的时候,还要对各个层进行更详细的分析。

三、分层解耦与依赖注入

  前面已经分析了解耦的必要性,那么,OSharp 开发框架中的解耦,是怎样实现的呢?OSharp 中的解耦工作,主要是通过 Autofac 这个 IoC 组件来完成的。Autofac 是一个轻量级的,对系统污染与侵入性非常小的 IoC 组件,并且对 Webform、MVC、WebApi、SignalR等主流 ASP.NET Web 技术都有非常好的支持,是个相当不错的 IoC 组件。

  为了统一管理 IoC 相关的代码,并避免在 OSharp 底层类库中到处引用 Autofac 这个第三方组件,OSharp 中定义了一个专门用于管理需要依赖注入的接口与实现类的空接口 IDependency:

1  /// <summary>
2  /// 依赖注入接口,表示该接口的实现类将自动注册到IoC容器中
3  /// </summary>
4  public interface IDependency
5  { }

  这个接口没有任何方法,不会对系统的业务逻辑造成污染,所有需要进行依赖注入的接口,都要继承这个空接口,例如:

  业务单元操作接口:

1 /// <summary>
2 /// 业务单元操作接口
3 /// </summary>
4 public interface IUnitOfWork : IDependency
5 {
6     ...
7 }

  实体仓储操作接口:

1 /// <summary>
2 /// 实体仓储模型的数据标准操作
3 /// </summary>
4 /// <typeparam name="TEntity">实体类型</typeparam>
5 /// <typeparam name="TKey">主键类型</typeparam>
6 public interface IRepository<TEntity, TKey> : IDependency where TEntity : EntityBase<TKey>
7 {
8     ...
9 }

  账户模块业务契约:

1 /// <summary>
2 /// 业务契约——账户模块
3 /// </summary>
4 public interface IIdentityContract : IDependency
5 {
6     ...
7 }

  在需要引用 注入对象 的地方,统一使用“构造函数注入”的方式来进行注入。

  实体仓储实现类:

 1 /// <summary>
 2 /// EntityFramework的仓储实现
 3 /// </summary>
 4 /// <typeparam name="TEntity">实体类型</typeparam>
 5 /// <typeparam name="TKey">主键类型</typeparam>
 6 public class Repository<TEntity, TKey> : IRepository<TEntity, TKey> where TEntity : EntityBase<TKey>
 7 {
 8     private readonly IUnitOfWork _unitOfWork;
 9
10     public Repository(IUnitOfWork unitOfWork)
11     {
12         _unitOfWork = unitOfWork;
13     }
14
15     ...
16
17 }

  账户模块业务实现类:

 1 /// <summary>
 2 /// 业务实现——账户模块
 3 /// </summary>
 4 public partial class IdentityService : ServiceBase, IIdentityContract
 5 {
 6     private readonly IRepository<User, int> _userRepository;
 7     private readonly IRepository<Role, int> _roleRepository;
 8     private readonly IRepository<Organization, int> _organizationRepository;
 9
10     /// <summary>
11     /// 初始化一个<see cref="IdentityService"/>类型的新实例
12     /// </summary>
13     public IdentityService(IRepository<User, int> userRepository,
14         IRepository<Role, int> roleRepository,
15         IRepository<Organization, int> organizationRepository)
16         : base(userRepository.UnitOfWork)
17     {
18         _userRepository = userRepository;
19         _roleRepository = roleRepository;
20         _organizationRepository = organizationRepository;
21     }
22 }

  Autofac 是支持批量子类注册的,有了 IDependency 这个基接口,我们只需要 Global 中很简单的几行代码,就可以完成整个系统的依赖注入匹配:

 1 ContainerBuilder builder = new ContainerBuilder();
 2 builder.RegisterGeneric(typeof(Repository<,>)).As(typeof(IRepository<,>));
 3 Type baseType = typeof(IDependency);
 4
 5 // 获取所有相关类库的程序集
 6 Assembly[] assemblies = ...
 7
 8 builder.RegisterAssemblyTypes(assemblies)
 9     .Where(type => baseType.IsAssignableFrom(type) && !type.IsAbstract)
10     .AsImplementedInterfaces().InstancePerLifetimeScope();//InstancePerLifetimeScope 保证对象生命周期基于请求
11 IContainer container = builder.Build();
12 DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

  如此,只有站点主类库需要引用 Autofac,而不是到处都存在着注入的相关代码,大大降低了系统的复杂度。

四、开源说明

 (一)github.com

  OSharp项目已在github.com上开源,地址为:https://github.com/i66soft/osharp,欢迎阅读代码,欢迎 Fork,如果您认同 OSharp 项目的思想,欢迎参与 OSharp 项目的开发。

  在Visual Studio 2013中,可直接获取 OSharp 的最新源代码,获取方式如下,地址为:https://github.com/i66soft/osharp.git

  

 (二)nuget

  OSharp的相关类库已经发布到nuget上,欢迎试用,直接在nuget上搜索 “osharp” 关键字即可找到
  

系列导航

  1. 【开源】OSharp框架解说系列(1):总体设计
  2. 【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构
  3. 【开源】OSharp框架解说系列(2.2):EasyUI复杂布局及数据操作
  4. 【开源】OSharp框架解说系列(3):扩展方法
  5. 【开源】OSharp框架解说系列(4):架构分层及IoC
  6. 【开源】OSharp框架解说系列(5.1):数据层设计
  7. 【开源】OSharp框架解说系列(5.2):EntityFramework的封装
时间: 2024-09-30 22:55:38

【开源】OSharp框架解说系列(4):架构分层及IoC的相关文章

【开源】OSharp框架解说系列(1):总体设计

〇.前言 哈,距离前一个系列<MVC实用构架设计>的烂尾篇(2013年9月1日)已经跨了两个年头了,今天是2015年1月9日,日期已经相映,让我们开启新的航程吧. 前一个系列讲的主要是我对架构设计的理解以及怎样用好EntityFramework的一些想法,在技术细节上并没有太多的考究.不幸的是,不少同学把这个架构当作框架来用了,里边留的很多坑,坑苦了很多人,真是误人子弟,深表愧疚.于是重新整理代码,整理思路,鼓捣出了这个我们将要详解的开源框架:OSharp.这次,我们真的深入地说框架了,而不是

【开源】OSharp框架解说系列(6.1):日志系统设计

〇.前言 日志记录对于一个系统而言,重要性不言而喻.日志记录功能在系统开发阶段,往往容易被忽略.因为开发阶段,程序可以调试,可以反复的运行以查找问题.但在系统进入正常的运行维护阶段,特别是在进行审计统计的时候,追踪问题的时候,在追溯责任的时候,在系统出错的时候等等场景中,日志记录才会显示出它不可替代的作用.记录的日志,平时看似累赘,只有在需要的时候,才会拍大腿后悔当初为什么不把日志记录得详细些. 日志系统,是一个非常基础的系统,但由于需求的复杂性,各个场景需要的日志分类,来源,输出方式各有不同,

【开源】OSharp框架解说系列(5.2):EntityFramework数据层实现

〇.前言 上篇 的数据层设计中,我们主要设计了数据对对外开放的 实体基类EntityBase<TKey>,单元操作接口IUnitOfWork 和 数据仓储接口IRepository<TEntity, TKey>,下面我们来解说怎样来使用 EntityFramework 对这些数据访问需求进行实现.EntityFramework 的实现中,我们不仅要实现以上设计的两个接口,还要做以下几件事: 设计一个与 业务实体解耦的 EntityFramework数据上下文类 设计 实体加载方案,

【开源】OSharp框架解说系列(3):扩展方法

〇.前言 扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型.重新编译或以其他方式修改原始类型. 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用. 对于用 C# 和 Visual Basic 编写的客户端代码,调用扩展方法与调用在类型中实际定义的方法之间没有明显的差异. 最常见的扩展方法是 LINQ 标准查询运算符,它将查询功能添加到现有的 System.Collections.IEnumerable 和 System.Collections.Generic.

【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构

〇.前言 要了解一个东西长什么样,至少得让我们能看到,才能提出针对性的见解.所以,为了言之有物,而不是凭空漫谈,我们先从UI说起,后台管理页面的UI我们将使用应用比较普遍的easyui框架. 以前在用easyui的时候,每个页面都得从0做起,或者不厌其烦地由以前的页面通过“复制-粘贴”的方式来修改,久页久之,就会造成页面庞大且难以维护.其实,前端的html,javascript代码与后端的代码是一样的,通过一定的组织,把重复的代码抽离出来,同样也通过达到很好的复用率.而MVC的天生的Layout

【开源】OSharp框架解说系列(2.2):EasyUI复杂布局及数据操作

一.目录 一.目录 二.EasyUI复杂布局 三.EasyUI动态工具栏 四.EasyUI增删改操作 五.开源说明 系列导航 二.EasyUI复杂布局 接上篇,前面我们已经定义了一个 datagrid父视图 _DataGridLayout.cshtml,实现一个表格是相当的容易.但是,实际业务中,并非所有的数据列表并非只是单一的datagrid列表,还可能需要把datagrid与其他组件配合使用,比如角色信息是来源于各个组织机构的,就需要增加一个组织机构的分类,来更好的管理各种角色.最终效果图如

OSharp3.0框架解说系列:新版本说明及新功能规划预览

前言 时间过得真快,小半年又过去了. OSharp在github.com开源已经半年了,半年时间里,我们发现开源并没有给OSharp带来什么发展,关注的人不多,提交Bug的人更少,至于愿意参与到项目中来,给OSharp提交代码的人,0. 大环境如此,我也没什么可说的. 一个人的开源,开的不是源,是寂寞. 为了OSharp项目能继续发展下去,也为了团队的积极性(大家都懂的,如果只有你一个人在贡献,别人都只索取,你的热情坚持不了多久的),我们做了一个决定…… OSharp3.0不再开源 从OShar

OSharp3.0框架解说系列(6.2):操作日志与数据日志

前言 在<[开源]OSharp框架解说系列(6.1):日志系统设计>中,我们已经设计并实现了一个可扩展的日志系统,只要定义好输出端的Adapter,就可以以任意形式输出日志信息. 在日志开发中,有些日志记录需求是常规需要的,比如操作日志,数据变更日志,系统异常日志等,我们希望把这些常规需求都集成到OSharp框架当中.有了内置的支持,在做开发的时候,只需要很简单的配置,就可以实现相关需求. 关于三类日志,这里先简要描述一下: 操作日志:粗略描述系统用户(如管理员.业务人员.会员等)对系统的业务

【开源】OSharp框架学习系列(1):总体设计及系列导航

OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依赖注入.ORM.对象映射.日志.缓存等等功能,都只定义了一套最基础最通用的抽象封装,提供了一套统一的API.约定与规则,并定义了部分执行流程,主要是让项目在一定的规范下进行开发.所有的功能实现端,都是通过现有的成熟的第三方组件来实现的,除了EntityFramework之外,所有的第三方实现都可以轻