什么是以领域为中心的架构及其误解?


讨论分离业务和技术代码的好处,并解决常见的误解。

“域”是“业务域”的缩写。在这里,业务在广义上指的是应用程序旨在解决的现实问题(例如,待办事项列表、在线商店或游戏)。

可更换性
以领域为中心的架构(洋葱、干净、六角形……)将基础架构分为驱动端(由主适配器组成)和从动端(辅助适配器)。驱动端是应用程序的入口点:通过主要参与者,它触发应用程序的域(即核心)。被驱动端表示依赖的外部机制,例如数据库和第三方API。

支持以域为中心的体系结构的最常用论据是辅助适配器(驱动端)的易替换性。例如:

  • 在新项目中,您可以遵循基于选项的方法:从没有数据库的情况下开始(例如,通过存储在内存中或文件系统中)并将该决定推迟到最后负责的时刻(精益软件开发中的一个关键概念)。
  • 如果/当时间到了(并防止供应商锁定)时,替换数据库很容易。
  • 依赖技术并不局限于数据库。许多其他地方正在发生技术波动。例如,如果某些外部服务计划从 GRPC 切换到 REST,则只有一个更改位置(适配器)。
  • 一些测试策略依赖于轻松换出辅助适配器,例如使用内存中实现代替真实的数据库存储库或使用伪造的 API 代替外部 API。这使得测试更快、更稳定,并允许单独测试域(尽管默认情况下我更喜欢垂直测试)。

可替换性也适用于应用程序的入口点(驾驶侧)。例如:

  • 作为测试策略的一部分,您可以直接以域为目标,而不是通过入口点(例如,Web API、服务器端网页),入口点应该很薄并且不包含业务逻辑。
  • 您可以构建一个管理 UI(例如,基于 CLI 的后台办公室),它使用与实际应用程序相同的域。
  • 为相同的用例提供不同的入口点很容易(例如,Web API 和建立在相同域操作之上的网站)。

超越可替换性
尽管易于替换是一个重要的优势,但将其视为有利于以领域为中心的架构的最大好处是一种常见的误解。主要好处是:

  • 促进领域驱动思维(侧重于理解和建模业务领域,而不是被数据或数据库驱动)。
  • 鼓励领域和技术之间的关注点分离,这增强了代码的可维护性;易于更换只是一个很好的副作用。

请注意,将域与基础设施隔离的努力不能被视为面向未来,因为它使代码库在今天变得更好,同时支持可替换性和隔离,而无需额外成本。

将焦点转移到领域
干净的六边形架构是与领域驱动设计密切相关的架构模式。因此,应用程序应该以领域为中心和领域驱动,而不是由数据或数据库驱动,这是三层架构中的典型趋势。基础架构决策(例如,Web API 定义和数据库模式)应该作为域需求的结果发生。域不应该知道相应适配器中隔离的周围基础设施。

“我们中的许多开发人员长期以来一直使用分层模型,以至于它已成为第二天性,我们认为围绕数据库规划应用程序是世界上最正常的事情。首先规划和开发应用程序的业务方面不是更有意义吗?” 

易于替换有助于将重点从技术转移到应用程序的目标,并将技术方面视为细节。它迫使开发人员将软件视为一组行为,而不是数据库编辑器或 CRUD 提供程序。代码库和面向公众的 API 的设计应该围绕用例(以用户为中心)而不是 CRUD(以技术为中心)。

隔离
曾经,我不得不维护一个代码库,其中 SQL 查询遍布各处;这既麻烦又可怕。辅助适配器将与数据相关的决策隔离在一个单一的内聚位置。例如,ORM 对象和操作应该只存在于存储库中。一个更简单的例子:如果 JSON 属性发生变化,则爆炸半径被限制在单个文件中。

当我阅读涉及技术的代码时,我不想阅读业务代码,反之亦然(单层抽象)。业务领域规则太重要了,不能与技术细节混在一起。想象一下在更改重要业务规则时处理数据库异常、网络错误、缓存、UI 模板和重试。

