干净的体系结构:域驱动的设计,第1部分

2020-12-14 22:02:42

上一次我们讨论软件体系结构时,我向您介绍了分层体系结构。今天,我们将从清洁架构(也称为洋葱或端口和适配器架构)开始,更具体地说是域驱动设计。

干净架构作为一个概念比分层架构要复杂得多。因此,我决定将文章分为两部分。在今天的文章中,我将尝试简单地解释域驱动设计背后的思想。

我们需要了解领域驱动的设计,因为它位于Clean体系结构的中心(从图形上和字面上看)。

域驱动的设计具有其技术性(我们将详细检查),但是首先,DDD是一种思考方式。

从名称可以看出,这种设计方法是由业务领域驱动的。换句话说,我们完全专注于域的业务逻辑。

集成和基础结构功能我们考虑了域模型不知道也不依赖的细节。

反过来,业务域可以具有子域,并且这些子域尽可能彼此隔离。

让我们提供一个简单的示例。假设我们必须使用域驱动设计为送餐系统创建一个应用程序。

这里的域是订购,付款,与餐厅沟通以及交付和跟踪的整个过程。

对上面的示例使用DDD应该创建一个封装的核心,该核心由具有严格边界的清晰定义的业务实体组成。不要惊慌。我们将解释一切。

域驱动设计最困难的事情是建立域及其子域的正确逻辑模型,并定义适当的边界。这是开发人员不能也不应该单独做的事情。

开发人员应与业务人员紧密合作。只有商务人员知道他们如何精确地经营自己的业务,只有他们才能向开发人员说明域和子域。

在这一点上,开发人员不应考虑任何技术性。他们的任务是仅从业务逻辑的角度对域及其子域进行建模。他们没有考虑数据库,持久性,表示形式,其他集成等问题。他们创建的模型是仅限于任何外部依赖项的纯对象。

开发人员和业务人员应为系统中的业务对象创建通用语言。这种通用语言称为无处不在的语言。在这个花哨的字眼后面隐藏着达成共识的简单目标。应该命名术语(这也适用于代码),以便任何人都可以理解。

有界上下文是一种模式,其主要思想是在不同子域之间绘制边界。这样做是因为我们希望每个子域都尽可能独立。

从上面的示例中,我们可以看到我们的食品配送系统具有不同的子域。这些子域中的两个是订单管理和交付管理。代表两个子域的模型应该隔离。订单管理模型应该对交付模型一无所知,反之亦然。

如果我们有一个应在两个子域中表示的模型,则可以复制该模型。例如,两个子域都需要一个客户作为模型。复制模型时,我们仅提取特定子域所需的属性。因此,在不同的子域中使用具有相同名称但属性不同的模型是很正常的(当然,我们在两个子域中都通过统一的ID来跟踪该模型)。

贫血模型是仅具有属性而没有任何逻辑的模型。通常,它们也只能使用公共的setter和getter进行糟糕的封装。例如,当我们有一个以数据库为中心的体系结构(如分层体系结构)时,就使用贫血模型。

丰富的模型具有业务逻辑。它们被很好地封装。我们使用方法来执行所有特定操作,而不是使用公共方法。丰富的模型集合是只读的,只能通过方法和构造函数进行修改。丰富模型无法创建,也不能以无效状态存在。该实现应保证在进行任何操作之后,该模型处于有效状态。

使用域驱动的设计,我们将面对构建其核心的几个不同元素。他们是:

域驱动设计中的实体是具有唯一标识的丰富模型元素。例如,我们可能有一个Order实体。 Order实体具有标识符(例如autoincrement bigint)。使用该标识,我们跟踪实体并建立关系。

值对象是没有特定标识的模型元素。它们的识别基于所有属性的比较(按值)。

它们还可以包含业务逻辑。值对象主要作为其他实体的属性存在。

值对象的一个​​很好的例子是Order实体的交货地址。

集合是一组应该一起处理的对象。每个聚合都有一个聚合根。聚合根是负责控制其他对象的主要对象。

对聚合中元素的任何命令或请求都应通过聚合根。

在我们的示例中,对于食品配送系统,这种汇总是Order和OrderItem实体的分组。在这种情况下,Order实体是聚合根。如果要向订单添加新的OrderItem,则应通过聚合根来进行。

根级别“以下”的实体不应允许在聚合外部进行状态修改。

正如我们所说,在域驱动设计中,模型应尽可能地封装和隔离。 例如,“订单”聚合不应引用“交货”聚合,也不应被“交付”聚合引用。 他们不仅不应该相互了解,而且禁止基于合同的注射。 交流的唯一正确方法是通过领域事件(您可能会争论一些,但我坚信这一点)。 如果创建了新订单,则订单集合将发布OrderCreatedEvent事件。 对此事件感兴趣的所有其他聚合都可以处理并处理它。 域事件是域模型的一部分,但是其工件在域层之外进行处理。 在下一篇文章中,我们将详细介绍。 众所周知,工厂是用于创建对象的设计模式。 在域驱动的设计中,工厂模式将域聚合的创建与应用程序层隔离开来。

在最佳情况下,工厂应接收普通的DTO(或仅原始类型),并包含尽可能少的逻辑。

存储库是另一种众所周知的模式。其背后的想法是封装与数据存储的通信并提供抽象层。

在域驱动的设计中,每个聚合根都有一个存储库。但是正如我们所说,域模型不应该熟悉任何基础架构机制。这就是为什么我们不将存储库实现放在域模型中,而是将基础结构层放在其中的原因。

在域模型中,我们仅放置存储库的合同(接口)。下一篇文章中还将对此进行详细讨论。

我们与业务人员紧密合作,旨在为业务的不同领域建模。

我们仅使用简单的类和原始类型创建模型(实体,值对象,集合)。我们不在乎持久性,UI,外部集成,外部库等。这些东西不属于我们的域核心。

模型具有复杂的逻辑,但是我们严格封装了该行为。即使最初创建后,它们始终处于有效状态。

通常,我们将不同的模型进行分组,因为它们不能仅靠它们自己处于有效状态。这些组我们称为集合。

模型是孤立存在的,因此它们无法直接通信。因此,我们使用域事件。

域核心不依赖任何东西。其他层(例如应用程序和基础结构)也依赖于此。

就像我在上面说的那样,域驱动设计是Clean体系结构的核心概念,这就是为什么必须熟悉这种思维方式的原因。一开始很困惑是很正常的做法,但这并不会让您感到沮丧。

下一步,我建议您阅读Eric Evans的绝妙著作“域驱动设计:解决软件核心中的复杂性”。阅读时,请尝试将知识应用于没有DDD的系统。相信我,这项运动对您有很大帮助。

在下一篇文章中,我们将了解域模型如何适应Clean体系结构以及所有这些复杂性背后的观点。

下次我评论时,请在此浏览器中保存我的姓名,电子邮件和网站。