领域驱动设计心得

实现领域驱动设计心得
1. 分层
2. 领域
3. 领域服务
4. 聚合根
5. 实体
6. 值对象html

https://www.zhihu.com/question/25089273
http://www.cnblogs.com/netfocus/archive/2012/02/12/2347938.html
http://blog.jobbole.com/99298/程序员

分层

领域驱动设计分层
基础结构层,领域层,应用层和表现层。
表现层(UI层):负责界面展现
应用层:负责业务流程。
领域层:负责领域逻辑。
基建层:负责提供基建。web

分类的依据:越往上,预期变更越频繁;越往下,预期变更越少。
对程序员来讲,表现层和基建层很容易分清,一个只管展现,一个只管提供持续储存,网络传输等基本设施,都没有业务等参与,容易混淆的是应用层和领域层,在这两层中存在的,就是应用模型和领域模型。数据库

模型属于哪一层,有个粗略的判断方式,若是一个实体(entity)和针对实体的增删改查,就属于领域层;若是是一个场景,好比出如今UI菜单上的选项,就属于应用层。
前者是针对实体的操做,每个实体都只有增删改查这样的操做,与之相反的是,要完整实现“购买商品”这个场景,也许须要检查库存,建立订单,建立交易等多个操做。网络

聚合根
具备全局惟一的标识,架构

聚合根,实体,值对象的区别
从标识的角度:
聚合根具备全局的惟一标识,而实体只有在聚合内部有惟一的本地标识,值对象没有惟一标识。
从是否只读的角度:
聚合根除了惟一标识外,其余全部状态信息都理论上可变,实体是可变的,值对象是只读的。
从生命周期的角度:
聚合根有独立的生命周期,实体的生命周期从属于其所属的聚合,实体彻底由其所属的聚合根管理维护。并发

聚合根,实体,值对象对象之间如何创建关联
聚合根到聚合根:经过ID关联
聚合根到其内部的实体,直接对象引用。
聚合根到值对象,直接对象引用。
实体对其余对象的引用规则:1)能引用其所属聚合的聚合根,实体,值对象;
2)能引用外部聚合根,但推荐以ID的方式关联,另外也能够关联某个外部聚合内的实体,但必须是ID关联,不然就出现同一个实体的引用被两个聚合根持有,这是不容许的,一个实体的引用只能被其所属的聚合根持有。框架

聚合

(以及聚合根):聚合表示一组领域对象(包括实体和值对象),用来表述一个完整的领域概念。而每一个聚合都有一个根实体,这个根实体又叫作聚合根。举个简单的例子,一个电脑包含硬盘、CPU、内存条等,这一个组合就是一个聚合,而电脑就是这个组合的聚合根。博主以为关于聚合的划分学问仍是挺大的,须要在实践中慢慢积累。同一个实体,在不一样的聚合中,它多是聚合根,也可能不是,须要根据实际的业务决定。聚合根是聚合所表述的领域概念的主体,外部对象须要访问聚合内的实体时,只能经过聚合根进行访问,而不能直接访问。dom

如何识别聚合与聚合根

含义:一个界限上下文可能包含多个聚合,每一个聚合都有一个根实体,叫作聚合根。一个界限上下文表明着某个独立的业务场景,这个业务场景操做的惟一对象老是该上下文边界的聚合根。svg

如何识别实体和值对象

实体是包含实体数据和行为的结合体。实体会被传递到聚合外部,可是由于实体在任何聚合外部都不能被修改,不然聚合的不变性没法确保,因此不能被修改,必须制度。
值对象和实体的区别:(1)实体拥有惟一标识,而值对象没有;(2)实体容许变化,而值对象不容许变化;(3)判断两个实体相等的方法是判断实体的标识相等,而判断两个值对象相等的标准是值对象内部全部属性值相等;

例子:
订单模型
order(订单)至少对应于一个orderLineItem(订单条),对orderLineItem为主体,彻底是在面对orderLineItem在作工做,好比向Order中增长明细,修改OrderLineItem的某个明细对应的商品的购买数量,从Order中移除某个明细,等等相似操做,咱们历来不会从OrderLineItem为出发点去执行一些业务操做;另外,从生命周期去理解,那么OrderLineItem离开Order没有任何存在的意义,也就是说OrderLineItem的生命周期是从Order的。因此,咱们能够很确信的回答,OrderLineItem是一个实体。

