设计微服务域模型

Define one rich domain model for each business microservice or Bounded Context.

为每个业务微服务或限界上下文(Bounded Context)定义一个丰富的领域模型。

Your goal is to create a single cohesive domain model for each business microservice or Bounded Context (BC). Keep in mind, however, that a BC or business microservice could sometimes be composed of several physical services that share a single domain model. The domain model must capture the rules, behavior, business language, and constraints of the single Bounded Context or business microservice that it represents.

你的目标是为每个业务微服务或限界上下文(BC)创建一个单一且内聚的领域模型。但请记住,一个限界上下文或业务微服务有时可能由多个物理服务组成,而这些服务共享同一个领域模型。领域模型必须捕获它所代表的单个限界上下文或业务微服务的规则、行为、业务语言和约束。

The Domain Entity pattern    领域实体模式(The Domain Entity pattern)

Entities represent domain objects and are primarily defined by their identity, continuity, and persistence over time, and not only by the attributes that comprise them. As Eric Evans says, "an object primarily defined by its identity is called an Entity." Entities are very important in the domain model, since they are the base for a model. Therefore, you should identify and design them carefully.

实体代表领域对象,它们主要由其身份(identity)、连续性以及随时间推移的持久性来定义,而不仅仅是由构成它们的属性来定义。正如 Eric Evans 所说:“主要由其身份定义的对象被称为实体。” 实体在领域模型中非常重要,因为它们是模型的基础。因此,你应该仔细地对它们进行识别和设计。

An entity's identity can cross multiple microservices or Bounded Contexts.

一个实体的身份可以跨越多个微服务或限界上下文。

The same identity (that is, the same Id value, although perhaps not the same domain entity) can be modeled across multiple Bounded Contexts or microservices. However, that does not imply that the same entity, with the same attributes and logic would be implemented in multiple Bounded Contexts. Instead, entities in each Bounded Context limit their attributes and behaviors to those required in that Bounded Context's domain.

相同的身份(即相同的 Id 值,尽管可能不是同一个领域实体)可以在多个限界上下文或微服务之间进行建模。然而,这并不意味着具有相同属性和逻辑的同一个实体会在多个限界上下文中被实现。相反,每个限界上下文中的实体应将其属性和行为限制在该限界上下文的领域所需的范围内。

For instance, the buyer entity might have most of a person's attributes that are defined in the user entity in the profile or identity microservice, including the identity. But the buyer entity in the ordering microservice might have fewer attributes, because only certain buyer data is related to the order process. The context of each microservice or Bounded Context impacts its domain model.

例如,买家(buyer)实体可能拥有在个人资料或身份微服务中用户(user)实体所定义的大部分个人属性,包括其身份标识。但是,在订购微服务中的买家实体可能只拥有较少的属性,因为只有特定的买家数据与订单处理过程相关。每个微服务或限界上下文的语境都会影响其领域模型。

Domain entities must implement behavior in addition to implementing data attributes.

领域实体除了实现数据属性外,还必须实现行为。

A domain entity in DDD must implement the domain logic or behavior related to the entity data (the object accessed in memory). For example, as part of an order entity class you must have business logic and operations implemented as methods for tasks such as adding an order item, data validation, and total calculation. The entity's methods take care of the invariants and rules of the entity instead of having those rules spread across the application layer.

DDD 中的领域实体必须实现与实体数据(即在内存中访问的对象)相关的领域逻辑或行为。例如,作为订单实体类的一部分,你必须将业务逻辑和操作作为方法来实现,用于执行诸如添加订单项、数据验证和总计计算等任务。实体的方法负责处理实体的不变量(invariants)和规则,而不是将这些规则散布在应用层中。

Figure 7-8 shows a domain entity that implements not only data attributes but operations or methods with related domain logic.

图 7-8 展示了一个领域实体,它不仅实现了数据属性,还实现了相关的领域逻辑操作或方法。

Diagram showing a Domain Entity's pattern.

Figure 7-8. Example of a domain entity design implementing data plus behavior

图 7-8. 一个同时实现了数据与行为的领域实体设计示例

A domain model entity implements behaviors through methods, that is, it's not an "anemic" model. Of course, sometimes you can have entities that do not implement any logic as part of the entity class. This can happen in child entities within an aggregate if the child entity does not have any special logic because most of the logic is defined in the aggregate root. If you have a complex microservice that has logic implemented in the service classes instead of in the domain entities, you could be falling into the anemic domain model, explained in the following section.

