1
头图

DDD要解决什么问题?

第一本关于DDD的书, 书名就是<领域驱动设计- 软件核心复杂度应对之道>, 表明DDD其实就是为了应对软件复杂度的挑战.

是什么造成了软件的复杂度?

  1. 规模
  2. 结构
  3. 变化

规模

其实包括了两方面, 一方面是因为需求越来越多, 功能越来越多. 另一方面是因为功能点之间是有联系的, 牵一发而动全身, 因此规模随着业务的发展很容易呈指数级的上升.

结构

另一方面文中提到很大程度是因为质量属性造成结构复杂. 我觉得其实还有别的方面. 我们来分析一下我们设计结构的目的:

  1. 性能, 资源利用相关的. 支持横向拓展的架构, 支持分库分表的结构, 微服务提高资源利用效率. 总线转分布式通讯
  2. 为了提高代码可维护性. 例如我们设计分层架构就是为了不同层次职责分离, 提高可读性和可拓展性
  3. 提升沟通协作和管理效率. 例如要符合康威定律(任何组织在设计一套系统时, 所交付的设计方案在结构上都与该组织的沟通结构保持一致)
  4. 降低业务复杂度. 例如我们拆分出不同的子领域来避免认知过载.

简单的逻辑是不需要结构的, 但随着逻辑的增多, 我们需要引入结构来降低复杂度, 结构一定程度上也会让逻辑变得复杂, 但只要收益比成本要高, 增加结构就是合理的. 但由于对结构的误用, 或者定义不够清晰, 职责划分不同明确导致不仅没有降低复杂度, 而且还增加了复杂度.

例如我们对性能过度考虑, 为应对高性能的挑战而增加各种中间件和进行异步化改造. 又例如我们虽然进行了分层架构的设计, 但到处都有不符合分层原则的代码出现. 或者我们错误的使用了康威定律, 导致了软件复用性大大降低, 提高了维护和拓展成本.

上面两个原因影响的其实是理解能力, 而变化影响的是我们预测能力.

变化

预测能力的不足会让我们产生过度设计或者设计不足. 设计的本质在于如何和做取舍, 如何平衡不同条件的影响使其达到最优. 如何抽象才是最合理的, 这点很大程度上依赖每个人对业务的理解和经验的积累.

设计元模型包含什么

领域驱动设计是以领域作为驱动力, 使用领域模型作为核心来进行设计.

为了降低规模, 领域又可以拆分成子领域. 为了让资源得到最有效的利用, 子领域可以分为核心子领域, 支撑子领域, 通用子领域, 依靠不同类型的子领域来合理分配资源.

使用限界上下文来确定业务能力的自治边界. 通过上下文映射来表达限界上下文的协作关系.

通过分层架构将领域独立出来, 隔离技术复杂度和业务复杂度.

通过了领域专家进行沟通, 在统一语言的指导下获取到领域模型.

领域模型包含了实体, 值对象, 领域服务领域事件. 领域逻辑都会封装到这些对象中.

聚合是最小的业务单元, 可以封装多个实体或者值对象, 并维持边界范围内业务完整性.

工厂仓储负责聚合的生命周期管理.

问题空间和解空间

软件世界可以一分为二, 一边是问题空间, 包含了用户的痛点, 爽点, 痒点, 是用户的问题, 是软件的核心价值. 另一边是解空间, 包含了对问题的解决方案和具体实现.

软件系统的构建实则是对问题空间的求解, 以获得构成解空间的设计方案.

我们只有明确了用户的问题是什么, 才能找到最合适的解决方案.

用户可能会说, 在订单提交后, 我要修改订单的功能. 其实修改订单是一个解决方案, 属于解空间的内容. 那用户的问题是什么呢? 用户是担心不小心点错了. 
那根据这个问题我们可以有非常多的解决方案, 例如提交订单前后商品列表的确认.提交按钮设计得更合理一些. 增加取消订单的入口让用户重新提交订单等等, 在成本和体验之间选择一个平衡点, 让自己有更多的选择.

