如何进行高质量的DDD领域建模?什么是领域模型?如何捕捉?尺寸如何? - Manning

19-07-05 banq
                   

本文深入研究DDD和模型:它们是什么,它们之间的关系以及模型在领域驱动设计中的工作方式。

模型作为深入洞察的工具

让我们首先解释DDD对模型的意义,因为它们位于DDD的中心。在系统开发中,“模型”一词意味着许多事情 :流程上的UML图表,数据如何在数据库表格中布局,以及许多其他内容。

在DDD中,我们使用“模型”一词来解释我们如何捕捉我们对手头业务的基本理解,提炼成一组选定的概念。为什么我们需要这样的模型,它们应该是什么样的?

领域驱动设计不是灵丹妙药。当您的系统处理一个不容易掌握的问题时,领域驱动设计是非常适合的。在这些情况下,最关键的问题是理解领域的复杂性。然后,理解和建模才应该是您的主要关注点

如果您无法掌握各种技术方面的复杂性,那么您将获得一个不太有用的系统。但是,如果你无法掌握领域的复杂性,你就会得到一个接近于毫无价值的系统。在这方面,领域是关键的复杂性。

想象一下,您有一个处理机场托运行李的系统,领域的复杂性可能是您的关键复杂性。如果您无法正确表达行李从登机柜台到飞机、通过传送带和装载卡车的行程,那么行李可能无法及时进行正确的托运,或最终导致错误托运的行李。乘客会生气,相关单位公司将失去信任和金钱。

更糟糕的是,有重要的安全方面受到威胁。比如在检查行李上机时,但发现乘客还没有出现在登机口登记,那么行李系统必须确保他的行李已下机。如果系统没有正确设计,可能会欺骗它将行李装入特定的航班,或者不卸载它 - 这可能会产生严重的安全后果。

如果您未能深入而准确地了解行李处理,那么您不仅要构建一个有缺陷的系统; 你制造了一些对企业有害并且对客户有潜在危险的东西。这比坏的系统更糟糕,它使系统变得毫无意义。相反关闭这个有缺陷的系统,机场甚至可能会更好。这不是一个假设的例子; 由于行李系统的不足,20世纪90年代丹佛机场的开通推迟了一年半。

在这种情况下,理解和建模行李处理领域应该是您工作的重点,但是这时花时间优化数据库连接池将是一个糟糕的选择。关键的复杂性是领域。未能解决关键复杂性使得解决方案毫无意义

现在谈到安全性,很难掌握对领域足够的理解来确保系统在所有可能的情况下都表现良好。对于仁慈的“正常”数据来说,这很难做到,可能会出现所有奇怪的情况。以一种抵抗恶意数据的方式来做这件事更难。有人可能会试图通过向其发送奇怪的数据来攻击您的系统,将其操纵为做坏事。系统仍应以健全和安全的方式作出反应。

为了安全起见,必须专注于构建领域模型。避免许多安全问题作为副作用 - 尤其是业务完整性问题,但在某种程度上它还可以保护系统免受某些技术攻击。

当某些情况并不是正确的选择时,则会集中在对域进行建模。例如,如果您为网络路由器编写软件,那么I / O吞吐量是最重要的。在这种情况下,您的关键复杂性是技术性 ,但即使在这里,您也应该考虑一个草率的域模型是否可能有一个安全问题。关键复杂性始终是潜在的。请注意它是技术方面还是领域的。

领域建模的主要好处在于它可以作为深层学习的工具。在这个级别学习至关重要。“抓住商人的术语”并不难,我们可以使用相同的术语编写一份看起来不错的需求文档。但是,如果没有深入的学习,这样的文件将包含微妙的误解,不一致和逻辑漏洞。这些缺陷使得无法建立一个在棘手的情况下做正确事情的可靠系统 - 安全漏洞是最糟糕的后果。与领域专家合作创建一个领域模型,促进学习。

我们需要的是以稳定和安全的方式支持开发的领域模型。

要使域模型有效,它需要:

  • 简单地将我们的注意力集中在要点上
  • 要足够严格,它可以成为编写代码的基础
  • 深入理解,使系统真正有用和有用
  • 从务实的角度来看,是最好的选择
  • 向我们提供我们在谈论系统时可以使用的语言

