设计面向 DDD 的微服务

Domain-driven design (DDD) advocates modeling based on the reality of business as relevant to your use cases. In the context of building applications, DDD talks about problems as domains. It describes independent problem areas as Bounded Contexts (each Bounded Context correlates to a microservice), and emphasizes a common language to talk about these problems. It also suggests many technical concepts and patterns, like domain entities with rich models (no anemic-domain model), value objects, aggregates, and aggregate root (or root entity) rules to support the internal implementation. This section introduces the design and implementation of those internal patterns.

领域驱动设计(DDD)主张基于与你的用例相关的业务现实来进行建模。在构建应用程序的语境下,DDD 将问题视为一个个“领域(Domains)”。它将独立的问题区域描述为限界上下文(Bounded Contexts)(每个限界上下文都对应一个微服务),并强调使用一种通用语言来探讨这些问题。此外,它还提出了许多技术概念和模式,例如拥有丰富模型的领域实体(拒绝贫血领域模型)、值对象、聚合以及聚合根(或根实体)规则,以支撑系统的内部实现。本节将介绍这些内部模式的设计与实现。

Sometimes these DDD technical rules and patterns are perceived as obstacles that have a steep learning curve for implementing DDD approaches. But the important part is not the patterns themselves, but organizing the code so it is aligned to the business problems, and using the same business terms (ubiquitous language). In addition, DDD approaches should be applied only if you are implementing complex microservices with significant business rules. Simpler responsibilities, like a CRUD service, can be managed with simpler approaches.

有时,这些 DDD 的技术规则和模式会被视为一种障碍,导致实施 DDD 方法的学习曲线非常陡峭。但真正的重点不在于模式本身,而在于如何组织代码使其与业务问题保持一致,并使用与业务相同的术语(通用语言)。此外,DDD 方法仅应在实现包含大量复杂业务规则的微服务时采用。像 CRUD(增删改查)服务这类更简单的职责,完全可以使用更简单的方法来管理。

Where to draw the boundaries is the key task when designing and defining a microservice. DDD patterns help you understand the complexity in the domain. For the domain model for each Bounded Context, you identify and define the entities, value objects, and aggregates that model your domain. You build and refine a domain model that is contained within a boundary that defines your context. And that is explicit in the form of a microservice. The components within those boundaries end up being your microservices, although in some cases a BC or business microservices can be composed of several physical services. DDD is about boundaries and so are microservices.

在设计和定义微服务时,划定边界是最关键的任务。DDD 模式能帮助你理解领域中的复杂度。对于每个限界上下文的领域模型,你需要识别并定义用来对领域进行建模的实体、值对象和聚合。你需要构建并完善一个包含在界定你上下文的边界内的领域模型,而这种边界在形式上就明确表现为一个微服务。这些边界内的组件最终会成为你的微服务,尽管在某些情况下,一个限界上下文或业务微服务可能由多个物理服务组成。DDD 关注的是边界,微服务亦是如此。

Keep the microservice context boundaries relatively small    保持微服务的上下文边界相对较小

Determining where to place boundaries between Bounded Contexts balances two competing goals. First, you want to initially create the smallest possible microservices, although that should not be the main driver; you should create a boundary around things that need cohesion. Second, you want to avoid chatty communications between microservices. These goals can contradict one another. You should balance them by decomposing the system into as many small microservices as you can until you see communication boundaries growing quickly with each additional attempt to separate a new Bounded Context. Cohesion is key within a single bounded context.

确定限界上下文之间的边界在哪里,需要权衡两个相互竞争的目标。首先,你希望在初期创建尽可能小的微服务,但这不应该是主要的驱动因素;你应该围绕那些需要内聚性的事物来划定边界。其次,你希望避免微服务之间进行“喋喋不休”的频繁通信。这两个目标有时会相互矛盾。你应该通过不断将系统拆分为尽可能多的小型微服务来寻找平衡,直到你发现每尝试拆分出一个新的限界上下文,通信边界就会迅速膨胀为止。在单个限界上下文内部,保持内聚性是关键。

It is similar to the Inappropriate Intimacy code smell when implementing classes. If two microservices need to collaborate a lot with each other, they should probably be the same microservice.

这类似于在实现类时遇到的“不恰当的亲密关系(Inappropriate Intimacy)”代码坏味道。如果两个微服务之间需要频繁地相互协作,那么它们很可能应该被合并为同一个微服务。

Another way to look at this aspect is autonomy. If a microservice must rely on another service to directly service a request, it is not truly autonomous.

看待这个问题的另一个角度是自治性(autonomy)。如果一个微服务必须依赖另一个服务来直接响应某个请求,那么它就不是真正自治的。

