书评:域驱动设计

510页,于2004年出版。这本书对于一本技术书籍来说已经很老了,但是它的陈旧性非常好。 在我看来,它仅显示了几个不重要区域的年龄。

书中介绍的概念彻底改变了我作为开发人员的角色! 多次阅读它,并格外注意将其包含的所有知识内部化,这将是值得的。 为了真正理解这些概念并将它们集成在一起,您还应该在阅读本书的示例实施时构建一个项目。

我不能推荐这本书! 以下是我从阅读本书时获得的笔记,部分是每章的摘要,部分是我对内容的看法。

第1-3章

当以域驱动方式运行项目时,无处不在的语言是最重要的概念。 至关重要的是,必须使开发人员与领域专家进行交流。 一种方法是为领域专家构建原型以供评估。

核心学习内容是创建一个论坛,开发人员和领域专家可以在相互协作的同时互相学习以创建无所不在的语言。

开发人员擅长创建软件,但是该软件只能与对问题区域的理解一样好。 如果领域是复杂的,则在大多数情况下,领域专家会隐含一些知识,他们不容易交流,但是通过创建简单的原型并讨论领域模型,应该可以弥合这一差距。

使泛在语言与域模型保持同步是至关重要的。 如果一个改变,另一个也应该改变。 一个简单的例子:如果我们同意一个概念的名称,我们自然会在代码中为类名和变量使用该名称。 如果我们以后找到一个更好地描述该概念的名称,则必须更改代码以反映这一点。 如果我们不更改代码,则以后任何查看它的开发人员都可能不会将错误的名称与域概念联系起来。

我们不能要求领域专家告诉我们他们想要软件做什么。 它通常会导致过于详细的技术描述或抽象的概念。

开始讨论可能很困难,因为开始的进度会很慢,直到参与者彼此适应为止。

最初的领域模型(可能需要花费几次会议才能充实)应该产生一个原型。 我们进行原型设计是因为它使我们能够查看开发人员是否正确理解了域。 如果我们能够在输出有用信息的程序中连接域中的详细信息。 此过程是迭代的。

第4章,隔离域

系统由许多职责组成:UI,数据库,业务逻辑等。 将域相关代码与其他问题区分开来是很重要的。 如果业务逻辑分散在整个系统中,那么更改任何东西的难度就会越来越大。

一种方法是应用层,这是标准做法,并且使域对象杂乱无章。 除非需要,否则不要陷入依赖框架的陷阱,这一点很重要。 它可能变得重量级,并使域难以理解。

并非所有项目都受益于域驱动方法。 在某些情况下,您需要的是“智能UI”。如果业务规则非常简单,并且将来不再需要支持复杂的业务规则,那么域驱动方法的开销可能会太高。 在这种情况下,您可以将所有逻辑放入UI中而不必担心。 请记住,该方法应适合任务。

第5章,用软件表达的模型

域模型由基本元素实体,值对象,服务及其关联组成。

在现实生活中,存在许多多对多的关系。 但是,它们并不总是有用或容易与内部软件一起使用。 通常,有一些方法可以通过添加约束来更好地传达关联的意图。

例如,国家与总统之间的关系是双向的一对多关系。 但是方向之一更为重要,因为我们最开始的国家是国家,而不是总统。

我们可以通过在协会上添加诸如期限之类的约束来进一步证明这一点,因此我们可以问“谁是1998年美国总统”。

我们可以通过添加不同的约束来使关联更易于使用,例如:施加遍历方向,添加限定符以减少多重性或消除严格不需要的关联。

实体:实体不是由其属性定义的,而是由连续性和标识性的线程定义的。 即使您与另一个人具有相同的姓名和年龄,您仍然具有独特的身份。

跨多个系统的身份是相同的,例如带有病历的身份,在更换医院时,您仍然需要使用某些唯一的身份进行识别。 即使在系统之间未使用相同的标识符,实体仍然相同。 例如,当您将现金交给收银员并将其借记到商店帐户时,使用现金进行的交易仍然是同一笔交易。

价值对象:这些对象描述了事物的某些特征。 值对象比实体简单得多。 如果仅元素的属性很重要且不需要连续性,则它是一个值对象。

