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(代码已更新) 阅读目录: ...
随机推荐
- 珠峰2016,第9期 vue.js 笔记部份
在珠峰参加培训好年了,笔记原是记在本子上,现在也经不需要看了,搬家不想带上书和本了,所以把笔记整理下,存在博客中,也顺便复习一下 安装vue.js 因为方便打包和环境依赖,所以建意npm init ...
- 使用SQL SERVER存储过程实现历史数据迁移
今天讲下软件开发中最常见的历史数据迁移方式.在讲迁移之前,先简单介绍下几个基本概念. 1.什么是历史数据迁移? 简单直白地说:就是将一些创建时间比较久而且不常用的历史数据,存储到另一个地方(可以是另一 ...
- Android实现自动登录和记住密码
效果图: 在勾选自动登录后下次打开软件会直接跳过登录界面 代码: protected void onCreate(Bundle savedInstanceState) { super.onCreate ...
- AS插件快速生成javabean
https://blog.csdn.net/u010227042/article/details/103803198
- C++ windows 函数讲解(二)鼠标坐标
获得鼠标坐标: #include<bits/stdc++.h> #include<windows.h> using namespace std; int main() { PO ...
- 有备无患「GitHub 热点速览 v.21.38」
作者:HelloGitHub-小鱼干 数据库最重要的一个功能是容灾备份,备份不只是对数据库重要,对日常工作生活的我们一样重要,比如花了一个工作日写的代码没有备份(虽然可能只有 1 行-)总归是一个让人 ...
- 学习PHP中的国际化功能来查看货币及日期信息
做为一门在世界范围内广泛使用的编程语言,国际化能力往往是衡量一个编程语言是否能够大范围流行的重要内容.特别是对于 PHP 这种以 Web 页面编程为主战场的语言来说,国际化能力更是重中之重.在 PHP ...
- input竖直的输入框,文字从上到下排列
有的时候可能会有这样的需求,一个竖直的输入框,输入信息,文字也是从上到下排列: (但是在移动端或用轮播swiper时不起作用,可以用textarea代替input) <!DOCTYPE html ...
- Hystrix配置实战及feign超时配置失效
一.feign超时配置失效 最近项目上遇见feign超时配置总是失效.导致feign调用超过2s之后就会超时,会进行自动重试,重复调用两次服务,并且还是指定接口.这就更加奇怪.最后通过观察以及源码调试 ...
- CSS写一个缺角的div和菱形
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...