Layers in DDD microservices    DDD 微服务中的分层

Most enterprise applications with significant business and technical complexity are defined by multiple layers. The layers are a logical artifact, and are not related to the deployment of the service. They exist to help developers manage the complexity in the code. Different layers (like the domain model layer versus the presentation layer, etc.) might have different types, which mandate translations between those types.

大多数具有显著业务和技术复杂性的企业应用程序,都是由多个层级定义的。这些层级是一种逻辑产物,与服务的部署方式无关。它们的存在是为了帮助开发人员管理代码中的复杂度。不同的层级(例如领域模型层与表示层等)可能拥有不同的类型,这就需要在这些类型之间进行转换。

For example, an entity could be loaded from the database. Then part of that information, or an aggregation of information including additional data from other entities, can be sent to the client UI through a REST Web API. The point here is that the domain entity is contained within the domain model layer and should not be propagated to other areas that it does not belong to, like to the presentation layer.

例如,一个实体可能会从数据库中加载。然后,该实体的部分信息,或者包含来自其他实体附加信息的聚合数据,可以通过 REST Web API 发送给客户端 UI。这里的关键在于,领域实体被包含在领域模型层中,不应被传播到它不属于的其他区域,比如表示层。

Additionally, you need to have always-valid entities (see the Designing validations in the domain model layer section) controlled by aggregate roots (root entities). Therefore, entities should not be bound to client views, because at the UI level some data might still not be validated. This reason is what the ViewModel is for. The ViewModel is a data model exclusively for presentation layer needs. The domain entities do not belong directly to the ViewModel. Instead, you need to translate between ViewModels and domain entities and vice versa.

此外,你需要始终拥有由聚合根(根实体)控制的、保持“始终有效(always-valid)”的实体(参见“在领域模型层中设计验证”一节)。因此,实体不应直接绑定到客户端视图上,因为在 UI 层面,某些数据可能尚未经过验证。这正是 ViewModel 存在的意义。ViewModel 是专门为了满足表示层需求而存在的数据模型。领域实体并不直接属于 ViewModel。相反,你需要在 ViewModel 和领域实体之间进行相互转换。

When tackling complexity, it is important to have a domain model controlled by aggregate roots that make sure that all the invariants and rules related to that group of entities (aggregate) are performed through a single entry-point or gate, the aggregate root.

在处理复杂度时,拥有一个由聚合根控制的领域模型非常重要。聚合根能够确保与该组实体(聚合)相关的所有不变量和规则,都通过单一的入口点或网关(即聚合根)来执行。

Figure 7-5 shows how a layered design is implemented in the eShopOnContainers application.

图 7-5 展示了 eShopOnContainers 应用程序中是如何实现分层设计的。

Diagram showing the layers in a domain-driven design microservice.

Figure 7-5. DDD layers in the ordering microservice in eShopOnContainers

图 7-5. eShopOnContainers 中订购微服务的 DDD 分层

The three layers in a DDD microservice like Ordering. Each layer is a VS project: Application layer is Ordering.API, Domain layer is Ordering.Domain and the Infrastructure layer is Ordering.Infrastructure. You want to design the system so that each layer communicates only with certain other layers. That approach may be easier to enforce if layers are implemented as different class libraries, because you can clearly identify what dependencies are set between libraries. For instance, the domain model layer should not take a dependency on any other layer (the domain model classes should be Plain Old Class Objects, or POCO, classes). As shown in Figure 7-6, the Ordering.Domain layer library has dependencies only on the .NET libraries or NuGet packages, but not on any other custom library, such as data library or persistence library.

像 Ordering(订购)这样的 DDD 微服务包含三个层级。每个层级都是一个独立的 Visual Studio 项目:应用层是 Ordering.API,领域层是 Ordering.Domain,基础设施层是 Ordering.Infrastructure。在设计系统时,你需要确保每个层级只与特定的其他层级进行通信。如果将各个层级实现为不同的类库,这种做法会更容易强制执行,因为你可以清晰地界定类库之间设置了哪些依赖关系。例如,领域模型层不应依赖任何其他层级(领域模型类应当是普通旧式 CLR 对象,即 POCO 类)。如图 7-6 所示,Ordering.Domain 层类库仅依赖于 .NET 类库或 NuGet 包,而不依赖于任何其他自定义类库,比如数据类库或持久化类库。

Screenshot of Ordering.Domain dependencies.

Figure 7-6. Layers implemented as libraries allow better control of dependencies between layers

图 7-6. 将分层实现为类库,可以更好地控制层与层之间的依赖关系

The domain model layer    领域模型层

Eric Evans's excellent book Domain Driven Design says the following about the domain model layer and the application layer.