在某些情况下,概念是一个实体,在其他情况下,它是一个价值对象。 这取决于上下文。 例如,一个地址,如果您订购要交付的东西,那么您的室友是否也同时订购了东西就没有关系了。 传递服务可以将地址视为值对象。

但是,如果您订购了电气服务,则服务公司需要知道您的室友是否也订购了相同的地址。 在这种情况下,地址是一个实体。

服务:有时候,这不是问题。 一些重要的操作不自然地放在实体或值对象中。 它们是活动,但无论如何我们都必须使它们适合对象。

如果我们将动作放入错误的对象中,则该对象会失去清晰度并变得难以理解。 通常,动作也取决于许多不同的对象,因此依赖关系图变得难以理解。

应该将服务添加为声明为服务的独立接口。 此外,使服务成为无状态。 无状态行为使服务不依赖于任何状态,这使其行为更简单。

服务存在于不同的层中,不仅存在于域层中。 基础结构和应用程序层中还存在一些服务。 这种区分并不总是很清楚,并且需要注意正确地划分责任。

如果我们以本章中的示例为例: 银行有一个应用程序,如果帐户余额低于阈值,该应用程序会向客户发送电子邮件。

基础设施层服务:这种类型的解释很容易。 它们纯粹是技术性的,例如电子邮件或短信服务。 域和应用程序服务使用它们来实现消息的实际发送。

应用程序层服务:订购通知是应用程序服务的责任。 域层负责知道何时达到阈值。

引用这本书: 许多域或应用程序服务都建立在实体和值的总体之上,其行为类似于脚本,它们组织了域的潜力以实际完成工作。

第6章,域对象的生命周期

系统中的所有对象都有生命周期,它们会被创建,更改状态并被删除。 有些对象很简单,有些则很复杂。 我们需要特别注意复杂的对象。 面临的挑战是:

  1. 在整个生命周期中保持完整性
  2. 防止模型因管理生命周期的复杂性而陷入困境

将通过应用三种模式(聚合,工厂和存储库)进行管理。

集合:大多数业务模型包含对象之间的许多复杂关系。 它创建了模糊边界,最终我们可以得到巨大的互连对象层次结构。 这可能导致保持一致性的问题。 为避免这种情况,我们必须将某些对象提升为一组对象的集合。 仅在集合内部,我们需要保持一致性。

在集合外部,每个引用仅指向集合根。 并且通过聚合根访问对其他对象的所有访问。

这些规则使得可以在聚合内部保持一致性,因为访问受到控制。 关于聚合的更多想法。

工厂:创建对象可能很复杂。 我们不想将这种复杂性封装在对象的构造函数中。 书中的示例是:我们需要汽车制造厂来制造汽车。 汽车不需要知道如何制造自己。

构造逻辑被分离出来,成为一个单独的工厂。 另外,我们想创建一个域依赖的工厂接口,抽象出具体的实现。

工厂是创建集合的不错选择。 但是,如果我们需要在聚合内创建类的对象怎么办?

一种选择是在聚合根上添加一个工厂方法,例如,如果聚合根是采购订单,则它可能具有方法newItem()来创建“采购项目”对象并将其添加到聚合中。 如果新对象是聚合的一部分,则可以正常工作。

如果对象不是聚合的一部分,则它必须是聚合根,该根应具有用于构造的工厂。

在为工厂设计接口时,请记住以下几点:

  • 每个操作必须是原子的
  • 工厂将与其论点相结合

存储库:要对对象执行任何操作,需要对其进行引用。 该引用是通过遍历域模型中的关联获得的。 但是,我们并不总是希望添加关联,因为它会使模型更加混乱。 更好的方法是使用存储库从持久性重新创建域对象。

要访问聚合,我们经常需要根据对象属性搜索它们。 从集合中,我们可以遍历关联以到达其他对象。 我们不想允许免费的数据库查询,因为这会破坏域模型所施加的约束。

存储库实现了该类型所有对象的内存中集合的一种幻觉。 仅为需要直接客户端访问的聚合提供存储库,以保持模型的清晰度。

如果需要复杂的访问来搜索对象,则可以使用域语言来实现规范模式。

第7章,使用语言:扩展示例:

在本章中,将讨论一个系统示例。 该系统实现了一个用于处理货物运输的小域模型。

