参考《解构-领域驱动设计》

DDD 概要与实践感悟

复杂性

  • 系统的复杂性往往并不在技术上,而是来自领域本身、用户的活动或业务服务。当这种领域复杂性在设计中没有得到解决时,基础技术的构思再好也是无济于事。而系统的复杂度体现在三个方面:规模结构变化
  • 规模:指的是系统所支持的功能点,以及功能点与功能点之间的的关系。DDD 通过子领域,限界上下文,聚合等模式对问题进行拆分和归类,不断收窄问题域,保证聚合边界内所解决的问题集合足够收敛和可控。
  • 结构:指的是系统架构。系统架构是否分层;若分层,每层划分的职责边界是否清晰;架构的基本管理单元是什么,它决定了架构演进时的复杂度。DDD 通过分层架构,独立出领域层,且架构中的每层都有清晰的职责。整体架构的基本管理单元是聚合,它是一个完整的、自治的管理单元,当需要进行服务拆分时,可以直接以聚合作为基本单元进行拆分。
  • 变化:指的是系统响应需求变化的能力。快速响应变化的有效手段是分离不易变逻辑和易变逻辑,”以不变应万变”。而通过分层架构独立的领域层正是不易变的逻辑。领域层是对领域知识的封装,其提供的领域服务具有经验性和前瞻性,是对领域内稳定的领域规则的表达。而领域层以外的应用层和基础设施层则是易变逻辑的封装。保证核心的独立和稳定,通过在调整应用层和基础设施层来实现快速响应需求变化。

    领域驱动

  • 领域驱动指的是以领域作为解决问题切入点,面对业务需求,先提炼出领域概念,并构建领域模型来表达业务问题,而构建过程中我们应该尽可能避免牵扯技术方案或技术细节。而编码实现更像是对领域模型的代码翻译,代码(变量名、方法名、类名等)中要求能够表达领域概念,让人见码明义。

    实践经验

  • 思维模式转变

    • 实践 DDD 以前,我最常使用的是数据驱动设计。它的核心思路针对业务需求进行数据建模:根据业务需求提炼出类,然后通过 ORM 把类映射为表结构,并根据读写性能要求使用范式优化表与表之间的关联关系。数据驱动是从技术的维度解决业务问题,得出的数据模型是对业务需求的直接翻译,并没有蕴含稳定的领域知识/规则。一旦需求发生变化,数据模型就得发生变化,对应的库表的设计也需要进行调整。这种设计思维导致变化从需求穿透到了数据层,中间并没有稳定的,不易变的层级进行阻隔,最终导致系统响应变化的能力很差。
  • 协同方式转变

    • 过去由产品同学提出业务需求,研发同学根据业务需求的 tapd 进行技术方案设计,并编程实现。
    • 这种协同方式的弊端在于:无法形成能够消除认知差异的模型。产品同学从业务角度提出用户需求,这些需求可能是易变的、定制化的,而研发同学在缺少行业经验的情况下,往往会选择直译,即根据需求直接转换为数据模型。而研发同学从技术实现角度设计技术方案,其中涉及很多的技术细节,产品同学无法从中判断是否与自己提出的业务诉求和产品规划相一致,最终形成认知差异。且认知差异会随着迭代不断被放大,最后系统变成一个大泥球。

      领域驱动设计统一过程(DDDRUP)

  • 解构-领域驱动设计》提出的 DDDRUP 给出了更细致的步骤、步骤与步骤之间的衔接,以及明确的阶段里程碑,最重要的是 DDDRUP 可以串联 DDD 的所有概念和模式,非常便于初学者做知识梳理和上手实践。下文我会依照 DDDRUP 的步骤流程进行讲述,而非战略设计+战术设计的思路

  • dddrup.jpg

    分层

    ddd分层.jpg

    用户接口层

  • 用户接口层的核心职能:协议转换和适配、鉴权、参数校验和异常处理。

    1. ├── controller //面向视图模型&资源
    2. ├── ResultController.java
    3. ├── assembler // 装配器,将VO转换为DTO
    4. └── ResultAssembler.java
    5. └── vo // VO(View Object)对象
    6. ├── EnterResultRequest.java
    7. └── ResponseVO.java
    8. ├── provider // 面向服务行为
    9. ├── subscriber // 面向事件
    10. └── task // 面向策略
    11. └── TotalResultTask.java

    应用层

  • 应用层的核心职能:编排领域服务、事务管理、发布应用事件。

    1. ├── assembler // 装配器,将DTO转换为DO
    2. ├── ResultAssembler.java
    3. └── TotalResultAssembler.java
    4. ├── dto // DTO(Data Transfer Object)对象
    5. ├── cmd // 命令相关的DTO对象
    6. ├── ComputeTotalResultCmd.java
    7. ├── EnterResultCmd.java
    8. └── ModifyResultCmd.java
    9. ├── event // 应用事件相关的DTO对象, subscriber负责接收
    10. └── qry // 查询相关的DTO对象
    11. └── service // 应用服务
    12. ├── ResultApplicationService.java
    13. ├── event // 应用事件,用于发布
    14. └── adapter // 防腐层适配器接口

    领域层

  • 代码组织以聚合为基本单元

    1. ├── result // 成绩聚合
    2. ├── entity // 成绩聚合内的实体
    3. └── Result.java
    4. ├── service // 领域服务
    5. ├── ResultDomainService.java
    6. ├── event // 领域事件
    7. ├── adapter // 防腐层适配器接口
    8. ├── factory // 工厂
    9. └── repository // 资源库
    10. └── ResultRepository.java
    11. └── valueobject // 成绩聚合的值对象
    12. ├── GPA.java
    13. ├── ResultUK.java
    14. ├── SchoolYear.java
    15. └── Semester.java
    16. └── totalresult // 总成绩聚合
    17. ├── ... 这段有点长,其代码结构与成绩聚合一致,因此省略 ...

    基础设施层

  • 该层主要提供领域层接口(资源库、防腐层接口)和应用层接口(防腐层接口)的实现。

  • 代码组织基本以聚合为基本单元。对于应用层的防腐层接口,则直接以 application 作为包名组织。
    1. ├── application // 应用层相关实现
    2. └── adapter // 防腐层适配器接口实现
    3. ├── facade // 外观接口
    4. └── translator // 转换器,DO -> DTO
    5. ├── result // 成绩聚合相关实现
    6. ├── adapter
    7. ├── facade
    8. └── translator
    9. └── repository // 成绩聚合资源库接口实现
    10. └── ResultRepositoryImpl.java
    11. └── totalresult // 总成绩聚合相关实现
    12. ├── adapter
    13. ├── CourseAdapterImpl.java
    14. ├── facade
    15. └── translator
    16. └── repository
    17. └── TotalResultRepositoryImpl.java