如何学习领域驱动设计? - Vladik Khononov


Vladik Khononov 是《学习领域驱动设计》一书的作者。在这一集中,我们深入讨论了领域驱动设计 (DDD) 和 Vlad,首先分享了为什么理解业务领域在软件工程中至关重要,以及 DDD 如何帮助在领域专家和软件工程师之间建立共同的理解。Vlad 随后解释了 DDD 中的两个重要设计,即战略和战术设计,以及它们之间的关系。对于每个设计,Vlad 都涉及到一些重要的模式,例如有界上下文、上下文映射、子域、聚合、实体和值对象。最后,Vlad 就将 DDD 应用于棕地项目以及这些项目如何从一些 DDD 实践中获得最大收益提供了很好的建议。  
 
了解业务领域的重要性

  • 业务逻辑是软件的核心是有原因的。这就是构建软件的原因。
  • 你不是在构建一个软件来展示你的数据库有多快和可扩展,或者你的用户界面有多性感。您正在构建用于解决特定业务问题的软件。这个问题可以使某人更有效率,简化业务流程。如今,许多企业都建立在软件之上。那是他们的业务领域。
  • 软件工程最大的谎言是“要做软件工程师,你要写代码,而我是个骨子里的内向者”。我们不是为了写代码而写代码。我们正在编写代码来解决某人的问题。这个人,他们是领域专家。他们是最了解我们正在建模并在代码中实现它的业务领域的人。为了有效地解决这个问题,我们必须与他们沟通,与他们交谈。我们必须有效地与人们进行沟通和协作。所以软件工程不仅仅是代码。
  • 这就是为什么与领域专家的互动在实施软件中起着关键作用。你必须确保你理解你正在解决的问题。这很关键。如果不先了解问题,就无法提供软件解决方案。要么是错误的解决方案,要么是正确的解决方案,但对于不同的问题,两者都是无用的。
  • 如果我们将这种方法命名为领域驱动设计,我更愿意将其扩展一下,并说业务领域驱动的软件设计。所以我们这里有三个组件。
    • 首先,我们有一个必须掌握和理解的业务领域。
    • 其次,我们有软件设计。这就是我们在这里建造的。
    • 而我们中间有个“驱动”这个词,用数学语言来说,就像软件设计等同于业务领域的功能一样。
  • 如果您没有正确地获得该业务领域,那么您将获得错误的软件设计。您可能正在解决错误的问题,或者您可能正在尝试解决正确的问题,但效率低下。
  • 其次,正如领域驱动设计指导我们的那样,有很多设计决策是基于我们对业务领域的了解。
    • 这是战略设计决策,比如我们将使用哪些粗粒度组件来实现我们的系统?他们将如何相互交流?我们如何将这项工作分配给软件工程团队?
    • 我们有战术设计决策,例如,我们将使用什么设计模式来实现每个组件?我们将如何实现这个业务逻辑?我们将如何协调组件不同部分的交互?这是我们的内部架构。当然,我们有高级架构。组件如何相互协作?
  • 如果您错误地掌握了业务领域知识,那么这是做出一些错误决策的好机会,包括战略和战术决策。所以这就是为什么我们必须确保在我们尝试为它设计解决方案之前对问题领域有足够的了解。

 