第一点是; 要使用系统,必须定义许多用户级应用程序功能。 功能之一是跟踪查询,使我们能够查看特定货物的处理事件。 应用程序类是协调器。 他们一定不能对所提出的问题做出答案 。 那是领域层的责任。

不一定总有一个用于聚合根的存储库。 如果没有用于查找聚合的应用程序要求,并且仅从其他实体引用该聚合,则不需要存储库。

当我们的领域增长时,我们必须注意将领域对象分为模块。 不要陷入将所有实体分组到一个模块而将所有值对象分组到另一个模块的陷阱。 而是,模块应反映“客户”,“运输”等含义。 每个模块应包含相互关联的实体和值。

在该示例中,需要将外部系统集成到现有应用程序中。 为了确保外部系统不会破坏现有模型,添加了一个反腐败层以屏蔽模型。

在该示例中,外部系统保存有关可以预订多少特定货物类型的信息。 为了处理我们的系统与外部系统之间的转换,添加了“分配检查器”服务。 该服务充当反腐败层。

第8章,突破

当对模型进行持续改进时,我们只会看到小小的改进,直到达到ureka时刻,在改进过程中,改进使模型更具表现力,并在整个项目中发出了声音。

只有少量的改进为大的改进铺平了道路。 该模型成为表达对领域的深刻理解的深度模型。 它应该允许技术人员和业务人员之间的交流得到改善,因为该模型提供了一种共享的语言,可以增进对整个项目的理解。

本章讲述了一个有关作者工作的项目是如何取得突破的故事。 对域的新了解通常需要进行大量且令人恐惧的重构,以进行所需的更改。 它需要勇气做必​​要的改变。

在许多情况下,突破会澄清该模型,使其他问题可见。 这会导致一系列改进,使模型更加深入。

第9章,使隐式概念显式化

我们如何建立一个深层模型? 深度模型的强大之处在于它使我们能够表达领域中的知识,活动,问题和解决方案。 灵活简洁。

首先,开发人员是该领域的新手,那么我们如何从业务人员中提取深度模型呢? 它会逐渐发生,本章介绍了提取知识的方法。

首先必须找到概念。 通过听取团队的语言,它可以以不同的方式发生。 如果领域专家使用的术语表示领域中的概念,但它不是领域模型的一部分,则表明该概念需要添加到模型中。

听对话:在其他情况下,缺少的概念不属于对话。 然后,您必须在模型中最尴尬的地方进行挖掘和发明。 在每个新需求都会增加复杂性的地方。 在业务合作伙伴的帮助下,可以更好地理解此领域,并改进模型。

矛盾:在某些情况下,领域专家会以不同的方式看待事物。 有时甚至矛盾。 这通常表示可以实现更深层次模型的区域。

阅读这本书:如果已经有关于术语和基本知识的文献讨论,那么您也许可以从深思熟虑的观点开始。

显式约束:在进行面向对象的建模时,通常会出现对对象的约束。 例如,实现容量有限的存储桶的类是一个约束。 约束通常是隐式的,但使它们显式可以改善模型。 可以将约束分解为类中的单独方法,也可以完全分解为自己的类。

流程作为域对象:流程不应成为模型的主要部分。 但是某些流程封装了业务含义,可以作为服务实现。 决定方法是该过程是否是领域专家谈论的话题。

第10章,柔顺的设计

软件必须服务于用户,但首先,它必须服务于开发人员。 开发人员重构,扩展和构建该软件。 随着时间的流逝以及软件处于维护模式,开发人员仍将更改代码。

如果软件缺乏良好的设计,则更改变得越来越困难。 一旦开发人员对所做的更改不满意,就会出现重复。 开发人员对使用代码库会感到不满意。 这种影响通常出现在项目中,随着时间的推移,增加新功能或修复错误的工作量会不断增加,直到项目停滞不前。

确保软件可更改的方法是使设计柔顺,并补充深度建模。 使设计柔顺是一个反复的过程,在该过程中尝试了重构,并明确了隐式概念。 在本章中,作者介绍了要进行哪些实验以及如何更好地理解我们希望进行编码的体系结构。

开发人员在编程时有两个角色,一个角色是使用应用程序代码中的域对象的客户端。 域应该使表达应用程序所需的场景变得容易,并且元素应该自然地融合在一起。 在另一个角色中,开发人员致力于更改域。 它要求设计具有开放的变更能力,并且变更的后果必须易于理解。

