架构那点事系列二 - 大话3D

       近几年,架构领域兴起了很多新型架构思想。DDD成为继OOD之后又一个被人津津乐道的设计风格。.这里结合自己工作实践,和大家分享一下自己的DDD实践观,首先向大家推荐一篇关于DDD的文章(http://msdn.microsoft.com/en-us/magazine/hh547108.aspx.看看微软的卓越工作,从DataTable到EntityObject. - Net 4.0来了,随之为我们带来了EntityFramework)。这里,我们抛开语言特性,从本质上分析一下DDD的诸多实践要点。

       首先,我们需要知道DDD和OOD有何不同?在此之前,我们先从模型上进行下对比。对象模型,我们都知道它封装了数据(属性)和行为(方法和事件)。而领域模型则是驻足在某领域内的对象模型。相应地,它的行为是用于表达业务规则和特定的业务逻辑。更重要的是,每个实体都可能处于某一状态,并且与一组动态的验证规则相绑定;领域模型表述的是领域中各个类及其之间的关系。类关系是多样的,比如组合、聚合、继承、实现等,而数据模型不是一对多,就是多对多。从领域驱动设计的角度看,数据库只不过是存储实体的一个外部机制,是属于技术层面的东西。数据模型主要用于描述领域模型对象的持久化方式,应该是先有领域模型,才有数据模型,领域模型需要通过某种映射而产生相应的数据模型(数据依赖于实体,是实体的状态,离开实体的数据是毫无意义)。

       ok,有了基本认识后,我们再来看一下领域驱动设计所倡导的分层。如图:

     架构那点事系列二 - 大话3D

     基础结构层:不解释了。注意,这部分不会涉及任何业务逻辑。传统的数据访问层,也被放在了该层当中,因为数据的读写是业务无关的;
  领域层:包含了领域对象(实体、值对象)、领域服务以及它们之间的关系。这部分内容的具体表现形式就是领域模型。领域驱动设计提倡富领域模型,即尽量将业务逻辑归属到领域对象上,实在无法归属的部分则以领域服务的形式进行定义。
  应用层:该层不包含任何领域逻辑,但它会对任务进行协调,并可以维护应用程序的状态.因此,它更注重流程性的东西。在某些领域驱动设计的实践中,也会将其称为“工作流层”。
  表现层:这个好理解,跟三层里的表现层意思差不多,但请注意,“Web服务”虽然是服务,但它是表现层的东西.
  从上图还可以看到,表现层与应用层之间是通过数据传输对象(DTO)进行交互的,数据传输对象是没有行为的对象,存在的目的只是为了对领域对象进行数据封装,实现层与层之间的数据传递。为何不能直接将领域对象用于数据传递?因为领域对象更注重领域,而DTO更注重数据。不仅如此,由于“富领域模型”的特点,这样做会直接将领域对象的行为暴露给表现层。

        通过上面的回忆,这里大致总结出了DDD实践中需要注意的要点,如下:

       (1).构建领域对象

       DDD喜欢使用rich poco/pojo。当然,它"富裕"的程度要远远大于之前我们所熟悉的ActionForm。除此之外,某些领域模型可以提供用于创建新实例的公共工厂方法(在高并发多构造参数模型下,我会选择使用Builder模式构建)。如果模型类通常是独立的并且实际上不是层次结构的一部分,或者用于创建该类的步骤只是与客户端相关,则可以使用普通的构造函数。但是,在使用聚合根这样的复杂对象时,还需要我们实例化其它抽象级别DO。 DDD 引入了工厂对象的方式,这种方式可将客户端的需求与内部的对象及其关系和规则分离开来。最后,工厂还需要验证输入数据。为此,可使用前提条件(如Assert,Validate,Precondition等开源组件)来保证代码的清晰和高可读性。还可以使用后置条件来确保返回的实例处于有效状态,在.net中,常见的有Code Contracts,Data Annotations与VAB(Enterprise Library Validation Application Block)企业库验证应用程序块 等out-of-box technologies。Java中可以考虑比较流行的DBC框架。

       (2).识别聚合根.

       聚合根是一个通过组合其它实体而得到的实体。聚合根中的对象与外部没有直接的关联,也就是不存在这样的用例—不经过根对象而直接使用这些对象。换句话来说,聚合根负责维护处于有效状态的子对象并持久化这些对象。更通俗的讲,一个聚合是由一些列相联的Entity和Value Object组成(满足某些不变性规则),一个聚合有一个聚合根,聚合根是Entity,整个聚合被看成是一个数据修改的单元,也就是说整个聚合内的所有对象要么同时被保存,要么都不能保存,否则无法确保聚合内的对象的数据一致性。聚合内的所有实体和值对象应该总是一起被取出来一起被保存,因为一个聚合是一个数据持久化的单元。同时,需要注意两点:a.聚合不要设计的过大,过大的聚合很难确保不变性,从而很难确保数据的强一致性;b.聚合与聚合之间不要通过引用的方式来关联,而应该通过ID关联,这样具有更好的性能和可伸缩性。

      (3).仓储&领域服务

      仓储应理解为一个在内存中维护一系列聚合根的集合。仓储提供的接口应该总是接受聚合根或返回聚合根,不能返回聚合内的其他Entity或Value Object。不要把仓储理解为DAO,仓储属于领域模型的一部分,代表了领域模型向外提供接口的一部分,而DAO是表示数据库向上层提供的接口表示。仓储的目的也不是为了支持界面查询,不要为仓储设计一些是为了提供显示数据的接口,仓储提供的所有接口应该仅为领域模型使用。基本的仓储接口只需要三个:Add,Remove,GetById,其他的扩展接口可以根据业务需要扩展接口声明。

      领域服务表示领域模型中的一些业务操作,这些操作通常由多个聚合根或仓储或其他领域服务相互协作完成。领域服务表示领域模型中的一些业务操作,这些操作通常由多个聚合根或仓储或其他领域服务相互协作完成,那么需要为这些操作建立领域服务。首先根据各个聚合的ID获取到操作的相关聚合根,然后调用聚合根完成整个业务操作;比如资金转帐,这是经典的领域服务的例子;再比如在调用某个聚合根做一个数据更新之前需要先判断一些业务规则,但是这些判断规则不能在该聚合根内做,因为这样做可能会导致聚合根依赖于外部的领域服务或仓储,此时,应该交给领域服务来完成规则校验和聚合根数据更新的整个过程。

参考资料:

1.http://www.infoq.com/cn/articles/ddd-in-practice

2.http://msdn.microsoft.com/en-us/magazine/hh547108.aspx

3.http://msdn.microsoft.com/en-us/library/aa697427(v=VS.80).aspx

4.http://www.cnblogs.com/daxnet/archive/2010/07/07/1772581.html

5.http://www.methodsandtools.com/archive/archive.php?id=97p2

6.http://incubator.apache.org/isis/index.html

7.http://www.slideshare.net/jboner/scalability-availability-stability-patterns

上一篇:工业级应用中关于异常封装的一些感悟


下一篇:照镜子 - 内功修炼