模型是简化的

模型是一种更简单的现实形式。这是我们删除不相关部分的简化。例如,当您在机场办理行李托运时,系统无需显示您的鞋码。另一方面,它可能与表示袋子的重量有关。为了便于理解和编码系统,我们创建了一个包含行李重量的模型,但不包括乘客的鞋码。我们保留我们认为有关系的细节。

需要明确的是,模型不是图表。在许多其他上下文中,“模型”表示特定的图表类型,例如实体关系模型,这通常用于数据库设计,或者来自UML的类图。这些图表是模型的表示,但模型是对我们简化的现实如何工作的概念性理解。

领域驱动设计中“模型”的使用更接近于这个单词的另一种用法,即时短语“模型训练”意思。当建立模型训练时,建模者付出了很多努力来保持现实的某些方面,而完全忽略了其他方面。要保留哪些细节以及要扭曲的细节是构建训练模型以及领域模型的关键。

毫无疑问,我们在图1中看到的是模型火车。它看起来像火车,在铁轨上移动,但它不是真正的火车。我们认为它是一种火车模型,因为它保留了一些重要的属性,而我们允许它忽略其他一些属性。

让我们列出模型与现实共有的一些属性:

  • 颜色 - 我们认为特定列车的模型应该与原始列车具有相同的颜色。
  • 相对大小 - 我们希望保持比例。如果门的宽度是实际宽度的两倍,我们预计模型列车的比例相同。
  • 形状 - 我们希望模型火车及其细节具有相同的形状,例如前窗的曲率。
  • 运动 - 我们希望模型火车沿着轨道移动,就像真正的火车一样。

让我们列出一些模型与现实不同的属性,以及我们认为差异很好的地方:

  • 材料 - 当原件由其他材料制成时,模型火车由塑料或锡制成是可以的。
  • 绝对尺寸 - 如果真正的货车长30米,我们很好,它们在模型中要小得多。
  • 重量 - 模型更轻,这是可以的。
  • 推进方法 - 该型号没有蒸汽机; 它依靠电力运行。
  • 轨道曲率 - 模型中的曲线比现实中的曲线要紧,我们接受。

奇怪的是,找到模型火车和真实火车之间的差异比找到它们共有的东西更容易。尽管如此,我们仍然坚定地认为这是一个合适的火车模型。很明显,这个特定的模型已经设法捕捉到我们对火车的理解的基本要素。

似乎“颜色,相对大小和运动”足以让我们理解模型是火车。这三个属性是必要的 - 如果模型不能满足它们,我们就不会玩并假装它是火车。而这三个就足够了 - 如果模型不能满足其他一些期望,比如材料,我们仍会继续玩并假装它是一列火车。在一天结束时,模型是对现实的简化,我们仍然接受简化作为真实事物的有效表示。

我们现在离开了玩具领域,并带着我们的想法,即模型是对真实事物的简化理解。这适用于我们在系统开发中使用的模型。如果我们为一个人建模,我们可能会选择抓住一些属性:一个人有一个名字,一个年龄,一个特定的鞋子大小,并且可选择有一个宠物。同意,这是一个粗略的模型,但仍然是一个模型。

模型可能是一种简化,但它必须足够通用,以便我们可以捕获一些我们认为有趣的变体。在我们的例子中,我们想要允许不同的名字,不同的年龄和不同的鞋码,我们允许人们有或没有宠物。我们允许在模型中显示所有这些差异。我们不对不同身高的人做出任何区分,也不注意他们的发型。

我们可以用许多不同的方式来表示这个模型。我们可以使用纯文本来解释我们的意思。我们可以使用不同类型的图表来说明它。我们可以使用代码:伪代码或来自编程语言的实际代码。这里重要的一点是,这些表示形式都不是模型。特别是类图通常与作为模型混淆,但模型无关任何表示形式。模型是我们认为在我们的建模中必不可少的概念性理解 : 比如在这种案例情况下是名称,年龄,鞋子尺寸和宠物。

将模型保持为现实的简化版本的主要好处是简单模型更容易严格,这在我们以后从中构建软件时是必不可少的。