本章的其余部分将通过一系列模式来使用,以达到柔和的设计。

意图公开接口:开发人员永远不必知道要使用它的组件的实现。 如果需要一定程度的知识,我们就会失去封装的价值。 用描述类的作用和目的的方式命名类和操作很长的路要走。 使用“测试驱动开发”来确保代码的意图清晰易懂。

本章中的示例是一个简单的Paint类。 该类有一个名为“ paint”的方法,但是没有揭示该方法的作用。 相反,应将其重命名为“ mixIn”以表明该方法将涂料混合在一起。

无副作用的功能:操作可以分为两种类型:命令和查询。 命令修改系统的状态,并且查询仅读取信息。

当调用方法并且某些状态改变时,会发生副作用。 当我们在任意深度组合多个方法调用时,很难推断出触发了哪些副作用。 它限制了开发人员可以表达的丰富程度。

函数是一种我们知道不会产生副作用的方法。 在任何系统中,都应具有尽可能多的功能。 严格将命令分成非常简单的操作,这些操作不会返回域信息。

本章中的示例涉及两个对象,每个对象分别代表涂料的体积和颜色。 如果我们从上面使用mixIn方法,那么两个对象会发生什么? 涂料1的状态是否应更改为两种涂料的新组合颜色和体积? 涂料2的体积如何,混合后应变为0?

为避免此问题,绘画对象可以是不可变值的对象,以便mixIn返回第三个绘画对象,从而使其他两个绘画保持不变。 在这种情况下,推理起来容易得多。

断言:实体上仍然会有产生副作用的方法的集合。 断言使副作用明确且易于处理。

当调用将工作委托给其他方法的方法时,知道触发了哪些副作用的唯一方法是在程序中跟踪分支。 它破坏了封装。 它还依赖于实现,因为接口不执行任何有关破坏抽象的副作用的事情。

该概念已被“按合同设计”学校的编程语言所采用。 在C#中存在Spec Sharp,但没有对该概念的标准语言支持。

本章中的示例将油漆概念进一步重构为仅具有副作用的单一方法。 他们将断言的责任推迟到测试代码中。 在我看来,这迫使开发人员使用该组件读取测试以正确理解该组件。 我认为这有点儿没意思。 但是,要使其获得更好的语言支持,是必需的。

概念轮廓:此模式处理有效的分解。 处理分解时有两个极端。 在一种情况下,将这些概念分组为一个大的整体。 由于无法重复使用整体组件的部分且难以理解,因此会导致重复。 在另一种情况下,如果分解的粒度太细,则会迫使客户了解这些小碎片如何组合在一起。

我们应该尝试在领域中找到深层的一致性,并将设计元素分组为内聚单元。 该分组基于关于域的直觉。 从逻辑上讲,生成的接口应该在普遍存在的语言中有意义。

轮廓通常仅在经过大量重构以获取更深刻的见解后才会显示。

独立类:类中的所有依赖关系使推理变得更加困难。 依赖关系越多,测试就越困难。 难度将成倍增加。

低耦合是要追求的基本设计。 如果删除了所有依赖项,则该类可以自己理解。 理解模块时,这可以减轻认知负担。

关闭操作:在上面的模式中,我们冒着使模型陷入愚蠢的风险,因为我们将依赖项删除到表达性降低的地步。 此模式处理我们如何管理依赖项。

当合适时,我们应该将返回类型定义为与参数相同的类型。 我认为这就是所谓的流畅接口。

创建更具声明性的设计风格可以使我们受益。 与所有内容一样,这可以发挥到极致,例如模型驱动设计,其中代码生成工具生成实际代码。 这种方法并不总是足够灵活。 本章详细说明了如何将规范模式更改为更柔和和更具说明性。 它显示了如何扩展规范以使用和/或不使用运算符来组合规范。

第11章,应用分析模式

作为软件开发人员,您了解设计模式。 他们的目的是以一种行之有效的方式解决底层技术问题。

分析模式的方式相同,只不过它侧重于域。 它使两种模式都更加针对特定领域,但是同时,它也使我们能够捕获更多高级概念。 如果已经存在针对我们要实现的目标的分析模式,则可以帮助我们更快地生成一个好的解决方案,因为我们可以避免一些重构步骤。

