Domain Primitive(DP)DP概念
DP 是 DDD 中的一个基础概念,是 DDD 中可以执行的一个最小单元,最直接的体现是,将业务相关的参数定义在一个特定的领域中(比如一个 class 文件),封装成一个具有精准定义,自我验证,拥有行为的 ValueObject 。
行为指相关业务代码
Value Object
区别于 Entity,拥有 id,是一个表的实例,而 VO 没有 id,更多的强调数据,不需要对应任何表,只是一个数据的集合,一个值对象,它的一个最大特点是 Immutable(不可变性),这个值对象自从被创建出来后不会被改变,所以说这个对象中的属性最好都是被 private final 修饰 。
DP原则
- 将隐性的概念显性化
例:电话号是用户的一个属性,属于隐形概念,但实际上获取电话号的地区号(行为)才是真正的业务逻辑,因此需要将电话号的概念显性化,在“电话号”对象里实现该行为 。 - 将隐性的上下文显性化
例:当我们做支付功能时,实际上需要的一个入参对象是支付金额 + 货币类型,因此可以把这两个概念组合成一个独立的完整概念:Money - 封装多对象行为
例:如果一段业务逻辑涉及到多个对象,就需要用 DP 包装起来
1接口清晰度,通过接口参数是否能够显示业务
2数据校验和错误处理是否统一维护,数据校验的依赖性,比如地区号的获取依赖于电话号,那地区号校验就要放在“电话号”类中做校验
3业务逻辑代码的清晰度,胶水代码是否存在于核心业务代码中,区分核心业务代码和非核心业务代码(胶水代码:从一些入参里抽取一部分数据,然后调用一个外部依赖获取更多的数据,然后通常从新的数据中再抽取部分数据用作其他的作用 。)
DDD应用架构【Domain Driver Design DDD领域驱动模型】View Object(VO)-- 视图对象,代表展示层需要显示的数据,通常由 DTO 转换后展示 。
Data Transfer Object(DTO)-- 数据传输对象,主要作为 Application 层的入参和出参 。
Entity - 基于领域逻辑的实体类,拥有 ID,它的字段和数据库储存不需要有必然的联系,不仅包含数据,还有行为,字段也不仅仅是 String 等基础类型,而应该尽可能用 Domain Primitive 代替,可以避免大量的校验代码 。
Data Object(DO)- DO 是单纯的和数据库表的映射关系,每个字段对应数据库表的一个 column,DO 只有数据,没有行为 。
Repository - 只负责定义 Entity 对象的存储和读取方法,返回对象一般为 Entity 。
RepositoryImpl - 实现数据库存储读取的细节,通过加入 Repository 接口,底层的数据库连接可以根据实际情况用不同的实现类来替换 。
Mapper(DAO) - DAO 对应的是一个特定的数据库类型的操作,CRUD 。
Builder/Factory - 实现 DO 与 Entity 之间的转化 。
Anti-Corruption Layer(ACL)- 防腐层,很多时候我们的系统会去依赖其他的系统,而被依赖的系统可能包含不合理的数据结构、API、协议或技术实现,如果对外部系统强依赖,会导致我们的系统被”腐蚀“ 。这个时候,通过在系统间加入一个防腐层,能够有效的隔离外部依赖和内部逻辑,无论外部如何变更,内部代码可以尽可能的保持不变 。
ACL作用
- 适配器:很多时候外部依赖的数据、接口和协议并不符合内部规范,通过适配器模式,可以将数据转化逻辑封装到 ACL 内部,降低对业务代码的侵入 。比如转化对方的入参和出参,序列化反序列化等,让入参出参更符合我们的标准 。
- 缓存:对于频繁调用且数据变更不频繁的外部依赖,通过在 ACL 里嵌入缓存逻辑,能够有效的降低对于外部依赖的请求压力 。同时,很多时候缓存逻辑是写在业务代码里的,通过将缓存逻辑嵌入 ACL,能够降低业务代码的复杂度 。
- 兜底:如果外部依赖的稳定性较差,一个能够有效提升我们系统稳定性的策略是通过 ACL 起到兜底的作用,比如当外部依赖出问题后,返回最近一次成功的缓存或业务兜底数据 。这种兜底逻辑一般都比较复杂,如果散落在核心业务代码中会很难维护,通过集中在 ACL 中,更加容易被测试和修改 。
- 功能开关:有些时候我们希望能在某些场景下开放或关闭某个接口的功能,或者让某个接口返回一个特定的值,我们可以在 ACL 配置功能开关来实现,而不会对真实业务代码造成影响 。
- 对于依赖的外部对象,我们抽取出所需要的字段,生成一个内部所需的 VO 或 DTO 类
- 构建一个新的 Facade(GateWay),在 Facade 中封装调用链路,将外部类转化为内部类
- 针对外部系统调用,同样的用 Facade 方法封装外部调用链路