一个领域模型实体通过方法来实现行为,也就是说,它不是一个“贫血”的模型。当然,有时候你确实可能会遇到一些实体,它们作为实体类的一部分并不实现任何逻辑。这种情况可能会发生在聚合内的子实体上,如果该子实体没有任何特殊的逻辑(因为大部分逻辑都定义在聚合根中)。如果你有一个复杂的微服务,其逻辑是在服务类(Service classes)中实现的,而不是在领域实体中实现的,那么你可能就落入了“贫血领域模型”的陷阱,这将在下一节进行解释。

Rich domain model versus anemic domain model    丰富的领域模型 vs 贫血的领域模型

In his post AnemicDomainModel, Martin Fowler describes an anemic domain model this way:

在《AnemicDomainModel》(贫血领域模型)一文中,Martin Fowler 这样描述贫血领域模型:

The basic symptom of an Anemic Domain Model is that at first blush it looks like the real thing. There are objects, many named after the nouns in the domain space, and these objects are connected with the rich relationships and structure that true domain models have. The catch comes when you look at the behavior, and you realize that there is hardly any behavior on these objects, making them little more than bags of getters and setters.

“贫血领域模型的基本症状是,乍一看它就像是真正的领域模型。里面有很多对象,很多对象是以领域空间中的名词来命名的,并且这些对象之间有着真实领域模型所具备的丰富关系和结构。但当你审视它的行为时,就会发现这些对象上几乎没有任何行为,这使得它们充其量不过是一堆带有 getter 和 setter 方法的‘数据袋子’。”

Of course, when you use an anemic domain model, those data models will be used from a set of service objects (traditionally named the business layer) which capture all the domain or business logic. The business layer sits on top of the data model and uses the data model just as data.

当然,当你使用贫血领域模型时,这些数据模型会被一组服务对象(传统上称为业务层)所使用,而这组服务对象捕获了所有的领域或业务逻辑。业务层位于数据模型之上,并且仅仅将数据模型当作数据来使用。

The anemic domain model is just a procedural style design. Anemic entity objects are not real objects because they lack behavior (methods). They only hold data properties and thus it is not object-oriented design. By putting all the behavior out into service objects (the business layer), you essentially end up with spaghetti code or transaction scripts, and therefore you lose the advantages that a domain model provides.

贫血领域模型仅仅是一种过程式的设计风格。贫血的实体对象并不是真正的对象,因为它们缺乏行为(方法)。它们只持有数据属性,因此这并不是面向对象的设计。通过将所有行为剥离到服务对象(业务层)中,你最终得到的本质上是意大利面条式的代码或事务脚本(transaction scripts),因此你也就失去了领域模型所能提供的种种优势。

Regardless, if your microservice or Bounded Context is very simple (a CRUD service), the anemic domain model in the form of entity objects with just data properties might be good enough, and it might not be worth implementing more complex DDD patterns. In that case, it will be simply a persistence model, because you have intentionally created an entity with only data for CRUD purposes.

尽管如此,如果你的微服务或限界上下文非常简单(比如一个 CRUD 服务),那么以仅包含数据属性的实体对象形式存在的贫血领域模型可能就已经足够好了,而且可能不值得去实现更复杂的 DDD 模式。在这种情况下,它仅仅是一个持久化模型,因为你有意创建了一个仅用于 CRUD 目的且只包含数据的实体。

That is why microservices architectures are perfect for a multi-architectural approach depending on each Bounded Context. For instance, in eShopOnContainers, the ordering microservice implements DDD patterns, but the catalog microservice, which is a simple CRUD service, does not.

这正是微服务架构非常适合根据每个限界上下文采用多架构方法的原因。例如,在 eShopOnContainers 项目中,订购(ordering)微服务实现了 DDD 模式,但目录(catalog)微服务作为一个简单的 CRUD 服务,则没有采用 DDD 模式。

Some people say that the anemic domain model is an anti-pattern. It really depends on what you are implementing. If the microservice you are creating is simple enough (for example, a CRUD service), following the anemic domain model it is not an anti-pattern. However, if you need to tackle the complexity of a microservice's domain that has a lot of ever-changing business rules, the anemic domain model might be an anti-pattern for that microservice or Bounded Context. In that case, designing it as a rich model with entities containing data plus behavior as well as implementing additional DDD patterns (aggregates, value objects, etc.) might have huge benefits for the long-term success of such a microservice.

有些人说贫血领域模型是一种反模式(anti-pattern)。这实际上取决于你正在实现什么。如果你正在创建的微服务足够简单(例如一个 CRUD 服务),那么遵循贫血领域模型并不是一种反模式。然而,如果你需要应对一个包含大量不断变化业务规则的微服务领域的复杂性,那么贫血领域模型对于该微服务或限界上下文来说就可能是一种反模式。在这种情况下,将其设计为包含数据加行为的实体丰富模型,并实现额外的 DDD 模式(如聚合、值对象等),可能会对该微服务的长期成功带来巨大的好处。