帖子模型
帖子和回复之间的关系比较弱,不像订单和订单项同样是一个内聚的关系;帖子和回复以及回复的回复之间就像是一个链,帖子是链中的第一个结点,回复的回复是第三个结点。帖子若是被删除了,就是这个链的头被移除了,但这个链的后面的结点仍是在那里的。
实际上你没理由必定要把帖子和回复内聚在一块儿成为一个总体的,这样作会带来问题的,好比性能问题和并发问题,当我要发表回复时,每次都要取出帖子,而后往里面增长回复,而后保存整个帖子,在并发的状况下这是不行的。
若是帖子和回复在一个聚合内,聚合意味着“修改数据的一个最小的单元”,聚合内的全部对象都要当作是一个总体最小单元进行保存,这么要求是韵味聚合的意义是维护聚合内的不变性,数据一致性。仔细分析,你会发现帖子和回复之间没有数据一致性要求,因此你不须要设计在同一个聚合内。
从场景的角度,咱们有发表帖子,发表回复,这两个不一样的场景,发表帖子建立的是帖子,而发表回复建立的是回复,可是订单就不同了,咱们有建立订单,修改订单这两个场景,这两个场景都是围绕这订单这个聚合展开的。

识别顺序:先找出哪些实体多是聚合根,再逐个分析聚合根的边界,即该聚合根应该聚合哪些实体或者值对象;最后再划分界限上下文。

聚合边界肯定法则:根据不变性规则,不变性规则有两类:1)聚合边界必须具备哪些信息,若是没有这些信息就不能称为一个有效的聚合;2)聚合内的某些对象的状态必须知足某个业务规则。

领域服务和领域事件

领域服务的定义:当领域中某个重要过程或转换操做不属于实体或值对象的天然职责时,应该在模型中添加一个做为独立接口的操做,并将其申明为service。
领域服务层的做用:处理实体和实体之间的关系。DDD的设计原则之一是尽可能丰满领域模型,主张充血的领域模型。处理实体和实体之间的关系通常来讲领域模型,因此最好仍是放在领域层。

咱们举个例子:好比权限管理,若是须要一个功能,将指定的用户赋予制定的权限,好比接口这样定义:
void AssignPower(TB_USERS oUser,TB_ROLE oRole)
按照咱们前面聚合的划分,这个接口是应该放在用户聚合仍是角色聚合里面呢?很显然都不合适,这歌接口包含两个聚合的实体,因此像这种状况,能够考虑领域服务去解决。

领域事件

领域事件解耦代码。代码间解耦用事件,系统间解耦用MQ。
例子:订单系统和计费系统。大部分的业务流程都是由订单系统触发,而后计费系统作出相应的变动,最终,咱们决定使用领域事件来说订单系统和计费系统。经过orderEvent和BillEvent来将两个系统解耦开,而后将Event放到一个公共的Module中达到Module级别的解耦,效果完美。
总结使用领域事件解耦业务流程的应用场景:
1. 若是一个业务流程须要贯穿几个不一样的受限上下文,那么能够经过以发布领域事件的方式来避免上游系统耦合下游系统,这种解耦方式的收益最大,由于其有利于后期系统的拆分。
2. 若是在同一个受限上下文中,也能够经过发布领域事件的方式来达到领域间解耦。

仓储repository

仓储做用之一是用于数据的持久化。从架构层面来讲,仓储用于链接领域层和基础结构层,领域层经过仓储访问存储机制,而不用过于关心存储机制的具体细节。按照DDD的设计原则,仓储的做用对象的领域模型的聚合根,也就是说每一个聚合都有一个独立的仓储。

使用仓储的意义:
1. 站在领域层更关心领域逻辑的层面,仓储做为领域层和基础结构层的链接组件,使得领域层没必要过多的关注存储细节。在设计时,将仓储接口放在领域层,而将仓储的具体实现放在基础机构层,领域层经过接口访问数据存储,而没必要过多的关注仓储存储的细节。
2. 站在架构的层面,仓储解耦了领域层和ORM之间的联系,这一点也就是不少人设计仓储模式的缘由,好比我要更换ORM框架,咱们只须要改变仓储的实现便可,对于领域层和仓储的接口基本不不须要作任何改变。

