​ 自Eric Evans于 2003 年出版关于该主题的书以来,领域驱动设计(DDD) 一直存在。几年前,当我加入一个存在数据一致性问题的项目时,我自己也接触到了 DDD。重复显示在数据库中,一些信息根本没有保存,可以随时随地遇到乐观的锁定错误。我们通过使用战术领域驱动设计的构建基块来解决这个问题。

​ 从那时起,我更多地了解了领域驱动设计,并尝试在适当的时候将其用于我的项目。然而,在过去的几年里,当我与其他开发人员交谈时,他们中的许多人听说过领域驱动设计这个词,但他们不知道这意味着什么。在本文系列中,我将简要介绍一下我所看到和理解的领域驱动设计。内容大多基于书籍领域驱动设计:处理复杂的软件心脏由埃里克埃文斯和实施领域驱动设计沃恩弗农。然而,我试图用我自己的话来解释一切,也注入了我自己的想法、观点和经验。

现在让我们从第一个主题开始,即战略领领域驱动设计。

什么是领域?

就领域驱动设计而言,它是我们感兴趣的定义的第二部分。在这种情况下,活动是组织所做的一切,知识是组织如何做。我们还将在域名概念中添加组织开展活动的环境

子域

领域概念非常广泛和抽象。为了使它更具体和有形,它有必要把它分成较小的部分称为子域。找到这些子域并不总是一件容易的事情.

在查找子域之前,应该考虑子域类别。所有子域可分为三类:

  1. 核心域
  2. 支撑子域
  3. 通用子域

这些类别不仅可以帮助找到的子域,它们还将帮助优先安排的开发工作。

核心域是什么使组织与众不同。如果一个组织在其核心领域表现特别出色,就不可能成功(甚至存在)。因为核心域如此重要,它应该获得最高优先级、最大努力和最佳开发人员。对于较小的域,可能只识别单个核心域,较大的域可能具有多个域。应该准备从零开始实现核心域的功能。

支持子域是组织成功所必需的子域,但它不属于核心域类别。它也不是通用的,因为它仍然需要对有关组织进行某种程度的专业化。也许能够从现有解决方案开始,并对其进行调整或将其扩展到的特定需求。

通用子域是一个子域,不包含任何对组织特别的东西,但总体解决方案仍然需要工作。可以尝试使用现成的软件来为的通用子域保存大量时间和工作。一个典型的例子是用户身份管理。

值得注意的是,根据组织的作用,相同的子域可以分为不同的类别。对于专门从事身份管理的公司来说,身份管理是一个核心领域。然而,对于一家专门从事客户关系管理的公司来说,身份管理是一个通用的子域。

最后,值得指出的是,所有子域对整体解决方案都很重要,无论它们属于哪个类别。然而,它们确实需要不同的努力量,而且对质量和完整性的要求也可能不同。

假设我们正在为小型诊所构建 EMR(电子病历)系统。我们已经确定了以下子域:

  1. 管理患者病历的患者记录(个人信息、病史等)。
  2. 用于订购实验室测试和管理测试结果的实验室。
  3. 安排就诊的日程安排。
  4. 用于存储和管理附在患者记录上的文件的文件档案(如不同的文档、X 光照片、扫描的纸质文件)。
  5. 身份管理,以确保合适的人能够访问正确的信息。

现在,我们如何分类这些子域?最明显的是文件存档身份管理,它们显然是通用的子域。但是其他人呢?这取决于是什么使这个特殊的EMR系统在市场上脱颖而出。

Example subdomains

由于我们正在构建 EMR 系统,因此可以非常安全地假设患者记录是一个核心领域。如果我们要通过建立一个系统来利用市场,使所有的诊所通过智能和创新的调度更高效地工作,那么调度可能也是一个核心领域。否则,它是一个支持子域,也许建立在一些现有的调度引擎之上。同样的推理可以应用于实验室子领域:如果我们业务案例的一个重要部分是患者记录和实验室之间的无缝集成,那么实验室很可能是一个核心领域。否则,它是一个支持子域。

从问题到解决方案

有时会发现称为"问题域"的域。这源于这样一个事实,即领域定义了软件将要解决的问题(毕竟,软件的制作是有原因的)。沃恩弗农将一个领域分成一个问题空间和一个解决方案空间。问题空间集中在我们试图解决哪些业务问题上。子域属于这个空间。

解决方案空间侧重于如何解决问题空间中的问题。它更具体,更技术,包含更多的细节。那么,我们如何把我们的问题转化为解决方案呢?

统一专家语言

要能够为域创建软件,需要一种描述领域的方法。拥有关系数据模型或类似的东西是不够的。不仅需要能够描述事物及其关系,还需要描述事件、流程、业务不变、事物如何随着时间而变化等动态。需要能够与的开发人员和域专家讨论和推理域。你需要的是一种无处不在的语言

