领域驱动设计精粹Domain-Driven-Design Distilled

好、坏、高效的设计

我们有很多同学沉醉于开发周期的紧张安排中,并没有做出合适的设计

  • 软件开发被考虑为成本
  • 开发者沉迷于追求技术,但忽视了与具体问题相结合来考虑,缺乏思考和设计
  • 数据库和数据模型的设计给予过高优先级,然后忽略了流程和操作的设计
  • 开发者与业务逻辑模型的命名并没有保持一致,所以在开发后会有很大偏差
  • 项目乐观预估时间,或者有时间枷锁产生大泥球架构
  • 开发者通常在UI、持久化组件中添加业务逻辑,或者在业务逻辑中执行持久化操作
  • 存在阻塞、缓慢和加锁的数据库查询, 阻止用户执行时间敏感的 业务操作
  • 存在的错误的抽象,开发者试图使用一般性解决方案来解决当前和未来所有的需求而不是专注现有的需求
  • 存在强耦合的服务,即访问一个服务,该服务直接调用另外一个服务。这种服务可能导致的是失败的业务逻辑可能会导致脏数据的产生

战略设计

  • 分离领域模型使用的工具是限界上下文(Bounded Context), 在显式限界上下文使用通用语言来构建领域模型
  • Subdomains 可以帮助隔离遗留的无限制的复杂系统
  • 多种限界上下文可以使用ContextMapping来进行交互

战术设计

  • entities 和value objects 实体和值对象通过Aggregate聚合模式来聚集在一起
  • Domain Events 领域事件可以使不同的限界上下文之间共享一些信息
  • 整个模型构建过程使团队获得巨大的对于业务逻辑的认知

BoundedContext

  • 通用语言: 严格严密、准确、紧凑
  • 核心域: Core Domain 界限上下文中最能影响策略设计的领域,应该是组织赖以谋生的关键领域
  • 大泥球架构就是包含太多通用语言关于模型的定义,混乱而缺乏精确边界

Ubiquitous Language

  • 不要将Core Domain仅限于名词。相反,请考虑将您的核心域表示为一组关于域模型应该做什么的具体方案。

Architecture

Subdomains

  • Core subdomain
    • 核心业务逻辑
    • 必须具备的优势
  • Supporting subdomain
    • 无现成解决解决方案
    • 支撑Core subdomain
  • Generic subdomain
    • 现成的解决方案
    • 甚至都不需要自己去开发

Context Map

不同的限界上下文之间沟通需要使用Context Map

几种不同的Map:

  • Partnership
  • Shared Kernel
    • 可能是同一团队开发
    • 统一的领域模型
  • Customer-Supplier
  • Conformist
  • Anticorruption Layer
  • Open Host Service
  • Published Language
  • Separarte Ways
  • Big Ball of Mud
    • 最好创建一个ACL反腐败层来与BBM通信

落脚于技术采取三种比较好的方式: RPC 、HTTP、 Messaging,使用SOAP或其他方法的RPC的主要问题是它缺乏健壮性,使用消息系统来通信集成的过程需要消息系统保证AT-LEAST-ONCE-DELIVERY ,并且要求消费方消费具备幂等性

有的时候使Domain Event包含足够的信息满足所有的消费者是有好处的,有的时候使Domain Events信息简单靠回查来获得消费者所需的信息。信息太多可能有安全问题或者传输成本问题

Aggregate战术设计-聚合

实体是一个独立的模型。每一个实体都会有一个唯一标示其不同的id,聚合就是多个实体的组合,其中一个叫做聚合根Aggregate root。Value Object代表的是一类不可修改的常数项的概念,它没有唯一标示,通常用来描述、衡量一个实体,如果比较实体是否相等,就看其属性的值对象是否保持一致

每个聚合形成事务一致性边界,就意味在单个聚合中,所有组成部分提交到数据库时必须要保持一致

事务不单是指技术实现的某种细节,关键是在于隔离对聚合修改,无论怎么修改,确实要保证最后的一致性。

事务边界的原因是业务,因为业务决定了在任何给定时间集群的有效状态。换句话说,如果聚合未存储在整个有效状态,则根据业务规则,执行的业务操作将被视为不正确。

聚合设计的原则:适合面向对象的方法

  • 单个事务中仅修改或提交一个Aggregate实例,这就是为什么聚合被认为是事务一致性边界的原因
  • 业务规则是确定单个事务结束时必须是完整,完整和一致的驱动因素。
  • 保护聚合内的业务不变量
  • 设计小聚合
    • 方便事务处理,也方便测试
    • SRP单一职责原则
  • 仅通过标识/id引用其他聚合
    • 有助于加强规则同一事务只能修改单一聚合实例,只有聚合标识,没有简单方法来获得直接对象引用
    • 聚合可以轻松存储在任何类型的持久化机制中,如关系数据库、文档数据库、键值存储和数据网格。
  • 使用最终一致性来更新其他聚合

有效的软件模型总是基于一组解决业务处理方式的抽象,但是,需要为每个被建模的概念选择适当的抽象级别

  • 软件编程语言模型与领域专家模型不匹配
  • 抽象级别太高,当开始为每种类型的细节建模时,将陷入深深的模范
  • 您将拥有比您需要的代码更多的代码,因为您正在尝试解决一个无关紧要的问题,这个问题首先应该无关紧要。

确定合适大小的聚合

  • 关注设计小聚合的第二原则,首先只使用一个实体创建每个聚合,这将成为聚合根,使用您认为与单个根实体关系最密切的字段、属性、属性值填充每个实体
  • 根据第一原则保护聚合边界内的业务不变量,查看是否必须更新定义的任何其他聚合以响应对聚合A1所做的修改。列出就和及其一致性规则,这些规则将指示所有基于反应的更新的时间范围
  • 询问领域专家可能需要多长时间才能进行每次基于修改的更新,是立即还是在N秒/分钟/小时/天内。
  • 对于可以在给定经过时间之后更新的每个聚合,将使用聚合设计的第四个原则,使用最终一致性来更新其他聚合

Domain Events

抽象封装域事件

一般情况下,域事件包含触发域事件行为所需的数据就可以了

命令与事件的不同在于命令可以由于各种原因被拒绝,但域事件是历史结果,不能在逻辑上被拒绝

Event Sourcing

事件源可以描述为将Aggregate实例发生的所有域事件持久化为对该Aggregate实例的更改的记录。您可以存储发生在其中的所有单个域事件,而不是将Aggregate状态作为一个整体保存

将事件流重新应用于Aggregate允许将其状态从持久性重构回内存。换句话说,当使用事件源时,由于任何原因从内存中删除的聚合完全从其事件流重构。

如果您的主要关注点之一是性能,那么您将会喜欢了解缓存和快照。首先,性能最高的聚合将是那些缓存在内存中的聚合,每次使用时都不需要从存储中重构它们。使用Actor模型将actor作为聚合[Reactive]是保持聚合状态缓存的更简单方法之一。您可以使用的另一个工具是快照,其中可以最佳地重构已从内存中逐出的聚合的加载时间,而无需从事件流重新加载每个域事件。这转换为维护数据库中Aggregate(对象,actor或记录)的某些增量状态的快照。

Event Storming

Event Stroming是一种快速设计技术,旨在让领域专家和开发人员参与快节奏的学习过程

命令、聚合、域事件都可以算作时间估算单位