DDD 概要与实践感悟
复杂性
- 系统的复杂性往往并不在技术上,而是来自领域本身、用户的活动或业务服务。当这种领域复杂性在设计中没有得到解决时,基础技术的构思再好也是无济于事。而系统的复杂度体现在三个方面:规模、结构和变化
- 规模:指的是系统所支持的功能点,以及功能点与功能点之间的的关系。DDD 通过子领域,限界上下文,聚合等模式对问题进行拆分和归类,不断收窄问题域,保证聚合边界内所解决的问题集合足够收敛和可控。
- 结构:指的是系统架构。系统架构是否分层;若分层,每层划分的职责边界是否清晰;架构的基本管理单元是什么,它决定了架构演进时的复杂度。DDD 通过分层架构,独立出领域层,且架构中的每层都有清晰的职责。整体架构的基本管理单元是聚合,它是一个完整的、自治的管理单元,当需要进行服务拆分时,可以直接以聚合作为基本单元进行拆分。
变化:指的是系统响应需求变化的能力。快速响应变化的有效手段是分离不易变逻辑和易变逻辑,”以不变应万变”。而通过分层架构独立的领域层正是不易变的逻辑。领域层是对领域知识的封装,其提供的领域服务具有经验性和前瞻性,是对领域内稳定的领域规则的表达。而领域层以外的应用层和基础设施层则是易变逻辑的封装。保证核心的独立和稳定,通过在调整应用层和基础设施层来实现快速响应需求变化。
领域驱动
领域驱动指的是以领域作为解决问题切入点,面对业务需求,先提炼出领域概念,并构建领域模型来表达业务问题,而构建过程中我们应该尽可能避免牵扯技术方案或技术细节。而编码实现更像是对领域模型的代码翻译,代码(变量名、方法名、类名等)中要求能够表达领域概念,让人见码明义。
实践经验
思维模式转变
- 实践 DDD 以前,我最常使用的是数据驱动设计。它的核心思路针对业务需求进行数据建模:根据业务需求提炼出类,然后通过 ORM 把类映射为表结构,并根据读写性能要求使用范式优化表与表之间的关联关系。数据驱动是从技术的维度解决业务问题,得出的数据模型是对业务需求的直接翻译,并没有蕴含稳定的领域知识/规则。一旦需求发生变化,数据模型就得发生变化,对应的库表的设计也需要进行调整。这种设计思维导致变化从需求穿透到了数据层,中间并没有稳定的,不易变的层级进行阻隔,最终导致系统响应变化的能力很差。
协同方式转变
解构-领域驱动设计》提出的 DDDRUP 给出了更细致的步骤、步骤与步骤之间的衔接,以及明确的阶段里程碑,最重要的是 DDDRUP 可以串联 DDD 的所有概念和模式,非常便于初学者做知识梳理和上手实践。下文我会依照 DDDRUP 的步骤流程进行讲述,而非战略设计+战术设计的思路
-
分层
用户接口层
用户接口层的核心职能:协议转换和适配、鉴权、参数校验和异常处理。
├── controller //面向视图模型&资源│ ├── ResultController.java│ ├── assembler // 装配器,将VO转换为DTO│ │ └── ResultAssembler.java│ └── vo // VO(View Object)对象│ ├── EnterResultRequest.java│ └── ResponseVO.java├── provider // 面向服务行为├── subscriber // 面向事件└── task // 面向策略└── TotalResultTask.java
应用层
应用层的核心职能:编排领域服务、事务管理、发布应用事件。
├── assembler // 装配器,将DTO转换为DO│ ├── ResultAssembler.java│ └── TotalResultAssembler.java├── dto // DTO(Data Transfer Object)对象│ ├── cmd // 命令相关的DTO对象│ │ ├── ComputeTotalResultCmd.java│ │ ├── EnterResultCmd.java│ │ └── ModifyResultCmd.java│ ├── event // 应用事件相关的DTO对象, subscriber负责接收│ └── qry // 查询相关的DTO对象└── service // 应用服务├── ResultApplicationService.java├── event // 应用事件,用于发布└── adapter // 防腐层适配器接口
领域层
代码组织以聚合为基本单元
├── result // 成绩聚合│ ├── entity // 成绩聚合内的实体│ │ └── Result.java│ ├── service // 领域服务│ │ ├── ResultDomainService.java│ │ ├── event // 领域事件│ │ ├── adapter // 防腐层适配器接口│ │ ├── factory // 工厂│ │ └── repository // 资源库│ │ └── ResultRepository.java│ └── valueobject // 成绩聚合的值对象│ ├── GPA.java│ ├── ResultUK.java│ ├── SchoolYear.java│ └── Semester.java└── totalresult // 总成绩聚合├── ... 这段有点长,其代码结构与成绩聚合一致,因此省略 ...
基础设施层
该层主要提供领域层接口(资源库、防腐层接口)和应用层接口(防腐层接口)的实现。
- 代码组织基本以聚合为基本单元。对于应用层的防腐层接口,则直接以 application 作为包名组织。
├── application // 应用层相关实现│ └── adapter // 防腐层适配器接口实现│ ├── facade // 外观接口│ └── translator // 转换器,DO -> DTO├── result // 成绩聚合相关实现│ ├── adapter│ │ ├── facade│ │ └── translator│ └── repository // 成绩聚合资源库接口实现│ └── ResultRepositoryImpl.java└── totalresult // 总成绩聚合相关实现├── adapter│ ├── CourseAdapterImpl.java│ ├── facade│ └── translator└── repository└── TotalResultRepositoryImpl.java
