前面构造了一个后台管理的界面布局,下面开始讲解整个项目的分层设计。
关于分层,网上已经存在相当多的讨论了,这也是一个程序员初学架构设计最先会碰到的问题。
看完OSharp的分层设计,我想,你应该多少能得到一些启示。
注:OSharp 开发框架的前身是《MVC实体架构设计》系列中讲到的那个架构示例,所以有很多知识点那个系列讲到了,就不会在这个系列再重复了,如果有什么觉得不太明白的可以参考《MVC实体架构设计》系列。
〇、前言
一、目录
二、框架整体分层设计
三、分层解耦与依赖注入
四、开源说明
系列导航
关于该不该分层以及分层的好处,网上已经有相当多的讨论了,这里就不再赘述。这里引述一篇比较有代表性的文章《分层式结构的优缺点》以供参考,全文如下:
分层式结构究竟其优势何在?Martin Fowler在《Patterns of Enterprise Application Architecture》一书中给出了答案:
- 开发人员可以只关注整个结构中的其中某一层;
- 可以很容易的用新的实现来替换原有层次的实现;
- 可以降低层与层之间的依赖;
- 有利于标准化;
- 利于各层逻辑的复用。
概括来说,分层式设计可以达至如下目的:分散关注、松散耦合、逻辑复用、标准定义。
一个好的分层式结构,可以使得开发人员的分工更加明确。一旦定义好各层次之间的接口,负责不同逻辑设计的开发人员就可以分散关注,齐头并进。例如UI人员只需考虑用户界面的体验与操作,领域的设计人员可以仅关注业务逻辑的设计,而数据库设计人员也不必为繁琐的用户交互而头疼了。每个开发人员的任务得到了确认,开发进度就可以迅速的提高。
松散耦合的好处是显而易见的。如果一个系统没有分层,那么各自的逻辑都紧紧纠缠在一起,彼此间相互依赖,谁都是不可替换的。一旦发生改变,则牵一发而动全身,对项目的影响极为严重。降低层与层间的依赖性,既可以良好地保证未来的可扩展,在复用性上也是优势明显。每个功能模块一旦定义好统一的接口,就可以被各个模块所调用,而不用为相同的功能进行重复地开发。
进行好的分层式结构设计,标准也是必不可少的。只有在一定程度的标准化基础上,这个系统才是可扩展的,可替换的。而层与层之间的通信也必然保证了接口的标准化。
分层式结构同样也具有一些缺陷:
- 降低了系统的性能。这是不言而喻的。如果不采用分层式结构,很多业务可以直接造访数据库,以此获取相应的数据,如今却必须通过中间层来完成。
- 有时会导致级联的修改。这种修改尤其体现在自上而下的方向。如果在表示层中需要增加一个功能,为保证其设计符合分层式结构,可能需要在相应的业务逻辑层和数据访问层中都增加相应的代码。
谈到分层之间的那些接口,大概马上就会有很多同学来吐槽了,不愿意用接口,原因无非如下:
以上是通常拒绝使用接口的理由。
但是,从我个人的看法,从接口使用的利弊来说,利是远远大于弊的:
补充:关于接口是否有必要的讨论,iteye.com上有一个帖子讨论得非常热烈,强烈推荐看一下:
主题:在项目架构中如何进行分层才是最合理的?
OSharp开发框架约定的分层方案,依然是传统的三层(数据层 - 业务层 - 展现层)分层方式,但也有自己的特点:
如上图所示,OSharp的数据层的作用是向业务层提供数据实体的数据库持久化操作,向业务层与展现层提供进行数据查询的查询数据集。对外API很简单,主要是 IUnitOfWork 与 IRepository<TEntity> 两个接口,同时定义了支持泛型主键类型的实体模型基类 EntityBase,数据传输对象接口 IAddDto 与 IEditDto。
OSharp的业务层以模块为划分,一个模块是业务内聚的一个或多个实体组成的操作单元,由三个部分组成:
业务层有如下特点:
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,而不是到处都存在着注入的相关代码,大大降低了系统的复杂度。
OSharp项目已在github.com上开源,地址为:https://github.com/i66soft/osharp,欢迎阅读代码,欢迎 Fork,如果您认同 OSharp 项目的思想,欢迎参与 OSharp 项目的开发。
在Visual Studio 2013中,可直接获取 OSharp 的最新源代码,获取方式如下,地址为:https://github.com/i66soft/osharp.git
OSharp的相关类库已经发布到nuget上,欢迎试用,直接在nuget上搜索 “osharp” 关键字即可找到