依赖方向朝内,每个环可以依赖它本身这一层及其所有内部的层,但不能依赖它外部的层
用来放实体、值对象、聚合等领域模型的
业务逻辑都应该尽量内聚在这一层
这一层是最纯净的,不需要依赖任何其它东西
用于协调进出Entities层的数据流
通过调用和编排领域模型来实现用例
在DDD中,这一层通常是Application Service层
是很薄的一层,只用来做一些比较简单的事情
这一层叫“接口适配层”,它其实是主要用来与外部进行适配的
比如Web请求进来的Controllers(写)和Presenters(读)
这一层会将User Cases或Entities层需要的数据结构与外层的数据结构做一个转换
比如操作数据库、调用第三方接口等
这一层主要是框架和驱动层,比如数据库驱动、WEB框架、UI等
日常编码中很少会在这一层编写代码
业务流程通常是先从controller进来,调用Use Cases层
但有时候Use Cases层需要调用presenter
如果直接调用,就破坏了“向内依赖规则”
这个时候可以用“依赖反转”来做
1、在Use Cases层定义两种抽象类或者接口:Use Case Input Port和Use Case Output Port
2、Use Case Interactor去实现了Use Case Input Port
3、Presenter去实现了Use Case Output Port
这样Use Cases层就不用依赖外部的层了
同样的道理,也可用于对数据库、第三方接口等的交互场景
内部是整洁架构的模式,并且和DDD很好的结合了起来
a、领域层:最中间的是领域模型+领域服务
b、应用层:也包含两个环 里面是App Services,外面是C/Q处理器、事件监听器
c、大红色轮廓包起来的Application Core
这一层定义了很多接口(也可以说是端口)
比如持久化、第三方服务、搜索、CQ总线、事件总线等等
也接收处理命令和查询
当只使用领域模型做不到的时候,就需要领域服务
什么情况下领域模型做不到,领域服务就做得到?
一个很常见的场景是创建模型的时候有业务逻辑
虽然创建模型通常是放在Factory里面,但Factory里面并不适合放业务逻辑
而这个时候领域模型还没有创建,自然就只能放在领域服务里面了
不止是需要当前这个聚合根的数据,可能还需要其它的数据
这个读取操作当然不可能放到领域层去做,通常把它放在应用层
但应用层通常是一个聚合根对应一个ApplicationService
正常的流程是调用Repository接口获取一个领域模型对象
然后对它进行操作,再保存回数据库
那如果需要获取其它数据怎么办呢?尤其是可能与当前领域模型无关的数据,比如“最近评论时间”
通过依赖反转的方式 实现一个Use Case Output Port抽象类即可 具体原理详见'跨越边界和依赖反转'
个人认为在领域层创建比较好
因为创建领域事件其实也算是一种业务逻辑
并且只是创建一个领域事件的话,不会依赖任何外部的东西,放在领域层没有什么问题
那什么时候发送领域事件呢?按照整洁架构的规则,不应该在领域层发送
因为事件总线(或者事件发送器的实现)在最外层
如果在领域层发送,虽然有依赖倒置,但感觉也跨越太多层次了
不是一个好的实践
跨聚合根应该使用事件通信,但事件的实现方式有多种
如果是异步,那就保证不了强一致的事务
只能用一些技术手段去尽量保证最终一致
推荐使用maven/gradle的模块化
因为模块之间是有依赖关系的
只要不去改依赖的配置,就永远是单向依赖的
具体来说,可以把整洁架构上面的层级分成一个个模块
然后在配置文件里面定义它们的依赖关系
比如应用层模块,依赖领域层模块
接口和适配层模块依赖应用层模块和领域层模块
定义一个聚合根应该很简单,根据业务来就是了
比如用户、订单、商品、库存等等
但有时候我们很容易把聚合根定义得很大
聚合根太大可能会有问题,比如代码过多、测试用例过多、性能不好等等
只是要找到合适的“借口”,要拆得有理有据
截止目前原理简介已经介绍过了 但如果没有具体项目实践的话 相信还不足以深刻理解 接下来进入项目实践环节
https://gitee.com/pingfanrenbiji/ddd
项目结构
interface-adapter 接口适配层(适配dubbo、rest接口等协议)
application 应用层(实现用例的地方,eg:电商场景里的用户下单是个用例)
domain 领域层(写领域逻辑的地方,eg:电商场景里的用户下单包含订单逻辑、商品逻辑、以及优惠逻辑等)
infrastructure 基础层(放Cache、MQ框架、数据库持久实现等的地方)
采用依赖倒置的依赖关系
具体代码请自行下载项目查看 这里仅点到为止
定义一个方法 参数为具体的请求指令 比如当前方法接收生成商品指令
这里仅定义接口 具体实现在application模块
application接口调用命令总线
命令总线在core模块
方式1
如果你的项目是spring项目 则需要在配置文件中注入
方式2
如果你的项目是springboot项目
ddd-spring-boot-starter jar包会读取spring.provides获取加载bean的来源
ddd-spring-boot-autoconfigure 定义好要初始化的bean
加载所有的命令相关的bean注册到命令总线中
上文收到application调用命令总线转发接口
这里是模版设计模式
具体实现类在aplication模块中实现
调用core模块的事件总线方法
获取线程池中的一个线程去启动事件执行器
事件执行器
抽线类实现该接口
该事件执行器的抽象方法的具体实现在application 模块中
结合原理简介和项目实践 Do you understand DDD?
believe oneself !!!
后续会介绍下 隐私计算之隐匿查询