无处不在的语言是域专家和开发人员始终用来描述和讨论域的语言。除了代码本身之外,此语言是领域驱动设计流程中最重要的可交付语言。语言的很大一部分是域名专家已经使用的领域术语,但可能还需要与域专家合作发明新的概念和流程。因此,域专家的积极参与对于领域驱动设计的成功至关重要。如果客户对投入时间和精力来教导是他们的域名并帮助创建无处不在的语言不感兴趣,要么尝试说服客户改变主意,要么选择其他设计方法。

可以以各种方式记录无处不在的语言。一个好的起点是创建术语表。业务流程可以使用流道图和流程图等图形方式进行描述。UML 可用于描述事物与状态图之间的关系,以描述状态如何随着不同事物在不同过程中的移动而变化。子域也是无处不在的语言的一部分,甚至可能需要为不同的子域定义语言的不同"方言"。这种无处不在的语言的体现是域模型,它最终将转化为工作代码。换句话说,域模型与数据模型或 UML 类图不同

无处不在的语言有一个很好的功能,即它告诉你你是否在正确的轨道上。如果能够轻松地使用该语言解释业务概念或流程,则意味着正走在正确的轨道上。另一方面,如果发现自己难以解释某些问题,则很可能从语言中以及从域名模型中遗漏了某些东西。当这种情况发生时,应该抓住一个域专家,去寻找丢失的棋子。甚至可能偶然发现一个启示,该启示将现有的模型完全颠倒过来,并导致一个远超优于的域名模型。

引入边界上下文

在一个完美的世界中,将只有一种专家语言和一种模式可以解释关于单个域的一切。不幸的是,事实并非如此,除了非常小和简单的域。业务流程可能会重叠甚至冲突。同一个词可能意味着不同的东西,或不同的词可能意味着相同的东西在不同的上下文。根据如何看待问题,问题空间中可能有(而且经常是)多种解决问题的方法。

而不是试图找到大统一模型,我们选择接受事实,而不是引入的东西称为边界上下文。有界限的语境是域的一个不同部分,在这个领域中,无处不在的语言的特定子集或方言始终是一致的。换句话说,我们正在应用划分和征服,并将域模型拆分为更小、或多或少具有明确定义边界的独立模型。每个有界限的上下文都有自己的名称,此名称是无处不在的语言的一部分。

边界上下文和子域之间不一定有一对一的映射。由于有界限的上下文属于解决方案空间,是问题空间的子领域,因此应该将边界上下文视为许多可能的解决方案中的一种替代解决方案。因此,单个子域可以包含多个边界上下文。可能还会发现自己处于单个边界上下文跨越多个子域的情况。没有针对此的规则,但它表明可能需要重新思考的子域或上下文边界。

就我个人而言,我喜欢将边界上下文视为单独的系统(例如,在 Java 世界中单独的可执行 JAR 或可部署的 WAR)。一个完美的现实世界的例子是微服务,其中每个微服务可以被认为是它自己的边界上下文。但是,这并不意味着必须将所有有限制的上下文作为微服务实现。有边界的上下文也可以是单个单一系统内的单独子系统。

让我们重温上一个示例中的 EMR 系统,更具体地说,患者记录核心域。我们可以在那里找到什么样的边界环境?现在我不是医疗保健软件的专家, 所以我只会弥补一些, 但希望, 你会得到这个想法。

该系统支持医生预约和物理治疗的服务。此外,对新患者有单独的建档程序,让他们接受咨询、拍照和进行初步评估。这将导致核心域内的以下边界上下文:

Example bounded contexts

  1. 用于管理患者个人信息的个人信息(姓名、地址、财务信息、医疗背景等)。
  2. 新患者引入系统。
  3. 医生在检查和治疗病人时使用的医学检查
  4. 物理治疗师在检查和治疗患者时使用的物理治疗。

在一个非常简单的系统中,可能会将所有内容压缩到单个上下文中,但此 EMR 更先进,并为所提供的每种类型的服务提供简化和优化的功能。然而,我们仍然在同一核心子域内。

上下文之间的关系

在非平凡的系统中,很少有(如果有的话)有界限的上下文是完全独立的。大多数上下文与其他上下文有某种关系。识别这些关系不仅在技术上很重要(系统在技术上将如何相互通信),而且对于如何开发它们也很重要(开发系统的团队将如何相互通信)。

识别边界上下文之间关系的最简单方法是将上下文分类为上游上下文下游上下文。把背景想象成河边的城市。上游的城市将东西倒入河中,到达下游城市。有些东西对下游城市至关重要,所以他们从河里取回了它。其他东西是有害的,可以直接损害下游城市。

上下游有其利弊。上游上下文不依赖于任何其他上下文,这在某种程度上使它能够自由地向任何方向发展。然而,任何变化的后果在下游环境中都可能很严重,这反过来又可能对上游环境施加限制。下游上下文受其对上游上下文的依赖性的限制,但无需担心进一步破坏下游的其他上下文,这在某种程度上使下游上下文的开发人员比上游上下文的开发人员更自由。

可以使用从下游上下文指向上游上下文的依赖图,或者使用 U 和 D 角色,以图形方式描述关系。

Different ways of documenting context relationships graphically

