DDD核心模式详解
一、背景
这里通过《实现领域驱动设计》一书来选一些核心的DDD设计模式讲解,方便读者更加深入理解DDD的概念和轮廓,这些核心模式是众多DDD模式的基础,可以说玩DDD就是玩模式,玩建模,玩设计。这里我们用更全面的眼光带着问题辩证地看这些模式概念,并仔细揣摩其中的含义。
二、核心模式&概念
2.1 实体模式
概念说明:主要由标识定义的对象被称作ENTITY。ENTITY(实体)有特殊的建模和设计思路,具有生命周期。ENTITY可以是任何事物,只要满足两个条件即可,一是它在整个生命周期中具有连续性,二是它的区别并不是由那些对用户非常重要的属性决定的。ENTITY可以是一个人、一座城市、一辆汽车、一张彩票或一次银行交易。
注意:这里的实体ENTITY不是持久层DAO中的实体,也不是Java中的实体Bean,而是领域模型中的一个特征。这里的标识代表着领域模型中的要素是什么,有什么行为,有什么属性。这个行为就代表着有生命周期,有连续性。
举例:你去一家餐馆吃饭,你通过收银台点餐付款,这里客户和收银员都是人,这两个角色代表的人就是两个实体。比如你去银行存款,这是一次存款交易,有开始有结束,这次交易记录了交易时间,账户,卡号,银行信息,金额信息等,可以认为这个交易就是一个实体。
思维导图:
2.2 值对象模式
概念说明:用于描述领域的某个方面而本身没有概念标识的对象称为VALUE OBJECT(值对象)。VALUE OBJECT被实例化之后用来表示一些设计元素,对于这些设计元素,我们只关心它们是什么,而不关心它们是谁。当我们只关心一个模型元素的属性时,应把它归类为VALUE OBJECT。我们应该使这个模型元素能够表示出其属性的意义,并为它提供相关功能。
注意:VALUE OBJECT应该是不可变的。不要为它分配任何标识,而且不要把它设计成像ENTITY那么复杂。其实大多数情况下我们会对事物进行分类,贴标签,这些类型,标签其实就可以算一种值对象。值对象大多数时候值是不变的,但是也不是绝对的。
举例:商品的一些颜色,尺码,规格信息;实体上的唯一标示ID;业务编码配置类数据;业务枚举;相对固定的日期,字符串;上面这些可能还是有点抽象,举个例子,进行员工数据模型设计的时候假设员工类型有外包,正式员工,顾问,实习生这几种类型,那么这几种类型对应的code值就是值对象,说白了,年龄,性别其实也是。
思维导图:
2.3 聚合模式
概念说明:AGGREGATE就是一组相关对象的集合,我们把它作为数据修改的单元。每个AGGREGATE都有一个根(root)和一个边界(boundary)。边界定义了AGGREGATE的内部都有什么。根则是AGGREGATE所包含的一个特定ENTITY。对AGGREGATE而言,外部对象只可以引用根,而边界内部的对象之间则可以互相引用。除根以外的其他ENTITY都有本地标识,但这些标识只在AGGREGATE内部才需要加以区别,因为外部对象除了根ENTITY之外看不到其他对象。
注意:单纯看聚合,和聚合根并没有发现他有多大作用,但是其本质上是想通过聚合来将领域中的模型关系梳理起来,比如多个领域对象是一组业务上关系紧密的模型,那么就可以通过聚合来管理这些对象的生命周期。如何识别聚合和创建聚合根是非常有挑战的,我们需要更加谨慎的设计聚合。
我们应该将 ENTITY和 VALUE OBJECT分 门 别 类地 聚集 到 AGGREGATE中 , 并定 义 每 个AGGREGATE的边界。在每个AGGREGATE中,选择一个ENTITY作为根,并通过根来控制对边界内其他对象的所有访问。只允许外部对象保持对根的引用。对内部成员的临时引用可以被传递出去,但仅在一次操作中有效。由于根控制,因此不能绕过它来修改内部对象。这种设计有利于确保AGGREGATE中的对象满足所有固定规则,也可以确保在任何状态变化时AGGREGATE作为一个整体满足固定规则。
在书中关于设计聚合有一些原则,大概有以下几点:1.设计小聚合 2.通过唯一标识引用其他聚合 3.在多个聚合中使用最终一致性 4.在聚合内保证事务的一致性,在一次事务中只修改一个聚合实例 这些原则可以帮助我们设计更好的聚合根
举例:假设要设计一个hr办公系统,要对员工,组织,账号这些进行建模,那么单独对员工进行分析可以建立员工实体,员工薪资实体,员工教育经历实体,员工工作经历实体,这些其实就是一对多的关系,那么聚合根实体就是这些实体的全部,相当于做了一次封装。针对员工的修改就是先走这个聚合根实体。
思维导图:
2.4 工厂模式
概念说明:应该将创建复杂对象的实例和AGGREGATE的职责转移给单独的对象,这个对象本身可能没有承担领域模型中的职责,但它仍是领域设计的一部分。提供一个封装所有复杂装配操作的接口,而且这个接口不需要客户引用要被实例化的对象的具体类。在创建AGGREGATE时要把它作为一个整体,并确保它满足固定规则。
注意:对象的创建本身可以是一个主要操作,但被创建的对象并不适合承担复杂的装配操作。将这些职责混在一起可能产生难以理解的拙劣设计。让客户直接负责创建对象又会使客户的设计陷入混乱,并且破坏被装配对象或AGGREGATE的封装,而且导致客户与被创建对象的实现之间产生过于紧密的耦合。
举例:创建和修改员工聚合根实体的工厂类。
使用场景:当创建一个对象或创建整个AGGREGATE时,如果创建工作很复杂,或者暴露了过多的内部结构,则可以使用FACTORY进行封装。
思维导图:
2.5 仓库模式
概念说明:为每种需要全局访问的对象类型创建一个对象,这个对象相当于该类型的所有对象在内存中的一个集合的“替身”。通过一个众所周知的全局接口来提供访问。提供添加和删除对象的方法,用这些方法来封装在数据存储中实际插入或删除数据的操作。提供根据具体条件来挑选对象的方法,并返回属性值满足查询条件的对象或对象集合(所返回的对象是完全实例化的),从而将实际的存储和查询技术封装起来。只为那些确实需要直接访问的AGGREGATE根提供REPOSITORY。让客户始终聚焦于模型,而将所有对象的存储和访问操作交给REPOSITORY来完成。
使用场景:实体或者值对象与存储层之间的互相转换,比如员工聚合根在工厂模式中并不一定直接根存储层打交道,这里可以通过仓库来补足创建对象的场景,比如从持久层而来。
思维导图:
2.6 服务模式
概念说明:当领域中的某个重要的过程或转换操作不是ENTITY或VALUE OBJECT的自然职责时,应该在模型中添加一个作为独立接口的操作,并将其声明为SERVICE。定义接口时要使用模型语言,并确保操作名称是UBIQUITOUS LANGUAGE中的术语。此外,应该使SERVICE成为无状态的。** 注意:我们需要区分属于领域层的Service和哪些属于其他层的service,并划分职责,以便明确区分。创建服务类不一定需要接口。** 使用场景:交易服务,转账服务,下单服务 思维导图:
2.7 模块模式
概念说明:选择能够描述系统的MODULE,并使之包含一个内聚的概念集合。这通常会实现MODULE之间的低耦合,但如果效果不理想,则应寻找一种更改模型的方式来消除概念之间的耦合,或者找到一个可作为MODULE基础的概念(这个概念先前可能被忽视了),基于这个概念组织的MODULE可以以一种有意义的方式将元素集中到一起。找到一种低耦合的概念组织方式,从而可以相互独立地理解和分析这些概念。对模型进行精化,直到可以根据高层领域概念对模型进行划分,同时相应的代码也不会产生耦合。MODULE的名称应该是UBIQUITOUS LANGUAGE中的术语。MODULE及其名称应反映出领域的深层知识。
注意
1.MODULE并不仅仅是代码的划分,而且也是概念的划分。一个人一次考虑的事情是有限的(因此才要低耦合)。不连贯的思想和“一锅粥”似的思想同样难于理解(因此才要高内聚)。2.技术框架对打包决策有着极大的影响,有些技术框架是有帮助的,有些则要坚决抵制。
使用场景:用户模块,权限模块,交易模块等
思维导图:
2.8 领域概念和模式
说明:领域驱动设计的一个本质就是领域,如何定义领域,如何划分领域,如何识别领域之间的关系,如何构建领域,通过领域我们可以揭开面向对象编程设计的面纱。
思维导图:
思维导图中关于领域的模式有很多,我们把领域作为抓手去进行DDD是最好的方式。领域的背后就是领域模型了,更上层则是应用上下文了,所以这里领域的概念被单独拿出来其实是有个承上启下的作用的。前面几个模式是更基础的,这里的一些领域模式之后就会有更复杂的DDD模式了。
2.9 上下文概念和模式
说明:上下文是另外一个领域驱动设计无法忽视的核心要素,通过上下文和上下文图可以发现相关领域之间的关系,进而可以帮助进行模型转换,帮助数据流动。
思维导图:
2.10 事件溯源模式
说明:事件溯源模式是领域驱动建模的另一大工具,通过事件的角度识别领域中发生的业务活动,进而识别出领域对象,通过事件的脉络串联整个领域内部和外部。
思维导图:
三、问题案例
这里我们通过一些反问或者问题案例去说明如何将这些模式应用到实践中,同时加深对上面这些模式的理解。
3.1 值对象和实体的区别?如何判断我一个业务流程中的对象哪些算实体哪些算值对象?
答思维导图:
根据业务流程中的业务活动区分有多少事物,事件,角色,活动,然后进一步根据实体的特征识别出业务活动有哪些实体,实体一般会包含实体也会包含值对象。值对象的话一般跟业务活动没有直接的关系,可以从实体中获取值对象。
3.2 DDD中的实体跟Java Bean有什么区别?跟数据库实体有什么区别?
答:模型ENTITY与Java的‚实体bean‛并不是一回事。实体bean本打算成为一种用于实现ENTITY的框架,但它实际上并没有做到。大多数ENTITY都被实现为普通对象。不管它们是如何实现的,ENTITY都是领域模型中的一个根本特征。数据库实体实际上是面向表结构的,体现的是一种静态的数据结构关系。DDD中的实体比表结构所体现的更丰富,容易变化。
3.3 聚合根的大小怎么定义,怎么使用?
答:设计聚合根其实有一些原则和注意事项,通常来说可以通过实体关系,业务活动关联紧密程度去定义。聚合根可以用在工厂和仓库中,也可以用在一些查询CQRS架构中。
3.4 如何使用工厂模式?
答:工厂模式有很多种,比如设计模式中的工厂方法模式,抽象工厂模式,建造者模式。比较常见的就是在实体上加一个Lombok的@Builder注解。
3.5 我不是这个行业的,我怎么知道这个行业里面有哪些领域,怎么划分领域和上下文?
答:请领域专家喝个下午茶,拿个小本子问一些行业内的核心业务流程,不同的人做了什么。这里的领域专家宽泛一点的就是做过这个行业的资深技术,产品,运营。严格一点的就是从事这个行业很久且具有深刻认知的人。上面这是一种方式,另外一种方式就是通过这个领域的一些相关产品,新闻,书籍梳理一些领域活动,但是很可能遗漏关键信息,容易陷入自以为是的尴尬境地。
3.6 工厂模式和仓库模式的区别?
答:工厂负责处理对象生命周期的开始,而仓库帮助管理生命周期的中间和结束。工厂负责制造新对象,仓库负责查找已有对象。
3.7 仓库模式与数据库访问对象(DAO)的区别?
答:首先不能把所有的持久化抽象都称为DAO。DAO主要从水库表的角度来看待问题,并且提供CRUD操作。表模块,表数据网关和活动日志应该在事务脚本里面。DAO模式通常只对数据库表的一层封装。仓库则更加偏向于对象,通常被用于领域模型中。通常来说可以将仓库当作DAO来看待。
3.8 领域驱动设计中的领域模型有哪些组成要素/部分?
答思维导图:
3.9 为什么通过领域建模出的对象模型与数据库模型有出入,到项目后期以至于偏离了DDD的方向?
答:首先领域建模出的对象模型其实有很多重复或者重叠,另外对象关系也容易变化,对象层次也不一样。在数据库实体模型中对象的关系显得更加纯粹,简单,一旦确定关系则数据结构也随之明朗。但是领域模型中的对象改起来会比数据库模型的改动更加复杂。这其实就要求当领域模型发生变动的时候数据库模型也要做适当的调整,不过到一定程度数据库模型改动的成本会更高。书籍里有一种指导方式是保持领域模型与数据库模型的松散关系,不是所有领域实体都对应数据库表。
四、总结
上面这些实际上是《实现领域驱动》和《领域驱动设计》的一些核心要点,算是读书笔记和一些个人心得。通过总结概念说明然后带着疑问学习可以更深入了解DDD的知识体系。总的来说DDD中的一些核心概念模式还是要牢牢掌握的,这为后面的领域建模实战和代码落地打下基础。
原文始发于微信公众号(神帅的架构实战):DDD核心模式详解
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/241751.html