[译]领域驱动设计速成课程

原文链接:https://blog.bytebytego.com/p/a-crash-course-on-domain-driven-design

为复杂领域开发软件是一项具有挑战性的任务。

随着问题领域的复杂性不断增长,创建准确表示业务概念、规则和流程的软件变得越来越困难。设计不良的软件很快就会变成难以理解、难以维护和扩展的混乱代码。

领域驱动设计(DDD)为这个问题提供了解决方案。

DDD 是一种软件开发方法,它通过强调对核心领域和业务逻辑进行建模的重要性并使用这些模型作为软件设计的基础来解决领域复杂性。

领域驱动设计的核心是:

近年来,领域驱动设计的需求愈发迫切。基于微服务和云计算的架构已导致系统由众多以复杂方式交互的小组件组成。如果没有清晰且定义明确的领域模型来指导其设计,此类系统很快就会变成“一团泥球”。

在本文中,我们将了解领域驱动设计的基础知识及其关键概念,这些概念可以帮助我们构建与核心领域和业务逻辑一致的更易于维护和扩展的系统。

img

领域驱动设计的核心原则

领域驱动设计 (DDD) 专注于创建与底层业务领域紧密结合的软件系统。

它旨在通过将领域模型置于开发过程的中心来弥合技术实现和业务需求之间的差距。

DDD 有三个核心原则:

让我们更详细地探讨每个原则。

创建丰富的领域模型

DDD 的基础在于创建一个丰富的领域模型,该模型可以准确捕捉问题领域的关键概念、关系和业务规则。该模型不是由开发团队单独创建的,而是通过与对业务有深入了解的领域专家密切合作而产生的。

将领域知识提炼为可用模型的过程称为知识消化。

在此过程中,开发团队与领域专家密切合作,以确定和完善与问题最相关的概念和规则。这种协作工作通常包括集思广益、试验不同的模型设计,以及根据开发过程中获得的反馈和见解进行迭代改进。

例如,在银行系统中,与财务部门领域专家进行的知识会议将重点了解账户、客户、交易和利息计算等核心概念。

目标是创建一个模型来捕捉这些基本概念及其关系,这些模型可以被领域专家理解,并可作为软件系统的实践基础。

使用通用语言

随着领域模型的成型,开发团队和领域专家会形成一种共同语言。DDD 将此称为通用语言,因为它渗透到项目的各个方面。

img

通用语言直接基于领域模型。

代码中的类、方法和变量的名称均源自模型概念。同样,在讨论和文档中,团队使用通用语言的术语和短语,以确保清晰的沟通并避免歧义。

通过一致使用通用语言,代码直接反映模型,使得模型更容易理解和修改。

例如,如果银行领域模型包含“帐户”、“存款”和“取款”等概念,那么这些相同的术语将在代码、开发人员和领域专家之间的讨论以及项目文档中一致使用。当每个人都使用相同的语言时,任何理解上的歧义或不一致之处都会很快显现出来。

这是一个简单的代码示例,演示了在编写类及其方法时如何使用通用语言。

public class Account {
private String accountNumber;
private double balance;
public void deposit(double amount) {
// Perform deposit logic
balance += amount;
}
public void withdraw(double amount) {
// Perform withdrawal logic
if (balance >= amount) {
balance -= amount;
} else {
throw new InsufficientFundsException("Insufficient funds for withdrawal");
}
}
}

领域模型到软件设计

在 DDD 中,领域模型不仅仅是一个概念工具。它是软件设计的基础。软件的结构和行为反映了模型的结构和行为。

这种方法称为模型驱动设计。

在实践中,这意味着代码中的类、关系和行为直接对应于领域模型中的概念、关系和规则。设计不是由技术考虑或基础设施细节驱动,而是由有效表达领域模型的需求驱动。

例如,如果银行领域模型定义了一个“帐户”概念,其中包含“存款”和“取款”等行为,那么软件设计将包括一个“帐户”类,其中包含“存款”和“取款”方法。这些方法如何工作的实现细节将由模型中捕获的规则和要求指导。我们已经在上一节中看到了这一点。