The Value Object pattern    值对象模式

As Eric Evans has noted, "Many objects do not have conceptual identity. These objects describe certain characteristics of a thing."

正如 Eric Evans 所指出的那样:“许多对象并没有概念上的身份标识(Identity)。这些对象只是用来描述事物的某些特征。”

An entity requires an identity, but there are many objects in a system that do not, like the Value Object pattern. A value object is an object with no conceptual identity that describes a domain aspect. These are objects that you instantiate to represent design elements that only concern you temporarily. You care about what they are, not who they are. Examples include numbers and strings, but can also be higher-level concepts like groups of attributes.

实体(Entity)必须拥有身份标识,但在系统中,也有很多对象是没有身份标识的,比如值对象模式。值对象是一种没有概念身份、仅用于描述领域某个方面的对象。这些对象是你为了临时表示某些设计元素而实例化的。你关心的是它们“是什么(what)”,而不是它们“是谁(who)”。数字和字符串就是典型的例子,但它也可以是更高层次的概念,比如一组属性的集合。

Something that is an entity in a microservice might not be an entity in another microservice, because in the second case, the Bounded Context might have a different meaning. For example, an address in an e-commerce application might not have an identity at all, since it might only represent a group of attributes of the customer's profile for a person or company. In this case, the address should be classified as a value object. However, in an application for an electric power utility company, the customer address could be important for the business domain. Therefore, the address must have an identity so the billing system can be directly linked to the address. In that case, an address should be classified as a domain entity.

在某个微服务中是实体的东西,在另一个微服务中可能就不是实体,因为在后者的限界上下文中,它的含义可能完全不同。例如,在一个电子商务应用中,地址可能根本没有身份标识,因为它可能只是个人或公司客户资料的一组属性。在这种情况下,地址应该被归类为值对象。然而,在一家电力公司的应用程序中,客户地址对于业务领域来说可能至关重要。因此,地址必须拥有身份标识,以便计费系统能够直接关联到该地址。在那种情况下,地址应该被归类为领域实体。

A person with a name and surname is usually an entity because a person has identity, even if the name and surname coincide with another set of values, such as if those names also refer to a different person.

一个拥有名字和姓氏的人通常是一个实体,因为一个人是有身份标识的,即使名字和姓氏与另一组值完全相同(比如碰巧有同名同姓的另一个人),他们依然是不同的个体。

Value objects are hard to manage in relational databases and ORMs like Entity Framework (EF), whereas in document-oriented databases they are easier to implement and use.

值对象在关系型数据库和像 Entity Framework (EF) 这样的 ORM 中比较难管理,而在面向文档的数据库中,它们则更容易实现和使用。

EF Core 2.0 and later versions include the Owned Entities feature that makes it easier to handle value objects, as we'll see in detail later on.

EF Core 2.0 及更高版本包含了“拥有实体(Owned Entities)”功能,这使得处理值对象变得更加容易,我们稍后会详细看到这一点。

The Aggregate pattern    聚合模式

A domain model contains clusters of different data entities and processes that can control a significant area of functionality, such as order fulfillment or inventory. A more fine-grained DDD unit is the aggregate, which describes a cluster or group of entities and behaviors that can be treated as a cohesive unit.

领域模型包含了不同的数据实体和流程的集群(clusters),这些集群可以控制一大块功能区域,比如订单履行或库存管理。而在 DDD 中,一个更细粒度的单元就是聚合(Aggregate),它描述了一组可以被视为一个内聚单元的实体和行为的集群。

You usually define an aggregate based on the transactions that you need. A classic example is an order that also contains a list of order items. An order item will usually be an entity. But it will be a child entity within the order aggregate, which will also contain the order entity as its root entity, typically called an aggregate root.

你通常会根据所需的事务(transactions)来定义一个聚合。一个经典的例子就是一个包含订单项列表的订单。订单项通常是一个实体,但它会是订单聚合内的一个子实体,而该聚合还会包含订单实体作为其根实体,这个根实体通常被称为聚合根(Aggregate Root)。

Identifying aggregates can be hard. An aggregate is a group of objects that must be consistent together, but you cannot just pick a group of objects and label them an aggregate. You must start with a domain concept and think about the entities that are used in the most common transactions related to that concept. Those entities that need to be transactionally consistent are what forms an aggregate. Thinking about transaction operations is probably the best way to identify aggregates.

