本文主要介绍了DDD的一些基础概念:
- 领域模型:领域实体、领域服务以及值对象;建模一定要从真实的领域业务出发,多与领域专家进行沟通来完善模型。
- 聚合与聚合根:它的主要作用是用来确保各种关系下的实体的数据一致性;但是确认聚合根这个过程,实际上也是对业务的梳理过程。
- 架构分层: 每一层都职责清楚;依懒于接口来降低耦合。
- 封装和测试: 所有的业务都放到领域层,同时对领域层进行单元测试来确保最核心的逻辑不会遭到破坏。
为什么要分层?三层架构是不是就是表现层、业务逻辑层和数据访问层?”
分层嘛,不就是将具有不同职责的组件分离开来,组成一套层内部高聚合,层与层之间低耦合的软件系统吗?不错!这是分层的目标。但是,我们应该如何分层呢?
领域驱动设计的讨论同样也是建立在层模式的基础上的,但与传统的分层架构相比,它更注重领域架构和技术架构的分离。
DDD中的分层架构很好的应用了关注点分离原则Separation of Concerns(SOC),每一层做好自己的事情,减少交叉。
表现层
表现层提供用来完成任务的用户界面,如webform wpf asp.net mvc 以及winform等,
一般而言,我们把表现层显示的任何数据称为视图模型,把任何从屏幕离开触发一个后台操作的数据称为输入模型,大多数时候这两个模型是相同的。
就分层应用程序而言,MVC,MVP,MVVM都是表现层的模式,
应用程序层
应用程序层是一个附加层,介于领域层和UI之间,是你编排用例实现的地方,其中包含的方法几乎一一对应于表现层的用例,
一般情况下,应用程序层和表现层一一对应,因为不同的表现层可能会有不同的用例,
应用程序层引用领域层和基础设施层,对业务逻辑一无所知,不包含任何与业务相关的状态信息,
应用程序层有时候需要调用外部服务,比如WCF或者WebApi,又或者是第三方的服务,这种情况一般是把对外部服务的调用封装成适配器,放在基础设置层,
这样就把对外部服务的调用转化成了对基础设施层的调用。
领域层
领域层包含了几乎所有的业务逻辑,由一组领域模型和一组服务构成,
领域模型:
包含数据和行为,与之相对的一个是贫血模型,什么是贫血模型,如果只是类缺少方法,对象模型并不算是贫血,如果实体的逻辑
放在了实体类的外面,那才是真的贫血,毕竟如果把逻辑放到了实体类的外面,他实际上是违反了说,别问原则
领域服务:
它包含了一些逻辑上有关系并且操作多个实体的行为,
基础设施层
基础设施层是与具体技术有关的东西,比如EF,安全,日志,IOC,跟踪,缓存等,
除了DDD,现在还流行另外一个词汇TDD。但是不知道大家有没有注意到DDD(Domain-Driven Design)中的D代表着设计,而TDD(Test-Driven Development)中的D代表着开发,你有没有曾几何时把领域驱动设计说成领域驱动开发呢?当然我们确实是可以根据领域驱动来开发,但是DDD被设计出来的完美初衷却是设计。TDD强调的已经是开发了,要求开发人员先写单元测试然后再通过不断的迭代重构让单元测试通过,以此来实现功能。这样做的好处是强迫让开发人员清楚正确的理解需求,要知道这年头没有正确理解需求就开始写代码的程序员大有人在,并且我不认为需求就是业务,需求已经是将本来的业务理解之后,转化为了通过计算机可以实现的一些功能定义,通常是业务分析师或者项目经理会去完成这个工作。而DDD中的D(领域)更像是本来的业务,所以在领域驱动设计的时候,开发人员或者架构师直接与领域专家(或者说客户)进行沟通来建模,这些业务模型也是以后开发人员进行设计和实现的依据。
领域模型被当作开发人员之间,开发人员与领域专家之间沟通的桥梁,这样可以闭免开发人员用错误的方式去实现功能。实际上很多优秀的开发人员,都会很自然的将现实世界中的问题进行抽象,然后用计算机的语言表示出来,我们称之为面向对象。但是由于缺少亲临其境的体验,往往会离真实的业务模型有一些距离。
我们举一个例子来说明一下这个问题,假如我们要开发一个电子商务的网站,这个需求已经非常清楚了,现在那么多的电子商务网站直接照抄一个就可以了。现在我们来做一个下单的功能,来看看怎么去实现 。
作为一个高级程序员,我们得用面向对象的方式去开发,先建类。于是我们有了用户,订单,订单项的类,用户创建订单然后往订单里面添加商品,添加订单项的时候为了方便,我们只需要传入产品ID和数量就可以了,于是Order类有一个AddItem的方法。
总结:个人感觉没有必要太强调Repository的概念,从领域实体的生命周期(创建-持久化到数据库-销毁-从数据库重建)你会发现其实这个过程很普遍,并不是只有DDD才有的。所以我认为Repository主要是将数据访问功能给隔离开,避免领域实体对基础设施层的依懒。那它和三层有什么区别? BLL 引用DAL不也是依懒于接口么?给我的感觉是,DDD的领域实体持久化这一块就是三层里面的思路。这可能是在学习DDD初期的想法,因为真实的大型项目中是不会直接把领域实体给持久化的,那个叫DTO,于是Repository<>里面放的就不是我们的领域实体了,而是将领域实体转换成对应的DTO。
是否一定要使用DTO呢?领域实体和DTO互相转换,最后到了表现层DTO还要和ViewModel转换,会不会带来复杂性和性能上的损失?Repository和EF还有Unit Of Work怎么来协调?抱怨写单元测试么?怎么样让写单元测试不变成只是走过场而已? 这些问题留给我们后面再解决吧。