Books - Domain-Driven Refactoring - 3

Chapter 4, Tactical Patterns

Tactical Patterns  战术模式

Having explored the key strategic patterns in the previous chapter, we now shift our focus to the tactical patterns of DDD. The patterns we will cover include entities, value objects, aggregates, repositories, services, and modules.

在上一章中探讨了关键的战略模式后,我们现在将重点转向领域驱动设计的战术模式。我们将涵盖的模式包括实体、值对象、聚合、仓库、服务和模块。

We will conclude the chapter with an in-depth analysis of events, in which we will discuss their significance. We will clarify the distinctions between domain events and integration events, ensuring you grasp their importance and correct application in DDD.

本章我们将以对事件的深入分析作为结尾,其中我们将讨论其重要性。我们将阐明领域事件和集成事件之间的区别,确保你理解它们在 DDD 中的重要性及正确应用方式。

By the end of this chapter, you will have a comprehensive understanding of these tactical patterns and their critical role in effective DDD.

到本章结束时,你将对这些战术模式及其在有效 DDD 中的关键作用有一个全面的了解。

In this chapter, we will cover the following topics:

在本章中,我们将涵盖以下主题:

* Understanding tactical patterns in DDD

理解 DDD 中的战术模式

* Services and modules  服务和模块

* Domain and integration events

领域和集成事件


Technical requirements  技术要求

Throughout the book, we will refer to an example application to demonstrate the practical implementation of the concepts discussed. You can access the complete code for this example application on GitHub at the following URL: https://github.com/PacktPublishing/Domain-driven-Refactoring/branches. This repository will serve as a valuable resource throughout the rest of the book, allowing you to explore the code in detail and experiment with the patterns we cover.

在整本书中,我们将引用一个示例应用程序来展示所讨论概念的实际实现。您可以在以下 URL 的 GitHub 上访问此示例应用程序的完整代码:https://github.com/PacktPublishing/Domain-driven-Refactoring/branches。这个存储库将在本书的其余部分作为一个宝贵的资源,允许您详细探索代码并尝试我们涵盖的模式。

By the end of this chapter, and as you progress through the book, you will not only gain theoretical knowledge but also practical skills in applying DDD principles using C#. This hands-on approach will solidify your understanding and prepare you to tackle real-world challenges in your software development refactoring.

到本章结束时,随着你阅读本书,你不仅将获得理论知识,还将掌握使用 C#应用 DDD 原则的实践技能。这种实践方法将巩固你的理解,并为你应对软件开发重构中的实际挑战做好准备。


Understanding tactical patterns in DDD

理解 DDD 中的战术模式

If the purpose of the strategic pattern is to identify the boundaries to split our domain into many subdomains, the purpose of the tactical pattern is to identify the components inside each bounded context.

如果战略模式的目的在于识别边界以将我们的领域划分为多个子域,那么战术模式的目的在于识别每个限界上下文内的组件。

In this section, we will cover the tactical aspects of DDD, where different patterns play distinct roles depending on the complexity and strategic importance of the subdomain. We must remember that not all subdomains are created in the same way. Different subdomains have different levels of complexity and different strategic importance. If you are writing code for a generic subdomain, you most likely do not need any special patterns. Sometimes, create, read, update, and delete (CRUD) is more than enough! A model that represents the table in your database is enough, and the use of an object-relational mapper (ORM) is sometimes helpful.

在这一部分,我们将探讨领域驱动设计(DDD)中的战术层面,不同的模式根据子域的复杂性和战略重要性扮演着不同的角色。我们必须记住,并非所有子域都是同等创建的。不同的子域具有不同的复杂程度和不同的战略重要性。如果你正在为一个通用子域编写代码,你很可能不需要任何特殊模式。有时,创建、读取、更新和删除(CRUD)就足够了!一个代表你数据库中表的模型就足够了,有时使用对象关系映射器(ORM)会很有帮助。

But what happens if you have to write code for your core subdomain? In this case, you need to have a model that represents your business problem—not just a collection of properties. A model composed of only properties is called an anemic domain, and this should not be your goal. A domain model has to be a full business model, with properties and behaviors. It has to be consistent from the moment it is instantiated and has to remain consistent in all its states. This consistency is upheld by enforcing business rules, invariants, and transactions, all of which must be guaranteed and protected for the entire lifespan of your domain model.

但如果您需要为您的核心子域编写代码呢?在这种情况下,您需要一个能够代表您的业务问题的模型——而不仅仅是一组属性。仅由属性组成的模型称为贫血领域,这不应是您的目标。领域模型必须是一个完整的业务模型,包含属性和行为。它必须在实例化时保持一致性,并在所有状态下保持一致。这种一致性是通过强制执行业务规则、不变量和事务来维护的,所有这些都必须在您的领域模型的整个生命周期中得到保证和保护。

Take, as an example, our ERP for managing a brewery, and particularly our core subdomain, Sales. The flow to create a new sales order has to satisfy the following constraints, and many others:

以我们用于管理酿酒厂的 ERP 系统为例,特别是我们的核心子域 Sales 。创建新销售订单的流程必须满足以下约束,以及其他许多约束:

* The customer should be able to create a new order. They should have enough credit to do that.

客户应该能够创建新订单。他们应该有足够的信用额度来这样做。

* The beers must be available in our warehouse or, at least, we should be able to restock them reasonably quickly.

啤酒必须在我们的仓库中有库存,或者至少我们应该能够相对快速地补货。

* The delivery address should be valid and within the delivery area.

收货地址必须有效且在配送区域内。

The domain model we create must implement behaviors to verify each of the preceding conditions. As you can see, a sales order has many objects and is a useful way to introduce some tactical patterns, which we are going to cover in the following sections.

我们创建的领域模型必须实现行为来验证上述每个条件。如您所见,销售订单包含许多对象,并且是介绍一些战术模式的有效方式,这些模式我们将在接下来的章节中涵盖。

Entities  实体

An entity is an object that is primarily defined by a unique identifier, often referred to as its identity. This identity distinguishes one entity from another, even if their other attributes are identical. For example, in a business domain, a customer might be uniquely identified by a customer ID or a government-issued identifier such as a social security number.

实体是一个主要由唯一标识符定义的对象,通常称为其身份。这个身份区分一个实体与另一个实体,即使它们的其它属性相同。例如,在业务领域中,客户可能通过客户 ID 或政府颁发的标识符(如社会安全号码)来唯一识别。

What makes entities special is their life cycle: they are created, modified, and eventually deleted, but their identity remains constant throughout. Even if their details (such as a customer’s address or phone number) change, the entity itself remains the same as long as its identity is preserved.

使实体特殊的是它们的生命周期:它们被创建、修改,最终被删除,但它们的身份始终保持不变。即使它们的详细信息(如客户的地址或电话号码)发生变化,只要它们的身份得到保留,实体本身仍然是相同的。

Unlike a value object, which we will analyze shortly, the attributes of an entity can change over time. For example, a customer’s address or contact information may change but the customer remains the same entity.

与我们将要分析的值对象不同,实体的属性可能会随时间变化。例如,客户的地址或联系信息可能会改变,但客户仍然是同一个实体。

Entities are persisted in storage systems such as databases. This persistence ensures that the state of the entity can be retrieved and maintained across the different sessions or uses of the application.

实体存储在数据库等存储系统中。这种持久性确保了实体的状态可以在不同的会话或应用程序使用中被检索和保持。

In our sales order, the object that contains the data of the order, such as customer data, order and delivery date, and the payment attribute, represents an entity. We will see that the sales order entity also contains the methods to implement the behavior of our business because we shouldn’t create an anemic domain.

在我们的销售订单中,包含订单数据(如客户数据、订单和交付日期以及支付属性)的对象代表一个实体。我们将看到,销售订单实体还包含实现我们业务行为的方法,因为我们不应该创建贫血领域。

