学习领域驱动设计 (DDD) - Abrodi

22-12-22 banq

我正在阅读Vlad Khononov的《学习领域驱动设计》一书。通过阅读开头和它的评论,我可以看出它可能是关于领域驱动设计的最好的书。
令人兴奋的是,作者自己在一开始就解释了他是如何努力学习领域驱动设计和理解事物是如何连接的。

领域驱动设计
领域驱动设计(DDD)是一种专注于领域的软件设计方法。它涉及在所有利益相关者(包括开发人员、业务分析师和领域专家)之间建立对领域的共同理解,并使用这些知识来指导软件的设计和开发。
通过这种方式,我们可以针对业务及其用户的特定需求构建软件设计,并且可以轻松扩展和维护。
DDD可以分为战略和战术两部分。

一、战略设计
DDD 的战略工具用于帮助使软件的设计与业务目标和目标保持一致,并指导开发过程。

1、分析业务领域
业务领域是指公司运营的特定领域或问题。
为实现其业务领域的目标和指标,公司必须在多个子领域开展业务。子域是具有其独特特征和要求的特定区域。子域分为三种类型:核心域、通用域和支持域。

  • 核心域:核心子域使公司有别于竞争对手,例如发明新产品或优化流程以节省成本。
  • 通用域:通用子域是复杂的业务活动,所有公司都以相同的方式执行并且不提供竞争优势。这些子域需要经过实战检验的实施,因为创新或优化不是必需的。
  • 支持域:支持子域支持业务,但与核心子域相比不提供任何竞争优势。


2、发现领域知识
沟通和知识共享对于软件项目的成功至关重要。工程师需要了解业务领域并与领域专家共享相同的模型,以设计和构建能够适应业务需求的出色软件。

3、模型
模型是事物或现象的简化表示,它有意强调某些方面而忽略其他方面。考虑到特定用途的抽象。
一个有效的模型是地图。它不包含所有细节,但足以实现其目的。



可能值得指出的是,抽象的目的不是模糊,而是创造一个新的语义层次,在这个层次上人们可以绝对精确。

4、无处不在的语言
领域驱动设计引入了一种工具来帮助解决这个问题:无处不在的语言。所有利益相关者用来交流的语言。
不应将领域专家的语言翻译成其他语言以供开发人员理解。领域专家、开发人员和其他所有人都应该通过语言以相同的方式交流和理解上下文。

在培养一种无处不在的语言时,我们实际上是在构建业务领域的模型。该模型应该捕捉领域专家的心智模型——他们关于业务如何运作以实现其功能的思维过程。
基于 wiki 的词汇表和 Gherkin 测试等工具可以帮助记录和维护一种无处不在的语言。

5、管理领域复杂性
每当我们发现领域专家的心智模型存在冲突时,我们就需要将无处不在的语言分解为单独的有界(限界)上下文。通用语言在其限界上下文中应该是一致的,但相同的术语在不同的限界上下文中可以具有不同的含义。

将无处不在的语言分解为有界上下文是一个设计决策,同时会发现子域。

一个团队可以拥有多个限界上下文,但限界上下文只能由一个团队处理,不能由多个团队处理,以避免误解和误解。

每个有界上下文的生命周期都与其余部分分离。每个有界上下文都可以独立于系统的其余部分而发展。但是,限界上下文必须一起工作才能形成一个系统。

您的限界上下文的大小将取决于您的特定领域。有时有一个小的有界上下文更清楚,有时它没有意义,而更大的上下文更合适。

6、集成限界上下文
限界上下文可以独立实现和处理。另一方面,他们必须整合形成最终系统,这是我们的目标。
这些是契约,限界上下文必须协作的点。
在最简单的场景中,所有限界上下文都由一个团队处理。另一种情况是多个团队在系统的不同限界上下文中工作。不同的团队可以通过两种方式进行协作:伙伴关系和共享内核模式。

  • 伙伴合作关系

每当其中一个限界上下文发生影响另一个限界上下文的更改时,团队就会以临时方式进行通信。他们把事情搞清楚并立即合作。
这适合处于相同或相似时区的团队,因为它是同步发生的。
  • 共享内核