Eric Evans 的经典著作《领域驱动设计》(Domain Driven Design)中,关于领域模型层和应用层有如下论述:

Domain Model Layer: Responsible for representing concepts of the business, information about the business situation, and business rules. State that reflects the business situation is controlled and used here, even though the technical details of storing it are delegated to the infrastructure. This layer is the heart of business software.

领域模型层:负责表示业务的概念、关于业务状况的信息以及业务规则。反映业务状况的状态在这里被控制和使用,尽管存储该状态的技术细节被委托给了基础设施层(Infrastructure)。这一层是业务软件的核心(Heart)。

The domain model layer is where the business is expressed. When you implement a microservice domain model layer in .NET, that layer is coded as a class library with the domain entities that capture data plus behavior (methods with logic).

领域模型层是业务逻辑被表达的地方。当你在 .NET 中实现微服务的领域模型层时,该层通常被编码为一个类库(Class Library),其中包含捕获数据和行为(包含逻辑的方法)的领域实体。

Following the Persistence Ignorance and the Infrastructure Ignorance principles, this layer must completely ignore data persistence details. These persistence tasks should be performed by the infrastructure layer. Therefore, this layer should not take direct dependencies on the infrastructure, which means that an important rule is that your domain model entity classes should be POCOs.

遵循“持久化无知”(Persistence Ignorance)和“基础设施无知”(Infrastructure Ignorance)原则,这一层必须完全忽略数据持久化的细节,这些任务应由基础设施层来完成。因此,这一层绝不能直接依赖于基础设施。这意味着一个重要的规则:你的领域模型实体类应该是 POCOs(Plain Old CLR Objects,即普通的 .NET 对象)。

Domain entities should not have any direct dependency (like deriving from a base class) on any data access infrastructure framework like Entity Framework or NHibernate. Ideally, your domain entities should not derive from or implement any type defined in any infrastructure framework.

领域实体不应该对任何数据访问基础设施框架(如 Entity Framework 或 NHibernate)有直接的依赖(例如继承自某个基类)。理想情况下,你的领域实体不应该继承或实现任何在基础设施框架中定义的类型。

Most modern ORM frameworks like Entity Framework Core allow this approach, so that your domain model classes are not coupled to the infrastructure. However, having POCO entities is not always possible when using certain NoSQL databases and frameworks, like Actors and Reliable Collections in Azure Service Fabric.

大多数现代 ORM 框架(如 Entity Framework Core)都支持这种方法,因此你的领域模型类不会与基础设施耦合。然而,在使用某些 NoSQL 数据库和框架(如 Azure Service Fabric 中的 Actors 和 Reliable Collections)时,拥有纯粹的 POCO 实体并不总是可能的。

Even when it is important to follow the Persistence Ignorance principle for your Domain model, you should not ignore persistence concerns. It is still important to understand the physical data model and how it maps to your entity object model. Otherwise you can create impossible designs.

尽管遵循领域模型的“持久化无知”原则非常重要,但你也不应忽视持久化方面的问题。理解物理数据模型以及它如何映射到你的实体对象模型仍然非常重要,否则你可能会设计出无法实现的模型。

Also, this aspect does not mean you can take a model designed for a relational database and directly move it to a NoSQL or document-oriented database. In some entity models, the model might fit, but usually it does not. There are still constraints that your entity model must adhere to, based both on the storage technology and ORM technology.

此外,这并不意味着你可以将一个为关系型数据库设计的模型直接迁移到 NoSQL 或文档型数据库中。在某些实体模型中,模型可能适用,但通常情况下并不适用。基于存储技术和 ORM 技术,你的实体模型仍然必须遵守一些约束。

The application layer    应用层

Moving on to the application layer, we can again cite Eric Evans's book Domain Driven Design:

转到应用层,我们再次引用 Eric Evans 的《领域驱动设计》:

Application Layer: Defines the jobs the software is supposed to do and directs the expressive domain objects to work out problems. The tasks this layer is responsible for are meaningful to the business or necessary for interaction with the application layers of other systems. This layer is kept thin. It does not contain business rules or knowledge, but only coordinates tasks and delegates work to collaborations of domain objects in the next layer down. It does not have state reflecting the business situation, but it can have state that reflects the progress of a task for the user or the program.

应用层:定义软件应该执行的任务,并指挥表达性的领域对象来解决问题。该层负责的任务对业务来说是有意义的,或者是与其它系统的应用层进行交互所必需的。这一层保持得很“薄”(Thin)。它不包含业务规则或知识,而只是协调任务,并将工作委托给下一层的领域对象协作完成。它不包含反映业务状况的状态,但它可以包含反映用户或程序任务进度的状态。

