DDD:战略性领域驱动设计

战略性领域驱动设计

自从Eric Evans于2003年出版有关该主题的书以来,域驱动设计(DDD)就已经存在。几年前,我本人加入了DDD,当时我加入了一个遭受数据一致性问题的项目。数据库中出现重复项,根本没有保存一些信息,并且您可能随时随地遇到乐观的锁定错误。我们设法通过使用战术领域驱动设计的构建基块来解决此问题。

从那时起,我就学到了更多有关领域驱动设计的知识,并且在适当的时候尝试在我的项目中使用它。但是,在过去几年中,当我与其他开发人员交谈时,他们中的许多人都听说过术语域驱动设计,但是他们不知道这意味着什么。在本系列文章中,我将简要介绍并了解域驱动的设计。内容很大程度上基于埃里克·埃文斯(Eric Evans)的《领域驱动的设计:解决软件核心中的复杂性》和沃恩·弗农(Vaughn Vernon)的《实现领域驱动的设计》一书。但是,我试图用自己的语言解释一切,并注入自己的思想,观点和经验。

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

什么是领域?

如果我在MacBook上的字典应用程序中查找单词domain,则会得到以下定义:

由特定统治者或政府拥有或控制的领土范围

  • 指定的活动或知识范围…

苹果词典
对于领域驱动的设计,这是我们感兴趣的定义的第二部分。在这种情况下,活动是组织所做的一切,知识是组织如何进行的。我们还将组织进行活动的环境添加到领域概念中。

子域名

领域的概念非常广泛和抽象。为了使其更加具体和有形,将其分成称为子域的较小部分是有意义的。找到这些子域并不总是一件容易的事,如果您弄错了它们,那么当难题中的各个部分突然无法很好地融合在一起时,您可能会遇到麻烦。

在寻找子域之前,您应该考虑一下子域类别。所有子域都可以分为三类:

  • 核心领域

  • 支持子域

  • 通用子域

这些类别不仅可以帮助您找到子域,还可以帮助您确定开发工作的优先级。

一个核心领域是什么使一个组织的特殊和其他组织的不同。一个组织如果不能在其核心领域格外出色,就无法成功(甚至无法生存)。由于核心领域非常重要,因此应该获得最高优先级,最大的努力和最好的开发人员。对于较小的域,您可能只标识一个核心域,较大的子域可能有多个。您应该准备从头开始实现核心领域的功能。

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

一个通用子域是不包含任何特殊的组织,但仍需要整体的解决方案,以工作的子域。通过尝试为通用子域使用现成的软件,您可以节省大量时间和工作。一个典型的例子是用户身份管理。

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

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

例子

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

    1. 患者记录,用于管理患者的病历(个人信息,病历等)。
    1. 实验室订购实验室测试和管理测试结果。
    1. 调度安排预约。
    1. File Archive(文件存档),用于存储和管理附加到患者记录的文件(例如,不同的文档,X射线照片,扫描的纸质文档)。
    1. 身份管理,以确保合适的人可以访问合适的信息。

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

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

从问题到解决方案

有时您会发现称为“问题域”的领域。这是因为领域定义了软件要解决的问题(毕竟,首先要制造软件是有原因的)。Vaughn Vernon将领域划分为问题空间和解决方案空间问题空间集中在我们要解决的业务问题上。子域属于此空间。

解决方案空间集中在如何解决问题空间中的问题上。它更具体,更技术,并且包含更多细节。那么,我们如何将问题转化为解决方案?

无所不在的语言

为了能够为领域创建软件,您需要一种描述领域的方法。具有关系数据模型或类似数据是不够的。您不仅需要描述事物及其之间的关系,还需要描述事件,流程,业务不变量,事物随时间的变化等动态变化。您需要能够与其他开发人员以及领域专家讨论和讨论有关该领域的问题。您需要的是一种无处不在的语言。

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

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

