文章目录
- 1. DDD的实现架构
-
- 1.1. 三层架构
- 1.2. 传统DDD分层架构
- 1.3. 清洁(Clean)架构
- 1.4. 六边形架构
- 1.5. 垂直切片架构
- 1.6. CQRS架构的特点
-
- 1.6.1. 命令和查询分离
- 1.6.2. 不同的数据访问方式
- 1.6.3. 领域事件实现数据同步
- 2. 各种架构总结
- 3. 参考资料
1. DDD的实现架构
DDD的实现架构有很多种,这些架构都是一种关注点分离模式的实现,也是SOLID单一职责原则的体现,将人们关注的一个职责与其他职责分离,不要试图混合在一起。传统的SOA架构在这方面有很大缺陷,造成了一种单体耦合的架构,虽然这样的大型服务能够实现一定程度的复用和重用,但是在重用和解耦之间需要有一个取舍,在这两者之间如果非选择一个,那么首先选择解耦。通过解耦可以实现更小粒度的重用,虽然这种重用粒度太细小会使得提高生产效率方面的效果不是很明显,但是随着系统的扩展和复杂性的提高,其优点将逐步体现。
1.1. 三层架构
常见的三层架构:表现层、应用层和数据层
- 表现层传入失血模型DTO对象传输数据
- 应用层委派领域层执行业务决策(可能需要DTO→领域模型类)
- 应用层委派基础设置层(仓储)进行数据保存
1.3. 清洁(Clean)架构
清洁(Clean)架构是著名软件工程大师RobertC.Martin提出的一种架构整洁清晰之道,也是当前各种语言开发的目标架构。
六边形架构的特点:
-
只有两个世界,六边形里面是所有的业务模式/逻辑,外面是基础设施,二者通过端口/适配器组件联系。端口根据调用请求方向又分为API和SPI两种。适配器通过软件技术组件来实现业务领域端口和具体技术之间的适配转换。
API(应用程序编程接口)和SPI(服务提供者接口)的主要区别:API是被调用者、被驱动者,是供外界调用和使用的接口;SPI是主动调用者、主驱动者,驱动调用基础设施或第三方API。SPI可以收集所有由业务领域检索的信息或从第三方获得某些服务所需的接口。
-
依赖关系始终从外部进入内部,确保了业务域的隔离,如果以后更改基础架构,业务逻辑将可以重写。
-
六边形内的一切一定不能依赖任何技术框架,包括诸如Jackson或JPA之类的外部注释。
代码结构:
-
Domain 领域层 放领域实体 充血模型
-
application
这个包下有两个子包
- port 六边形的端口接口 放纯接口(各种service,根据输入输出方向不同分为SPI和API)
- service 放接口的实现 各种service实现
-
adapter 适配器层
- 各种数据类型转换的类型
- 子包web 放controller~ dto之类的包也放在这
- 子包持久层 放持久层相关的类
异步应用的六边形架构代码结构:
- domain 领域层 充血模型
- api 接受调用的api服务类
- spi 调用其他三方服务或基础设施的代码
- presentation 与UI有关的WEB类,包括controller等
- infrastructure 数据库仓储 mysql,kafka等
调用顺序 presentation → api → domain → spi → infrastructure
1.5. 垂直切片架构
垂直切片架构是来自JimmyBogard的CQRS实践总结,它是对清洁架构以及六边形架构的否定。根据这些架构的分层方法,一个业务功能会跨越这些分层执行,当需要增加或修改一个功能时会涉及在这些层内实现多次修改,JimmyBogard的想法是:如果根据功能进行分“层”(称为“片”)则会大大提高开发效率,这种“片”是垂直于分层的:
CQRS源于BertrandMayer设计的命令查询分离(CQS)原理,CQS声明一个类只能有两种方法:改变状态并返回void的方法和返回状态但不改变状态的方法。
根据CQS思想,任何功能可以划分为读取/查询和命令/写入两大功能,写后再读也归为写功能。因此如果将功能粗暴简单地分为读写两种功能,开发团队也可以由此划分为两种:DDD业务逻辑实现和数据 表分析。
CQRS的实现可以分为三个步骤:
- 拆分查询读取和命令写入。读取是从数据源获取数据,写入则是往数据源写入数据,二者方向相反。
- 使用不同的数据访问。查询模型可以专门使用缓存等优化的查询数据库,而命令模型则使用自己的写数据库。
- 使用领域事件实现写入数据库和查询数据库之间的同步。
以上这三步完成任何一步都可以称为CQRS,第二步和第三步是针对大规模系统的。
根据数据进出的方向分为Command命令模型和Query查询模型两种。这里使用Handler而不是服务描述,突出了Handler作为专门处理命令或查询的一种方式,但是真正进行命令或查询处理的并不是Handler本身,而是它委托给领域模型或查询模型。这里的Handler也是一种命令或查询的传递处理方式,它接受来自前端的请求,这种请求被打包成命令方式。Handler以类似MVC控制器的方式来接受命令或查询(也可以是来自异步消息机制),然后递交给领域模型或查询模型进一步处理。查询模型与领域模型是两种完全不同的模型,领域模型是完全基于DDD原则建立的,而查询模型则是根据数据查询要求来建立的,两种模型的设计依据不同。当然基于领域模型实现各种查询也是有好处的。领域模型中提供了业务逻辑规则,这种规则可能对输出查询也是有作用的。
关于Command对象:
CQRS是命令和查询分离的模式或架构,因此Command命令对象是CQRS的关注重点。命令表达用户的意图,是用户希望计算机系统做想让它做的事情,命令通过发出请求的方式到达计算机系统,通过响应让用户知晓他的命令是否被计算机成功执行。
传入领域层的数据都是以命令对象的形式封装的,这样领域层所依赖的输入数据不再是DTO,因为DTO属于技术级别,如果领域模型的输入参数依赖DTO,那么就会造成领域层依赖技术层,这就违背了领域驱动设计的宗旨。
作为CQRS架构实现,领域模型接受的应该是命令,也就是用户的意图表达。
CQRS的好处之一是将领域驱动设计和数据驱动设计分离,在查询读取模型中可以使用数据驱动设计,而在命令传入执行的模型路线中必须使用领域驱动设计,因此,从请求的起点开始,沿着请求传入方向,逐个检查请求经过的各个环节,以确保请求数据在传入领域层之前已经被转换为命令对象。
当然,这样系统还有很多的类,他们服务于不同的分层,有着不一样的单一的职责:Form服务于表现层、DTO负责在技术级别层次之间传输纯数据、命令服务于领域层、领域模型的实体服务于业务领域和需求、仓储实体服务于数据表、数据表服务于长久保存的目的。
如果用户界面希望显示什么字段,可直接在Form对象中增加,是不是需要放入命令对象,那就要考察这个字段是否属于业务领域范畴,如果这个字段是控制显示方式的,那么它属于应用程序的逻辑,不是业务逻辑,当然不需要放入命令对象;如果属于业务逻辑放入命令对象了,也要考察这个字段对领域模型的影响:为什么设计领域模型时没有考虑这个字段,是否意味着流程改变或分支流程的出现,是否会出现新的有界上下文和聚合(这个影响比较大)。
应用逻辑和业务逻辑的区别需要一定的敏感性,学习领域驱动设计的一个好处就是培养业务逻辑的识别,有了业务逻辑识别,就自然对应用逻辑变得敏感了,这样就能逐渐走上业务与技术分离的架构路线,保证业务逻辑在领域模型中得到不断重构和发展,成为系统的核心资产。
1.6.2. 不同的数据访问方式
-
应用CQRS的第一步是将大量服务重构为单独的查询和命令。
-
实现CQRS的第二步是为查询模型和命令模型使用不同的存储引擎。例如,ElasticSearch用于查询端,JPA/MySQL/Oracle用于命令端,使用MongoDB等NoSQL或Redis缓存用于查询,在命令端的RDBMS中将聚合存储为JSON。当然,不同的存储引擎之间也需要互相同步。
规格模式有三种形式:
- 用于验证:验证一个对象,看它是否满足某些业务要求,或者是否已经准备就绪,检查状态是否符合要求。
- 用于筛选过滤:从一个集合中筛选出符合指定要求的对象。例如,带有指定条件的SQL查询语句。
- 按需创建:创建一个对象时指定该对象必须满足某种要求。例如,下订单时,要求厂家按照自己指定的规格生产产品。
规格模式是由“谓语”升华而来的。谓语可以用AND、OR、NOT来组合和修改,这些逻辑运算对于谓语是封闭的,因此,规格的组合也表现为一种操作封闭性。
UML类图:
这种方式的特点在于命令模型:这里使用EventStore作为持久存储,而不是RDBMS和ORM;不保存实际的对象状态,而是保存事件流。这种模式被命名为事件溯源(EventSourcing),后文讨论。
2. 各种架构总结
从清洁架构、六边形架构到CQRS架构,这些架构都从不同方面关注系统的职责功能。清洁架构和六边形架构是将业务与技术相分离,这是一个大的分离解耦方向,而CQRS则是将业务领域分离为查询和命令两种方式。通过这两种不同方式的切分,DDD领域模型将切实得到隔离和保护,这些领域模型能够不受各自具体技术发展的影响,成为组织内真正的核心资产。
3. 参考资料
- 规格模式
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!