最后请记住,上下文可以同时是上游上下文和下游上下文,具体取决于的立场。

上下文映射和集成模式

一旦我们知道我们的上下文是什么,它们是如何相关的,我们必须决定如何整合它们。这涉及几个重要问题:

  1. 上下文边界在哪里?
  2. 上下文在技术上将如何传达?
  3. 我们如何在上下文的域模型之间绘制地图(即我们如何从一种统一的专家语言转换到另一种语言)?
  4. 我们如何防范上游发生的不需要或有问题的变化?
  5. 我们如何避免给下游环境带来麻烦?

这些问题的答案将被汇编成上下文图。上下文映射可以这样图形记录:

An example context map

为了便于创建上下文图,有一组现成的集成模式适用于大多数使用案例。根据选择的集成模式,可能需要在上下文地图中添加其他信息,使其真正有用。

合作关系

两个上下文的团队都合作。接口 - 无论它们是什么 - 演变,以便它们能够适应两种上下文的发展需求。相互依赖的功能是适当的规划和安排,以便它们对两个团队造成的伤害尽可能小。

共享内核

两种上下文共享一个共同的代码基数,即内核。任何团队都可以修改内核,但不能不首先咨询其他团队。为了确保不引入意外的副作用,需要持续集成(自动测试)。为了保持事情尽可能简单,共享内核应尽可能小。如果许多模型代码最终都位于共享内核中,则这可能是上下文实际上应合并为一个大上下文的标志。

客户-供应商

上下文处于上下游关系中,这种关系是正规化的,因此上游团队是供应商,下游团队是客户。因此,即使两个团队都可以或多或少地独立地在系统上工作,上游团队(供应商)也需要考虑下游团队(客户)的需求。

墨守成规

上下文处于上下游关系中。但是,上游团队没有动力满足下游团队的需求(例如,它可能从大型供应商处订购)。下游团队决定遵守上游团队的模型,无论它发生什么。

反腐败层

上下文处于上下游关系中,上游团队不关心下游团队的需求。但是,下游团队决定创建一个抽象层,保护下游上下文免受上游上下文变化的影响,而不是符合上游模型。此反腐败层允许下游团队与最适合其需求的域模型合作,同时仍与上游上下文集成。当上游上下文发生变化时,反腐败层也必须改变,但下游上下文的其余部分可以保持不变。将此策略与连续集成相结合可能是个好主意,其中使用自动测试来检测上游界面的变化。

开放领域服务

使用明确定义的协议,通过明确定义的服务提供对系统的访问。协议是开放的,以便任何需要的人可以与系统集成。网络服务和微服务就是这种集成模式的一个很好的例子。这种模式不同于其他模式,因为它不关心上下文和发展它们的团队之间的关系。最终可能会将开放主机服务模式与任何其他模式相结合。

使用此模式的关键是保持协议简单和稳定。大多数系统客户应该能够从此协议中获取所需的内容。为剩余的特殊情况创建特殊集成点。

已发布语言

这是我个人觉得最难正确解释的整合模式。以我看,已发布的语言是与开放领域服务最接近的语言,并且经常与该集成模式一起使用。记录的语言(例如基于 XML)用于系统的输入和输出。只要符合已发布的语言,就无需使用特定的库或特定的规格实施。已出版语言的实际例子是代表数学公式的 MathML 和用于表示地理信息系统中的地理特征的 GML。

请注意,不一定需要将 Web 服务与已发布的语言一起使用。还可以设置文件被放入目录,并通过存储另一个文件中的输出的批次作业处理。

单独方法

这种集成模式很特别,因为它根本不执行任何集成。尽管如此,它仍然是一个重要的模式,保持在工具箱,并可能最终节省大量的金钱和时间。当两个上下文之间的集成的好处不再值得努力时,最好是将上下文彼此分离,让它们独立演变。原因可能是系统已经发展到不再相关的地步。下游上下文实际使用的上游上下文提供的(很少)服务在下游上下文中重新实现。

为什么战略领领域驱动设计很重要?

我相信战略领域驱动的设计最初是为大型项目设计的,但我认为也可以从中受益,在较小的项目 - 即使最终没有在项目中使用 DDD 的任何其他部分。

就我个人而言,战略领领域驱动设计的主要要点如下:

  1. 它引入了边界。小范围变更是我所有爱好项目中一个不变的因素。最终,他们变得更加详尽,而不是乐趣的工作或完全不切实际的单独完成。在客户项目上工作时,我必须努力工作,不要因为过度思考或过度设计而造成技术范围的变更。边界 - 无论他们在哪里 - 帮助我将项目分成较小的部分,并在正确的时间专注于正确的部分。
  2. 它并不要求我找到一个超级模型,在所有情况下都有效。它认识到,在现实世界中,往往有许多较小的模型在或多或少明确界定的上下文中。它不打破这些模型,而是拥抱它们。
  3. 它可以帮助思考的系统将带来的价值,以及应该将大部分精力投入到实现最大价值的位置。我有个人经验的项目,其中正确识别,然后专注于核心领域将产生巨大的变化