构建领域模型的关键模式

构建领域模型是领域驱动设计中最重要的方面。然而,一些关键的模式和实践可以帮助我们实现最佳结果。

让我们详细了解一下其中的主要内容。

分层架构

在领域驱动设计 (DDD) 的背景下,分层架构有助于隔离领域模型并促进明确的关注点分离。

下图显示了应用程序如何构建成层。

img

通常,分层架构由四个主要层组成:

例如,在电子商务系统中,领域层将包含产品、订单和客户等核心业务概念。UI 层将具有产品展示、购物车和结账的屏幕。应用层将处理订单流程。基础设施层将提供持久性、消息传递等功能。

实体

在领域驱动设计 (DDD) 中,实体是一个基本概念,表示具有独特身份和连续性线索的对象。

实体具有几个关键特征,可以将其与域中的其他对象区分开来:

要理解实体的概念,请考虑银行系统。在这种情况下,BankAccount 是实体的一个典型示例。

每个银行账户都有一个唯一的账号作为其身份。无论不同账户之间可能具有什么共同属性,这个账号都可以将一个账户与另一个账户区分开来。

img

银行账户的信息可能会随着存款、取款或账户持有人信息更新而发生变化。尽管发生了这些变化,但账户仍保留其身份。在整个生命周期内,账户始终是同一个账户。

值对象

值对象代表域的描述性方面,但没有自己的身份。它们仅由其属性定义,而不是具有与其他对象区分开来的单独身份。

值对象的一些关键特征如下:

例如,在客户实体中,表示特定位置的地址是值对象的典型示例。它由街道、城市和邮政编码等属性定义。此外,任何两个具有相同详细信息的地址都被视为同一地址。

img

其他一些值对象示例是“MonetaryAmount”、“GeographicLocation”、“Color”等。

服务

服务是一个概念,表示不自然地属于实体或值对象职责的操作或行为。它们封装了不属于任何特定对象但仍在领域模型中发挥重要作用的逻辑。

服务的主要特征如下:

例如,航空预订系统可以有一个“FlightSchedulingService”,负责根据提供的标准查找可用的航班。此服务中的方法可以将出发机场和目的地机场作为输入,并返回可用航班列表。这样的操作自然不属于任何单个实体,例如“机场”或“航班”,因为它涉及协调来自多个来源的信息并应用业务规则。

img

聚合

在领域驱动设计 (DDD) 中,聚合是一种通过将相关对象分组为一个有凝聚力的单元来帮助管理领域模型的复杂性的模式。

它们具有几个主要特征,例如:

为了说明聚合的概念,让我们考虑一个订单处理系统中的例子。

在此示例中,“订单”及其关联的“订单项”形成自然聚合。“订单”封装了与订单处理相关的业务规则和不变量,例如计算总金额、应用折扣以及确保订单项的有效性。

img

从代码角度来看,在处理 LineItem 时,Order 聚合可能如下所示:

public class Order {
private String orderId;
private List<LineItem> lineItems;
private BigDecimal totalAmount;
private OrderStatus status;
public void addLineItem(LineItem lineItem) {
lineItems.add(lineItem);
totalAmount = totalAmount.add(lineItem.getSubtotal());
}
public void removeLineItem(LineItem lineItem) {
lineItems.remove(lineItem);
totalAmount = totalAmount.subtract(lineItem.getSubtotal());
}
}
public class LineItem {
private String productId;
private int quantity;
private BigDecimal unitPrice;
}

外部对象(例如“客户”或“付款”)将仅引用“订单”实体,而不直接引用“LineItems”。对“LineItems”的任何操作或修改(例如添加、删除或更新项目)都将通过“订单”实体执行。

存储库

存储库是一种模式,它为特定类型的实体或聚合根提供持久层上的抽象。它充当类似集合的接口,封装底层数据访问和存储机制。