隔离的好处也适用于领域:通过将其隔离为应用程序的核心,您拥有业务规则的单一真实来源和集中的入口点。隔离域可确保您永远不会跳过任何业务规则,无论入口点(主适配器)如何——可以是 Web API、GUI、CLI、作业、测试等。毫无疑问,隔离是值得的即使您从不交换数据库也是如此。当您考虑它的好处时,它的成本几乎为零。

对我而言,考虑到对代码可读性和易维护性的积极影响,隔离是采用以领域为中心的架构的最令人信服的理由。

业务逻辑存在于实体和用例中,但它也可以以其他形式出现,例如值对象。

何时使用它
以领域为中心的架构告诉您将业务领域代码与技术/基础设施相关代码分开。因此,如果涉及业务逻辑,那么遵循领域驱动架构是有意义的,即使是原始形式。包括后端服务(例如,Web API)和全栈服务(例如,服务器端呈现的 Web 应用程序、独立的本机应用程序)、游戏、设备驱动程序以及任何编码业务意图的内容。排除纯技术后端服务。前端应用程序(例如 Web、本机移动)可以从以领域为中心的架构(例如,将内部 DTO 与外部类型、意图驱动的代码库分离)中获得一些灵感,但它们不是以领域为中心的,因为它们不编码业务域规则。

以领域为中心的架构(干净的或六边形的)是关于代码设计的,而不是系统架构。它与单体与微服务的争论无关。这是在每个代码库级别上发生的决定。遵循限界上下文方法,每个上下文可以而且应该自主地以领域为中心。这些上下文之间的调解器是每一侧的适配器。

简单的应用程序呢?这值得么?您正在构建一个在温度单位之间进行转换的简单命令行实用程序。您需要输入解析/验证、单位转换和格式化/打印。你从一个单一的功能开始,但过了一段时间,你可能会有一个小泥球混合不同的关注点。很快你就会觉得有必要将它们分开。它们可以分为三个功能,分别映射到主适配器(解析)、域(单位转换)和辅助适配器(打印)。考虑到可读性、凝聚力和易于更改的好处,投资于这种分离并不要求太多。这些函数甚至可以存在于同一个文件中。

简而言之,重要的是将高级域代码(应用程序的意图)与低级技术代码(I/O)明确分开,而不管应用程序的大小。

精益架构
反对以领域为中心的架构的一个常见论据是,它们不值得付出代价。这是建立在他们需要额外工作的假设之上的,而事实上,不以领域为中心就是成本所在。

我花了一段时间才理解以领域为中心的架构。它们似乎势不可挡且过于死板,感觉就像如果你不忠实地遵守其中的一个,你就没有使用任何一个。那不是建设性的。

遵循以领域为中心的架构不应该是一个二元决定。

我从来没有看过这本书(对预构建/占位符架构保持警惕)。我只是应用一个精益和自适应的架构——只做当下需要的事情,同时保留选项。干净和六角形只是一种灵感。

一个好的代码库设计从简单开始,然后顺畅地适应不断变化的需求。避免“这需要是通用的”兔子洞。无论您做什么,都不要预先进行大的设计。适可而止。

以领域为中心的架构不强制执行任何框架或技术,也不需要任何特定的文件/类结构。开发人员需要提出模式和约定来具体化架构决策。然而,他们最终对他们产生了强烈的影响。例如:

  • 该域不应引用 JSON、HTTP 或 SQL 等技术术语。领域必须独立于基础设施,这鼓励依赖注入等模式。
  • 尖叫screaming架构指出应用程序应面向其业务/用户意图。每个用例都有一个文件有助于应用程序表达其意图(它还可以作为代码自我文档)。

请记住,以领域为中心的架构是达到目的的手段,而不是目标本身。这将帮助您决定需要什么以及何时值得。从为什么开始,并确保您始终遵循一组原则而不是其他人的理想架构。

停止考虑干净的六边形架构,努力以领域为中心。简单地将领域与技术分开是唯一在很长一段时间内都很重要的原则。此外,请确保域使用通用语言UL,这意味着它的代码(例如,变量、函数、类)遵守应用程序域中商定的现实世界术语。这在代码自文档和支持团队沟通方面也起着关键作用。作为后续阶段,它会为值对象、实体、用例和适配器建模。没有必要进一步复杂化;这种精简的架构涵盖了大多数情况。当您权衡收益时,付出的努力是最小的。