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(代码已更新) 阅读目录: ...
随机推荐
- Java学习笔记--注解和反射
注解和反射 1. 注解 注解作用: 对程序做出解释 被其他程序读取 注解格式: @注释名,还可以添加一些参数值,例如@SuppressWarnings(value="unchecked&qu ...
- Maven项目之间关系介绍
Maven项目之间的关系 依赖关系 单纯的项目A中需要项目B中的资源,将项目B打成Jar包被A依赖,此时项目A直接调用项目B中资源即可. 项目A和项目B此时形成最基本的依赖关系. 继承关系 需要场景: ...
- 迷你商城后端管理系统 ———— stage2 项目的核心代码实现
应用程序主函数接口 @SpringBootApplication(scanBasePackages = {"org.linlinjava.litemall.db", "o ...
- PHP中的日期相关函数(二)
上回文章中我们介绍了三个时间日期相关的对象,不过它们的出镜频率并不是特别地高.今天学习的对象虽说可能不少人使用过,但是它的出镜频率也是非常低的.它们其实就是我们非常常用的那些面向过程的日期函数的面向对 ...
- python刷题第二周
1: 第3章-5 字符转换 (15 分) 本题要求提取一个字符串中的所有数字字符('0'--'9'),将其转换为一个整数输出. 输入格式: 输入在一行中给出一个不超过80个字符且以回车结束的字符串. ...
- curl 理解
PHP使用CURL详解 CURL是一个非常强大的开源库,支持很多协议,包括HTTP.FTP.TELNET等,我们使用它来发送HTTP请求.它给我 们带来的好处是可以通过灵活的选项设置不同的HTTP ...
- selenium下拉选择框处理
HTML: (一)通过xpath层级标签定位 driver.find_element_by_xpath(".//*[@id='Resolution']/option[2]").cl ...
- AT2368-[AGC013B]Hamiltonish Path【构造】
正题 题目链接:https://www.luogu.com.cn/problem/AT2368 题目大意 给出 \(n\) 个点 \(m\) 条边的一张无向图,然后求一条路径满足 路径长度不小于二. ...
- 生动直观的Gif图告诉你如何安装Python安装第3方库,在线安装离线安装全都搞定
前言 学Python的小伙伴都知道,Python学习过程中需要装不少的第3方的库,今天就和大家一起分享下第3方库的安装方法 在线安装(推荐安装式式) 点开Pycharm--file--Project- ...
- Node.js躬行记(12)——BFF
BFF字面意思是服务于前端的后端,我的理解就是数据聚合层.我们组在维护一个后台管理系统,会频繁的与数据库交互. 过去为了增删改查会写大量的对应接口,并且还需要在Model.Service.Router ...