之前也看过一些DDD的文章,然后昨天看了 阿里的DDD之后,有一些想法和总结在此发表一下。
Controller层
业务强相关,主要是用来非常清楚的表示业务逻辑
Interface层
- 职责:主要负责承接网络协议的转化、Session管理等
- 接口数量:避免所谓的统一API,不必人为限制接口类的数量,每个/每类业务对应一套接口即可,接口参数应该符合业务需求,避免大而全的入参
- 接口出参:统一返回Result
- 异常处理:应该捕捉所有异常,避免异常信息的泄漏。可以通过AOP统一处理,避免代码里有大量重复代码。
功能组成
- 网络协议的转化:通常这个已经由各种框架给封装掉了,我们需要构建的类要么是被注解的bean,要么是继承了某个接口的bean。
- 统一鉴权:比如在一些需要AppKey+Secret的场景,需要针对某个租户做鉴权的,包括一些加密串的校验
- Session管理:一般在面向用户的接口或者有登陆态的,通过Session或者RPC上下文可以拿到当前调用的用户,以便传递给下游服务。
- 限流配置:对接口做限流避免大流量打到下游服务
- 前置缓存:针对变更不是很频繁的只读场景,可以前置结果缓存到接口层
- 异常处理:通常在接口层要避免将异常直接暴露给调用端,所以需要在接口层做统一的异常捕获,转化为调用端可以理解的数据格式
- 日志:在接口层打调用日志,用来做统计和debug等。一般微服务框架可能都直接包含了这些功能。
规范
- 一个Interface层的类应该是“小而美”的,应该是面向“一个单一的业务”或“一类同样需求的业务”,需要尽量避免用同一个类承接不同类型业务的需求。
- Interface层的HTTP和RPC接口,返回值为Result,捕捉所有异常
- Application层的所有接口返回值为DTO,不负责处理异常
Application层
- 入参:具像化Command、Query、Event对象作为ApplicationService的入参,唯一可以的例外是单ID查询的场景。
- CQE的语意化:CQE对象有语意,不同用例之间语意不同,即使参数一样也要避免复用。
- 入参校验:基础校验通过Bean Validation api解决。Spring Validation自带Validation的AOP,也可以自己写AOP。
- 出参:统一返回DTO,而不是Entity或DO。
- DTO转化:用DTO Assembler负责Entity/VO到DTO的转化。
- 异常处理:不统一捕捉异常,可以随意抛异常。
核心类
- ApplicationService应用服务:最核心的类,负责业务流程的编排,但本身不负责任何业务逻辑
- DTO Assembler:负责将内部领域模型转化为可对外的DTO
- Command、Query、Event对象:作为ApplicationService的入参
- 返回的DTO:作为ApplicationService的出参
ACL防腐层
在Application层的在ApplicationService中,经常会依赖外部服务,从代码层面对外部系统产生了依赖。为了降低依赖,所以有了这么一个层级
ACL防腐层的简单原理如下:
- 对于依赖的外部对象,我们抽取出所需要的字段,生成一个内部所需的VO或DTO类
- 构建一个新的Facade,在Facade中封装调用链路,将外部类转化为内部类
- 针对外部系统调用,同样的用Facade方法封装外部调用链路
DTO Assembler
通常,Entity转DTO是有成本的,无论是代码量还是运行时的操作。手写转换代码容易出错,为了节省代码量用Reflection会造成极大的性能损耗。所以推荐使用MapStruct这个库
Entity VO层
Mapper
如果你用的是mybatis的话,那么对应的mapper就是在这里
Repository
数据库层面的防腐层,避免在之后数据后改动字段之后,实体类也需要改动
Entity
常说的vo类,主要就是与数据库表字段的映射