领域驱动设计如何提供帮助
  • 我其实相信【软件项目的高失败率是可以解决的】。如果我们寻找这么多软件项目失败的原因——如果您查看针对该主题进行的研究,您会发现其中相当多的(如果不是全部)由于沟通问题或与其他相关的问题而失败沟通。它可以是团队成员之间的沟通,软件工程师和领域专家之间的沟通,管理和工程团队之间的沟通问题。
  • 领域驱动设计的核心原则是在项目的不同利益相关者之间建立共同的理解,以便他们可以用同一种语言谈论和推理该软件系统。在 DDD 术语中,它被称为无处不在的语言。
  • 一旦我们能够促进同一种语言的交流,而无需在知识到达目的地之前进行多次翻译,一切都会变得更有效率。这可以解决我们大量的返工,很多错误的假设。最终,我相信它将带来更多成功的软件项目。
  • 一切都在变化,尤其是如果您正在为一家初创公司工作。不仅我们作为软件工程师不知道如何以最有效的方式实现事物,而且业务人员也不知道他们将如何解决他们所关注的某个问题。
  • 他们知道,“嘿,我们想做点什么。我们要怎么做?我们将进行迭代。我们将测试一些解决方案,直到找到最优化的解决方案”。
  • 在那段时间里,一切都会改变。对业务领域的理解将不断发展。不仅你会越来越多地了解它,而且那些商业人士,那些领域专家,他们也会学习。随着他们获得更多知识,这必须反映在软件设计决策中。
  • 如果该组件业务领域发生变化,它必须反映在软件设计中。可以有许多类型的更改,从描述业务领域的更有效方式、更好的术语、对业务流程的更好理解开始。
  • 而且,整个业务领域都可以改变。现在,一家公司从一个业务目标开始并在整个过程中改变它并不罕见,因为例如,前一个业务目标在财务上不可行。
  • 它必须反映在软件设计中。未能对业务领域的变化做出反应会随着时间的推移产生技术债务,最终,它会导致你承担非常大的技术债务和一大堆泥浆。

 
DDD 战略设计
  • 我认为战略设计更为重要。因为您可以编写非常成功的项目,同时仅纳入战略设计决策。但是,如果你只关注战术设计决策,那么这可能不会有好的结果,至少以我的经验来看是这样。
  • 首先,战略设计是关于在软件工程师和业务领域专家之间建立共同的理解。这是关于培养共同的语言和共同的理解。
    • 为此,您在交流中使用的该语言有一些要求。它的主要要求是每个术语应该有一个且只有一个含义。
    • 与软件不同,人有时是如此不可预测。他们可以说一件事,但由于上下文不同,他们可以表达另一件事。在做出软件设计决策时,我们不能允许这样做。我们必须确保每条知识都是明确的。
    • 领域驱动设计说:“嘿,当你拥有如此无处不在的依赖于上下文的语言时,请在代码中明确地对其进行建模。定义该语言适用的上下文。它是有界上下文”。这是第一个战略设计决策。
    • 它确保您拥有一个由有界上下文包含的模型,这很清楚。它的每一个术语只有一个含义。
    • 由于一些历史问题和错误,(许多人)认为限界上下文必然是微服务。这不是真的。有界上下文是您最大的有效单体,因为它包含一个没有冲突的模型。您可以进一步分解它。我们的想法是找到那些边界,以确保您在其中拥有正确的模型,而不会发生冲突或冲突。
  • 其次,系统不会自行构建。为此,我们需要我们,软件工程师。在任何组织中,可以有一个团队或多个团队从事同一个项目。这又会影响您设计这些有界上下文的方式,因为它们必须相互交互。
    • 领域驱动设计说:“嘿,这是你在设计这些组件时必须考虑的事情,它们将如何集成。”
    • 这是我们根据业务领域和团队结构做出的第二个战略设计决策。
    • 工程团队的结构也可以改变。这应该会影响这些有界上下文相互集成的方式。

 