普遍存在的语言具有一个不错的功能,那就是告诉您是否走在正确的道路上。如果您可以使用该语言轻松地解释业务概念或流程,则表明您处在正确的轨道上。另一方面,如果您发现自己在努力解释某些东西,那么很可能是语言和领域模型都缺少了一些东西。发生这种情况时,您应该找一位领域专家,然后去寻找缺失的部分。您甚至可能偶然发现了一个启示,该启示使您现有的模型完全颠倒了,并导致了一个比以前的模型更好的领域模型。

引入有限的上下文

在一个完美的世界中,将只有一种无处不在的语言和一种模型可以解释有关单个域的所有信息。不幸的是,事实并非如此,只有非常小的和简单的域除外。业务流程可能重叠甚至冲突。在不同的上下文中,相同的单词可能表示不同的事物,或者不同的单词可能表示相同的事物。在问题空间中解决问题的方法可能有(而且通常是)多种以上,具体取决于您的查看方式。

我们没有选择寻找大统一模型,而是选择接受事实并引入一种称为有界上下文的方法。有界上下文是域的一个独特部分,在该域中无处不在的语言的特定子集或方言始终保持一致。换句话说,我们正在应用分而治之,并将领域模型拆分为较小的,或多或少独立的,具有明确定义的边界模型。每个有界上下文都有其自己的名称,该名称是无处不在的语言的一部分。

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

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

例子

让我们重新回顾上一个示例中的EMR系统,更具体地说,患者记录核心域。我们在那里可以找到什么样的边界环境?现在,我还不是医疗保健软件方面的专家,所以我将只作一些介绍,但希望您能想到。

该系统支持医生预约和理疗服务。此外,对于新患者,有一个单独的入职流程,对他们进行采访,拍照并进行初步评估。这导致核心域中的以下有限上下文:

    1. 个人信息管理患者的个人信息(姓名,地址,财务信息,医疗背景等)。
    1. 为将新患者介绍到系统中而入职
    1. 医生在检查和治疗患者时使用的体检
    1. 理疗师在检查和治疗患者时使用的物理疗法。

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

上下文之间的关系

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

识别有限上下文之间关系的最简单方法是将上下文分类为上游上下文下游上下文。将上下文视为河流旁的城市。上游的城市向河里倾倒东西,河又到达下游的城市。有些东西对下游城市来说是必不可少的,因此他们从河里取回了东西。其他东西是有害的,并且可能对下游城市造成直接损害(“sh*t rolls downhill”).。

处于上游或下游具有其优点和缺点。上游上下文不依赖于任何其他上下文,这在某种程度上使其可以自由地向任何方向发展。但是,任何更改的后果在下游环境中可能都是严重的,这反过来可能会在上游环境中施加限制。下游上下文受其对上游上下文的依赖而受到限制,但无需担心在下游进一步破坏其他上下文,这在某种程度上使下游上下文的开发者比上游上下文的开发者更自由。

您可以通过使用从属关系图(从下游上下文指向上游上下文的箭头)或通过使用U和D角色以图形方式描述关系。

最后请记住,上下文可以同时是上游上下文和下游上下文,具体取决于您所处的位置。

上下文映射和集成模式

一旦了解了上下文以及它们之间的关系,我们就必须决定如何整合它们。这涉及几个重要的问题:

    1. 上下文边界在哪里?
    1. 上下文将如何进行技术交流?
    1. 我们如何在上下文的领域模型之间进行映射(即,我们如何从一种普遍存在的语言转换为另一种普遍存在的语言)?
    1. 我们将如何防范上游发生的不必要或有问题的变化?
    1. 我们如何避免对下游环境造成麻烦?

这些问题的答案将被编译到上下文映射中。可以通过以下方式以图形方式记录上下文映射:

为了使创建上下文映射更加容易,有一组适用于大多数用例的现成集成模式。根据您选择的集成模式,您可能必须向上下文映射中添加其他信息才能使其真正有用。

合伙

两种情况下的团队合作。接口(无论它们是什么)都在不断发展,以适应两种环境的发展需求。对相互依存的功能进行了适当的计划和安排,以使它们对两个团队造成的伤害尽可能小。

共享内核