文章插图
Application Layer(应用层):Application Service、Repository、ACL 类属于应用层,应用层依赖领域层,但不依赖具体实现 。
Infrastructure Layer(基础设施层):ACL,Repository 等的具体实现类,通常依赖外部具体的技术实现和框架 。
Domain-Driven Design(DDD):领域驱动设计,架构思路是先写 Domain 层的业务逻辑,然后再写 Application 层的组件编排,最后才写每个外部依赖的具体实现 。
解耦实现

文章插图
DTO Assembler:将 1 个或多个相关联的 Entity 转化为 1 个或多个 DTO 。
Data Converter:Entity 到 DO 的转化器 。
转换一般使用 MapStruct 库
Repository:当成一个中性的类,使用语法如 find、save、remove,使用中性的 save 接口,然后在具体实现上根据情况调用 DAO 的 insert 或 update 接口,根据 Aggregate 的 ID 是否存在且大于 0 来判断一个 Aggregate 是需要更新还是插入 。这样做是为了概念上和数据库解绑,不去直接使用 insert、select、update、delete 。
Repository 复杂实现:一次操作中,并不是所有 Aggregate 里的 Entity 都需要变更,但是如果用简单的写法,会导致大量的无用 DB 操作 。具体实现参照:Repository复杂实现
领域层设计规范Entity
通过聚合根保证主子实体的一致性
在稍微复杂一点的领域里,通常主实体会包含子实体,这时候主实体就需要起到聚合根的作用,即:
- 子实体不能单独存在,只能通过聚合根的方法获取到 。任何外部的对象都不能直接保留子实体的引用 。
- 子实体没有独立的 Repository,不可以单独保存和取出,必须要通过聚合根的 Repository 实例化 。
- 子实体可以单独修改自身状态,但是多个子实体之间的状态一致性需要聚合根来保障 。
一个实体类不能直接在内部直接依赖一个外部的实体或服务 。正确的对外部依赖的方法有两种:
- 只保存外部实体的 ID:强烈建议使用强类型的 ID 对象,而不是 Long 型 ID 。强类型的 ID 对象不单单能自我包含验证代码,保证 ID 值的正确性,同时还能确保各种入参不会因为参数顺序变化而出 bug 。
- 针对于“无副作用”的外部依赖,通过方法入参的方式传入 。如果方法对外部依赖有副作用,不能通过方法入参的方式,只能通过 Domain Service 解决 。
分层理解Interface层
1网络协议的转化:通常这个已经由各种框架给封装掉了,我们需要构建的类要么是被注解的 bean,要么是继承了某个接口的 bean 。
2统一鉴权:比如在一些需要 AppKey+Secret 的场景,需要针对某个租户做鉴权的,包括一些加密串的校验
3Session 管理:一般在面向用户的接口或者有登陆态的,通过 Session 或者 RPC 上下文可以拿到当前调用的用户,以便传递给下游服务 。
4限流配置:对接口做限流避免大流量打到下游服务
5前置缓存:针对变更不是很频繁的只读场景,可以前置结果缓存到接口层
6异常处理:通常在接口层要避免将异常直接暴露给调用端,所以需要在接口层做统一的异常捕获,转化为调用端可以理解的数据格式
7日志:在接口层打调用日志,用来做统计和 debug 等 。一般微服务框架可能都直接包含了这些功能 。
规范:
- Interface 层的 HTTP 和 RPC 接口,返回值为 Result,捕捉所有异常 。
- 一个 Interface 层的类应该是“小而美”的,应该是面向“一个单一的业务”或“一类同样需求的业务”,需要尽量避免用同一个类承接不同类型业务的需求 。
1ApplicationService 应用服务:最核心的类,负责业务流程的编排,但本身不负责任何业务逻辑 。
2DTO Assembler:负责将内部领域模型转化为可对外的 DTO 。
3Command、Query、Event 对象:作为 ApplicationService 的入参 。
4返回的DTO:作为 ApplicationService 的出参 。
Command、Query、Event对象