关于存储库的几个要点如下:

例如,“CustomerRepository”将提供诸如add(customer)、remove(customer)和findByID(id)等方法来管理客户实体的持久性,而不公开存储方法的细节。

img

这些方法允许客户端代码以模仿“客户”对象集合的方式与“CustomerRepository”进行交互。

客户端可以添加、删除和查询客户,而无需担心数据如何存储在数据库中。在后台,存储库可以使用 ORM 工具、SQL 查询或任何其他持久性机制与底层存储系统进行交互。

工厂

在领域驱动设计中,工厂是一种封装复杂实体或聚合创建的模式。它为客户端提供了一个创建对象的接口,而无需暴露创建过程的细节或所涉及的依赖关系。

工厂的一些主要特征如下:

例如,电子商务系统中的“OrderFactory”可以获取所有必要的详细信息(客户、商品、数量等),创建“Order”和“LineItem”实例,将它们适当地链接在一起,并将生成的订单汇总返回给客户端。

img

DDD 的战略设计概念

在处理大型项目中的大型模型时,应遵循一些战略设计理念,以获得领域驱动设计的好处。

有界上下文

在大型软件项目中,领域模型通常包含多个子域,每个子域都有自己的一组概念、实体和业务规则。

尝试在所有这些子域中维护单一、统一的模型可能会导致模型复杂且混乱,并且随着时间的推移会变得难以理解、推理和维护。

解决这一挑战的方法是引入“有界上下文”的概念。

有界上下文是领域模型中的特定区域,代表特定子领域或一组相关概念。每个有界上下文都封装了其统一模型,该模型根据该子领域的特定需求和要求量身定制。

定义良好的有界上下文具有以下特征:

为了理解有界上下文的概念,我们来看一下电子商务系统的例子。在这个系统中,我们可以识别出几个子域,每个子域都可以是一个单独的有界上下文:

img

有界上下文之间的关系

不同的有界上下文之间可能存在关系和交互。这些关系通常通过不同上下文的模型之间的共享概念或翻译来表示。

建立有界上下文之间关系的一些关键模式如下:

1 - 上下文映射

在复杂的领域驱动设计 (DDD) 项目中,多个有界上下文代表系统的不同子域或部分。理解整个系统格局可能会变得困难。创建上下文映射将大有裨益。

上下文图是项目中的有界上下文及其之间关系的高级可视化表示。它提供了系统的鸟瞰图,突出显示了关键边界和集成点。

上下文图应该提供以下内容的清晰概述:

2 - 共享内核

共享内核代表多个团队或有界上下文常用和依赖的领域模型的一部分。它封装了领域中的核心概念、实体及其关系,这些概念、实体及其关系与系统的不同部分相关。

例如,假设有两个有界上下文 - 销售和履行。销售有界上下文包括产品目录、定价、促销、购物车和订单下达。履行有界上下文处理库存、供应商、装运和交付。

虽然这些有界上下文关注的重点各不相同,但“订单”是它们共同的一个概念。“订单”源自销售有界上下文,但一旦下达,就需要由履行有界上下文来履行。因此,订单构成了销售和履行之间的共同核心。两个团队需要就“订单”的含义及其生命周期达成一致。

img

3 - 反腐败层

当将新系统与具有不兼容或不良模型的遗留系统或外部合作伙伴的系统集成时,保护新系统模型的完整性和纯度至关重要。

这就是防腐层模式发挥作用的地方。

防腐层充当新系统与现有系统或外部合作伙伴系统之间的绝缘层。它提供了一种转换机制,使新系统能够与其他系统进行交互,而不会损害其自身的模型和设计原则。

img

工作原理如下:

概括

在本文中,我们学习了领域驱动设计的基础知识以及它如何帮助您基于核心领域设计系统。

让我们简要总结一下所学内容:

[译]数据库扩展策略速成课程
Github Action 发布 Jar 到 Maven 中央仓库