识别聚合可能会比较困难。聚合是一组必须保持数据一致性的对象,但你不能随便挑一组对象就给它们贴上聚合的标签。你必须从一个领域概念出发,思考在与该概念相关的最常见事务中使用了哪些实体。那些需要在事务中保持一致性的实体,就构成了一个聚合。思考事务操作可能是识别聚合的最佳方式。

The Aggregate Root or Root Entity pattern    聚合根或根实体模式

An aggregate is composed of at least one entity: the aggregate root, also called root entity or primary entity. Additionally, it can have multiple child entities and value objects, with all entities and objects working together to implement required behavior and transactions.

一个聚合至少由一个实体组成:即聚合根,也称为根实体或主实体。此外,它还可以包含多个子实体和值对象,所有这些实体和对象协同工作,以实现所需的行为和事务。

The purpose of an aggregate root is to ensure the consistency of the aggregate; it should be the only entry point for updates to the aggregate through methods or operations in the aggregate root class. You should make changes to entities within the aggregate only via the aggregate root. It is the aggregate's consistency guardian, considering all the invariants and consistency rules you might need to comply with in your aggregate. If you change a child entity or value object independently, the aggregate root cannot ensure that the aggregate is in a valid state. It would be like a table with a loose leg. Maintaining consistency is the main purpose of the aggregate root.

聚合根的目的是确保聚合内部的一致性;它应该通过聚合根类中的方法或操作,成为更新该聚合的唯一入口。你应该只能通过聚合根来修改聚合内的实体。它是聚合的“一致性守护者”,会考虑到你在聚合中可能需要遵守的所有不变量(invariants)和一致性规则。如果你独立地修改一个子实体或值对象,聚合根就无法确保该聚合处于有效状态。这就像一张桌子松了一条腿一样。保持一致性是聚合根的主要目的。

In Figure 7-9, you can see sample aggregates like the buyer aggregate, which contains a single entity (the aggregate root Buyer). The order aggregate contains multiple entities and a value object.

在图 7-9 中,你可以看到一些聚合的示例,比如买家(buyer)聚合,它只包含一个实体(聚合根 Buyer)。而订单(order)聚合则包含多个实体和一个值对象。

Diagram comparing a buyer aggregate and an order aggregate.

Figure 7-9. Example of aggregates with multiple or single entities

图 7-9. 包含多个或单个实体的聚合示例

A DDD domain model is composed from aggregates, an aggregate can have just one entity or more, and can include value objects as well. Note that the Buyer aggregate could have additional child entities, depending on your domain, as it does in the ordering microservice in the eShopOnContainers reference application. Figure 7-9 just illustrates a case in which the buyer has a single entity, as an example of an aggregate that contains only an aggregate root.

一个 DDD 领域模型是由聚合组成的。一个聚合可以只包含一个实体,也可以包含多个实体,并且还可以包含值对象。需要注意的是,根据你的领域需求,买家(Buyer)聚合可以包含额外的子实体,就像在 eShopOnContainers 参考应用的订购微服务中所做的那样。图 7-9 仅仅展示了一个买家只包含单个实体的情况,作为“仅包含一个聚合根的聚合”的示例。

In order to maintain separation of aggregates and keep clear boundaries between them, it is a good practice in a DDD domain model to disallow direct navigation between aggregates and only having the foreign key (FK) field, as implemented in the Ordering microservice domain model in eShopOnContainers. The Order entity only has a foreign key field for the buyer, but not an EF Core navigation property, as shown in the following code:

为了保持聚合的分离并在它们之间划清界限,在 DDD 领域模型中,一个很好的实践是禁止聚合之间的直接导航,而只保留外键(FK)字段。这一做法在 eShopOnContainers 的订购微服务领域模型中得到了实现。如下面的代码所示,订单(Order)实体只包含一个指向买家的外键字段,而没有包含 EF Core 的导航属性:

public class Order : Entity, IAggregateRoot
{
    private DateTime _orderDate;
    public Address Address { get; private set; }
    private int? _buyerId; // FK pointing to a different aggregate root
    public OrderStatus OrderStatus { get; private set; }
    private readonly List<OrderItem> _orderItems;
    public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
    // ... Additional code
}

Identifying and working with aggregates requires research and experience. For more information, see the following Additional resources list.

识别和处理聚合(Aggregates)需要投入一定的研究并积累实践经验。欲了解更多信息,请参阅下方的‘额外资源’列表。

posted @ 2026-05-17 18:03  菜鸟吊思  阅读(15)  评论(0)    收藏  举报