A microservice's application layer in .NET is commonly coded as an ASP.NET Core Web API project. The project implements the microservice's interaction, remote network access, and the external Web APIs used from the UI or client apps. It includes queries if using a CQRS approach, commands accepted by the microservice, and even the event-driven communication between microservices (integration events). The ASP.NET Core Web API that represents the application layer must not contain business rules or domain knowledge (especially domain rules for transactions or updates); these should be owned by the domain model class library. The application layer must only coordinate tasks and must not hold or define any domain state (domain model). It delegates the execution of business rules to the domain model classes themselves (aggregate roots and domain entities), which will ultimately update the data within those domain entities.

在 .NET 中,微服务的应用层通常被编码为一个 ASP.NET Core Web API 项目。该项目实现了微服务的交互、远程网络访问以及 UI 或客户端应用所使用的外部 Web API。如果采用 CQRS 方法,它包含查询(Queries)、微服务接受的命令(Commands),甚至微服务之间的事件驱动通信(集成事件)。代表应用层的 ASP.NET Core Web API 绝不能包含业务规则或领域知识(特别是关于事务或更新的领域规则);这些应该由领域模型类库拥有。应用层必须只协调任务,且不能持有或定义任何领域状态(Domain Model)。它将业务规则的执行委托给领域模型类本身(聚合根 Aggregate Roots 和领域实体),由它们最终更新这些领域实体内的数据。

Basically, the application logic is where you implement all use cases that depend on a given front end. For example, the implementation related to a Web API service.

本质上,应用逻辑(Application Logic)是你实现所有依赖于特定前端(Front end)用例(Use Cases)的地方。例如,与 Web API 服务相关的实现。

The goal is that the domain logic in the domain model layer, its invariants, the data model, and related business rules must be completely independent from the presentation and application layers. Most of all, the domain model layer must not directly depend on any infrastructure framework.

目标是:领域模型层中的领域逻辑、不变量(invariants)、数据模型和相关业务规则,必须完全独立于表示层(Presentation)和应用层。最重要的是,领域模型层绝不能直接依赖于任何基础设施框架。

The infrastructure layer    基础设施层

The infrastructure layer is how the data that is initially held in domain entities (in memory) is persisted in databases or another persistent store. An example is using Entity Framework Core code to implement the Repository pattern classes that use a DBContext to persist data in a relational database.

基础设施层是将最初保存在领域实体(内存中)的数据持久化到数据库或其它持久化存储中的方式。一个典型的例子是使用 Entity Framework Core 代码来实现仓储模式(Repository pattern)的类,这些类使用 DBContext 将数据持久化到关系型数据库中。

In accordance with the previously mentioned Persistence Ignorance and Infrastructure Ignorance principles, the infrastructure layer must not "contaminate" the domain model layer. You must keep the domain model entity classes agnostic from the infrastructure that you use to persist data (EF or any other framework) by not taking hard dependencies on frameworks. Your domain model layer class library should have only your domain code, just POCO entity classes implementing the heart of your software and completely decoupled from infrastructure technologies.

根据前面提到的“持久化无知”和“基础设施无知”原则,基础设施层绝不能“污染”领域模型层。你必须通过不引入对框架的硬依赖(Hard dependencies),来保持领域模型实体类对所使用的基础设施(EF 或任何其他框架)的无知。你的领域模型层类库应该只包含你的领域代码,即实现你软件核心的 POCO 实体类,并且完全与基础设施技术解耦。

Thus, your layers or class libraries and projects should ultimately depend on your domain model layer (library), not vice versa, as shown in Figure 7-7.

因此,你的层(类库、项目)最终应该依赖于你的领域模型层(库),而不是反过来,如图 7-7 所示。

Diagram showing dependencies that exist between DDD service layers.

Figure 7-7. Dependencies between layers in DDD

图 7-7. DDD 中各层之间的依赖关系

Dependencies in a DDD Service, the Application layer depends on Domain and Infrastructure, and Infrastructure depends on Domain, but Domain doesn't depend on any layer. This layer design should be independent for each microservice. As noted earlier, you can implement the most complex microservices following DDD patterns, while implementing simpler data-driven microservices (simple CRUD in a single layer) in a simpler way.

在 DDD 服务中,应用层依赖于领域层和基础设施层,基础设施层依赖于领域层,但领域层不依赖于任何层。这种分层设计对于每个微服务来说都应该是独立的。正如前面提到的,你可以遵循 DDD 模式来实现最复杂的微服务,同时以更简单的方式(例如在单层中实现简单的 CRUD)来实现那些简单的数据驱动型微服务。

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