有界上下文重叠。他们共享在两种情况下都使用的必要部分。此范围内的任何更改都需要立即反映在两个限界上下文中。


您可能希望拥有 CI 或类似的东西,以确保每当共享内核中的某些内容发生更改时,它都会重新运行必要的检查,例如两个有界上下文(如果不是更多)中的测试。
当复制成本高于共享内核引入的强耦合时,这是合适的。

  • 客户-供应商

客户-供应商关系是一种上游-下游关系。一个充当客户,另一个充当供应商。大多数时候我们的力量不平衡,但两支球队都可以独立取得成功。


三种模式:遵从者模式、反腐败层模式和开放主机服务模式。

  • 上游遵从下游conformist

在这里,下游有界上下文会根据上游提供的任何内容进行调整。上游环境可以快速发展,而下游环境必须在上游环境的变化影响下游环境时进行调整。这种模式有利于上游上下文。


  • 反腐层

这种模式仍然对上游有利,但下游的限界上下文却不愿意遵从。



相反,通过反腐败层,它将上游限界上下文的模型转换为适合其需求的模型。

  • 开放主机服务

这种模式有利于消费者。供应商提供与其上下文分离的公共接口,这意味着公共接口和供应商可以独立发展。
公共界面接口使用的不是通用语言,而是一种已发布的语言,一种消费者可以理解的语言。





7、上下文映射
上下文映射是我们可以创建的图表,用于理解不同的限界上下文如何集成。它表明:

  • 高层设计
  • 沟通模式
  • 组织问题

它应该从一开始就实施并随着集成的变化而更新,以便它始终反映当前状态。


二、战术设计
战术设计是关于如何做的。它是关于事情如何实现的,更多是在技术层面上。

1、实现简单的业务逻辑
适用于支持子域等简单业务逻辑的两种模式:Transaction Script 和 Active Record。

  • 交易事务脚本

此模式用于将系统内的业务逻辑组织为简单的过程脚本。这些过程确保每个操作都是一个事务,要么失败要么成功,并且中间没有状态。
  • 活动记录Active Record

当业务逻辑仍然简单但需要对复杂的数据结构进行操作时,可以使用此模式,然后可以将这些数据结构实现为活动记录。活动记录对象是一种数据结构,提供简单的 CRUD 数据访问方法。


2、处理复杂的业务逻辑
域模型是包含行为和数据的域的对象模型。DDD 的战术模式是此类对象模型的构建块:

  • 聚合Aggregates
  • 值对象
  • 领域事件
  • 领域服务


值对象
业务领域中可以建模为值的特定事物应该是值对象。值对象是特定值的模型,它包含数据和行为:用于操作数据的方法。值对象不是唯一的并且是不可变的。
一个例子是电话号码。

聚合Aggregates
聚合是实体的层次结构。与值对象相比,实体始终是唯一的。它是由它的身份而不是它的属性来定义的。它可以由值对象组成。实体用于模拟真实世界的对象。一个例子是一个人。
聚合边界中包含的数据必须高度一致才能实现其业务逻辑。聚合中的数据只能通过其公共接口进行修改。
聚合充当事务边界,因此,它的所有数据都必须作为一个原子事务提交给数据库。

领域事件
领域事件由聚合发布。这允许聚合与可以订阅这些事件的外部实体进行通信。

领域服务
领域服务是无状态对象,用于封装不特定于任何聚合或值对象的业务逻辑。它们通常用于执行涉及多个不同聚合或值对象的操作。



三、时间维度建模
当您需要深入了解系统时,您可以使用事件溯源模式在域模型的聚合中对时间维度进行建模。
在事件溯源领域模型中,对聚合状态的所有更改都作为一系列域事件存储在事件存储中。系统可以表示为其过去事件的总和。
事件溯源领域模型的好处:

  • 时间旅行
  • 深刻的洞察力
  • 改进审计和调试



四、架构模式
架构模式是关于我们如何组织代码库和划分职责的。本章介绍了三种模式。