这两个上下文共享一个共同的代码库,即内核。任何团队都可以修改内核,但必须先咨询其他团队才能修改内核。为确保不会引入意外的副作用,需要进行持续集成(带有自动测试)。为了使事情尽可能简单,共享内核应该保持尽可能小。如果大量模型代码最终出现在共享内核中,则可能表明上下文实际上应该合并为一个大上下文。

客户供应商

上下文处于上游-下游关系中,并且这种关系被形式化,使得上游团队是供应商,下游团队是客户。因此,即使两个团队可以在其系统上或多或少地独立工作,但仍需要上游团队(供应商)考虑下游团队(客户)的需求。

遵从者

上下文处于上游-下游关系。但是,上游团队没有动力去满足下游团队的需求(例如,可以从较大的供应商处订购该服务)。下游团队决定顺应上游团队的模型,无论情况如何。

反腐层

上下文处于上游-下游关系中,上游团队并不关心下游团队的需求。但是,下游团队决定不创建符合上游模型的抽象层,以保护下游上下文免受上游上下文的更改,而不是遵循上游模型。此反腐败层使下游团队能够使用最适合其需求的领域模型,同时仍与上游环境进行集成。当上游上下文更改时,反腐败层也必须更改,但是其余下游上下文可以保持不变。将这种策略与持续集成相结合可能是一个好主意,在这种方法中,自动化测试可用于检测上游界面的变化。

开放式主机服务

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

使用此模式的关键是使协议保持简单和稳定。您的大多数系统客户端都应该能够从该协议中获得所需的东西。为其余的特殊情况创建特殊的积分点。

发布语言

这是我个人认为最难正确解释的集成模式。从我的角度来看,发布的语言相对于开放式主机服务而言是最接近的语言,并且通常与该集成模式一起使用。文档化的语言(例如基于XML)用于系统的输入和输出。只要您符合发布的语言,就无需使用特定的库或特定的规范实现。已发布语言的真实示例是用于表示数学公式的MathML和用于表示地理信息系统中的地理特征的GML。

请注意,您不一定需要将Web服务与发布的语言一起使用。您还可以进行设置,在该设置中,将文件拖放到目录中,并由将输出存储在另一个文件中的批处理作业进行处理。

隔开方式

这种集成模式的特殊之处在于它根本不执行任何集成。仍然,这是保留在工具箱中的重要模式,最终可能会节省大量金钱和时间。当不再需要两个上下文之间的集成带来的好处时,最好将上下文彼此分开并让它们独立地发展。这样做的原因可能是系统已经简单地发展到不再相关的地步。上游上下文实际使用的下游上下文所提供的(很少)服务在下游上下文内重新实现。

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

我相信战略性领域驱动设计原本是用于大型项目的,但我认为即使在项目中不使用DDD的任何其他部分,您也可以从小型项目中受益。

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

    1. 它引入了边界。在我所有的业余爱好项目中,范围蠕变都是一个恒定因素。最终,他们变得比工作更有趣,甚至变得更不现实,独自完成工作变得完全不现实。在处理客户项目时,我必须努力工作,以免因对事物的过度思考或过度设计而导致技术范围的攀升。边界-无论位于何处-都可以帮助我将项目分成较小的部分,并在正确的时间专注于正确的部分。
    1. 并不需要我找到在所有情况下都适用的超级模型。它认识到,在现实世界中,在或多或少明确定义的上下文中通常有许多较小的模型。它没有破坏这些模型,而是拥抱它们。
    1. 它可以帮助您考虑系统将要带来的价值,以及应该尽最大的努力来获得最大的价值。我从一些项目中获得了个人经验,在这些项目中,正确地识别然后专注于核心领域将产生巨大的变化。不幸的是,我还没有听说过战略性DDD,并且浪费了时间和金钱。

我也知道自己很擅长使用这种方法来识别风险:为了找到子域和有界上下文而找到子域和有界上下文。当我学到一些喜欢的新东西时,我非常想在现实世界中尝试一下。有时候这可能意味着我要去寻找不存在的东西。我的建议是始终从一个核心域和一个有界上下文开始。我仔细地进行了域建模,如果存在其他子域和有界上下文,它们最终会显示出来。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享