文章插图
CQE vs DTO
CQE:CQE 对象是 ApplicationService 的输入,是有明确的“意图”的,所以这个对象必须保证其“正确性” 。
DTO:DTO 对象只是数据容器,只是为了和外部交互,所以本身不包含任何逻辑,只是贫血对象 。
规范
- Application 层的所有接口返回值为 DTO,不负责处理异常,可以直接抛异常,不用统一处理 。
- ApplicationService 的接口入参只能是一个 Command、Query 或 Event 对象,CQE 对象需要能代表当前方法的语意 。唯一可以的例外是根据单一ID查询的情况,可以省略掉一个 Query 对象的创建 。
- CQE 对象的校验应该前置,避免在 ApplicationService 里做参数的校验 。可以通过 JSR303/380 和 Spring Validation 来实现 。
- 针对于不同语意的指令,要避免 CQE 对象的复用,比如常见的场景是“Create 创建”和“Update 更新”应该分开 。
判断是否业务流程的几个点:
1不要有 if/else 分支逻辑:通常有分支逻辑的,都代表一些业务判断,应该将逻辑封装到 Domain Service 或者 Entity 里,但这不代表完全不能有 if 逻辑,比如:
ItemDO item = itemService.getItem(cmd.getItemId());if (item == null) {throw new IllegalArgumentException("Item not found");}但这里仅仅代表了中断条件,具体的业务逻辑处理并没有受影响 。2不要有任何计算逻辑 。
3数据的转化交给其他对象来做,数据的转化可以交给其他对象来做 。

文章插图
COLA4.0架构图

文章插图
1适配层(Adapter Layer):负责对前端展示的路由和适配,对于传统B/S系统而言,adapter 就相当于 MVC 中的 controller,包括 vo、和assembler 类似的转换类(实现 vo 与 dto 之间转换);
2应用层(Application Layer):主要负责获取输入,组装上下文,参数校验,调用领域层做业务处理,如果需要的话,发送消息通知等 。层次是开放的,应用层也可以绕过领域层,直接访问基础实施层(利用 mapper 访问),包括 executorImpl、assembler(实现 dto 与 model 之间转换或 dto 与 po 之间转换)、dto、rpc(实现 Client 中的 facade);
3领域层(Domain Layer):主要是封装了核心业务逻辑,并通过领域服务(Domain Service)和领域对象(Domain Entity)的方法对 App 层提供业务实体和业务逻辑计算 。领域是应用的核心,不依赖任何其他层次,包括abilityImpl;
4基础实施层(Infrastructure Layer):主要负责技术细节问题的处理,比如数据库的 CRUD、搜索引擎、文件系统、分布式服务的 RPC 等 。此外,领域防腐的重任也落在这里,外部依赖需要通过 gateway 的转义处理,才能被上面的Domain层使用,包括po、converter(实现 model 与 po 之间转换);
5Client(封装成 SDK 供外部调用):api(facade)、dto;
各个包结构的简要功能描述

文章插图
本文来自博客园,作者:这个杀手冷死了,转载请注明原文链接:https://www.cnblogs.com/pandacode/p/15679382.html
- 春季老年人吃什么养肝?土豆、米饭换着吃
- 三八妇女节节日祝福分享 三八妇女节节日语录
- 老人谨慎!选好你的“第三只脚”
- 校方进行了深刻的反思 青岛一大学生坠亡校方整改校规
- 脸皮厚的人长寿!有这特征的老人最长寿
- 长寿秘诀:记住这10大妙招 100%增寿
- 春季老年人心血管病高发 3条保命要诀
- 眼睛花不花要看四十八 老年人怎样延缓老花眼
- 香槟然能防治老年痴呆症? 一天三杯它人到90不痴呆
- 老人手抖的原因 为什么老人手会抖