1、分层架构
本质上,这种模式将代码库组织成三层:

  • 表示层:触发程序行为的部分,同步和异步
  • 业务逻辑层:包含业务逻辑的部分。这是软件的核心。
  • 数据访问层:这是持久性区域。它的含义比数据库更广泛。它可以是实例的云存储。





2、端口和适配器
这种模式也称为六边形架构。这是因为业务逻辑被移到了中心,而不是依赖于它下面的另一层。
请记住我们是如何提到业务逻辑是软件的核心,在这种类型的架构中,它作为软件的核心真正闪耀,一切都围绕着它发展。

  • 端口代表业务逻辑和外部依赖关系之间的接口
  • 适配器实现端口并处理与外部依赖项的通信





3、命令-查询责任分离 (CQRS)
此模式将应用程序的读取和写入职责分开。我们有读取和命令端。

  • 命令执行模型负责数据的写入
  • 读取模型负责读取数据

由于读写分离,我们在中间有投影引擎。它的职责是向应用程序的读取端提供最新的数据视图。通过使投影与命令端的数据保持同步,投影引擎确保查询端始终可以访问最新版本的数据。




五、通信模式
这一章内容相当广泛。它引入了多种不同的模式来集成系统的组件。

  • 模型翻译以实施反腐败层或开放主机服务。
  • 无状态转换会在运行中发生,因为传入 (OHS) 或传出 (ACL) 请求已发出。
  • 有状态翻译更复杂,因为它们需要数据库。

  • 可靠发布聚合领域事件的发件箱模式。它是可靠的,因为它在数据库中存储了每个需要发布的域事件的记录。 发件箱图案的图像:

  • Saga 模式实现简单的跨组件业务流程,这些将是跨多个有界上下文的实例的流程。


Saga 模式的图:


  • 如果您正在处理一个更复杂的场景,其中saga 模式不适合(有条件的情况),那么请使用流程管理器模式。

流程管理器模式:




六、在实践中应用领域驱动设计
1、设计启发式
:
启发式是经验法则。我们可以将其用作指南,但这并不意味着它总是正确的。本章的作者深入探讨了何时使用模式、架构,以及最重要的是什么测试方法适合它们。

战术设计决策树:



2、不断发展的设计决策
变化是不变的。我们必须拥抱变化并适应以保持企业竞争力。随着领域的发展,我们必须更改系统的设计以匹配当前的业务需求。
组织结构也可能在此过程中发生。然后,我们必须以适合新团队之间沟通与合作的方式设计软件。

  • 如果子域的功能增加,找到更细粒度的子域以做出更好的设计决策。
  • 避免让限界上下文尝试做太多事情。确保限界上下文中的模型仅用于解决特定问题。
  • 使聚合尽可能小。使用强一致性数据的启发式方法来寻找将业务逻辑移动到新聚合中的机会。


3、事件风暴
EventStorming 是一个团队可以用来发现和学习领域的研讨会。团队不仅会以有界的上下文结束,而且他们会通过讨论和知识共享从彼此身上学到很多东西。

  • 建立一种无处不在的语言
  • 对业务流程建模
  • 探索新的业务需求
  • 恢复领域知识

会议需要 2 到 4 个小时才能完成。如果您决定这样做,请参考本书的每一步。这些步骤应用作指南,但您可以根据团队的需要对其进行调整。
Miro/Whiteboard 在会话结束时将如何显示的图:





七、现实世界中的领域驱动设计
本章是关于实际应用领域驱动设计的。不是每个人都是 DDD 方面的专家,而且 DDD 不是你要么全力以赴要么什么都不做的事情。
应用 DDD 的第一步应该是分析组织的业务领域及其战略。
在应用 DDD 提供的工具时,我们必须循序渐进。大的重写风​​险太大。最好采取安全和渐进的步骤。
EventStorming 非常适合建立对领域和无处不在的语言的基础共享理解。我们还可以用它来不断地重新发现知识并理解事物的发展。


八、与其他方法和模式的关系
1、微服务