模型很严格

领域模型不是现实的淡化版本; 它在丰富中失去了它的严格性。人是复杂的生命,有很多属性和很多关系。当我们决定专注于名字,年龄,鞋码和宠物时,我们会失去很多丰富感。但是我们在“人”的意思中获得了精确度 - 这种精确度使得用软件表示这个实体成为可能。了解该域的人称为领域专家

编写软件是两种来自不同方向并且需要以富有成效的方式进行会面的专业人员之间的协作:业务人员和开发人员。每个人都有不同的需求,必须满足这些需求来创建出色的软件。业务人员需要看到他们熟悉的术语,而不是准技术的mumbo-jumbo。如果他们无法表达他们的领域,我们就失败了。但是,在用户界面或打印报告的标题中使用熟悉的单词作为标签是不够的。系统还必须以业务人员认为合理的、一致和易懂的方式行事。

为此,领域模型必须严格。如果模型不严格并且包含模糊性,那么系统的一部分可能以一种方式表现而另一部分表现为另一种方式。例如,值机柜台的屏幕可能会显示“行李数量”,门口的另一个屏幕可能会显示“行李数”,装载人员使用的平板电脑可能会显示“行李箱”。

更糟糕的是,一些这些术语可能会将随身携带数量作为数字的一部分,而其他术语则不然。每当工作人员互相交谈时,他们每个人都必须记住对方正在看的屏幕,并记住从他们看到的号码中添加或减去随身携带物。有时会产生误解,丢失行李。该系统使业务失败,甚至领域专家都认为它没有意义。

另一个可耻的变体是模型在术语中是一致的,但在约束和关系上过于宽松。这通常是使用“标准系统”和“配置”到域的结果,这是使用企业资源规划(ERP)产品的常用方法。

最初为制造业创建的ERP,用于规划机器和原材料的使用。由于工厂不同,ERP系统可以高度配置,以满足每个工厂的需求。如今,它们经常被描述和销售为“标准系统”,可以配置为处理任何领域,而在引擎盖下它们仍然是相同的材料流系统。但是,这一业务已成功销售此类系统到其他领域,以处理各种客户投诉,比如警方调查或其他完全不同的领域。

如果你想配置物流系统来处理警察调查,你需要做一些非直观的抽象:“警察可以看作是一台机器,关于入室盗窃的报道可以看作是一堆原始的材料,在调查过程中由警察机器完善。“为了将一个领域变成另一个领域,你需要越来越少的具体,越来越不精确。

比如有一个“对象管理系统”,其中一切都是“对象”。通过用户界面,您可以更新对象的属性,但它几乎不了解这些对象代表什么。您通常可以填写属性和关系的任意组合。

过于宽泛的系统容易出错,这种宽容甚至可能导致安全漏洞。这需要快乐的业务人员和快乐的开发人员来建立一个好的系统。两个群体都需要满足他们的基本需求。

关注业务人员是很重要的。他们需要认识到他们习惯使用的领域,我们应该选择他们熟悉的术语。无法满足领域专业人士的需求是一个很大的错误。不能满足其他专业人士如开发人员的需求也是一个同样大的错误。作为开发人员,我们需要严格。说“大多数人有一只宠物”是不够的。我们需要知道“有宠物”是否仅限于只有一只宠物。

这是成为开发人员需要一些勇气的地方。我们需要问问题,使模型严格,没有含糊之处。

如果我们问“可以有多只宠物吗?”我们可能会得到答案,“哦,这真的很不寻常。”这给我们留下了两个选择。要么我们想要,“那么我需要允许一个宠物清单,”或者我们认为,“只允许一只宠物。”

在第一种情况下,我们最终编写的系统可能比必要的复杂性更多,迟早会有一些奇怪的组合将会发生。在第二种情况下,我们不允许多只宠物,只是在几个月后发现有一些顾客 - 也许是我们在收购另一家公司时获得的顾客 - 有两只或更多宠物时才会被打击。业务人员甚至会将责备转向我们。