本章所指向的唯一资源是Martin Fowler的Analysis Patterns,但它是一本比较老的书。 就我所能找到的内容而言,关于该主题的文章似乎还不够多。

第12章,将设计模式与模型相关

设计模式在软件开发文献中是众所周知的。 但是,设计模式的重点主要是技术。 但是,某些模式也可以在域模型中使用。 我们的想法需要有所不同。 作者展示了使用策略模式和复合模式的两个示例。

策略(策略):域模型包含对域有意义且在技术上没有动机的过程。 在许多业务领域中,需要有不同的过程来解决同一问题。 即使策略模式是出于技术动机,也恰好符合此目的,可以将流程的不同部分分为不同的策略。

本章示例是关于查找包裹运送路线的。 我们可能有一项政策,可以为旅途的每一个环节节省资金,或者寻找最快的路线。 在这种情况下,该策略不仅出于技术原因而实施,而且出于领域价值而同样重要。

合成:在许多领域模型中,我们最终将概念建模,这些概念由我们可以任意排列的部分组成为树状结构。 如果我们看不到这一点,那就开始将每个部分实现为一个独特的概念,而不是认为它是可组合的。 然后我们将以重复结束,这将阻止模型的灵活性。

本章中的示例是由路线构成的路线。 路线是一个复杂的概念,由带腿的路线组成。 由于每个“子路线”都可以由不同的人来计划和管理,因此它需要一个概念。 这是复合材料闪耀的地方。

第13章,重构以更深入地了解

有三点要考虑以获得深刻的见解

  1. 居住在域中
  2. 继续以不同的方式看待事物
  3. 与领域专家保持不间断的对话

在本章中,作者将逐步探讨如何改进领域模型。

发起:获得更好模型的第一步是看问题。 由于缺少概念,它可能会显示在代码的笨拙部分中。 模型中的语言可能与领域专家使用的语言断开了联系。 但是,找到问题所在后,即可重构模型。

探索团队:如果我们已经有了完善模型的想法,则可以直接重构代码。 但是在某些情况下,寻求新模型的工作会涉及更多,并且需要团队投入更多时间和精力。

几个开发人员和领域专家组成的团队在会议室中花了½小时到1½小时的时间来草拟新模型。 这应该导致对模型有一个大概的想法。 团队可能需要睡一会儿,再聚在一起才能得出有用的结论。 使它起作用的关键点是:

  • 自我决定,一个小团队可以即时组建,以探讨设计问题。 只需要一支短暂的团队。
  • 范围和睡眠,几天内进行的两三个简短会议应该提供一个值得尝试的模型。
  • 在团队其他成员和领域专家的集体讨论中,锻炼无所不在的语言,使用该语言并完善其用法。

背景技术:利用书籍中有关该领域的知识。 如果有分析模式,请使用它们。 设计模式通常还可以用于在领域中对概念进行建模。

开发人员设计:我们为用户开发软件,但也必须为开发人员开发。 当重构代码以获得更深入的见解时,代码将一次又一次地更改。 当设计柔顺时,很容易看到意图,也很容易预测更改后会发生什么。 这就是使设计适用于开发人员的原因。

时间:如果您等到可以为更改做出充分辩解之前,则等待时间太长。 推迟的更改越多,更改的成本就越高,难度就越大。 大多数团队对重构都过于谨慎。 可以很容易地看出重构的实现成本很高。 但是,解决不良设计的成本通常高于重构成本。 很难看。

在以下情况下重构:

  • 设计与团队对领域的理解不匹配
  • 重要概念隐含在设计中
  • 有机会让重要的部分变得更柔软

作为机会的危机:请注意,在阅读有关重构的文章时,这似乎是一个缓慢而渐进的过程。 重构并非经常如此,它为突然的洞察力打下了基础,这些洞察力揭示了该领域中的某些内容,这些内容反映出重构向萌芽的方向发展。

第14章,维护模型完整性

本章以及本书的其余部分将进行战略设计,因此它的层次更高。 我认为建议更适合于拥有更多人的大型项目。 但是,在开始一个新项目时,有许多要点是很有意义的。