微服务不是让整个系统成为一个服务,而是将整个系统分解成多个更小的服务。起初,这看起来不错,因为每项服务都有特定的重点,因此更改的理由更少。如果您在一个组织中有多个团队,在系统的不同部分工作,这也很好。



然而,这并不总能降低复杂性。我们需要在这里考虑两个复杂性:

  • 全局复杂性:整个系统的复杂性。所有的微服务最终共同组成了整个系统。
  • 本地复杂性:每个服务的复杂性。服务的重点越窄,复杂性越低。

太多的微服务增加了全局的复杂性,而整个系统具有很高的局部复杂性。我们需要在这里找到一个中间立场。




2、深度模块
微服务应该是深度的。深度模块的概念来自《软件设计哲学》一书。深度服务封装局部复杂性并降低全局复杂性。
添加图片注释,不超过 140 字(可选)
一个好的启发式方法是将服务与子域的边界对齐。:

  • 子域专注于什么
  • 子域自然很深
  • 子域包含彼此密切相关的用例




3、限界上下文
微服务和限界上下文经常互换使用。这是一种困惑。它们具有不同的含义,但是,所有微服务都是有界上下文,但情况并非总是如此。
该系统可以分解为范围广泛的有界上下文,这意味着它们具有太多功能而无法成为微服务。我们不要忘记,微服务是严格的物理边界。





九、事件驱动架构 (EDA)
EDA 中,系统的组件通过异步事件消息相互通信。一个组件要么有订阅者,要么被订阅到另一个组件。
添加图片注释,不超过 140 字(可选)
有两种类型的消息:

  • 事件:描述已经发生的变化的消息
  • 命令:描述必须完成的操作的消息


1、事件
至于事件,有三种类型的事件:

  • 事件通知:这会通知订阅者事件已经发生,但是,有关更改的附加信息,订阅者必须对数据进行后续查询。这里的好处是数据是高度一致的。
  • 事件承载状态传输 (ECST):这不仅告诉订阅者发生了什么,还包括所有必要的数据。改变的不仅仅是数据。ECST 包含的数据可以由订阅者缓存。这里的缺点是数据最终是一致的。但是,这消除了对后续查询的需要。
  • 领域事件:该事件描述了生产者业务领域的变化。领域事件有点介于事件通知和 ECST 之间。但是,领域事件旨在建模和描述业务领域。与事件通知相比,预计不会与其他组件集成。

书中不同对象的外观示例:

eventNotification = {
"type": "marriage-recorded",
"person-id": "01b9a761",
"payload": {
"person-id": "126a7b61",
"details": "/01b9a761/marriage-data"
}
};
ecst = {
"type": "personal-details-changed",
"person-id": "01b9a761",
"payload": {
"new-last-name": "Williams"
}
};
domainEvent = {
"type": "married",
"person-id": "01b9a761",
"payload": {
"person-id": "126a7b61",
"assumed-partner-last-name": true
}
};



十、陷阱
作者提出了他在实践中看到的三个陷阱:

  • 实现耦合:当订阅者订阅一个组件并耦合到它们的实现细节时,就会发生这种情况。意思是,提供了一个公共接口来将生产者的实现细节与其订阅者分离。
  • 功能耦合:当生产者的多个订阅者每次收到事件消息时总是必须以相同的方式投射状态时,就会发生这种情况。
  • 时间耦合:只要某些事情需要按照严格的顺序发生才能起作用,就会发生这种情况。


1、好的例子
EDA 做得很好的一个很好的例子。它不包括提到的陷阱:




真漂亮。组件是解耦的,状态已经被预测。这给了每个组件更多的自主权。减少需要完成或修复的压力和精神负担。

2、启发式
实施 EDA 的启发式方法:

  • 假设可能发生的最坏情况。
  • 使用公共和私人活动。在公开实施细节时要谨慎。确保 OHS 使用限界上下文的已发布语言。
  • 评估一致性要求以确定适合哪种类型的事件。



结论
这本书是非凡的。它对初学者友好,并且比仅仅触及表面更深入。这也很实用。
我喜欢它,它一定是目前关于 DDD 的最好的书。

1