摆脱这种困境的方法是积极询问模型中应该包含的内容:“我们应该允许多只宠物,还是我们只限制一只宠物?”决定是否应该覆盖不寻常的多宠物人群技术决策 - 这是一个业务决策。如果我们没有系统支持,那么必须通过单独的手动例程来处理它们。

另一方面,为许多多样性提供可能性也不是免费的。允许越来越多的通用模型很诱人; 迟早,一切都与其他一切都处于多对多的关系中,但从长远来看,这并没有带来任何好处。很难预见并概述一般模型的后果。

假设有一个功能允许一个人与另一个人“交换宠物”。如果我们还允许每人拥有多个宠物,那么我们需要弄清楚交换宠物意味着什么。这是否意味着A获得B人的所有宠物,反之亦然?或者我们只交换一只宠物?

如果我们不让模型反映业务领域,我们就会让商人失望。如果我们不让模型严格,我们就会让开发人员失望。一个好的模型必须反映业务领域并且要严格

模型严格意味着我们能够使用模型作为基础来构建代码。一个好的模型必须反映业务领域并且要严格。

当我们设计软件时,我们做出类似的选择 我们对复杂现象进行简单表示。让我们看一下教科书的面向对象示例,其中忽略了许多属性和关系,只剩下一个人的狭窄视图:

class Person { 
    private String name;
    private int age;
    private int shoeSize;
    private Animal pet;
    void growOlder() {
       this.age++;
    }
    void swapPetWith(Person other) {      ...
    }
}

在这个设计中,我们删除了一个人可能拥有的大量属性和行为,将其减少为四个基本属性。省略细节似乎会使系统变得更干巴,但它给我们带来了很大的好处。

我们通过省略细节获得的是准确的可能性。在人的领域中,“人”是一个复杂的交互,但在我们的域模型中,一个Person是具有名称,年龄,鞋子大小和变老能力的东西。当我们使用“人”这个词时,这些属性特征和能力都涵括了我们的意思。我们失去了丰富性,但是我们得到精确度

模型深刻理解

现实世界的问题更为复杂:比如 机场行李搬运,我们在领域模型中捕获的严格理解比大多数人想象的要深刻。事实上,我们需要捕获的知识甚至比大多数领域专家在日常工作中的理解更深刻,特别是当他们根据具体情况处理情况时。

这样做的原因是我们不仅需要足够的理解才能在域中工作,我们需要足够深入的理解来构建机器。让我们将其与骑自行车的挑战进行比较。

我们大多数人都是骑自行车的专家。我们可以通过骑自行车并骑行来证明这一点,即使在极具挑战性的条件下,例如在崎岖不平的道路和大风天气中,甚至在一只手臂下携带大包装时。这需要专业知识 - 将其与在阳光明媚的夏日学习骑平地的孩子所面临的困难相比较。

这种专业知识可与领域专家的专业知识相媲美。他们知道领域是如何运作的。例如,航运专家知道如果在条件变得艰难时如何布置货物集装箱,例如当集装箱错误地从船上卸下,并且在相当长的时间内没有其他船舶离开同一目的地时,也就无法再找到船舶装运这一个错误卸下的集装箱。领域专家可以处理甚至棘手的每个案例。

不幸的是,我们编写软件系统所需要的理解更深入。我们没有奢侈的“在现场”来处理任何出现的情况,能够评估和即兴解决问题。我们正在编写一个应该这样做的程序,没有我们(我们所有的专业知识)以人的形式存在。我们面临的挑战不是骑自行车,而更像是骑自行车的机器人。

如果我们要建造一个骑自行车的机器人,我们对自行车骑行的理解需要比大多数专家更深刻,甚至专业的自行车信使或BMX专业人士。例如:骑自行车时如何右转?想想它几秒钟 - 你可能已经完成了一千次。大多数人自发地回答:“我拉上右侧车把。”不幸的是,由于离心力的原因,这样做会导致你跌落到沥青路面上。你下意识地做的是把把手转向左边,导致你在短时间内向右倾斜。几毫秒后,您向右倾斜到合适的角度,然后将车把向右转 - 让您右转。您向右倾斜的角度正是补偿离心力所需的角度,您向右转,安全稳定。