模型必须在逻辑上保持一致才有意义。 当系统必须跨越一个大域时,理想的情况是拥有一个模型。 但是,随着模型的增长,这样做变得困难/不可能。 本章中的示例是两个团队在同一系统上工作。 一个团队实现了一个名为Charge的对象。 当另一个团队开始实现计费模块时,他们也需要一个Charge对象并重用代码,并且他们重​​用了模型中已经存在的实现。 但是,它们的更改在原始实现中创建了错误。

他们最终分为两个模型。 创建统一模型通常成本高昂或不可行。 一些风险是

  • 一次可能会尝试过多的旧式替换
  • 大型项目可能会陷入困境,因为协调开销超出了他们的能力
  • 具有特殊要求的应用程序可能必须使用无法完全满足其需求的模型,从而迫使他们将行为放到其他地方。
  • 相反,尝试用单一模型满足每个人的需求可能会导致复杂的选择,使模型难以使用。

本章的其余部分提供了有关如何创建边界以及在不同模型之间进行通信的模式。 模式列表有点长。

有限的上下文:在构建大型软件系统时,即使是在一个团队中,如果我们不小心,也会出现不同的模型。 与外部系统集成时,很容易看出这两个系统具有不同的概念模型。 但是,同一代码库中也可能会出现不同的模型。 代码的某些部分可能反映了对代码的较早理解。 当合并模型的错误时,会使系统可靠性降低,更难以理解。

模型适用于上下文,因此我们应该首先明确定义模型适用的上下文。 在相同环境下工作的团队需要进行大量沟通才能形成共识。 如果团队仅偶尔进行一次交流,那么他们将无法在相同的环境中工作,那么该模型将变得支离破碎。

定义有界上下文时,它具有两个优点,在上下文内部工作的团队知道他们必须保持模型的一致性。 此外,任何在外部工作的团队都将使用翻译层与模型进行通信,从而为他们提供更大的自由度。

当模型发生断裂时,其余模式会提供建议。

持续集成:在有限的上下文中,由于丢失了有价值的信息和选择,因此有时无法将其分解为较小的上下文。 多个问题可能会挑战模型。 如果开发人员不了解模型中的概念并建立相似的概念,那么我们将以重复和两个不同的概念来解决问题。 如果开发人员过分谨慎并且知道模型中的概念,但由于存在更改重复的风险,也会发生类似的问题。

有关更好地控制代码和增强代码信心的建议是

  • 可复制的构建
  • 自动化测试
  • 集成代码更改的规则
  • 使用团队中普遍使用的语言促进交流

持续集成仅在有界上下文中使用,而在多个上下文中则不需要它。

上下文映射:对于多个有界上下文,将需要将它们互连。 当发生这种情况时,重要的是要有一个模型来避免团队开始融合上下文之间的边界。 上下文图与项目管理和软件设计重叠。

坐在一起的团队成员自然会开始共享有限的环境。 但是,请注意位于其他位置的团队成员,这需要额外的集成工作才能共享相同的上下文。 在许多情况下,一个带有不同有界上下文名称的小图足以使开发人员看到该区别。

有界上下文之间的接触点对于测试很重要,因为测试可以在错误成为问题之前就发出警报。

共享内核:在某些情况下,跨多个有界上下文重用模型的相同部分具有重要价值。 但是,连续集成太昂贵了,在这种情况下,可以定义一个共享内核,其中包含域模型的子集。 子集是共享的,并且必须对此代码部分中的任何更改进行协调。

客户/供应商开发团队:在许多系统中,都有子系统从我们的系统接收数据。 子系统也可以使用另一种语言来构建,从而使得代码共享变得不可能,并且它还可以服务于另一个用户组。 自然,这些系统处于不同的受限上下文中。 系统之间存在微妙的平衡。 如果子系统开发人员不想实施我们要求的更改,则可能无法开发我们的系统。 同样,如果我们有否决权阻止子系统中的更改,那么它将使子系统难以开发。

可以通过使所有团队参与计划以确保优先顺序一致来解决差异。 开发自动验收测试还可以确保系统之间的交互保持正常运行。

确保团队能够协调合作是至关重要的,如果没有关系商店就可以崩溃。

确认者:如果无法建立客户/供应商关系,则团队可能需要转变为遵循者。 如果需要集成到另一个系统,我们有两个选择。 要么设置一个反腐败层,以吸收其他团队所做的任何更改。 或者,如果模型在很大程度上兼容,则可以直接使用它,并且我们的系统符合其他系统的模型。