子域
  • 术语子域很难定义。这个想法是子域是一个业务构建块。
  • 假设我们有两家公司在同一个行业工作。所以他们将拥有相同的业务领域。但他们不是同一家公司。他们有不同的方式。他们在他们的市场上竞争。他们有不同的产品。因此,它们的不同之处在于它们的子域。
  • 子域就像拼图一样,完成了公司计划如何在其业务领域取得成功的全貌。这些是您必须实施的活动,因为您的业务战略,因为您希望如何将自己与竞争对手区分开来,或者因为您必须这样做,因为这是法律,例如会计,您必须这样做。
  • 子域作为起始启发式,通常与组织单位、部门相关联。但这是子域的粗粒度边界。
  • 通常,当我们深入研究时,我们会发现更多,用软件工程术语来说,是相互关联的连贯用例集,它们要么处理相同的数据,要么实现相关的业务逻辑。我们能够找到那些相互关联的用例集,它们通常是子域的更优边界。
  • 子域的全部目的不是描述组织的工作方式。为此,我们需要别的东西。这就是业务能力。这是一个更复杂的模型,因为它是分层的。有不同层次的业务能力。子域更简单。我们这里只有两个层次。我们有业务域,在它之下是子域。
  • 所以我们必须专注于对我们正在构建的软件系统真正重要的东西。我们必须决定我们找到的子域的边界是否足以满足我们的目的。
  • 根据域驱动设计,我们有三种类型的子域。
  • 第一个是核心子域。
    • 核心子域是公司如何将自己与竞争对手区分开来。这是你拥有的东西,但竞争对手没有。至少你打算比他们赚更多的钱。这就是各种知识产权,比如在他们的组织中发明的专利或聪明的算法。他们可能会提出一种新的方法来解决某些问题,或者他们只是优化现有的解决方案,从而以更便宜的方式更有效地解决它。
    • 这是您希望它留在您的公司中的东西,因为如果您的竞争对手拥有它,那是个坏消息。这意味着您只是失去了一点竞争优势。
  • 第二种类型的子域是通用子域。
    • 它们与核心相反。所以这里没有竞争优势。这些是业内所有公司都使用的解决方案,也可能在其他行业中使用。这里的一个很好的例子是加密算法。
    • 所以这是一个通用的子域。解决了一些问题。你有问题,但你有一个现有的解决方案。你只需要继续使用它。它可以购买现成的产品,采用开源解决方案。但无论如何,底线是您和您的竞争对手将使用相同的解决方案并且不会影响您的业务。
  • 第三种子域是支持子域,它们位于中间。
    • 同样,与一般情况一样,这里没有竞争优势。但另一方面,作为核心子域,这是您必须在内部实现的。你必须在内部实施它,不是因为你想这样做,而是因为你别无选择。因为没有可用的通用解决方案。
    • 在本质上,支持子域通常比核心子域简单得多。这涉及一些简单的实现。因为它很简单,甚至可能有一个通用的解决方案,但您会选择不使用它,而只是实现自己的解决方案,因为推出自己的解决方案比集成另一个解决方案更容易。实施比集成更容易。
  • 我的经验法则是深入研究子域,然后决定是否应该进一步分解它。只有进一步分解才会产生不同类型的子域。

 
DDD 战术设计
  • 战略设计是关于你正在实施的。将是什么组件?战术设计是关于我们将如何实施它们。
  • 我们有不同的方式来实现组件和业务逻辑。这些模式是我们正在实现的一种子域的功能。
  • 如果它是通用的,那么我们只是使用现有的解决方案。如果它是一个核心子域,我们需要一个设计模式来解决该核心子域的复杂性。如果这是一个支持子域,我们需要简单的模式,否则,我们将过度设计。
  • 战术设计决策涵盖的另一个方面是我们将如何构建组件的整体工作,例如其内部组件。它将如何与数据库一起工作?它将如何与用户界面等一起工作?
  • 我们有架构模式,即分层架构,通常在支持子域或集成通用子域时更有用。我们有端口和适配器,也叫六边形架构,也叫洋葱架构,也叫干净架构。
  • 我们有 CQRS 架构。您将使用哪种模式来设计架构和实现业务逻辑取决于正在使用的业务子域的类型。

 