The following code shows an example of an entity in our ERP. We use the library named Muflone (https://github.com/CQRS-Muflone/Muflone), which we wrote to support us, and we will get into it in the following chapters. For the moment, assume that Entity is just a base class with basic properties, such as Id:

以下代码展示了我们 ERP 中一个实体的示例。我们使用了名为 Muflone (https://github.com/CQRS-Muflone/Muflone)的库,该库是我们为支持自身而编写的,我们将在接下来的章节中深入探讨。目前,假设 Entity 只是一个包含基本属性的基础类,例如 Id :

public class SalesOrderRow : Entity

{

    internal BeerId _beerId;

    internal BeerName _beerName;

    internal Quantity _quantity;

    internal Price _beerPrice;

    protected SalesOrderRow()

    {

    }

}

The first thing you will notice is that the SalesOrderRow class implements the base class, Entity.

你会首先注意到的是, SalesOrderRow 类实现了基类 Entity 。

Secondly, you can see that the property types are not primitive types (e.g., int or decimal) but custom types that come from the ubiquitous language. We cannot stress enough that it is very important to share decisions about the behavior of this object with everyone who is working on it.

其次,你可以看到属性类型不是基本类型(例如 int 或 decimal ),而是来自通用语言的定制类型。我们必须反复强调,与所有参与该对象开发的人员共享关于其行为决策是多么重要。

Finally, all the properties are declared internal because only the methods inside this namespace are allowed to change their values.

最后,所有属性都声明为 internal ,因为只有这个命名空间内的方法才被允许修改它们的值。

This encapsulation enforces a clear separation between the internal state of the entity and external access. An entity is defined not just by its properties but, more importantly, by the behaviors that operate on those properties.

这种封装强制在实体的内部状态和外部访问之间保持清晰的分离。实体的定义不仅由其属性决定,更重要的是由那些操作这些属性的行为决定。

If you’re wondering how application services can access these properties, the answer is: they can’t. Instead, a component within the domain (such as a mapper) is responsible for transforming entities into data transfer objects (DTOs). These DTOs are then used by the external layers of the application, preserving the integrity of the domain. Direct exposure of domain objects to external layers would break the principle of encapsulation and could lead to unwanted dependencies.

如果你在想应用服务如何访问这些属性,答案是:它们不能。相反,领域内的一个组件(例如映射器)负责将实体转换为数据传输对象(DTO)。然后这些 DTO 被应用的外部层使用,以保持领域的完整性。领域对象直接暴露给外部层会破坏封装原则,并可能导致不希望的依赖。

An entity in DDD is a core concept that emphasizes the importance of a unique identity. This identity remains constant even as the entity’s attributes change, making entities suitable for representing objects that need to be tracked individually throughout their life cycle. Understanding entities helps with designing systems that accurately reflect the uniqueness and persistence of real-world objects, ensuring that the business logic can manage these objects’ states and identities consistently.

在领域驱动设计中,实体是一个核心概念,它强调唯一身份的重要性。这种身份即使在实体的属性发生变化时也保持不变,这使得实体适合表示需要在整个生命周期中单独跟踪的对象。理解实体有助于设计能够准确反映现实世界对象独特性和持久性的系统,确保业务逻辑能够一致地管理这些对象的状态和身份。

Value objects  值对象

A value object is defined by its attributes rather than a unique identity. Two value objects are considered equal if all their attributes match, regardless of their location or instance within the system. This lack of identity makes them ideal for representing descriptive concepts in the domain, such as an address or a monetary amount.

值对象是由其属性定义而非唯一身份的对象。如果两个值对象的所有属性都匹配,则被视为相等,无论它们在系统中的位置或实例如何。这种缺乏身份的特性使它们非常适合表示领域中的描述性概念,例如地址或金额。

A key characteristic of value objects is immutability: once they have been created, their state cannot be changed. If you need to modify a value object, you create a new instance with the updated attributes. This immutability ensures consistency and thread safety, as the state of a value object cannot be altered unexpectedly.

值对象的一个关键特征是不变性:一旦创建它们,它们的状态就无法更改。如果需要修改 value 对象,请使用更新的属性创建新实例。这种不可变性确保了一致性和线程安全性,因为值对象的状态不会意外更改。

Value objects are typically short-lived and easily replaceable as they describe properties rather than tracking specific instances. For example, an address in a sales order is a value object because the system doesn’t need to uniquely identify that address; it only cares about its descriptive attributes.

值对象通常生存期较短且易于替换,因为它们描述属性而不是跟踪特定实例。例如,销售订单中的地址是一个 value 对象,因为系统不需要唯一标识该地址;它只关心它的描述性属性。

In DDD, distinguishing between a value object and an entity depends on context. Use a value object when the identity is irrelevant and the focus is on attributes:

在 DDD 中,区分值对象和实体取决于上下文。当标识无关且焦点在属性上时,请使用 value 对象:

* An address in an order is a value object because its attributes (e.g., street and city) define its value and it doesn’t need a unique identity

订单中的地址是一个值对象,因为它的属性(例如,街道和城市)定义了它的值,并且它不需要唯一的标识

* A customer, on the other hand, is an entity because it has a unique identifier (e.g., customer ID) that persists over time, even if its properties, such as the address, change

另一方面, 客户是一个实体,因为它具有随时间而保留的唯一标识符(例如,客户 ID),即使其属性(如地址)发生变化也是如此

This distinction has practical implications. If a customer’s address changes, the old value object can be discarded and a new one created. Since value objects lack unique identities, there’s no need to maintain or track the old instance.

这种区别具有实际意义。如果客户的地址发生更改,则可以丢弃旧的 value 对象并创建新的 value 对象。由于值对象缺少唯一标识,因此无需维护或跟踪旧实例。

When designing a system, it is crucial to decide whether a concept should be modeled as a value object or an entity. This decision affects how you manage persistence, how you handle equality checks, and how you design the overall data model.

在设计系统时,决定应将概念建模为价值对象还是实体至关重要。此决定会影响您管理持久性的方式、处理相等性检查的方式以及设计整体数据模型的方式。

The following code is an example of a value object. Just as entities inherit from a common Entity base class, value objects inherit from a ValueObject base class. This allows you to avoid rewriting shared logic for each value object in your code base:

以下代码是 value 对象的一个示例。正如实体继承自公共 Entity 基类一样,值对象继承自 ValueObject 基类。这样,您可以避免为代码库中的每个 value 对象重写共享逻辑:

public class Price(decimal value, string currency) : ValueObject

{

    public readonly decimal Value = value;

    public readonly string Currency = currency ?? throw new NotImplementedException(nameof(currency));

    protected override IEnumerable<object> GetEqualityComponents()

    {

        yield return Value;

        yield return Currency;

    }

}

We want to reiterate that the big difference between an entity and a value object is that the former needs to have an ID, while the latter has no ID because it is defined by its attributes.

我们想重申一下,实体和值对象之间的最大区别在于,前者需要有 ID,而后者没有 ID,因为它是由其属性定义的。

Notice also that each attribute is readonly. That is because a value object is an immutable object, which means if you need to modify it, you have to replace it with a new one.

另请注意,每个属性都是只读的。这是因为 value 对象是不可变对象,这意味着如果你需要修改它,你必须用一个新的替换它。

Value objects are crucial in DDD for representing concepts that can be defined entirely by their attributes, without needing a unique identity. They are immutable, defined only by their attributes, and are often used to describe aspects or characteristics of entities. Understanding the difference between value objects and entities aids in building a clear and consistent domain model, ensuring that the system accurately represents the real-world processes it supports.

值对象在 DDD 中至关重要,用于表示可以完全由其属性定义的概念,而无需唯一标识。它们是不可变的,仅由其属性定义,通常用于描述实体的各个方面或特征。了解值对象和实体之间的差异有助于构建清晰一致的域模型,确保系统准确表示它支持的实际流程。

Aggregates

An aggregate is a group or cluster of domain objects, which can include entities and value objects. These domain objects are logically related and are treated as a single unit for the purpose of data changes. This means that changes to the state of any part of the aggregate are considered changes to the whole aggregate.

聚合是一组或一组域对象,其中可以包括实体和值对象。这些域对象在逻辑上是相关的,并且出于数据更改的目的,它们被视为一个单元。这意味着对聚合任何部分状态的更改都被视为对整个聚合的更改。

The aggregate has a special entity known as the aggregate root. This aggregate root serves as the entry point for accepting and interacting with the objects within the aggregate. It controls all access to the internal objects, ensuring that the integrity and invariants of the aggregate are maintained. The aggregate root is the only object that can be directly referenced from outside the aggregate.

聚合具有一个称为聚合根的特殊实体。此聚合根用作接受聚合中的对象并与之交互的入口点。它控制对内部对象的所有访问,确保维护聚合的完整性和不变量。聚合根是唯一可以从聚合外部直接引用的对象。

Enforcing that all interactions go through the aggregate root, which encapsulates the internal structure and logic of the aggregate, helps maintain consistency, as the aggregate root can enforce business rules and constraints.

强制所有交互都通过聚合根,聚合根封装了聚合的内部结构和逻辑,这有助于保持一致性,因为聚合根可以强制执行业务规则和约束。

Aggregates define a consistency boundary. Within this boundary, all changes are made together in a single transaction. This means that the aggregate as a whole is always in a consistent state, even if some parts of it change.

聚合定义一致性边界。在此边界内,所有更改都在单个事务中一起进行。这意味着聚合作为一个整体始终处于一致状态,即使它的某些部分发生更改也是如此。

Defining appropriate aggregate boundaries is crucial in DDD. Aggregates should not be too large, which can lead to performance and complexity issues, nor too small, which can lead to consistency problems. The goal is to encapsulate related changes and maintain consistency within the aggregate.

定义适当的聚合边界在 DDD 中至关重要。聚合不应太大,否则会导致性能和复杂性问题,也不应太小,否则会导致一致性问题。目标是封装相关更改并保持聚合内的一致性。

Since all changes within an aggregate are made in a single transaction, it’s important to design aggregates that can efficiently maintain this consistency without compromising performance. This often involves trade-offs between strong consistency and the scalability of the system.

由于聚合中的所有更改都是在单个事务中进行的,因此设计能够有效保持这种一致性而不影响性能的聚合非常重要。这通常涉及在强一致性和系统的可伸缩性之间进行权衡。

Figure 4.1 shows the relationships between aggregates. You can see that the entities of an aggregate may only link to the aggregate root of other aggregates. Relationships between entities in different aggregates that are not defined as aggregate roots are not permitted!

图 4.1 显示了聚合之间的关系。您可以看到,聚合的实体只能链接到其他聚合的聚合根。不允许在未定义为聚合根的不同聚合中的实体之间建立关系!

Now, let’s move our attention to the SalesOrder aggregate of our ERP by looking at the following piece of code:

现在,让我们通过查看以下代码将注意力转移到 ERP 的 SalesOrder 聚合上:

public class SalesOrder : AggregateRoot

{

    internal SalesOrderNumber _salesOrderNumber;

    internal OrderDate _orderDate;

    internal CustomerId _customerId;

    internal CustomerName _customerName;

    internal IEnumerable<SalesOrderRow> _rows;

    protected SalesOrder()

    {

    }

    internal static SalesOrder CreateSalesOrder(SalesOrderId salesOrderId, Guid correlationId, SalesOrderNumber salesOrderNumber,

        OrderDate orderDate, CustomerId customerId, CustomerName customerName, IEnumerable<SalesOrderRowJson> rows)

    {

        return new SalesOrder(salesOrderId, correlationId, salesOrderNumber, orderDate, customerId, customerName,

            rows);

    }

    private SalesOrder(SalesOrderId salesOrderId, Guid correlationId, SalesOrderNumber salesOrderNumber, OrderDate orderDate,

        CustomerId customerId, CustomerName customerName, IEnumerable<SalesOrderRowJson> rows)

    {

        // Check SalesOrder invariants

    }

}

The SalesOrder class derives from the AggregateRoot base abstract class so that everyone knows this is the entry point for the SalesOrder aggregate. The goal of this object is to maintain the integrity of SalesOrder as a whole, starting from its creation.

SalesOrder 类派生自 AggregateRoot 基抽象类,因此每个人都知道这是 SalesOrder 聚合的入口点。此对象的目标是从创建 SalesOrder 开始维护其整体完整性。

Also, all subsequent changes of status are the responsibility of this object, which exposes the method to implement the business behaviors. These methods can be reached through domain services or command handlers, depending on the pattern you used to implement the aggregate, but you can see all these cases shortly in this chapter.

此外,所有后续的状态更改都由此对象负责,该对象公开了实现业务行为的方法。这些方法可以通过域服务或命令处理程序来访问,具体取决于您用于实现聚合的模式,但您很快就会在本章中看到所有这些情况。

An aggregate in DDD is a key concept that organizes related domain objects into a cohesive unit. The aggregate root is the only part of the aggregate accessible from outside, ensuring encapsulation and consistency. Aggregates define transaction boundaries, which are crucial for maintaining the integrity of the system’s state. By managing how domain objects are accessed and modified, aggregates help enforce business rules and invariants, providing a robust structure for complex business logic.

DDD 中的聚合是一个关键概念,它将相关域对象组织成一个内聚单元。聚合根是聚合中唯一可从外部访问的部分,可确保封装和一致性。聚合定义事务边界,这对于维护系统状态的完整性至关重要。通过管理域对象的访问和修改方式,聚合有助于实施业务规则和不变量,从而为复杂的业务逻辑提供强大的结构。

Repositories  存储 库

The repository pattern acts as an intermediary between the domain model and the data mapping layers. It provides a way to manage access to the data source and ensures that the domain model remains independent of the underlying data access implementation.

存储库模式充当域模型和数据映射层之间的中介。它提供了一种管理对数据源的访问的方法,并确保域模型保持独立于基础数据访问实现。

It provides an abstraction layer over the data access mechanism and hides the details of data storage, retrieval, and mapping, thus allowing the domain model to focus solely on the business logic. This also decouples the domain layer from the data access, promoting a clean separation of concerns.

它在数据访问机制上提供了一个抽象层,并隐藏了数据存储、检索和映射的细节,从而允许域模型只关注业务逻辑。这也将域层与数据访问分离,促进了关注点的干净分离。

The purpose of the repository is to provide an interface for accessing aggregates from a data store. It handles CRUD operations and hides the implementation details of the data source.

存储库的目的是提供一个接口,用于从数据存储访问聚合。它处理 CRUD 作并隐藏数据源的实现详细信息。

In this pattern, a repository interface is defined that includes methods for retrieving and storing objects. For example, a repository interface for the SalesOrder aggregate might look like this:

在此模式中,定义了一个存储库接口,其中包括用于检索和存储对象的方法。例如,SalesOrder 聚合的存储库接口可能如下所示:

public interface ISalesOrderRepository

{

    SalesOrder GetById(Guid id);

    IEnumerable<SalesOrder> GetAll();

    void Add(SalesOrder salesOrder);

    void Update(SalesOrder salesOrder);

    void Remove(SalesOrder salesOrder);

}

This interface provides a contract for data access operations without exposing details about the underlying implementation.

此接口为数据访问作提供协定,而无需公开有关底层实现的详细信息。

The implementation of the repository interface will vary depending on the data source.

存储库接口的实现将根据数据源而有所不同。

There are many benefits of using the repository pattern, starting with decoupling. The repository pattern decouples the domain logic from data access, allowing the domain object to focus solely on business logic. Using repository interfaces makes it straightforward to replace the data access layer with a mock or stub, facilitating the writing of unit tests. Thinking of the main goal of our exploration (refactoring), the repository centralizes data access logic, making it easier to maintain and modify your code after introducing it. That is possible because the repository ensures consistent handling of data access operations across the application, promoting a single source of truth for data access.

使用 repository 模式有很多好处,首先是解耦。存储库模式将域逻辑与数据访问分离,允许域对象仅关注业务逻辑。使用存储库接口可以直接将数据访问层替换为 mock 或 stub,从而简化单元测试的编写。考虑到我们探索(重构)的主要目标,仓库集中了数据访问逻辑,引入后更容易维护和修改你的代码。这是可能的,因为存储库可确保对整个应用程序中的数据访问作的一致处理,从而促进数据访问的单一事实来源。

To conclude, repositories are used to manage aggregates rather than individual entities, as aggregates represent the consistency within a domain model. The design of the repository and the domain model should reflect the domain’s needs, not the underlying data storage mechanisms. Often, the unit of work pattern is used alongside repositories to manage transactions and ensure the atomicity of operations.

总而言之,存储库用于管理聚合,而不是单个实体,因为聚合表示域模型中的一致性。存储库和域模型的设计应反映域的需求,而不是底层数据存储机制。通常,工作单元模式与存储库一起使用,以管理事务并确保作的原子性。

Unit of work  工作单元

A unit of work manages a list of objects impacted by a business transaction and coordinates saving changes and resolving concurrency issues.

工作单元管理受业务事务影响的对象列表,并协调保存更改和解决并发问题。

Factories  工厂

The factory pattern is a creational design pattern used in DDD to encapsulate the creation logic of aggregates. The primary purpose of the factory pattern is to provide a way to create objects without exposing the instantiation logic to the client and to ensure that the created objects are in a valid state. By centralizing creation logic in a factory, you maintain consistency, reduce duplication, and encapsulate the construction process.

工厂模式是 DDD 中用于封装聚合创建逻辑的创建设计模式。工厂模式的主要目的是提供一种方法来创建对象,而无需向客户端公开实例化逻辑,并确保创建的对象处于有效状态。通过将创建逻辑集中在工厂中,您可以保持一致性、减少重复并封装构建过程。

Creating an aggregate involves complex validation or setting up a graph of objects. A factory can encapsulate this complexity and ensure that the created aggregates always satisfy the business rules and constraints defined in the domain model. By using a factory, you ensure that the objects are always created in a consistent and valid state, preventing inconsistencies that might arise from ad hoc object creation.

创建聚合涉及复杂的验证或设置对象图。工厂可以封装这种复杂性,并确保创建的聚合始终满足域模型中定义的业务规则和约束。通过使用工厂,您可以确保对象始终以一致且有效的状态创建,从而防止因临时对象创建而可能出现的不一致。

Why do you have to use this pattern? Because, like the repository pattern, the factory pattern has many benefits. First, the factory provides a single point to enforce business rules during the creation of objects, ensuring that all created objects are valid. When aggregate creation logic changes, it only needs to be updated in one place, the factory, rather than throughout the application. This is possible because aggregate construction processes, especially those involving the setup of dependencies or state, are abstracted away from the client. To conclude, the factory can be mocked or stubbed in unit tests, allowing more controlled and isolated testing scenarios.

为什么必须使用此模式?因为,与 repository 模式一样,Factory 模式也有很多好处。首先,工厂提供了一个在对象创建过程中执行业务规则的单点,确保所有创建的对象都是有效的。当聚合创建逻辑更改时,它只需要在一个位置(工厂)进行更新,而不是在整个应用程序中进行更新。这是可能的,因为聚合构造过程,尤其是那些涉及依赖项或状态设置的过程,是从客户端中抽象出来的。总而言之,可以在单元测试中对工厂进行模拟或存根,从而允许更多受控和隔离的测试场景。

Factories should only be responsible for creating aggregates, not for complex domain logic. They should not perform tasks beyond object creation, such as coordinating activities or managing transactions. The methods in a factory should use terms from the ubiquitous language, making it clear what the factory creates and what parameters are required.

工厂应该只负责创建聚合,而不负责复杂的域逻辑。它们不应执行对象创建以外的任务,例如协调活动或管理事务。工厂中的方法应该使用来自通用语言的术语,明确工厂创建什么以及需要什么参数。

The primary goal of a factory is to ensure that the created aggregates are always in a valid state, adhering to the business rules and constraints of the domain. A good practice is to keep the constructor of entities private or protected and use factory methods for object creation. This ensures that the factory controls the instantiation process, working directly with the aggregate root to maintain the integrity and invariants of the entire aggregate.

工厂的主要目标是确保创建的聚合始终处于有效状态,并遵守域的业务规则和约束。一个好的做法是将实体的构造函数保持为私有或受保护,并使用工厂方法创建对象。这确保了工厂控制实例化过程,直接与聚合根一起工作,以维护整个聚合的完整性和不变性。

While factories ensure that aggregates are created in a consistent and valid state, they do not handle complex domain operations or cross-cutting concerns. This is where services come into play, providing a structured way to encapsulate domain logic that does not naturally belong to an entity or a value object.

虽然工厂确保以一致和有效的状态创建聚合,但它们不处理复杂的域作或横切关注点。这就是服务发挥作用的地方,它提供了一种结构化的方式来封装自然不属于实体或值对象的域逻辑。


Services and modules  服务和模块

In DDD, services and modules play a pivotal role in maintaining the clarity and separation of concerns within your application. Understanding the distinction between domain services and application services is crucial for architecting a robust and maintainable system.

在 DDD 中, 服务和模块在维护应用程序中关注点的清晰度和分离方面起着关键作用。了解域服务和应用程序服务之间的区别对于构建健壮且可维护的系统至关重要。

Let’s start with services and see what the two types of service are, the differences between them, and when to use them. As usual, this exploration will be illustrated by examples in the domain of our brewery ERP.

让我们从服务开始,看看这两种类型的服务是什么,它们之间的区别以及何时使用它们。像往常一样,这种探索将通过我们啤酒厂 ERP 领域的示例来说明。

Domain services  域服务

Domain services are an integral part of the domain model, encapsulating business logic that doesn’t naturally fit within an aggregate. They are used when a particular operation or process spans multiple entities or when the behavior does not belong to any specific entity. Domain services are pure, meaning they do not have side effects and typically do not rely on external systems such as databases or messaging systems.

域服务是域模型不可或缺的一部分,它封装了自然不适合聚合的业务逻辑。当特定作或进程跨越多个实体或行为不属于任何特定实体时,将使用它们。域服务是纯的,这意味着它们没有副作用,并且通常不依赖于外部系统,例如数据库或消息传递系统。

Consider the brewery ERP system. Imagine you need to calculate the total price of a sales order, including various discounts and taxes. This calculation involves multiple entities and value objects, such as SalesOrder, Customer, Product, and Discount. Placing this logic inside any single entity would violate the Single Responsibility Principle (SRP) and make the entity unnecessarily complex. Instead, you would create a domain service, PricingService, to handle this calculation:

以啤酒厂的 ERP 系统为例。假设你需要计算销售订单的总价,包括各种折扣和税款。此计算涉及多个实体和值对象,例如 SalesOrder、Customer、Product 和 Discount。将此逻辑放在任何单个实体中都会违反单一责任原则 (SRP),并使实体变得不必要地复杂。相反,您将创建一个域服务 PricingService 来处理此计算:

public class PricingService {

    private readonly TaxCalculator _taxCalculator;

    private readonly DiscountService _discountService;

    public PricingService(TaxCalculator taxCalculator, DiscountService discountService) {

        _taxCalculator = taxCalculator;

        _discountService = discountService;

    }

    public decimal CalculateTotalPrice(SalesOrder order) {

        decimal totalPrice = 0;

        // Sum the price for each row in the order

        foreach (var row in order.Rows) {

            totalPrice += row.Quantity * row. Price;

        }

        // Apply discount if applicable

        totalPrice -= _discountService.CalculateDiscount(order);

        // Calculate and add tax

        totalPrice += _taxCalculator.CalculateTax(totalPrice);

        return totalPrice;

    }

}

In PricingService, the CalculateTotalPrice method handles the logic of summing the prices of all rows in the sales order, applying any discounts, and then calculating and adding the tax. The tax calculation is delegated to a separate TaxCalculator service, and the discount calculation is delegated to DiscountService, each encapsulating specific parts of the pricing logic.

在 PricingService 中,CalculateTotalPrice 方法处理以下逻辑:对销售订单中所有行的价格求和,应用任何折扣,然后计算和添加税款。税款计算委托给单独的 TaxCalculator 服务,折扣计算委托给 DiscountService,每个服务都封装了定价逻辑的特定部分。

The TaxCalculator service is responsible for tax calculations. By isolating tax logic in its own service, we adhere to the SRP and ensure that each service has a clear and focused purpose:

TaxCalculator 服务负责税款计算。通过将税务逻辑隔离在自己的服务中,我们遵守 SRP 并确保每项服务都有明确且重点突出的目的:

public class TaxCalculator {

    private readonly decimal _taxRate;

    public TaxCalculator(decimal taxRate) {

        _taxRate = taxRate;

    }

    public decimal CalculateTax(decimal amount) {

        return amount * _taxRate;

    }

}

TaxCalculator takes a tax rate as a dependency and uses it to calculate the tax on a given amount. This service can be easily modified or extended to accommodate different tax rates or complex tax rules without affecting PricingService.

TaxCalculator 将税率作为依赖项,并使用它来计算给定金额的税款。这项服务可以很容易地修改或扩展以适应不同的税率或复杂的税收规则,而不会影响 PricingService。

DiscountService is responsible for applying discounts to the sales order. This service ensures that discount logic is encapsulated separately, making it easier to manage and modify. Here is the sample code for a simplified discount service:

DiscountService 负责将折扣应用于销售订单。此服务可确保 discount logic 单独封装,使其更易于管理和修改。以下是简化折扣服务的示例代码:

public class DiscountService {

    public decimal CalculateDiscount(SalesOrder order) {

        if (order.Discount == null) {

            return 0;

        }

       var result = //… do some calculations…

        return result

    }

}

DiscountService applies the appropriate discount to the order if one is available. This separation of discount logic allows PricingService to remain focused on its core responsibility of calculating the total price.

DiscountService 将适当的折扣应用于订单(如果有)。这种折扣逻辑的分离使 PricingService 能够继续专注于计算总价的核心责任。

Application services  应用服务

Application services sit above the domain layer, orchestrating the domain objects to fulfill a particular use case. They act as a bridge between the presentation layer (such as a web API or the user interface) and the domain layer, handling tasks such as transaction management, authentication, and authorization. Unlike domain services, application services can have side effects and interact with external systems.

应用程序服务位于域层之上,编排域对象以满足特定用例。它们充当表示层(例如 Web API 或用户界面)和域层之间的桥梁,处理事务管理、身份验证和授权等任务。与域服务不同,应用程序服务可能会产生副作用并与外部系统交互。

Returning to our brewery ERP, imagine the process of creating a new sales order. This involves several steps: validating the order details, checking product availability, calculating the total price, and finally, saving the order to the database. These steps encompass various domain operations orchestrated by an application service, as shown in the following code:

回到我们的啤酒厂 ERP,想象一下创建新销售订单的过程。这涉及几个步骤:验证订单详细信息、检查产品可用性、计算总价,最后将订单保存到数据库。这些步骤包括由应用程序服务编排的各种域作,如以下代码所示:

public class SalesOrderService {

    private readonly ISalesOrderRepository _salesOrderRepository;

    private readonly PricingService _pricingService;

    private readonly WarehouseService _warehouseService;

    public SalesOrderService(ISalesOrderRepository salesOrderRepository, PricingService pricingService, WarehouseService warehouseService) {

        _salesOrderRepository = salesOrderRepository;

        _pricingService = pricingService;

        _warehouseService = warehouseService;

    }

    public SalesOrder CreateSalesOrder(SalesOrderDto orderDto) {

        // Validate order details

        SalesOrder order = new SalesOrder(orderDto);

        // Check product availability

        _warehouseService.CheckAvailability(order);

        // Calculate total price

        decimal totalPrice = _pricingService.CalculateTotalPrice(order);

        order.SetTotalPrice(totalPrice);

        // Save order to the database

        _salesOrderRepository.Save(order);

        return order;

    }

In the preceding example, SalesOrderService orchestrates the entire process of creating a sales order, utilizing domain services such as PricingService and WarehouseService, and managing persistence through SalesOrderRepository.

在前面的示例中,SalesOrderService 协调创建销售订单、利用 PricingService 和 WarehouseService 等域服务以及通过 SalesOrderRepository 管理持久性的整个过程。

SalesOrderService starts by validating the order details and converting the DTO into a SalesOrder domain entity. It then checks the availability of the products in the order using WarehouseService. Next, it calculates the total price of the order, including discounts and taxes, by calling PricingService. Finally, it saves the completed order to the database using SalesOrderRepository.

SalesOrderService 首先验证订单详细信息并将 DTO 转换为 SalesOrder 域实体。然后,它使用 WarehouseService 检查订单中产品的可用性。接下来,它通过调用 PricingService 来计算订单的总价,包括折扣和税费。最后,它使用 SalesOrderRepository 将已完成的订单保存到数据库中。

Key differences between domain and application services

域服务和应用程序服务之间的主要区别

The primary difference between domain services and application services lies in their focus and scope. Domain services encapsulate domain-specific logic that spans multiple entities, while application services coordinate the use of domain objects to accomplish higher-level tasks.

域服务和应用程序服务之间的主要区别在于它们的重点和范围。域服务封装跨多个实体的特定于域的逻辑,而应用程序服务则协调域对象的使用以完成更高级别的任务。

When you need to encapsulate business logic that pertains to the domain but doesn’t naturally belong to a single entity, you should use domain services. This is especially useful for complex business rules or processes that involve multiple entities. On the other hand, application services should be used to manage use cases and workflows that involve multiple domain operations. They handle coordination tasks, transaction management, and interactions with external systems.

当需要封装与域相关但自然不属于单个实体的业务逻辑时,应使用域服务。这对于涉及多个实体的复杂业务规则或流程特别有用。另一方面,应用程序服务应用于管理涉及多个域作的用例和工作流。他们处理协调任务、事务管理以及与外部系统的交互。

To summarize, the proper use of domain and application services ensures a clean separation of concerns, making your code base more maintainable and scalable. In our brewery ERP system, domain services such as PricingService, TaxCalculator, and DiscountService, along with application services such as SalesOrderService, work together to handle the complexities of sales order processing, each fulfilling their specific roles within the architecture. By leveraging these concepts, you can build systems that are both robust and adaptable to changing business requirements.

总而言之,正确使用域和应用程序服务可以确保关注点的清晰分离,使您的代码库更具可维护性和可扩展性。在我们的啤酒厂 ERP 系统中,PricingService、TaxCalculator 和 DiscountService 等域服务以及 SalesOrderService 等应用程序服务协同工作以处理销售订单处理的复杂性,每个服务在架构中发挥其特定作用。通过利用这些概念,您可以构建既健壮又适应不断变化的业务需求的系统。

Modules  模块

A module is essentially a way to group related concepts and functionalities within a software system, encapsulating them to reduce interdependencies and promote a clear structure. By organizing code into well-defined modules, you can better maintain, understand, and evolve your application. A module plays a pivotal role in managing the complexity of large systems.

模块本质上是一种将软件系统中的相关概念和功能分组的方法,将它们封装起来以减少相互依赖性并促进清晰的结构。通过将代码组织到定义明确的模块中,您可以更好地维护、理解和发展您的应用程序。模块在管理大型系统的复杂性方面发挥着关键作用。

Modules serve as the building blocks for a robust and scalable system architecture in DDD. Their primary purpose is to help you manage the inherent complexity of a large application by dividing it into smaller, more manageable pieces. This modular approach allows each module to focus on a specific aspect of the domain, promoting a high degree of cohesion within the module while minimizing coupling between different modules.

模块是 DDD 中强大且可扩展的系统架构的构建块。它们的主要目的是通过将大型应用程序划分为更小、更易于管理的部分来帮助您管理大型应用程序的固有复杂性。这种模块化方法允许每个模块专注于领域的特定方面,促进模块内的高度凝聚力,同时最大限度地减少不同模块之间的耦合。

A module encapsulates a cluster of related domain concepts, which might include entities, value objects, aggregates, and services. This encapsulation helps you maintain a clear separation of concerns. Each module handles a specific aspect of the domain, making it easier to understand and manage. Moreover, well-defined interfaces and boundaries between modules simplify the communication between different parts of the system, reducing the risk of unintended side effects and making it easier to understand how different parts of the system interact.

模块封装了相关域概念的集群,其中可能包括实体、值对象、聚合和服务。这种封装有助于您保持关注点的清晰分离。每个模块都处理域的特定方面,使其更易于理解和管理。此外,模块之间定义明确的接口和边界简化了系统不同部分之间的通信,降低了意外副作用的风险,并使人们更容易理解系统不同部分的交互方式。

Implementing a modular approach in your projects can offer numerous benefits. Improved maintainability is a significant advantage. By breaking down the system into smaller, self-contained modules, you can make changes to one module without affecting the others. This reduces the risk of introducing bugs and makes it easier to test and maintain the code. Enhanced scalability is another benefit, as modules can be developed, deployed, and scaled independently, allowing you to address performance and scalability concerns more effectively. With clearly defined modules, different teams can work on separate parts of the system concurrently without stepping on each other’s toes, facilitating collaboration. Furthermore, modules can be designed to mirror the structure of the business domain, ensuring that the software system evolves in tandem with the business requirements, which leads to better alignment with business goals.

在您的项目中实施模块化方法可以带来许多好处。提高可维护性是一个显着的优势。通过将系统分解为更小的独立模块,您可以对一个模块进行更改而不影响其他模块。这降低了引入错误的风险,并使测试和维护代码变得更加容易。增强的可扩展性是另一个好处,因为模块可以独立开发、部署和扩展,使您能够更有效地解决性能和可扩展性问题。通过明确定义的模块,不同的团队可以同时处理系统的不同部分,而无需踩到彼此的脚趾,从而促进协作。此外,模块可以设计为反映业务领域的结构,确保软件系统与业务需求同步发展,从而更好地与业务目标保持一致。

Let’s consider our brewery ERP system that handles sales orders to illustrate the concept of modules in DDD. In this system, you might have several modules, each focusing on a different aspect of the brewery’s operations. For example, the Sales module is responsible for managing sales orders, customer interactions, and invoicing. It might include entities such as Order, Customer, and Invoice, along with services for processing orders and generating invoices. The Warehouse module manages the brewery’s inventory, tracking the availability of ingredients, finished products, and supplies. This module includes entities such as Ingredient, Product, and Stock, as well as services for updating inventory levels and generating stock reports. The Production module oversees the brewing process, including scheduling production runs, monitoring fermentation, and ensuring quality control. It might involve entities such as Batch, Recipe, and ProductionSchedule, with services for managing production workflows and tracking batch quality.

让我们考虑一下我们处理销售订单的啤酒厂 ERP 系统来说明 DDD 中模块的概念。在这个系统中,您可能有多个模块,每个模块都侧重于啤酒厂运营的不同方面。例如, 销售模块负责管理销售订单、客户交互和开票。它可能包括订单 、 客户和发票等实体,以及用于处理订单和生成发票的服务。 仓库模块管理啤酒厂的库存,跟踪原料、成品和供应品的可用性。该模块包括成分 、 产品和库存等实体,以及用于更新库存水平和生成库存报告的服务。 生产模块监督酿造过程,包括安排生产运行、监控发酵和确保质量控制。它可能涉及 Batch、Recipe 和 ProductionSchedule 等实体,以及用于管理生产工作流和跟踪批次质量的服务。

By organizing the brewery ERP system into these modules, you can ensure that each aspect of the brewery’s operations is handled independently, with clear boundaries between different areas of functionality.

通过将啤酒厂 ERP 系统组织到这些模块中,您可以确保啤酒厂运营的每个方面都得到独立处理,不同功能领域之间有明确的界限。

Modules in refactoring complex systems

Modules are particularly useful during the refactoring process of a complex system. As systems grow and evolve, they often become tangled and difficult to manage. By refactoring the system into well-defined modules, you can achieve several key benefits. One such benefit is the isolation of changes. When you need to modify a specific part of the system, you can do so within the confines of a single module, reducing the risk of unintended side effects. This also improves understanding, as clear module boundaries make it easier to understand the system’s structure and behavior, facilitating more effective debugging and problem-solving. Additionally, the modular design promotes incremental improvements. You can refactor and improve one module at a time, gradually enhancing the overall system without requiring a complete rewrite.

模块在复杂系统的重构过程中特别有用。随着系统的发展和发展,它们往往变得纠结且难以管理。通过将系统重构为定义明确的模块,您可以实现几个关键优势。其中一个好处是隔离更改。当您需要修改系统的特定部分时,您可以在单个模块的范围内进行修改,从而降低意外副作用的风险。这也提高了理解力,因为清晰的模块边界可以更轻松地理解系统的结构和行为,从而促进更有效的调试和问题解决。此外,模块化设计促进了渐进式改进。您可以一次重构和改进一个模块,逐步增强整个系统,而无需完全重写。

For instance, if the brewery ERP system needs to accommodate new sales channels, you can modify the Sales module without disrupting the Inventory or Production modules. This modular approach ensures that changes are contained and the overall system remains stable and maintainable.

例如,如果啤酒厂 ERP 系统需要适应新的销售渠道,您可以在不中断库存或生产模块的情况下修改销售模块。这种模块化方法可确保更改得到控制,并且整个系统保持稳定和可维护。

While modules help structure and isolate different parts of a complex system, they often need to communicate with each other to reflect meaningful changes. This is where events come into play, enabling decoupled interactions between modules and ensuring that important state changes are properly propagated throughout the system.

虽然模块有助于构建和隔离复杂系统的不同部分,但它们通常需要相互通信以反映有意义的变化。这就是事件发挥作用的地方,它实现了模块之间的解耦交互,并确保重要的状态更改在整个系统中正确传播。


Domain and integration events

域和集成事件

In the context of software development, events are notifications that signify that something significant has occurred within the system. An event represents a change of state or the occurrence of a meaningful action. For instance, in our ERP application, events could include a sales order being placed, a payment being processed, or a beer order being shipped. Events are crucial in decoupling various parts of an application, enabling them to communicate and react to changes without being tightly coupled to one another.

在软件开发的背景下, 事件是表示系统内发生重大事件的通知。事件表示状态的变化或有意义的动作的发生。例如,在我们的 ERP 应用程序中,事件可能包括正在下达销售订单、正在处理付款或正在发货的啤酒订单。事件对于解耦应用程序的各个部分至关重要,使它们能够相互通信和响应更改,而无需彼此紧密耦合。

They represent something that has happened in the domain that is of significance to the business. Events are used to communicate changes in the state of the domain. They are immutable facts that capture the state change and are often used to decouple different parts of the system, allowing them to react to changes independently. The fact that they represent something that has happened explains why they are written in past tense.

它们代表了领域中发生的对业务具有重要意义的事情。事件用于传达域状态的变化。它们是捕获状态变化的不可变事实,通常用于解耦系统的不同部分,使它们能够独立地对变化做出反应。它们代表已经发生的事情这一事实解释了为什么它们是用过去时写的。

A practical example is the SalesOrderCreated event, which could look something like this:

一个实际示例是 SalesOrderCreated 事件,它可能如下所示:

public sealed class SalesOrderCreated(SalesOrderId aggregateId, Guid commitId, SalesOrderNumber salesOrderNumber, OrderDate orderDate, CustomerId customerId, CustomerName customerName, IEnumerable<SalesOrderRow> rows) : DomainEvent(aggregateId, commitId)

{

    public readonly SalesOrderId SalesOrderId = aggregateId;

    public readonly SalesOrderNumber SalesOrderNumber = salesOrderNumber;

    public readonly OrderDate OrderDate = orderDate;

    public readonly CustomerId CustomerId = customerId;

    public readonly CustomerName CustomerName = customerName;

    public readonly IEnumerable<SalesOrderRow> Rows = rows;

}

The SalesOrderCreated event is a domain event that captures the creation of a sales order. It extends DomainEvent and contains essential information such as the order ID, customer details, order date, and the list of order items. By making this event immutable, we ensure that once it is published, its data remains unchanged, accurately representing a past occurrence in the business domain.

SalesOrderCreated 事件是捕获销售订单创建的域事件。它扩展了 DomainEvent 并包含基本信息,例如订单 ID、客户详细信息、订单日期和订单项目列表。通过使此事件不可变,我们确保一旦发布,其数据保持不变,准确地代表业务领域中过去发生的事件。

Later in this section, we will review the two kinds of events that exist in a DDD-oriented application:

在本节的后面部分,我们将回顾面向 DDD 的应用程序中存在的两种事件:

Domain events  域事件

Integration events  集成事件

But first, let’s review why they are a key concept in a modern application.

但首先,让我们回顾一下为什么它们是现代应用程序中的关键概念。

Importance of events in modern applications

事件在现代应用程序中的重要性

Modern applications are becoming increasingly complex, often involving multiple services and components that must work together seamlessly. Events provide a way to manage this complexity by promoting a reactive and decoupled architecture. They enable horizontal scaling by allowing different services to process events independently. This scalability is essential for handling large volumes of data and user interactions. Furthermore, events contribute to the resilience of systems, allowing them to continue functioning even if some components are temporarily unavailable. Events can be stored and processed later, ensuring that no data or critical operation is lost. Additionally, events offer flexibility by facilitating the integration of new features and services. New components can subscribe to existing events without necessitating changes to the existing system, promoting a more adaptable architecture. Ultimately, events enable decoupling by separating the sender and receiver, resulting in more maintainable and modular systems.

现代应用程序变得越来越复杂,通常涉及必须无缝协作的多个服务和组件。事件提供了一种通过推广响应式和解耦架构来管理这种复杂性的方法。它们通过允许不同的服务独立处理事件来实现水平扩展。这种可扩展性对于处理大量数据和用户交互至关重要。此外,事件有助于系统的弹性,即使某些组件暂时不可用,它们也能继续运行。事件可以稍后存储和处理,确保不会丢失任何数据或关键作。此外,活动通过促进新功能和服务的集成来提供灵活性。新组件可以订阅现有事件,而无需更改现有系统,从而促进更具适应性的架构。最终,事件通过分离发送方和接收方来实现解耦,从而产生更易于维护和模块化的系统。

Domain events  域事件

Let’s start with the first kind: domain events. These events, as you already know, are specific to the business domain and signify something that has happened within that domain. They are a critical concept in DDD, representing things that domain experts care about. For example, in our ERP application, events such as SalesOrderCreated, PaymentConfirmed, and BeerAvailabilityUpdated are domain events. These events reflect significant occurrences from a business perspective, capturing the essence of business operations and processes.

让我们从第一种开始: 域事件 。如您所知,这些事件特定于业务域,表示该域内发生的事情。它们是 DDD 中的一个关键概念,代表领域专家关心的事情。例如,在我们的 ERP 应用程序中,SalesOrderCreated、PaymentConfirmed 和 BeerAvailabilityUpdated 等事件是域事件。这些事件从业务角度反映了重大事件,抓住了业务运营和流程的本质。\

Domain events are characterized by their business relevance and . They represent occurrences that are significant from a business perspective and are of interest to domain experts. Domain events are immutable; once an event is created, it should not change. It captures a past occurrence and should remain a historical record. Additionally, domain events are self-descriptive. They should provide enough information for any interested party to understand what happened without requiring additional context or data.

域事件的特征在于其业务相关性和 .它们代表了从业务角度来看具有重要意义且领域专家感兴趣的事件。域事件是不可变的;事件创建后,不应更改。它记录了过去发生的事件,应该保留历史记录。此外,域事件是自我描述的。它们应该为任何相关方提供足够的信息来了解发生了什么,而无需额外的背景或数据。

An example is the BeerAvailabilityUpdated event, as shown in the following snippet:

一个示例是 BeerAvailabilityUpdated 事件,如以下代码片段所示:

public class BeerAvailabilityUpdated(BeerId aggregateId, Guid commitId, BeerName beerName, Quantity quantity) : DomainEvent(aggregateId, commitId)

{

    public readonly BeerId BeerId = aggregateId;

    public readonly BeerName BeerName = beerName;

    public readonly Quantity Quantity = quantity;

}

The preceding code includes all the information on the availability of a specific beer as a reaction to the payment confirmation for a specific sale order.

前面的代码包括有关特定啤酒可用性的所有信息,作为对特定销售订单付款确认的反应。

Integration events  集成事件

The second kind of event, and no less important, is integration events. They are used to communicate between different bounded contexts or external systems. While domain events are concerned with the business logic within a particular bounded context, integration events ensure that different parts of a distributed system stay in sync. They facilitate communication and coordination between different services or systems that need to work together to achieve a common goal.

第二种事件,同样重要,是整合事件 。它们用于在不同的有界上下文或外部系统之间进行通信。虽然域事件与特定边界上下文中的业务逻辑有关,但集成事件可确保分布式系统的不同部分保持同步。它们促进需要协同工作以实现共同目标的不同服务或系统之间的沟通和协调。

Integration events are characterized by their role in cross-boundary communication. They are used to notify other systems or bounded contexts about changes, ensuring that the entire system remains consistent and up to date. Integration events help achieve loose coupling between services, making the system more modular and easier to maintain. This decoupling allows services to evolve independently without disrupting other parts of the system. Additionally, integration events often lead to eventual consistency, where different parts of the system eventually reach a consistent state after processing the events.

融合活动的特点是它们在跨境沟通中的作用。它们用于通知其他系统或有界上下文有关更改的信息,确保整个系统保持一致和最新。集成事件有助于实现服务之间的松散耦合,使系统更加模块化且更易于维护。这种解耦允许服务独立发展,而不会中断系统的其他部分。此外,集成事件通常会导致最终一致性,即系统的不同部分在处理事件后最终达到一致状态。

Soon enough, during your refactoring journey or development of a new feature, you will reach a point where a domain event in a certain bounded context appears to be identical to the integration event that you wish to send outside to let others synchronize with it. The temptation to use the domain one is high; we know that laziness can sometimes prevail over discipline, but try to refrain from doing it. That’s because sooner or later the ubiquitous language of your bounded context will inevitably change, and while there is a chance that you will probably change that domain event, it is not guaranteed that the integration event needs to change accordingly. In such a case, the two events would diverge; if you used the same event for both tasks, you can already imagine the impact on your entire application, not only on a single component.

很快,在重构过程或开发新功能期间,您将达到某个点,即某个有限上下文中的域事件似乎与您希望发送到外部以让其他人与之同步的集成事件相同。使用域一的诱惑很大;我们知道懒惰有时会战胜纪律,但尽量避免这样做。这是因为边界上下文中无处不在的语言迟早会不可避免地发生变化,虽然你有可能改变该领域事件,但不能保证集成事件需要相应地改变。在这种情况下,这两个事件就会分道扬镳;如果您对这两个任务使用相同的事件,您已经可以想象它对整个应用程序的影响,而不仅仅是对单个组件的影响。

Use cases in our brewery application

我们啤酒厂应用中的用例

As mentioned in the previous chapters, our brewery application is composed of the following bounded contexts: Sales, Warehouse, Payment, and Shipping. Let’s explore how domain and integration events can be used within this system to facilitate communication and ensure seamless operations:

如前几章所述,我们的啤酒厂应用程序由以下边界上下文组成: 销售 、 仓库 、 付款和运输 。让我们探讨如何在该系统中使用域和集成事件来促进通信并确保无缝作:

  • Sales context: In the Sales context, a significant domain event would be SalesOrderCreated. This event is triggered when a customer places an order. It is crucial for the Sales context to track new orders and initiate the subsequent processes required to fulfill the order.

销售上下文 :在销售上下文中,重要的域事件是 SalesOrderCreated。当客户下订单时,会触发此事件。对于销售上下文来说,跟踪新订单并启动履行订单所需的后续流程至关重要。

  • Warehouse context: In the Warehouse context, a critical domain event would be BeerAvailabilityUpdated. This event is triggered when stock is reserved for an order. It is essential for managing inventory within the Warehouse context, ensuring that the necessary items are available and reserved for the customer order.

仓库上下文 :在仓库上下文中,关键域事件是 BeerAvailabilityUpdated。当为订单预留库存时,将触发此事件。这对于在仓库环境中管理库存至关重要,确保必要的物品可用并为客户订单保留。

  • Payment context: In the Payment context, a key domain event would be PaymentProcessed. This event is triggered when a payment is successfully processed. It is important for the Payment context to track the payment status and ensure that the order can proceed to the next stage.

付款上下文 :在付款上下文中,关键域事件将是 PaymentProcessed。成功处理付款时触发此事件。对于付款上下文,跟踪付款状态并确保订单可以进入下一阶段非常重要。

  • Shipping context: In the Shipping context, a significant domain event would be ShipmentCreated. This event is triggered when a shipment is created for an order. It is crucial for the Shipping context to manage deliveries and ensure that the order is dispatched to the customer.

运输上下文 :在运输上下文中,重要的域事件是 ShipmentCreated。当为订单创建装运时,将触发此事件。对于运输上下文来说,管理交货并确保订单已发送给客户至关重要。

  • Integration events across contexts: Integration events play a vital role in ensuring that different bounded contexts within the brewery application stay in sync and communicate effectively. When a SalesOrderCreated domain event is triggered in the Sales context, it will also trigger an integration event (that could have the same name) that is published to other bounded contexts. The Warehouse, Payment, and Shipping contexts subscribe to this event to initiate their respective processes. For example, the Warehouse context allocates stock, triggering the BeerAvailabilityUpdated domain event, which then triggers an integration event to notify the Sales context about stock reservation.

跨上下文的集成事件 :集成事件在确保啤酒厂应用程序中的不同边界上下文保持同步并有效通信方面发挥着至关重要的作用。在 Sales 上下文中触发 SalesOrderCreated 域事件时,它还将触发发布到其他有界上下文的集成事件(可能具有相同的名称)。Warehouse、Payment 和 Shipping 上下文订阅此事件以启动各自的流程。例如,Warehouse 上下文分配库存,触发 BeerAvailabilityUpdated 域事件,然后触发集成事件以通知 Sales 上下文有关库存预留的信息。

These domain and integration events form a chain of reactions across the system, ensuring that each bounded context remains informed and can act accordingly. To better understand how these events propagate and influence different parts of the application, let’s examine the overall event flow and how information moves throughout the system.

这些域和集成事件在整个系统中形成反应链,确保每个有界上下文保持知情并能够采取相应的行动。为了更好地了解这些事件如何传播和影响应用程序的不同部分,让我们检查一下整个事件流以及信息如何在整个系统中移动。

Event flow and information flow

事件流和信息流

To illustrate a complete flow of events and information within our application, consider the diagram in Figure 4.2:

为了说明我们应用程序中完整的事件和信息流,请考虑图 4.2 中的图表:

When a customer places an order in the Sales context, the system triggers the SalesOrderCreated domain event. This event is significant within the Sales context as it marks the initiation of the order fulfillment process. Concurrently, an integration event with the same name is published to the Warehouse, Payment, and Shipping contexts.

当客户在 Sales 上下文中下订单时,系统会触发 SalesOrderCreated 域事件。此事件在销售上下文中具有重要意义,因为它标志着订单履行流程的启动。同时,具有相同名称的集成事件将发布到 Warehouse、Payment 和 Shipping 上下文。

Upon receiving the SalesOrderCreated integration event, the Warehouse context allocates the necessary stock for the order. This allocation triggers the BeerAvailabilityUpdated domain event, which is crucial for managing inventory within the Warehouse context. The BeerAvailabilityUpdated event then becomes an integration event, notifying the Sales context about the successful reservation of stock for the order.

收到 SalesOrderCreated 集成事件后,Warehouse 上下文会为订单分配必要的库存。此分配会触发 BeerAvailabilityUpdated 域事件,这对于在仓库上下文中管理库存至关重要。然后,BeerAvailabilityUpdated 事件将成为集成事件,通知 Sales 上下文成功预留订单的库存。

Meanwhile, the Payment context, upon receiving the SalesOrderCreated event, processes the customer’s payment. This action triggers the PaymentProcessed domain event, which is essential for tracking payment status within the Payment context. The PaymentProcessed event becomes an integration event, notifying the Sales and Shipping contexts about the successful payment, allowing them to proceed with their respective processes.

同时,Payment 上下文在收到 SalesOrderCreated 事件后,会处理客户的付款。此作会触发 PaymentProcessed 域事件,这对于在 Payment 上下文中跟踪付款状态至关重要。PaymentProcessed 事件将成为集成事件,通知 Sales 和 Shipping 上下文有关成功付款的信息,从而允许他们继续执行各自的流程。

The Shipping context, after receiving both the SalesOrderCreated and PaymentProcessed events, creates a shipment for the order. This action triggers the ShipmentCreated domain event, which is significant for managing deliveries within the Shipping context. The ShipmentCreated event becomes an integration event, notifying the Sales context to update the customer about the shipment status and complete the order fulfillment process.

Shipping 上下文在收到 SalesOrderCreated 和 PaymentProcessed 事件后,为订单创建发货。此作会触发 ShipmentCreated 域事件,这对于管理 Shipping 上下文中的投放非常重要。ShipmentCreated 事件将成为集成事件,通知 Sales 上下文向客户更新装运状态并完成订单履行流程。

Sagas  传说

As you will see in Chapter 12, Orchestrating Complexity: Advanced Approaches to Business Processes, the flow we have just described is also called a choreography saga. In Chapter 12, you will delve into the two main sagas that could be used to handle complex business flows.

正如您将在第 12 章 编排复杂性:业务流程的高级方法中看到的那样,我们刚才描述的流程也称为编排传奇 。在第 12 章中,您将深入研究可用于处理复杂业务流的两个主要传奇故事。

Events, both domain and integration, play a pivotal role in modern complex applications by promoting decoupling, scalability, and maintainability. In our brewery application, domain events help manage business logic within bounded contexts, while integration events ensure seamless communication and synchronization across contexts. By understanding and leveraging these concepts, developers can build robust, scalable, and maintainable systems.

事件(包括领域和集成)通过促进解耦、可扩展性和可维护性,在现代复杂应用程序中发挥着关键作用。在我们的啤酒厂应用程序中,域事件有助于管理有限上下文中的业务逻辑,而集成事件则确保跨上下文的无缝通信和同步。通过理解和利用这些概念,开发人员可以构建健壮、可扩展且可维护的系统。


Summary  总结

In this chapter, we delved deeply into the main tactical patterns of DDD. We started with entities, which are objects defined not by their attributes but by a thread of continuity and identity. These entities possess a distinct identity that runs through different states and transformations over time, making them critical in modeling real-world scenarios where identity persistence is crucial.

在本章中,我们深入研究了 DDD 的主要战术模式。我们从实体开始,这些对象不是由其属性定义的,而是由连续性和身份的线索定义的。这些实体拥有独特的身份,随着时间的推移会经历不同的状态和转换,这使得它们对于对身份持久性至关重要的现实场景进行建模至关重要。

We moved on to value objects, which are defined entirely by their attributes. Value objects lack a distinct identity and are inherently immutable, offering a way to model concepts that can be described by their attributes alone, such as a quantity or a price. This immutability simplifies their use and ensures consistency across the system.

我们继续讨论值对象,这些对象完全由其属性定义。值对象缺乏独特的身份,并且本质上是不可变的,提供了一种对概念进行建模的方法,这些概念可以仅通过其属性(例如数量或价格)来描述。这种不变性简化了它们的使用并确保整个系统的一致性。

Aggregates were introduced as a key concept for maintaining consistency within the system. An aggregate is a cluster of entities and value objects that are treated as a single unit for data changes. The root entity of an aggregate guarantees the consistency of changes within the aggregate boundary, ensuring that all invariants are respected. This structure is vital for managing the complexity of business rules and data integrity.

引入聚合是作为保持系统内一致性的关键概念。聚合是实体和值对象的集群,它们被视为数据更改的单个单元。聚合的根实体保证聚合边界内更改的一致性,确保遵守所有不变量。这种结构对于管理业务规则的复杂性和数据完整性至关重要。

Repositories were covered next. We explained their role in encapsulating the logic required to access data sources. By using repositories, you can abstract the infrastructure or technology-specific code, providing a clean separation between the domain model and data access layers. This separation enhances the maintainability and testability of the code.

接下来介绍了存储库。我们解释了它们在封装访问数据源所需的逻辑方面的作用。通过使用存储库,可以抽象基础结构或特定于技术的代码,从而在领域模型和数据访问层之间提供干净的分离。这种分离增强了代码的可维护性和可测试性。

We also explored the concept of services, distinguishing between domain services and application services. Domain services encapsulate domain logic that doesn’t naturally fit within entities or value objects, offering a way to express operations and processes within the domain model. In contrast, application services are responsible for orchestrating the application’s use cases, serving as a bridge between the user interface and the domain layer.

我们还探讨了服务的概念,区分了域服务和应用程序服务。域服务封装了不自然适合实体或值对象的域逻辑,提供了一种在域模型中表达作和流程的方法。相比之下,应用程序服务负责编排应用程序的用例,充当用户界面和域层之间的桥梁。

Modules were presented as a means to organize the domain model into cohesive units, enhancing the clarity and maintainability of the system. By grouping related concepts together, modules help manage complexity and facilitate a clearer understanding of the domain structure.

模块被提出为将领域模型组织成有凝聚力的单元的一种手段,从而增强系统的清晰度和可维护性。通过将相关概念组合在一起,模块有助于管理复杂性并促进对领域结构的更清晰理解。

In the last section of the chapter, we delved into integration and domain events, underscoring their importance in modern application architectures. Integration events are pivotal for communication between bounded contexts or external systems, ensuring that changes in one part of the system are propagated and handled appropriately elsewhere. Domain events capture significant occurrences within the domain, making the system more responsive and robust by explicitly modeling the domain’s state changes and reactions.

在本章的最后一节中,我们深入研究了集成和领域事件,强调了它们在现代应用程序架构中的重要性。集成事件对于有界上下文或外部系统之间的通信至关重要,可确保系统某一部分的更改在其他地方传播和适当处理。域事件捕获域内的重要事件,通过显式建模域的状态变化和反应,使系统更具响应性和稳健性。

Events are a key concept in modern complex applications because they enable a decoupled and reactive system design. By embracing an event-driven approach, you can achieve greater scalability and flexibility, handling real-time data and operations more effectively. Events allow different parts of the system to remain loosely coupled while still collaborating seamlessly, facilitating a more resilient and adaptive architecture.

事件是现代复杂应用中的一个关键概念,因为它们支持解耦和反应式系统设计。通过采用事件驱动的方法,您可以实现更大的可扩展性和灵活性,更有效地处理实时数据和作。事件允许系统的不同部分保持松散耦合,同时仍能无缝协作,从而促进更具弹性和适应性的架构。

This chapter marks the end of Part 1, which reviewed the key concepts of DDD. In the next chapter, we will start to go deep into refactoring. You will learn about the principles of refactoring and how DDD can help in the process.

本章标志着第 1 部分的结束,该部分回顾了 DDD 的关键概念。在下一章中,我们将开始深入重构。您将了解重构的原理以及 DDD 如何在此过程中提供帮助。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜流冰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值