反腐败层:当我们负责与遗留系统或其他外部系统集成时,通常会有自己的模型。 重要的是,我们的域模型应尽可能具有表现力,因此我们不希望外部系统的模型泄漏并“污染”我们的域模型。

为了确保不会发生泄漏,将创建隔离层。 该层的目的是包含与外部系统进行通信所需的任何翻译,并对我们的域模型进行任何语义翻译。 如果该层很严格,它应该允许我们开发域模型,而不必担心外部系统的语义和模型。

反腐败层的公共接口通常是带有偶尔实体的服务列表。 这样的新层允许我们在一致的模型中重新抽象另一个系统的行为和模型。 图层本身通常是由某些外观,适配器和转换器构成的。

分立方式:集成系统很昂贵。 并非总是需要集成。 如果我们可以使用UI中指向外部系统的超链接进行管理,那将是一个便宜得多的解决方案。

开放式主机服务:每个有界上下文都需要一个翻译层,用于与上下文外部进行通信的每个组件。 但是,如果该组件需要由其他许多人使用,则创建客户翻译器可能会很麻烦。

在这种情况下,创建一个公开服务列表的协议可能很有意义。 像REST api服务或类似服务。

发布的语言:在两个有界上下文之间进行翻译需要使用共同的语言。 这种语言可能变得复杂且难以记录。 如果企业需要交换数据,他们可能不想遵守另一方的语言。

在某些领域,开发了一种语言来支持通用语言。 它的示例是BIAN,CML和许多XML模式。 如果已经存在一种语言,则值得研究是否可以使用它。

本章的其余部分专门讨论如何在不同的模式之间移动项目,从分离方法到共享内核再到持续集成等等。

第十五章,蒸馏

如果域变大,则变得难以管理。 通过提取领域,可以更清晰地传达核心概念。 随着我们对更深入的洞察力进行重构,模型变得更加清晰,但是当领域很大时,我们将如何管理它。 我们希望所有团队成员都能看到整体设计以及如何将其组合在一起。 该模型应通过具有可管理大小的核心模型来促进沟通,以允许新团队成员使用无所不在的语言。 蒸馏应指导重构,并将工作重点放在价值最大的模型领域。

本章包含一系列模式,以帮助我们实现这些目标。

核心领域:在大型系统中,将有许多重要组成部分。 但是,许多组件将掩盖域模型的本质。 如果系统难以理解,则很难更改。 并非设计的所有部分均得到同样完善。 关键核心应时尚且充分利用以创建功能。

核心应该容易与领域模型的其余部分区分开。 另外,它应该很小。 让最优秀的开发人员在核心上工作,使它质朴。 必须将其重构为一个深层模型并同时使其柔软。 我们应该根据其他部分对蒸馏核心的支持方式,着重于对系统其他部分的投资。

其余的模式可以帮助我们使核心更易于查看,使用和更改。

选择要包含在核心域模型中的哪一部分不是一件容易的事。 即使诸如通用货币之类的概念对于模型而言至关重要,但除非它是我们正在构建的货币交易应用程序,否则将其包含在核心中可能并不足够重要。

通用子域:模型的某些部分可能无法捕获或传达任何专业知识。 这些部分不应该是核心领域的一部分,众所周知和起辅助作用的一般概念将污染核心模型。

确定不是软件的主要动机的内聚子域。 将它们分成单独的模块,这些模块不引用任何专业。 此后,可以独立于核心域并以较低的优先级进行开发。 这种分离也使我们可以考虑开发子域的不同选择。

  1. 现成的组件
  2. 发布的设计或模型
  3. 外包实施
  4. 内部实施

每个选项在本章中都有不同的优点和缺点。

领域愿景声明:项目启动时没有模型,但是我们仍然需要一种集中精力的方法。 在项目的后期,我们需要交流系统的价值,而无需深入研究。

为了满足这些需求,可以创建愿景声明,这是关于一页纸的描述,描述了核心领域及其价值主张。 它应在项目开始时编写,并在发现新见解时进行修订。

突出显示的核心:愿景声明创建了核心领域的概述。 但这仍然取决于个人的参与。 如果团队没有出色的沟通技巧,那将不会有太大影响。