解空间的解决方案不仅要解决问题

  1. 控制软件系统的复杂度.
  2. 提升复用能力. 如果问题具有相似性, 解决方案就有复用的可能. 通过抽象寻找到不同问题的共性时, 相同的解决方案也可以运用到不同的问题.
  3. 可以响应问题的变化.

其实从不同的视角分析出来的需求是不同的, 解决方案也是不同

技术视角和用户视角

例如现在有个需求, 希望在使用微服务背景下, 服务的拆分和整合不影响API. 因为现在是不同服务提供不同API前缀来进行匹配, 因此需要进行网关来解决这个问题.

技术视角 -> 方案级需求

技术视角的思路寻找业界的网关方案, 根据网关的通用维度, 例如功能, 性能, 可拓展性, 成熟度, 社区活跃度等进行分析, 选择一个各方面都比较全面, 能满足需求的方案.

用户视角 -> 问题级需求

用户视角的思路是明确当前问题是什么, 希望未来达到的状态是什么. 然后选择一个最匹配最合适的方案.

方案级需求的优点是效率高, 可以应用的场景会相对广一些. 但缺点是不够精准的解决问题.

例如按照技术视角选择的方案虽然能解决我的问题, 但也可能是间接解决的, 例如我是想解决服务拆分整合导致API变化问题, 如果API能自动注册, 对用户无感自然是最好的, 但大部分的网关方案只提供了配置的功能而非自动配置, 虽然也能解决问题, 但不是特别友好.
又例如选择的网关的功能确实很强大, 但很多其实项目里面用不上, 这样其实也会让网关更加复杂, 提升了维护的难度.

战略设计和战术设计

战略设计控制和分解了战术设计的边界和粒度, 战术设计则以实证角度验证领域模型的有效性, 完整性和一致性, 进而以迭代的方式分别完成对限界上下文与领域模型的更新与演进, 各自形成设计过程的闭环.

战略设计

  1. 对问题空间进行合理分解, 识别不同的领域, 确定子领域的目标, 边界和建模策略
  2. 对问题空间进行解决方案的架构映射. 划分上下文.

战术设计

使用模型驱动设计, 主要是构建三种模型

  1. 领域分析模型
  2. 领域设计模型
  3. 领域实现模型

业务复杂度和技术复杂度

软件的复杂度也可以分为业务复杂度和技术复杂度

业务复杂度: 例如电商里面优惠卷, 包含了各种规则的优惠卷, 满100元减10元, 2件减10元, 2件8折等等, 也有时效性, 覆盖的商品, 覆盖的人群等等逻辑. 要把这些规则逻辑理清本来就很复杂, 这就是业务的复杂度.

技术复杂度: 为了应对质量属性, 例如为了应对三高, 高并发, 高可用, 高性能, 我们会引入很多中间件, 为了划分弹性边界而引入微服务. 这些就是技术复杂度.

理想情况下, 我们应该保证业务规则与技术实现是正交的.

image.png

图很生动, 在一个容器里面加入了业务复杂度和技术复杂度两种易燃气体, 会让容器内部变得更加不稳定, 简单的逻辑变更, 人员变更很容易成为引爆点, 导致发生爆炸.

领域模型

领域模型富含领域知识, 包含了业务概念, 业务规则, 和业务概念之间的关系.

模型是封装, 实现了对业务逻辑的隐藏; 模型是抽象, 提取了领域知识的共同特征, 保持了面对变化时良好拓展的可能性.

模型是面向领域知识的, 而非技术实现, 应该对领域建模和技术实现进行关注点分离, 保证领域模型的纯粹性.

领域模型不是完成后就可以束之高阁的, 领域模型的构建有3个阶段: 建立领域模型 -> 重构领域模型 -> 精炼领域模型

