DDD(Domain Driver Design)领域驱动模型
Repository复杂实现
领域层设计规范
Entity
通过聚合根保证主子实体的一致性
在稍微复杂一点的领域里,通常主实体会包含子实体,这时候主实体就需要起到聚合根的作用,即:
- 子实体不能单独存在,只能通过聚合根的方法获取到。任何外部的对象都不能直接保留子实体的引用。
- 子实体没有独立的 Repository,不可以单独保存和取出,必须要通过聚合根的 Repository 实例化。
- 子实体可以单独修改自身状态,但是多个子实体之间的状态一致性需要聚合根来保障。
不可以强依赖其他聚合根实体或领域服务
一个实体类不能直接在内部直接依赖一个外部的实体或服务。正确的对外部依赖的方法有两种:
- 只保存外部实体的 ID:强烈建议使用强类型的 ID 对象,而不是 Long 型 ID。强类型的 ID 对象不单单能自我包含验证代码,保证 ID 值的正确性,同时还能确保各种入参不会因为参数顺序变化而出 bug。
- 针对于“无副作用”的外部依赖,通过方法入参的方式传入。如果方法对外部依赖有副作用,不能通过方法入参的方式,只能通过 Domain Service 解决。
任何实体的行为只能直接影响到本实体(和其子实体),不能直接修改其他的实体类。
分层理解
Interface层
1 网络协议的转化:通常这个已经由各种框架给封装掉了,我们需要构建的类要么是被注解的 bean,要么是继承了某个接口的 bean。
2 统一鉴权:比如在一些需要 AppKey+Secret 的场景,需要针对某个租户做鉴权的,包括一些加密串的校验
3 Session 管理:一般在面向用户的接口或者有登陆态的,通过 Session 或者 RPC 上下文可以拿到当前调用的用户,以便传递给下游服务。
4 限流配置:对接口做限流避免大流量打到下游服务
5 前置缓存:针对变更不是很频繁的只读场景,可以前置结果缓存到接口层
6 异常处理:通常在接口层要避免将异常直接暴露给调用端,所以需要在接口层做统一的异常捕获,转化为调用端可以理解的数据格式
7 日志:在接口层打调用日志,用来做统计和 debug 等。一般微服务框架可能都直接包含了这些功能。
规范:
- Interface 层的 HTTP 和 RPC 接口,返回值为 Result,捕捉所有异常。
- 一个 Interface 层的类应该是“小而美”的,应该是面向“一个单一的业务”或“一类同样需求的业务”,需要尽量避免用同一个类承接不同类型业务的需求。
Application层
1 ApplicationService 应用服务:最核心的类,负责业务流程的编排,但本身不负责任何业务逻辑。
2 DTO Assembler:负责将内部领域模型转化为可对外的 DTO。
3 Command、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 更新”应该分开。
Application Service 是业务流程的封装,不处理业务逻辑
判断是否业务流程的几个点:
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;
3 领域层(Domain Layer):主要是封装了核心业务逻辑,并通过领域服务(Domain Service)和领域对象(Domain Entity)的方法对 App 层提供业务实体和业务逻辑计算。领域是应用的核心,不依赖任何其他层次,包括abilityImpl;
4 基础实施层(Infrastructure Layer):主要负责技术细节问题的处理,比如数据库的 CRUD、搜索引擎、文件系统、分布式服务的 RPC 等。此外,领域防腐的重任也落在这里,外部依赖需要通过 gateway 的转义处理,才能被上面的Domain层使用,包括po、converter(实现 model 与 po 之间转换)、rpc(实现 Client 中的 facade);
5 Client(封装成 SDK 供外部调用):api(facade)、dto;
各个包结构的简要功能描述