你不假思索地做到这一点,并且在不了解微妙的动觉机制的情况下 - 赫伯特戈德斯坦的经典力学是一本关于这一主题的优秀书籍。但如果我们想要建造一个骑自行车的机器人,这就是理解的必要深度。

这个骑自行车的机器人故事给了我们一些坏消息和一些好消息。坏消息是,如果我们查看领域专家的负责人,我们发现没有现成的模型。那里没有“真实”的模型。我们不能要求领域专家,并希望得到我们需要的所有答案。好消息是,与领域专家合作制作模型是一项有趣而有益的工作。这样做是一个迭代过程,探索许多可能的模型,并选择一个适合解决我们手头的问题的模型。

制作模型意味着选择一个

建模的一个常见神话是,某处有一个“真实”模型,通常被认为是嵌入领域专家的头脑中。事实并非如此。制作模型涉及许多可能模型之间的主动选择,我们需要选择最适合我们需求的模型。

在领域驱动设计中,我们有时会使用“蒸馏模型”这一短语。让我们与威士忌蒸馏器进行一段时间的比较。威士忌酒从一大批发酵麦芽汁开始 - 基本上是不可饮用的 - 然后加入一些热量并收集蒸汽。蒸馏器丢弃第一部分,其中含有丙酮。中间部分由大部分酒精,一些水和溶解在其中的天然香料组成。这被认为是好的部分并保留。最后一部分包括一些酒精,大量的水和一些不太吸引人的味道。这也被丢弃了。保留的是我们所说的威士忌。您对威士忌或您的口味的个人态度可能会有所不同,但您明白了。当我们提炼模型时,我们抛弃现实的某些部分并保留其他部分。

这里重要的一点是蒸馏器有很多方法可以完成它们的工作。他们有一个选择。保持中间部分是一种选择,因为蒸馏器的目标是获得具有某些特定风味的高酒精度结果。但蒸馏器本可以做出其他选择。如果蒸馏器需要丙酮,那么蒸馏看起来会有所不同。蒸馏器将保留第一部分并丢弃其余部分。同样,我们可以根据我们打算使用模型的方式,从同一个现实中提炼出不同的模型

我们描述了一个名字,年龄,鞋子大小和宠物的人是一个模型。另一种模型可以是按出生日期,出生地,母亲姓名和父亲姓名来描述一个人。两种模型都没有比另一种更正确。每个都是不同的,有利于其设计目的。如果我们为狗主人俱乐部保留一个注册表,第一个模型明显优于第二个模型。如果我们正在研究一个家庭如何通过迁移传播到世界各地,那么第一个模型就毫无价值,第二个模型非常优秀。

在进行建模时,请主动尝试查找表达域的不同模型。尝试找到三种不同的模型,并比较它们在表达域问题方面的优势。找到一个好的模型很重要,因为它可以以有效和明确的方式讨论域。一个好的模型形成一种语言。

模型形成了无处不在的语言

建模的一个有趣方面是模型创建了一种语言 - 我们谈论系统的语言。

首先,我们必须意识到,当域专家互相交谈时,他们会使用自己的语言。这是域语言。它可能听起来像英语,但它在一个方面是英语的一个子集 - 有很多常见的英语单词没有在这个领域专家语言中使用。另一方面,它是英语的超集 - 有很多特定领域的术语和惯用语在普通英语中没有使用。哪些领域专家互相交流是一种旨在实现有效沟通的语言。

花点时间考虑系统开发人员的领域专家语言。在我们自己中,我们很容易抛出对我们来说非常有意义的术语,但对于非开发人员来说完全不可能理解; 我们可以“汇集关系”或“制定一项战略。”金融,物流或医疗保健领域的专家也有自己的术语。

如果我们正在建立一个物流系统,从物流中获取术语并将其编码为软件系统似乎是一种合乎逻辑的方法。这是个好主意,但不幸的是有缺陷。物流专家使用的语言在逻辑上并不一致。这并不是因为他们的术语特别草率。软件开发人员的术语同样草率。