DDD如何控制软件复杂度

  1. 规模: 通过分而治之拆分领域, 然后根据业务能力, 划分出不同的独立自治的限界上下文.分而治之的过程首先是自顶向下持续分解的过程, 然后又是自底向上进行整合的过程.
  2. 结构: 需求分为业务需求和质量需求, 通过分层架构隔离业务复杂度和技术复杂度. 理想情况下, 我们应该保证业务规则与技术实现是正交的, 避免业务复杂度和技术复杂度相互影响.
  3. 变化. 领域模型不仅封装了领域知识, 而且还进行了适当的抽象来提高拓展性. 至于抽象是否合理应该结合业务进行分析, 需要对业务的发展有一定的洞察, 才能设计出拓展性较强的领域模型.

领域驱动设计统一过程

DDD具有很强的生命力和开放性, 只要不违背它的核心原则, 可以根据一些最佳实践拓展DDD的方法论. 例如CQRS, 事件风暴, 测试驱动开发等等. 张逸老师提出了领域驱动设计统一过程, 并对每个过程进行了细致的描述, 融合了很多工具和方法论, 具有非常很强的指导性. 后续几个大的篇章都是围绕着这个统计过程进行论述.

DDD的不足:

  1. 缺乏规范的统一过程. 书中甚至把战术模式放到前面, 让人以为没有战略设计也可以进入战术设计.
  2. 缺乏与之匹配的需求分析方法. 虽然从书中可以意识到缺乏好的需求分析方法会导致无法建立一个合适的模型, 但书中并没有指出有什么合适的需求分析方法.
  3. 缺乏规范化, 具有指导意义的架构体系. 4层架构过于抽象, 无法满足日新月异的技术发展. 例如用一个基础设施层囊括了所有的中间件, 第三方服务等等.
  4. 缺乏固有的指导方法. 没明确哪些时候应该做什么, 应该输出什么, 参与的角色是什么.

统一过程分为3个连续的阶段

  1. 全局分析阶段: 目标是探索和分析问题空间. 包含了对目标系统执行价值需求分析与业务分析.
  2. 架构映射阶段: 分别从组织级, 业务级, 系统级3个层面对问题空间进行求解, 完成从问题空间到解空间的架构映射
  3. 领域建模阶段: 就是对问题空间求解的过程, 目标就是建立领域模型. 领域模型分为分析模型, 设计模型, 实现模型.

image.png

横坐标是统一过程的不同阶段, 每个阶段的关注点会有所不同.
纵坐标是工作流, 也就是工作的内容, 包含了输入项, 工具和方法和输出项.

统一过程的工作流分为过程工作流和支撑工作流. 每个工作流都可能会贯穿多个阶段, 只是在不同阶段的侧重点可能会不同.

全局分析阶段

  • 价值需求分析: 明确愿景, 识别相关方, 识别系统范围, 明确目标系统当前的状态和预期的未来状态.
  • 业务需求分析: 根据不同的业务流程或者价值链, 通过划分不同阶段的场景, 分析不同的业务能力, 进而划分出不同的子领域, 并识别子领域是属于核心还是支撑还是通用.

image.png

架构映射阶段

  • 组织级映射:是站在组织的角度, 较为宏观的分析目标系统与外部系统的关系.
  • 业务级映射: 根据语义相关性和功能相关性对业务服务表达的业务知识进行归类, 识别出边界较为合理的限界上下文. 不同的上下文通过上下文映射来表达协作关系.
  • 系统级映射: 针对单个限界上下文, 建立系统分层架构. (可能理解有误, 埋个坑, 等后续看完再来更正)

领域建模阶段

  • 领域分析建模: 识别业务概念和业务规则, 形成领域模型概念图. 分析模型是一个很好的沟通工具, 我们可以通过分析模型和团队成员, 例如产品, 测试等进行沟通, 并在基础上形成统一语言, 指导分析模型的设计.
  • 领域设计建模: 识别聚合, 并识别聚合内的值对象和实体, 形成以聚合为核心的静态类图. 主要是开发人员根据多个不同维度, 例如业务复杂度, 拓展性, 性能等等去分析聚合的选择和粒度是否合理.
  • 领域实现建模: 代码实现. 需要和分析模型, 设计模型, 领域模型保持一致. 通过分层架构隔离业务和技术复杂度, 让代码也能很好的表达业务逻辑.
    image.png

简简单单
18 声望1 粉丝