工厂

工厂不负责对象的持久化,工厂把持久化职责委托给仓储来完成,仓储不该该直接和应用层打交道,对于整个领域层来讲,和应用层打交道的是service接口,而和持久化打交道的是repository接口。
举一个场景来讲,根据订单号获取一个聚合复杂对象采购订单,对采购订单进行修改后再进行保存。这个时候和持久化层存在两次交互,第一次是数据的读取,第二次是修改后数据的存入。

对于数据读取,到领域层则是Factory须要实例化一个聚合对象并返回应用层。而Factory将该工做分解到聚合里美的每个子实体,子实体经过Repository接口获取到ResultSet并进行OR转换后返回,Factory将拿到的全部实例化对象进行聚合返回一个完整的聚合对象实例。对于数据存储,仍然应该是Factory接管该操做,而后对数据进行分解后分别调用聚合中的每个实体的仓储接口自己的保存方法,对数据进行持久化,在Factory层进行完整的事务控制并返回结果。

六边形架构

六边形每条不一样的边表明了不一样种类型的端口,端口要么处理输入,要么处理输出。该架构存在两个区域,分别是外部区域和内部区域,在外部区域中,不一样的客户都可以提交输入,而内部的系统则用于获取持久化数据,并对程序输出进行存储(好比数据库),或者在中途将输出转发到另外的地方(好比消息)。

六边形架构也称为端口和适配器。对于每种外界类型,都有一个适配器与之相对应。外界经过应用层API与内部进行交互。
一般来讲,咱们不用本身实现端口,咱们能够将端口想成HTTP,而将适配器想成Java的Servlet或JAX-RX的REST请求处理类。或者,咱们能够为NServiceBus或RabbitMQ建立消息监听器,在这种状况下,端口是消息机制,而适配器则是消息监听器,由于消息监听器将负责从消息中提取数据,并将数据转化为应用层API(领域模型的客户)所需的参数。任何客户均可能向不一样的端口发出请求,可是全部的适配器都将使用相同的API。

应用程序经过公共API接收客户请求。应用程序边界,即内部六边形,也是用例(或用户故事)边界。换句话说,咱们应该根据应用程序的功能需求来建立用例,而不是客户数量或输出机制,当应用程序经过API接收到请求时,它将使用领域模型来处理请求,其中便包括对业务逻辑的执行。所以,应用层API经过应用服务的方式展示给外部。

六边形通常分为三层:
领域层(domain layer):核心业务逻辑,通常不包含任何技术实现或引用。
端口层(Port Layer):领域层以外,负责接收与用例相关的全部请求,这些请求负责在领域层中协调工做。端口层在端口内部做为领域层的边界,在端口外部则扮演了外部实体店角色。
适配器层:端口层以外,负责以某种格式接收输入,及产生输出。好比,对于HTTP用户请求,适配器会将其转换为对领域层的调用,并将领域层传回的响应进行封送,经过HTTP传回调用客户端,在适配器层不存在领域逻辑,它的惟一职责时在外部世界与领域层之间进行技术性的转换。适配器可以与端口的某个协议相关联并使用该端口,多个适配器可使用同一个端口,在切换到某种新的用户界面时,可让新界面同时使用相同的端口。

例子:
领域层想要获取某一个领域对象,来进行业务操做实现,而后告诉端口层说:“端口小弟,哥须要一个 XXX Domain Model,立马去叫人搞!”,端口小弟心想,老大发话了,得赶忙的啊,而后就在本身胸前,贴出了这样一段告示:能逮到 XXX Domain Model 的适配器杀手们,请速速到俺这里,必有重赏!告示一贴出,适配器杀手们蜂拥而至,而后根据本身的能力来进行判断,毕竟 XXX Domain Model 也不是那么容易擒服,也不是随便一个适配器杀手就能搞定的,这须要必定的能力,最后能作的适配器杀手进行揭此告示。

其实六边形架构,从内到外的一个过程的体现,反过来,从适配器到领域层也同样,这种方式通常是接收用户请求处理开始。 在六边形架构中,适配器是以组件性质的方式提供服务,他和领域层进行联系要经过端口层,这个端口层能够看做是服务的一种协议或规范,这就是SOA和REST的用武之地,来扮演适配器层和端口层的角色。