聆听任何两位经验丰富的开发人员的谈话,你会发现他们可以互换地使用“对象”,“实例”和“类”这两个词,就好像它们是同义词一样。而且我们知道它们不是,因为当我们向初学者解释面向对象时,我们会小心区分“类”和“对象”。但是当两位专家讨论时,他们可能会草率,因为他们无论如何都要相互理解和真正的讨论在其他地方,更高层次。当他们互相交谈时,不要变成语言警察,纠正领域专家。在和同龄人交谈时,让自己变得草率。

如果我们正在建立一个物流系统,如果我们能够形成一种能够以精确的方式谈论系统而又没有误解风险的语言,那会不会很好?这正是模型的内容。如果我们在物流专家和开发商之间共同决定“支路”是指使用同一车辆从一个地方到另一个地方的运输,我们已经决定“终止一条路线”意味着货物在目的地卸下,那么我们就可以使用这些术语并让自己理解。如果我们说,“如果两个运输工具在同一个码头终止各自的路线,那么它们可以在下一条路线上共同运输”,那么这个短语可以被明确地理解并且可以实现该功能。

在讨论系统的功能时,请使用作为模型一部分的单词和短语。通过这样做,您将很快意识到功能是否可以实现。如果使用模型中的术语来表达功能很尴尬,这是一个肯定的迹象,它实现起来很尴尬。这可能表明模型需要扩展以包含新术语,并且系统重构以保持一致性。

使用领域驱动设计的术语,我们希望模型在谈论系统时成为无处不在的语言。无处不在,在这种情况下,我们的意思是术语应该在我们谈论系统的任何地方使用。应在用户界面,手册,需求或用户故事,代码和数据库表中使用相同的术语。为什么在用户界面中调用某个“数量”,在手册中将其称为“金额”,并将数据库列命名为“卷”?坚持跨学科使用相同的语言有助于发现可能表现为错误或安全漏洞的歧义。

值得指出的是,持久性模型可能与概念模型略有不同。例如,我们可能必须将概念拆分为不同的表,并且我们可能需要连接不属于概念模型的表或合成键。同样,出于特定于实现的原因,代码中的类可能与概念模型中使用的术语略有不同。然而,我们捕获的理解仍然是相同的,并且当我们命名我们的构造(类或数据库表)时,我们尝试尽可能多地使用无处不在的语言中的术语。

这并不意味着我们正在变成语言警察部队。在谈论系统时,模型或领域模型语言是无处不在的语言。领域专家仍被允许在他们自己之间使用他们模糊的领域语言,就像在与其他开发人员的讨论中允许开发人员对“对象”与“类”的草率一样。

关于在普遍存在的语言中保持精确的重要一点是,当我们谈论系统时,我们需要精确。当业务专家和开发人员进行交互时,这一点尤其重要,并且误解的风险最高。在这些情况下,我们应该坚持使用普遍存在的语言的术语。坚持在任何需求文档中使用域模型中的单词。如果在域模型的术语中难以表达某些内容,则可能很难将其编写为软件。

值得指出的是,因为语言无处不在并不意味着它是普遍存在的。在谈论这个特定的系统时,这是无处不在的语言,而不是谈论其他系统(甚至是其他物流系统)。

不同的系统有不同的需求和不同的重点。他们有不同的模型和不同的语言。每种领域模型语言都是其领域内无处不在的语言,但不在外部。语言的上下文有一个外边界。在域驱动设计中,我们将其称为模型的有界上下文。在有界上下文中,模型中的每个单词都有明确定义的含义,但在有界上下文单词之外可能意味着完全不同的东西。

请在这里查看liveBook上的书并查看这个幻灯片

作者简介:Dan Bergh Johnsson,Daniel Deogun和Daniel Sawano几十年来一直致力于安全与开发。他们是开发人员的核心,并且理解安全性通常是一个侧面问题。他们还改进了工作习惯,使他们能够以促进安全的方式开发系统,同时专注于高质量的设计习惯 - 开发人员在日常工作中更容易记住这些习惯。这三位都是国际发言人,经常出席有关高质量发展和安全的会议。

                   

2
zjsong
2019-07-09 20:27

好久没来解道网了,突然今天看到这篇文章,觉得很有价值,以后还是要常来看看:)

abcaxyzx
2019-07-11 15:07

我们也要求用领域驱动,特来学习