DDD领域驱动设计-设计规范-Ⅵ
1. 前言
- 强调实体的概念,将现实世界与软件系统关联起来,便于不同岗位的人达成统一的认知。有助于业务理解和需求讨论。
- 明确业务规则和业务流程,将系统中的隐形业务逻辑在领域层显性展示出来。
- 分模块编写代码,有助于明确和细化代码功能,编写的代码质量更好,可以最大化满足SOLID原则。后续系统升级改造时,可精确定位到需要调整的模块,便于高效的维护业务代码。
2. DDD规范说明
2.1. 实体建模
一些对象主要不是由它们的属性定义的。它们实际上表示了一条“标识线”,这条线跨越时间,而且常常经历多种不同的表示。
- 在同一类模型实中需要区别开来,一个实体是唯一的东西;
- 每个实体有唯一标识来区别彼此;
- 实体有生命周期,我们可以对它多次修改,但它仍然还是同一个实体。
2.2. 聚合根
2.2.1. 聚合根配置
- 工厂(Factory):只是负责创建聚合根,聚合根内部的子实体,与实体的行为无关。创建与使用分开,保证类的单一权责规范。
- 领域服务(DomainService):完成聚合根内实体的相关行为,处理所有的业务逻辑,如业务判断,业务数据生成等。对于跨多个实体的应用,单独编写第三方领域服务处理。第三方领域服务的功能不属于单独某一个实体的行为。
- 仓库(Repository):仓库提供聚合根与底层数据的存储功能。仓库仅保存数据,查询数据,不做实体行为的逻辑处理。
2.2.2. 配置原则
- 对于简单的实体创建,可基于构造函数,或直接设置值生成实体,不一定非要使用工厂创建。
- 工厂,领域服务等都不能直接与底层的数据存储系统交互,他们都要通过仓库层来获取数据,存储数据。
- 聚合根统一配置仓库等模块,内部的子实体不用再单独配置仓库和工厂了。
2.3. 实体应用规范
2.3.1. 实体必须干净
2.3.2. 不可以强依赖其他聚合根实体或领域服务
2.3.3. 任何实体的行为只能直接影响到本实体
2.4. 领域服务
2.4.1. 单实体-领域服务
2.4.2. 跨对象事务型(多实体)-第三方领域服务
2.4.3.领域层操作实体是一种内存操作行为
- 在领域层中直接调用仓库层接口保存数据,是否可行。从代码上来说是可以的,单从职能上来说,领域层是一种内存模式的业务规则操作,不应该直接去保存数据。
- 若在领域层处理过程中需调用外部服务接口,包括外部服务的创建数据接口(一种特殊的非内存操作),这种还是放在领域服务中实现。因为实际场景中,经常存在调用外部服务接口,基于返回内容再次做业务逻辑处理的情况。
2.5. 事件通知
2.5.1. 副作用
2.5.2. 事件通知与第三方领域服务的区别
2.6. 工厂使用注意事项
- 实体必要数据完整,如传入订单号,订单号有效,且能获取到订单的信息,订单的信息能完善实体的必要数据;
- 实体合法,如业务审批实体,它一定是一个满足审批条件的实体;
- 好处:可以保证工厂创建出来的实体是一个合规可用的实体,对于传入的命令,先做数据层面的合规性验证,同时工厂可从数据层拿到数据,便于做数据加工和验证。
- 弊端:在工厂的内部去调用外部Repo或外部服务,会导致工厂类不够纯,单元测试需要mock所有的外部服务,测试覆盖度会有影响。
- 当系统中已经存在一个实体时,一般来说不需要工厂在去创建实体了,就不存在调用工厂的说法。
- 当系统中不存在实体时,需要初始化实体数据,此刻由应用层调用工厂创建。这个理论的依据是实体的创建与实体的使用要分离,领域层中的实体是实体的使用。本例不认可那种在实体中加一个创建实体的行为,创建行为调用工厂创建实体的模式,这种模式还是把实体的创建和实体的其他行为都放在了一起,这样就不需要引入工厂了,也达不到使用与创建分离的目的。
- 工厂是否可以直接接收CE(命令,事件消息)数据,本例设计的是可以,工厂接收到传入的指令,生成对应的实体。
2.7. 防腐层使用场景
- 发送短信,消息;
- 跨系统查询数据,调用外部系统业务接口;
- 调用支付宝,网银,微信等转账;
2.8. 仓库层注意事项
- 仓库层入参不应该使用底层数据格式,Repository操作的是Entity对象(实际上应该是Aggregate Root),而不应该直接操作底层的数据对象(数据表映射的贫血对象)。更近一步,Repository接口实际上应该存在于Domain层,根本看不到数据层的实现。这个也是为了避免底层实现逻辑渗透到业务代码中的强保障。
- 实体状态变更,行为处理,仓库层入参可以接收处理命令(如XxxUpdateCommand)。
- 仓库层接口放在领域层模块,但仓库层的实现放在基础层模块。
- 当发现数据存储要求更多的字段,实体缺乏某些数据项时(如一些加工生成的中间数据),不要将缺少的数据,通过参数的方式传递到仓库层。应反思实体是否设计的完善合理,尽可能的完善实体后,再存储数据。
- 仓库层的业务接口入参一般为实体,实体的唯一身份标识,部分基础数据类型。
- 仓库层的查询接口入参可以为Query对象,单个主键编码。
- 仓库层的数据操作接口,原则上由应用层调用,不要在领域层中调用,领域层一般调用查询接口。
2.9. 业务处理如何依赖实体行为
- 多实体强一致性:完成一个业务必须保证相关的实体同时完成,具有事务性质。可采用第三方领域服务处理,处理完成后,在应用层加上事务保证数据一致性的存储到系统。
- 实体副作用:完成一个实体后,其他实体监听处理,实体不依赖于其他实体的处理结果。如履约单完成后,发出一个事件。
- 多实体先后处理:在应用层,应用服务调用领域层实体相关的功能,做业务编排,先执行一个实体的行为,在执行其他实体的行为。如补偿单审批通过后,调用履约单的处理功能。
2.10. 领域驱动数据传输
2.11. 领域驱动设计常规流程
2.11.1. 产生实体模式
- 应用层准备数据,包括从网关接收传入的数据,调用外部服务得到的数据。
- 应用层准备好数据后,调用工厂创建实体。对于比较简单的实体,可不基于工厂创建,直接在应用层设置实体的值即可。
- 工厂生成实体,根据获取到的各个数据对象,组合生成领域实体。
- 领域层执行操作:基于传入的实体调用领域对象的方法对其进行操作。需要注意的是这个时候通常都是纯内存操作,非持久化。
- 应用层持久化:将操作结果调用仓库层数据保存接口,持久化实体数据,或操作外部系统产生相应的影响,包括发消息等异步操作。
- 仓库层接收到实体后,转换实体为数据表映射对象,最终保存数据;
2.11.2. 应用实体模式
- 客户端在不同的场景(Command,Event)下,传入数据到应用层(application),此刻是普通的DTO数据;
- 应用层基于传入的数据调用仓库层接口,仓库层基于实体唯一编码返回一个已经存在的实体信息;
- 实体在领域层实现核心业务规则后,返回对应的实体对象,实体的行为或DomainService的所有方法都是纯内存操作,无外部的副作用。(若实体信息修改无业务规则,应用层获取实体后,直接调用仓库层保存数据);
- 应用层得到领域层返回的实体对象后,调用仓库层接口,传入实体对象(或命令对象)保存数据;
- 仓库层接收到传入数据后,转换数据为数据表映射对象,最终保存数据;
2.12. 领域驱动注意事项
- 实体保存时,仓库层的入参不能为基础的数据表对象,入参对象应设置为实体;
- 其他命令操作时,应用层调用仓库层保存数据,入参可根据实际的情况传入对应的实体或入参命令对象(比如只是修改数据的个别字段可传入修改命令,修改大量字段信息,传入实体)。
- 领域层只做纯内存的业务规则操作,原则上不能在领域层中直接调用仓库层存储数据;
- 应用层的出参设置为DTO或基础数据对象(如主键编码),不能直接返回实体;
- 当一个业务涉及到多个实体时,不能在一个实体中直接调用另外一个实体(聚合根可调用内部的子实体),应该基于应用层或第三方领域服务协调处理;
- 明确各个层的职能,不要混用;
- 领域层做业务规则处理,针对不同的规则场景,建议采用策略设计模式,多用设计模式实现领域服务便于系统后续的扩展和调整;
- 涉及到主子记录的情况(如订单,子订单),一般建立聚合根,基于聚合根统一的保存数据;
- 当业务需要多个实体同时处理时,可在应用层统一加上事务管理;
- 针对一些基础配置信息,或比较简单的业务(CRUD),采用DDD模式也很费力,此刻不一定非要使用DDD模式,生搬硬套DDD模式感觉把简单事情复杂化了。
DDD领域驱动设计-设计规范-Ⅵ的更多相关文章
- DDD领域驱动设计-概述-Ⅰ
如果我看得更远,那是因为我站在巨人的肩膀上.(If I have seen further it is by standing on ye shoulder of Giants.) ...
- 浅谈我对DDD领域驱动设计的理解
从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品 ...
- DDD 领域驱动设计-商品建模之路
最近在做电商业务中,有关商品业务改版的一些东西,后端的架构设计采用现在很流行的微服务,有关微服务的简单概念: 微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成.系统中的各个微服务可被独 ...
- DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(3)
上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(2)> 这篇文章主要是对 DDD.Sample 框架增加 Transa ...
- DDD 领域驱动设计-两个实体的碰撞火花
上一篇:<DDD 领域驱动设计-领域模型中的用户设计?> 开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sample(代码已更新) 在 ...
- DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(2)
上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(1)> 阅读目录: 抽离 IRepository 并改造 Reposi ...
- DDD 领域驱动设计-领域模型中的用户设计
上一篇:<DDD 领域驱动设计-如何控制业务流程?> 开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sample(代码已更新,并增加了 ...
- DDD 领域驱动设计-如何控制业务流程?
上一篇:<DDD 领域驱动设计-如何完善 Domain Model(领域模型)?> 开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sa ...
- DDD 领域驱动设计-如何完善 Domain Model(领域模型)?
上一篇:<DDD 领域驱动设计-如何 DDD?> 开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sample(代码已更新) 阅读目录: ...
随机推荐
- 如何在云效流水线 Flow中构建属于自己的NPM仓库
如何在云效流水线 Flow中构建属于自己的NPM仓库,Flow 通过各种构建组件,对各种语言提供了制品打包能力,让用户可以快速的使用流水线构建制品,并通过后续的部署任务进行部署.Flow 已经完成了与 ...
- nmap使用命令(转载)原文地址https://www.jianshu.com/p/4030c99fcaee
- 获取发布版SHA1和调试版SHA1
总结 调试版: 常见问题 | 高德地图API (amap.com) 发布版: 首先需要生成签名 Android Studio生成签名文件,自动签名,以及获取SHA1和MD5值_donkor_的博客-C ...
- Shell系列(32)- 双分支if语句判断Apache服务是否启动
#!/bin/bash #截取httped进程,并把结果赋予变量test test=$(ps -aux | grep "httpd" | grep -v "grep&qu ...
- 接口测试checklist
静态测试 接口文档与设计文档对应 接口定义 接口定义与数据库定义 业务功能测试 系统全流程验证 逆向全流程验证 事务性测试 边界值测试 业务规则边界值 场景分析合理长度 场景分析合理数据量 输入.输出 ...
- 鸿蒙内核源码分析(并发并行篇) | 听过无数遍的两个概念 | 百篇博客分析OpenHarmony源码 | v25.01
百篇博客系列篇.本篇为: v25.xx 鸿蒙内核源码分析(并发并行篇) | 听过无数遍的两个概念 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度 ...
- 什么鬼?你还搞不懂json和字典的区别??
现在自动化培训烂大街,是个人都能说的上几个框架,面试如果问框架相关问题,求职者只需一瓶 82 年的雪碧,会吹的让你怀疑人生!所以面试官为了更清楚的知道你是停留在表面上的花拳绣腿还是有扎实的基础,就不会 ...
- ASP.NET Core中将Json字符串转换为JsonResult
ASP.NET Core中返回JsonResult 最近在使用NET 5.0做WebApi中,发现只能返回string类型,不能用JsonResult返回实体,于是查阅资料找到解决办法. 两种方式分别 ...
- 小米路由器4a千兆版刷openwrt
现在网上搜小米路由器4a千兆版刷机的都是刷的padavan的,很少能找到openwrt的刷机教程. 首先刷openwrt系统的时候要先刷入引导程序breed,网上有一篇帖子写的很详细(https:// ...
- 数据库MHA故障分析
一.故障分析 1.MHA故障以后是否正常:不正常 2.如果master恢复了?MHA还能自动恢复吗?:不能 3.主从恢复删除此文件 rm saved_master_binlog_from_192 ...