关于DDD
领域驱动设计(Domain Driven Design)是一种架构思想,目的是正确地划定一个业务的边界,令架构整体上更清晰。
这里记录下DDD的落地方案——从Bean的定义到架构如何构建。
Domain Primitive(DP)
DP的概念:
- DP是Immutable的Value Object;
- DP是业务的原始语言定义,比如我们要讨论用户中心这样的系统,就需要定义类似User、Name这样的DP;
- DP可以是业务对象的复杂组合,比如User由Name、Phone等组成,它需要保证构造时使用的Name、Phone是足以构成一个User对象。
DP规定了业务代码中Bean的编写规范,DP包含3个原则:
- Make Implicit Concepts Explicit(将隐性的概念显性化)
- Make Implicit Context Explicit(将 隐性的 上下文 显性化)
- Encapsulate Multi-Object Behavior(封装 多对象 行为)
Make Implicit Concepts Explicit(将隐性的概念显性化)
平时我们习惯写贫血模型的Bean:
1 | public class User { |
这么写的主要问题是对phone、address等字段的规定非常宽松,格式校验必须在业务代码中写:
1 | private boolean isInvalidName(String name) { |
这么写的缺点是:
1、接口清晰度差
1 | register("张三", "地球村", "0571-12345678"); |
像上面这样调用的话不会出现编译错误,但是运行时会报错。
2、数据校验和错误处理
register方法入口需要对参数格式进行校验,其实对每个传name、phone、address的地方都需要这么写一次校验,非常繁琐。
解决办法是对name、phone、address进行封装,即所谓的充血模型:
1 | public class PhoneNumber { |
- PhoneNumber是不可变(Immutable)的对象;
- 校验逻辑都在构造方法里,只要PhoneNumber被创建出来后,一定是被校验通过的,只要能带入参数里的一定是正确的或null,数据校验的工作被前置到了调用方;
Make Implicit Context Explicit(将 隐性的 上下文 显性化)
多个传参之间紧密关联,可以组合成一个Bean,比如支付时需要给出支付金额和支付货币,我们可以将这两个参数组合成一个完整的概念Money:
1 | public class Money { |
这里货币<amount, currency>是一个隐性的上下文概念,合并成Money后完成了显性化,可以在Money中添加一些对货币的限制。
Encapsulate Multi-Object Behavior(封装 多对象 行为)
将涉及到多个对象的行为封装为一个对象,比如将转换汇率这个操作转换为一个ExchangeRate
对象:
1 | public class ExchangeRate { |
架构
事务脚本的写法
最普通写业务的思路,基本是MVC架构,并把核心业务逻辑统统写到一个方法内,顶多把一些能复用的拆到一些子方法内。
事务脚本的主要问题是:
- 可维护性差
- 可扩展性差
- 可测试性差
改造思路 - 防腐层(ACL)
很多时候我们的系统会去依赖其他的系统,被依赖的系统可能会包含不合理的数据结构、API、协议或技术实现,如果对外部系统强依赖,会导致我们的系统被“腐蚀”。
解决办法是加入一个防腐层来隔离:
- 接口适配
- 缓存
- 降级
- 易测试
- 功能开关
比如,为了屏蔽业务系统和消息队列服务,可以使用一个抽象的AuditMessageProducer、AuditMessage实现对底层消息队列服务的隔离。
系统中有很多与外部系统相关的计算,比如接收外部系统传入的金额,对账户金额的计算、账户余额的校验、转帐限制、金额增减等。解决办法是使用DP封装。
对于跨多个域对象的行为,封装到业务Service不合适,但封装到单个的DP中也不合适,这种情况最好是单独为其创建一个Domain Service。
六边形架构(Hexagonal Architecture)
传统架构是分层的。
而Hex中,架构变成内外关系,具体到项目的组成会有以下6个组件:
- Types
保存对外暴露的Domain Primitives(DP),纯POJO,不依赖任何类库。 - Domain
核心业务逻辑,包含有状态的 Entity、领域服务 Domain Service、以及各种外部依赖的接口类(如 Repository、ACL、中间件等)。Domain 模块仅依赖 Types模块,也是纯 POJO 。 - Application
Application 模块主要包含 Application Ser vice 和一些相关的类。Application 模块依赖Domain 模块。还是不依赖任何框架,纯 POJO。 - Infrastructure
Infrastructure 模块包含了 Persistence、Messaging、External 等模块。比如:Persistence模块包含数据库 DAO 的实现,包含 Data Object、ORM Mapper、Entity 到 DO 的转化类等。Persistence 模块要依赖具体的 ORM 类库,比如 MyBatis。如果要用 Spring-Mybatis提供的注解方案,则需要依赖 Spring。 - Web
Web 模块包含 Controller 等相关代码。如果用 SpringMVC 则需要依赖 Spring。 - Start
Start模块是SpringBoot的启动类。
参考
- 《The Complete Works of Tao Technology 2020》 - 阿里技术专家详解 DDD 系列
- AnemicDomainModel - Martin Fowler