聚合模式
  • 聚合是一个包含数据和所有行为以及影响数据的所有业务逻辑的对象。换句话说,可以更改聚合数据的所有内容都应该驻留在聚合边界中。任何外部组件都不能更改其数据。
  • 通常,我们在实现一些复杂的业务逻辑时会使用这种模式。如果它很复杂,并且如果您允许外部组件继续修改您的数据,那么迟早会有一些业务规则影响这些数据。这些规则将被复制。复制业务逻辑通常不是一个好主意,但复制复杂的业务逻辑通常是一个糟糕的主意。这就是聚合模式应该阻止你做的事情。
  • 聚合不是数据库中的平面记录。相反,它是对象的层次结构。所有这些数据都可以驻留在同一个聚合中。
  • 下一个问题是我们在哪里停止将事物推入聚合?我们在哪里决定我们停止把东西放在哪里?
  • 聚合不仅是数据和行为的边界,也是事务的边界。这意味着聚合数据中的所有更改都应在一个原子事务中提交。
  • 所以我们必须寻找更小的边界,更有效的边界。[Vaughn Vernon] 所说的,使用事务边界作为选择聚合边界的启发式方法。确保它只包含为实现驻留在其中的业务逻辑而必须高度一致的数据。所以无论什么必须是强一致的,把它放在聚合中。其他所有可以最终一致的东西,都放在另一个聚合或另一个组件中。但总的来说,您应该只拥有必须高度一致的数据。
  • 你怎么知道你是需要强烈一致还是最终一致?尝试评估这两条路径。如果某件事最终是一致的,会发生什么?你能正确实现所有这些业务规则和业务逻辑吗?还是会导致数据损坏?当您以这种方式分析它时,通常很明显您需要哪些信息是高度一致的,以及您可以最终获得哪些信息是一致的。
  • 简而言之,这是一个聚合。它包含数据、影响数据的行为以及所有数据周围的事务边界。
  • 因为所有行为都属于聚合,所以我们必须公开一个允许我们执行它的公共接口。通常我们称之为命令。所以我们正在对聚合执行命令。通过命令,我的意思是,不是您放入消息总线或其他任何内容的消息。不,这是说你有一个公共方法的冒犯方式。这应该是修改聚合内容的唯一方法。
  • 聚合的公共接口中非常重要的部分是领域事件。因为有时聚合可能非常大。您最好将它们保持小,但这取决于您的业务领域。其中可能会发生一些重要的商业事件。领域事件必须以通用语言、业务语言来表述。
  • 因此,一旦更改,聚合提交到数据库,我们需要发布该域事件,以便任何对其感兴趣的组件都能够订阅,并执行与该域事件发生相关的事情。

 
实体模式
  • 当我们看到与聚合在同一级别讨论的实体时,我们开始认为这是您可以选择的两种模式,这是不对的。聚合是一个实体。聚合是对象的层次结构。所以它是实体的层次结构。实体是聚合中的构建块。
  • 我们需要该实​​体的主要原因是将其与值对象区分开来。所以值对象是这里重要的东西,而不是实体。正如你所说,它们之间的区别是一个实体有一个ID。另一方面,值对象可以仅通过其值来标识。
  • 这种区分允许我们将这些值对象建模为代码中的不可变对象。作为实体,值对象不是独立的。您不能单独使用值。它必须描述一些东西。因此,它是位于聚合中的实体的属性。
  • 这就是这三个核心模式的层次结构。聚合包含由值对象描述的实体。

 
为遗留系统实施 DDD
  • 不知何故,我们对领域驱动设计只能应用于新建项目有这种错误的理解,但这种情况非常罕见。我们的大部分工作是棕地项目。具有讽刺意味的是,棕地项目可以从一些 DDD 处理中受益最大。
  • 在棕地项目中实施领域驱动设计时,您需要的主要是耐心。你必须对一切保持耐心。
  • 在这些情况下,我通常建议从映射现有解决方案开始。所以你有这个想要发展的棕地项目,但在你开始考虑你想去哪里之前,你必须确保你知道你的起点。所以你必须映射你拥有的那些有界上下文。
  • 现在,当然,这些不会是领域驱动设计的有界上下文。它们不一定包含连贯的模型。但是,这些都是您想要更改的组件。所以你必须找到它们,映射你拥有的有界上下文。您必须映射它们如何相互交互,这些集成模式。
  • 一旦你这样做了,下一步就是开始学习业务领域。通常它是通过构建一种无处不在的语言来完成的。在现实生活中,这是具有挑战性的,尤其是对于棕地项目。因为如果有一种语言在公司里已经使用了一段时间,那么改变它需要很多时间。
  • 在这种情况下,我建议以身作则,继续与领域专家交谈。确保你理解他们。确保您可以使用相同的语言进行交流。尝试找出一些已经在代码中实现的关于业务领域的错误假设。一旦你开始培养共享语言,至少在你的代码中使用它,当你与人交谈时。
  • 在组织中采用这种无处不在的语言需要时间。但是,再次以身作则。确保您制作的任何内容都使用无处不在的语言。与人交流时,诉诸无处不在的语言背后的原则。
  • 为什么我们要确保每个术语只有一个含义?因为我们要消除假设。我们希望消除沟通中的不一致。因此,当您与人交谈时,您可以将这些原则作为尝试改变他们使用业务术语的方式的理由。