为了突出显示核心结构更改,需要在代码中进行。 但是,这样做并非总是可行的,并且常常需要缺少概述。 因此,一种更轻松的方法是突出显示核心。 一种方法是创建一个精简文档,其中包含一些基本对象以及一些图表。 它不应该是完整的设计文档。 最多三到七页。

突出显示核心的另一种方法是创建标记的核心。 在显示设计的文档中,标记了必不可少的元素,这可以像印刷设计文档中的便笺一样原始。 它使开发人员可以更轻松地浏览核心。

内聚机制:在OOP中,我们希望将算法中的“方法”与“内容”分开以隐藏复杂性。 但是,有时这种方法会达到极限。 当我们的代码开始肿且难以阅读时,因为我们的代码“太多”了,我们需要一种不同的方法。

创建一个轻量级框架,该框架使用意图揭示界面隐藏“如何”,现在可以使域代码更加清晰。

隔离的核心:当模型中的某些元素既服务于核心领域又提供支持角色时,核心可能会与通用概念耦合。 这会导致混乱,使模型不太清晰。

应重构代码,以将核心概念与支持概念分开。 这将增强代码的内聚性,同时减少与其他代码的耦合。

这些步骤通常是:

  • 确定核心领域
  • 将相关类移动到新模块,该模块以与它们相关的概念命名
  • 重构代码以删除与不直接相关的数据和概念的连接。 擦洗核心域以使其易于说明
  • 重构新分离的核心模块,使其更简单,更易于交流。
  • 重复另一个核心子域,直到分离的核心完成

第16章,大型结构

当系统变得非常庞大时,即使将其分解为模块可能也不够。 在某些情况下,系统会变得过于复杂,并且包含太多模块,以至于仅模块数量就会引起理解系统的问题。 在这种情况下,开发人员将看不到树木的森林。

必须创建跨越整个系统的规则或角色和关系的模式。 它应该允许您整体上理解每个部分的位置,而无需详细了解它们自己所拥有的部分。

不断发展的顺序:如果对系统的设计没有任何限制,它将演变成一个没人理解且难以维护的系统。 但是,如果我们施加严格的设计约束和前期假设,则会限制系统的建模能力,因此可能会妨碍系统的开发。 这会使开发人员简化系统以适应结构,或者根本没有结构。

当我们发现一个可以大大澄清系统中模型的结构时,应采用大规模结构。 不合适的结构要比没有结构差,因此要寻求最小的解决方案。

责任层:在大型结构中,如果每个单独的对象都具有手工职责,则没有足够的结构和指导方针来共同处理整个领域。 在职责上加上一些结构,使其更易于处理。

我们应该重构模型,以便每个域对象,集合和模块都适合一层的责任。 各层应传达域的现实情况或优先级。 主要是业务建模决策,如何构造层。

“较低”级别应在较低级别的背景下有意义,而“较低”级别应独立存在。

本章中提出了适合许多不同类型域的不同层示例。 这些层是面向业务的,因此根据作者的说法,所提供的层几乎适用于所有领域。

知识级别:域需要越通用,例如,如果我们需要对象根据用户可更改的规则进行交互,则系统变得越复杂。

它可以是CRM系统,其中每个安装都是通过配置针对特定客户需求定制的。 在这种情况下,我们需要一组独特的对象来描述和约束基本模型的结构和行为。 某种“元级别”。

可插拔组件框架:借助成熟的深层和精炼模型,机会不断涌现。 与需要互操作但基于相同抽象并独立设计的多个系统。 我们需要在系统之间进行许多转换,并且共享的内核是不可行的,因为团队之间无法紧密合作。

在这种情况下,我们可以提取一个包含接口的抽象核心,并创建一个框架,该框架允许多个实现可以轻松替换。

本章中的示例是Sematech CIM框架,它是用于半导体制造工业机器的框架。 每台机器的软件必须遵守CIM框架中设计的接口,但是当这样做时,它可以自由互换。

第17章,将策略整合在一起

三个驱动原理,上下文,蒸馏和大型结构是互补的原理。 我在本章中看到了很多优点,但是由于我没有从事任何大项目,因此本章并没有花费太多时间。

结论

这是一本很棒的书,值得多次阅读以内化所有知识。 我的巨大建议。