烂翻译系列之学习领域驱动设计——第十五章:事件溯源架构

As microservices, event-driven architecture (EDA) is ubiquitous in modern distributed systems. Many advise using event-driven communication as the default integration mechanism when designing loosely coupled, scalable, fault-tolerant distributed systems.

作为微服务,事件驱动架构(EDA)在现代分布式系统中无处不在。许多人建议在设计松耦合、可扩展、容错的分布式系统时,使用事件驱动的通信作为默认的集成机制。

Event-driven architecture is often linked to domain-driven design. After all, EDA is based on events, and events are prominent in DDD—we have domain events, and when needed, we even use events as the system’s source of truth. It may be tempting to leverage DDD’s events as the basis for using event-driven architecture. But is this a good idea?

事件驱动架构(EDA)经常与领域驱动设计(DDD)相关联。毕竟,EDA是基于事件的,而事件在DDD中占据重要地位——我们有领域事件,在需要时,我们甚至使用事件作为系统的真相来源。利用DDD的事件作为使用事件驱动架构的基础可能很诱人。但这是一个好主意吗?

Events are not a kind of secret sauce that you can just pour over a legacy system and turn it into a loosely coupled distributed system. Quite the opposite: careless application of EDA can turn a modular monolith into a distributed big ball of mud.

事件并不是一种神奇的“秘方”,你不能简单地将其应用于遗留系统,并期待它能瞬间转变为松耦合的分布式系统。事实恰恰相反:如果不谨慎地应用事件驱动架构(EDA),原本模块化的单体系统可能会变成一个分布式的大杂烩。

In this chapter, we will explore the interplay between EDA and DDD. You will learn the essential building blocks of event-driven architecture, common causes for failed EDA projects, and how you can leverage DDD’s tools to design effective, asynchronously integrated systems.

在本章中,我们将探讨事件驱动架构(EDA)和领域驱动设计(DDD)之间的相互作用。您将学习事件驱动架构的基本组成部分、EDA项目失败的常见原因,以及如何利用DDD的工具来设计有效、异步集成的系统。

Event-Driven Architecture

事件驱动架构

Stated simply, event-driven architecture is an architectural style in which a system’s components communicate with one another asynchronously by exchanging event messages (see Figure 15-1). Instead of calling the services’ endpoints synchronously, the components publish events to notify other system elements of changes in the system’s domain. The components can subscribe to events raised in the system and react accordingly. A typical example of an event-driven execution flow is the saga pattern that was described in Chapter 9.

简而言之,事件驱动架构(EDA)是一种架构风格,其中系统的组件通过交换事件消息来异步相互通信(见图15-1)。组件不是同步调用服务的端点,而是发布事件来通知系统的其他元素系统域中的变化。组件可以订阅系统中产生的事件并相应地做出反应。事件驱动执行流程的一个典型例子是第9章中描述的长事务模式(saga pattern)。

Figure 15-1. Asynchronous communication

图15-1.异步通信

It’s important to highlight the difference between event-driven architecture and event sourcing. As we discussed in Chapter 7, event sourcing is a method for capturing changes in state as a series of events.

重要的是要强调事件驱动架构和事件溯源之间的区别。正如我们在第7章中讨论的那样,事件溯源是一种将状态变化捕获为一系列事件的方法。

Although both event-driven architecture and event sourcing are based on events, the two patterns are conceptually different. EDA refers to the communication between services, while event sourcing happens inside a service. The events designed for event sourcing represent state transitions (of aggregates in an event-sourced domain model) implemented in the service. They are aimed at capturing the intricacies of the business domain and are not intended to integrate the service with other system components.

尽管事件驱动架构和事件溯源都是基于事件的,但这两种模式在概念上是不同的。EDA 指的是服务之间的通信,而事件溯源发生在服务内部。为事件溯源设计的事件表示在服务中实现的状态转换(事件溯源领域模型中聚合的状态转换)。它们旨在捕获业务领域的复杂性,而不是为了将服务与其他系统组件集成。

As you will see later in this chapter, there are three types of events, and some are more suited for integration than others.

正如您将在本章后面看到的,有三种类型的事件,其中一些比另一些更适合于集成。

Events

事件

In an EDA system, the exchange of events is the key communication mechanism for integrating the components and making them a system. Let’s take a look at events in more detail and see how they differ from messages.

在EDA系统中,事件的交换是集成组件并使其成为系统的关键通信机制。让我们更详细地了解事件,并看看它们与消息有何不同。

Events, Commands, and Messages

事件,命令和消息

So far, the definition of an event is similar to the definition of the message pattern. However, the two are different. An event is a message, but a message is not necessarily an event. There are two types of messages:

到目前为止,事件的定义与消息模式的定义相似。然而,两者是不同的。事件是一种消息,但消息并不一定是事件。消息有两种类型:

Event    事件

       A message describing a change that has already happened    描述已发生变化的消息

Command    命令

       A message describing an operation that has to be carried out    描述需要执行的操作的消息

An event is something that has already happened, whereas a command is an instruction to do something. Both events and commands can be communicated asynchronously as messages. However, a command can be rejected: the command’s target can refuse to execute the command, for example, if the command is invalid or if it contradicts the system’s business rules. A recipient of an event, on the other hand, cannot cancel the event. The event describes something that has already happened. The only thing that can be done to overturn an event is to issue a compensating action—a command, as it’s carried out in the saga pattern.

事件是已经发生的事件,而命令是执行某些操作的指令。事件和命令都可以作为消息进行异步通信。但是,命令可以被拒绝: 命令的目标可以拒绝执行命令,例如,如果命令无效或者与系统的业务规则相抵触。另一方面,事件的接收方不能取消该事件。事件描述了已经发生的事情。要推翻事件,唯一能做的就是发出补偿操作——一个命令,就像在长事务模式(saga pattern)中那样执行。

Since an event describes something that has already happened, an event’s name should be formulated in the past tense: for example, DeliveryScheduled, ShipmentCompleted, or DeliveryConfirmed.

由于事件描述的是已经发生的事情,因此事件的名称应以过去时态来表述:例如,DeliveryScheduled(配送已安排)、ShipmentCompleted(已发货)或DeliveryConfirmed(已确认收货)。

Structure

结构

An event is a data record that can be serialized and transmitted using the messaging platform of choice. A typical event schema includes the event’s metadata and its payload—the information communicated by the event:

事件是一个数据记录,可以使用所选的消息传递平台进行序列化和传输。一个典型的事件架构包括事件的元数据和有效载荷——事件所传递的信息:

{
 "type": "delivery-confirmed",
 "event-id": "14101928-4d79-4da6-9486-dbc4837bc612",
 "correlation-id": "08011958-6066-4815-8dbe-dee6d9e5ebac",
 "delivery-id": "05011927-a328-4860-a106-737b2929db4e",
 "timestamp": 1615718833,
1
 "payload": {
 "confirmed-by": "17bc9223-bdd6-4382-954d-f1410fd286bd",
 "delivery-time": 1615701406
 }
}

An event’s payload not only describes the information conveyed by the event, but also defines the event’s type. Let’s discuss the three types of events in detail and how they differ from one another.

事件的有效载荷不仅描述了事件所传递的信息,还定义了事件的类型。让我们详细讨论这三种事件类型以及它们之间的区别。

Types of Events

事件类型

Events can be categorized into one of three types: event notification, event-carried state transfer, or domain events.

事件可以分为三种类型:事件通知、事件携带的状态转移或领域事件。

Event notification

事件通知

An event notification is a message regarding a change in the business domain that other components will react to. Examples include PaycheckGenerated and CampaignPublished, among others.

事件通知是关于业务领域发生变化的消息,其他组件将对此做出反应。例如PaycheckGenerated(工资单已生成)和CampaignPublished(活动已发布)等。

The event notification should not be verbose: the goal is to notify the interested parties about the event, but the notification shouldn’t carry all the information needed for the subscribers to react to the event. For example:

事件通知不应该过于冗长: 目标是将事件通知感兴趣的各方,但通知不应包含订阅者响应事件所需的所有信息。例如:

{
 "type": "paycheck-generated",
 "event-id": "537ec7c2-d1a1-2005-8654-96aee1116b72",
 "delivery-id": "05011927-a328-4860-a106-737b2929db4e",
 "timestamp": 1615726445,
 "payload": {
 "employee-id": "456123",
 "link": "/paychecks/456123/2021/01"
 }
}

In the preceding code, the event notifies the external components of a paycheck that was generated. It doesn’t carry all the information related to the paycheck. Instead, the receiver can use the link to fetch more detailed information. This notification flow is depicted in Figure 15-2.

在前面的代码中,事件通知外部组件已生成工资单。它不包含与工资单相关的所有信息。相反,接收者可以使用链接来获取更详细的信息。此通知流程如图15-2所示。

Figure 15-2. Event notification flow

图15-2.  事件通知流

In a sense, integration through event notification messages is similar to the Wireless Emergency Alert (WEA) system in the United States and EU-Alert in Europe (see Figure 15-3). The systems use cell towers to broadcast short messages, notifying citizens about public health concerns, safety threats, and other emergencies. The systems are limited to sending messages with a maximum length of 360 characters. This short message is enough to notify you about the emergency, but you have to proactively use other information sources to get more details.

从某种意义上说,通过事件通知消息进行集成与美国的无线紧急警报(WEA)系统和欧洲的EU-Alert系统类似(见图15-3)。这些系统使用蜂窝基站广播简短消息,通知公民关注公共卫生问题、安全威胁和其他紧急情况。这些系统的消息长度限制在最多360个字符。这条简短的消息足以通知您紧急情况,但您必须主动使用其他信息源来获取更多详细信息。

Figure 15-3. Emergency alert system

图15-3.  紧急警报系统

Succinct event notifications can be preferable in multiple scenarios. Let’s take a closer look at two: security and concurrency.

简洁的事件通知在多种场景下可能更受欢迎。让我们更详细地了解其中两个场景:安全性和并发性。

Security    安全性

Enforcing the recipient to explicitly query for the detailed information prevents sharing sensitive information over the messaging infrastructure and requires additional authorization of the subscribers to access the data.

强制接收者明确查询详细信息可以防止通过消息传递基础结构共享敏感信息,并且要求订阅者具有访问数据的额外授权。

Concurrency    并发性

Due to the asynchronous nature of event-driven integration, the information can already be rendered stale when it reaches the subscribers. If the information’s nature is sensitive to race conditions, querying it explicitly allows getting the up-to-date state.

由于事件驱动集成的异步性质,信息到达订阅者时可能已经过时。如果信息的性质对竞态条件敏感,则显式查询它可以获得最新状态。

Furthermore, in the case of concurrent consumers, where only one subscriber should process an event, the querying process can be integrated with pessimistic locking. This ensures the producer’s side that no other consumer will be able to process the message.

此外,在并发消费者的情况下,只有一个订阅者应该处理一个事件,此时查询过程可以与悲观锁集成。这确保了生产者方面没有其他消费者能够处理该消息。

Event-carried state transfer

事件携带状态转移

Event-carried state transfer (ECST) messages notify subscribers about changes in the producer’s internal state. Contrary to event notification messages, ECST messages include all the data reflecting the change in the state.

事件携带的状态转移(ECST)消息通知订阅者生产者内部状态的变化。与事件通知消息相反,ECST消息包含反映状态变化的所有数据。

ECST messages can come in two forms. The first is a complete snapshot of the modified entity’s state:

ECST消息可以以两种形式出现。第一种是修改后实体的状态的完整快照:

{
 "type": "customer-updated",
 "event-id": "6b7ce6c6-8587-4e4f-924a-cec028000ce6",
 "customer-id": "01b18d56-b79a-4873-ac99-3d9f767dbe61",
 "timestamp": 1615728520,
 "payload": {
 "first-name": "Carolyn",
 "last-name": "Hayes",
 "phone": "555-1022",
 "status": "follow-up-set",
 "follow-up-date": "2021/05/08",
 "birthday": "1982/04/05",
 "version": 7
 }
}

The ECST message in the preceding example includes a complete snapshot of a customer’s updated state. When operating large data structures, it may be reasonable to include in the ECST message only the fields that were actually modified:

前面示例中的ECST消息包含了客户更新状态的完整快照。在处理大型数据结构时,在ECST消息中仅包含实际已修改的字段可能是合理的:

{
 "type": "customer-updated",
 "event-id": "6b7ce6c6-8587-4e4f-924a-cec028000ce6",
 "customer-id": "01b18d56-b79a-4873-ac99-3d9f767dbe61",
 "timestamp": 1615728520,
 "payload": {
 "status": "follow-up-set",
 "follow-up-date": "2021/05/10",
 "version": 8
 }
}

Whether ECST messages include complete snapshots or only the updated fields, a stream of such events allows consumers to hold a local cache of the entities’ states and work with it. Conceptually, using event-carried state transfer messages is an asynchronous data replication mechanism. This approach makes the system more fault tolerant, meaning that the consumers can continue functioning even if the producer is not available. It is also a way to improve the performance of components that have to process data from multiple sources. Instead of querying the data sources each time the data is needed, all the data can be cached locally, as shown in Figure 15-4.

无论ECST消息是包含完整快照还是仅包含更新后的字段,这一系列事件都允许消费者持有实体的本地状态缓存并与之交互。从概念上讲,使用事件携带的状态转移消息是一种异步数据复制机制。这种方法使系统具有更高的容错性,意味着即使生产者不可用,消费者也可以继续运行。这也是一种提高必须处理来自多个源的数据的组件性能的方法。而不是在每次需要数据时都查询数据源,所有数据都可以像图15-4所示那样在本地缓存。

Figure 15-4. Backend for frontend

图15-4.  前端后端(Backend for Frontend,简称BFF)

Domain event

领域事件

The third type of event message is the domain event that we described in Chapter 6. In a way, domain events are somewhere between event notification and ECST messages: they both describe a significant event in the business domain, and they contain all the data describing the event. Despite the similarities, these types of messages are conceptually different.

第三种事件消息是我们在第6章中描述的领域事件。从某种程度上说,领域事件介于事件通知和ECST消息之间:它们均描述了业务领域中的一个重要事件,并包含了描述该事件的所有数据。尽管存在相似之处,但这些消息类型在概念上是不同的。

Domain events versus event notification

领域事件与事件通知

Both domain events and event notifications describe changes in the producer’s business domain. That said, there are two conceptual differences.

领域事件和事件通知都描述了生产者业务领域的变化。但即便如此,它们在概念上仍有两个区别。

First, domain events include all the information describing the event. The consumer does not need to take any further action to get the complete picture.

首先,领域事件包含了描述该事件的所有信息。消费者无需采取任何进一步行动即可获得完整的情况。

Second, the modeling intent is different. Event notifications are designed with the intent to alleviate integration with other components. Domain events, on the other hand, are intended to model and describe the business domain. Domain events can be useful even if no external consumer is interested in them. That’s especially true in event-sourced systems, where domain events are used to model all possible state transitions. Having external consumers interested in all the available domain events would result in suboptimal design. We will discuss this in greater detail later in this chapter.

其次,建模意图是不同的,事件通知的设计意图是方便与其他组件的集成。而另一方面,领域事件旨在建模和描述业务领域。即使没有外部消费者对其感兴趣,领域事件也可能很有用。这在事件源系统中尤其如此,在事件源系统中,领域事件用于建模所有可能的状态转换。如果有外部消费者对所有可用的领域事件都感兴趣,那么这将导致设计不佳。我们将在本章后面更详细地讨论这一点。

Domain events versus event-carried state transfer

领域事件与事件携带状态转移

The data contained in domain events is conceptually different from the schema of a typical ECST message.

领域事件中包含的数据在概念上与典型的ECST消息架构不同。

An ECST message provides sufficient information to hold a local cache of the producer’s data. No single domain event is supposed to expose such a rich model. Even the data included in a specific domain event is not sufficient for caching the aggregate’s state, as other domain events that the consumer is not subscribed to may affect the same fields.

ECST消息提供了足够的信息来保存生产者数据的本地缓存。而任何单一的领域事件都不应该暴露如此丰富的模型。即使是特定领域事件中包含的数据也不足以缓存聚合的状态,因为消费者未订阅的其他领域事件也可能影响相同的字段。

Furthermore, as in the case of notification events, the modeling intent is different for the two types of messages. The data included in domain events is not intended to describe the aggregate’s state. Instead, it describes a business event that happened during its lifecycle.

此外,与通知事件的情况一样,这两种消息类型的建模意图是不同的。领域事件中包含的数据并不是为了描述聚合的状态,而是为了描述在其生命周期中发生的业务事件。

Event types: Example

事件类型:示例

Here is an example that demonstrates the differences between the three types of events. Consider the following three ways to represent the event of marriage:

以下是一个示例,展示了这三种事件类型之间的差异。考虑以下三种表示婚姻事件的方式:

eventNotification = {
 "type": "marriage-recorded",
 "person-id": "01b9a761",
 "payload": {
 "person-id": "126a7b61",
 "details": "/01b9a761/marriage-data"
 }
};
ecst = {
 "type": "personal-details-changed",
 "person-id": "01b9a761",
 "payload": {
 "new-last-name": "Williams"
 }
};
domainEvent = {
 "type": "married",
 "person-id": "01b9a761",
 "payload": {
 "person-id": "126a7b61",
 "assumed-partner-last-name": true
 }
};

marriage-recorded is an event notification message. It contains no information except the fact that the person with the specified ID got married. It contains minimal information about the event, and the consumers interested in more details will have to follow the link in the details field.

“marriage-recorded”是一个事件通知信息。除了持有指定身份证件的人已经结婚这一事实之外,它不包含任何信息。它包含有关事件的最少信息,而对更多细节感兴趣的使用者必须按照细节字段中的链接进行操作。

personal-details-changed is an event-carried state transfer message. It describes the changes in the person’s personal details, namely that their last name has been changed. The message doesn’t explain the reason why it has changed. Did the person get married or divorced?

“personal-details-changed”是一个事件携带状态转换消息。它描述了个人详细信息的变更,即他们的姓氏已经改变。该消息没有解释变更的原因。这个人是因为结婚还是离婚而改变了姓氏?

Finally, married is a domain event. It is modeled as close as possible to the nature of the event in the business domain. It includes the person’s ID and a flag indicating whether the person assumed their partner’s name.

最后,“married”是一个领域事件。它的建模尽可能接近业务领域中事件的本质。它包括此人的 ID 和一个标志,该标志指示此人是否采用了其伴侣的名字。

Designing Event-Driven Integration

设计事件驱动的集成

As we discussed in Chapter 3, software design is predominantly about boundaries. Boundaries define what belongs inside, what remains outside, and most importantly, what goes across the boundaries—essentially, how the components are integrated with one another. The events in an EDA-based system are first-class design elements, affecting both how the components are integrated and the components’ boundaries themselves. Choosing the correct type of event message is what makes (decouples) or breaks (couples) a distributed system.

正如我们在第3章中讨论的那样,软件设计主要围绕边界展开。边界定义了哪些内容属于内部,哪些内容属于外部,以及最重要的是,哪些内容跨越边界——本质上,即组件是如何相互集成的。在基于事件驱动架构的系统中,事件是头等的设计元素,它们既影响组件的集成方式,也影响组件本身的边界。选择正确类型的事件消息是构建(解耦)或破坏(耦合)分布式系统的关键。

In this section, you will learn heuristics for applying different event types. But first, let’s see how to use events to design a strongly coupled, distributed big ball of mud.

在本节中,您将学习应用不同类型事件的启发式方法。但首先,让我们看看如何使用事件来设计一个紧耦合、分布式的“大泥球”。

Distributed Big Ball of Mud

分布式大泥球

Consider the system shown in Figure 15-5.

思考如图15-5所示的系统。

The CRM bounded context is implemented as an event-sourced domain model. When the CRM system had to be integrated with the Marketing bounded context, the teams decided to leverage the event-sourced data model’s flexibility and let the consumer—in this case, Marketing— subscribe to the CRM’s domain events and use them to project the model that fits their needs.

CRM有界上下文被实现为事件源领域模型。当CRM系统需要与Marketing有界上下文集成时,团队决定利用事件源数据模型的灵活性,并让消费者(在这种情况下是Marketing)订阅CRM的领域事件,并使用这些事件来投影出符合其需求的模型。

When the AdsOptimization bounded context was introduced, it also had to process the information produced by the CRM bounded context. Again, the teams decided to let AdsOptimization subscribe to all domain events produced in the CRM and project the model that fits AdsOptimization’s needs.

当引入AdsOptimization有界上下文时,它也需要处理CRM有界上下文生成的信息。同样,团队决定让AdsOptimization订阅CRM中产生的所有领域事件,并根据其需求投影出相应的模型。

Figure 15-5. Strongly coupled distributed system

图15-5.  紧耦合分布式系统

Interestingly, both the Marketing and AdsOptimization bounded contexts had to present the customers’ information in the same format, and hence ended up projecting the same model out of the CRM’s domain events: a flattened snapshot of each customer’s state.

有趣的是,Marketing和AdsOptimization这两个有界上下文都需要以相同的格式展示客户信息,因此它们都从CRM的领域事件中投影出了相同的模型:每个客户状态的扁平化快照。

The Reporting bounded context subscribed only to a subset of domain events published by the CRM and used as event notification messages to fetch the calculations performed in the AdsOptimization context. However, since both AdsOptimization bounded contexts use the same events to trigger their calculations, to ensure that the Reporting model is updated the AdsOptimization context introduced a delay. It processed messages five minutes after receiving them.

Reporting有界上下文仅订阅了CRM发布的一部分领域事件,并将其用作事件通知消息,以获取AdsOptimization上下文中进行的计算。然而,由于两个AdsOptimization有界上下文都使用相同的事件来触发其计算,为了确保 Reporting模型得到更新,AdsOptimized上下文引入了一个延迟。它在收到信息五分钟后进行处理。

This design is terrible. Let’s analyze the types of coupling in this system.

这个设计很糟糕。让我们分析一下这个系统中的耦合类型。

Temporal Coupling

时间耦合

The AdsOptimization and Reporting bounded contexts are temporally coupled: they depend on a strict order of execution. The AdsOptimization component has to finish its processing before the Reporting module is triggered. If the order is reversed, inconsistent data will be produced in the Reporting system.

AdsOptimization和Reporting有界上下文之间存在时间耦合:它们依赖于严格的执行顺序。在触发Reporting模块之前,AdsOptimization组件必须完成其处理。如果顺序颠倒,Reporting系统中将产生不一致的数据。

To enforce the required execution order, the engineers introduced the processing delay in the Reporting system. This delay of five minutes lets the AdsOptimization component finish the required calculations. Obviously, this doesn’t prevent incorrect order of execution:

为了强制实行所需的执行顺序,工程师在 Reporting系统中引入了处理延迟。这个五分钟的延迟使 AdsOptimation 组件能够完成所需的计算。显然,这并不能防止执行顺序出错:

  • AdsOptimization may be overloaded and unable to finish the processing in five minutes.    AdsOptimization可能会过载,无法在五分钟内完成处理。
  • A network issue may delay the delivery of incoming messages to the AdsOptimization service.    网络问题可能会延迟将传入消息传递到AdsOptimization服务。
  • The AdsOptimization component can experience an outage and stop processing incoming messages.    AdsOptimization组件可能会出现故障并停止处理传入消息。

Functional Coupling

功能耦合

The Marketing and AdsOptimization bounded contexts both subscribed to the CRM’s domain events and ended up implementing the same projection of the customers’ data. In other words, the business logic that transforms incoming domain events into a state-based representation was duplicated in both bounded contexts, and it had the same reasons for change: they had to present the customers’ data in the same format. Therefore, if the projection was changed in one of the components, the change had to be replicated in the second bounded context.

Marketing和AdsOptimization这两个有界上下文都订阅了CRM的领域事件,并最终实现了相同的客户数据投影。换句话说,在这两个有界上下文中,将传入的领域事件转换为基于状态的表示形式的业务逻辑是重复的,并且它们有相同的变化原因:它们需要以相同的格式展示客户数据。因此,如果其中一个组件中的投影发生了变化,则必须在第二个有界上下文中复制该变化。

That’s an example of functional coupling: multiple components implementing the same business functionality, and if it changes, both components have to change simultaneously.

这是一个功能耦合的例子:多个组件实现了相同的业务功能,如果发生变化,则这两个组件都必须同时更改。

Implementation Coupling

实现耦合

This type of coupling is more subtle. The Marketing and AdsOptimization bounded contexts are subscribed to all the domain events generated by the CRM’s event-sourced model. Consequently, a change in the CRM’s implementation, such as adding a new domain event or changing the schema of an existing one, has to be reflected in both subscribing bounded contexts! Failing to do so can lead to inconsistent data. For example, if an event’s schema changes, the subscribers’ projection logic will fail. On the other hand, if a new domain event is added to the CRM’s model, it can potentially affect the projected models, and thus, ignoring it will lead to projecting an inconsistent state.

这种耦合类型更为微妙。Marketing和AdsOptimization这两个有界上下文都订阅了CRM事件源模型生成的所有领域事件。因此,CRM实现中的任何更改,如添加新的领域事件或更改现有领域事件的结构,都必须在订阅的有界上下文中得到体现!如果不这样做,可能会导致数据不一致。例如,如果事件的结构发生更改,订阅者的投影逻辑将会失败。另一方面,如果向CRM模型中添加了新的领域事件,它可能会影响投影模型,因此,忽略它会导致投影出不一致的状态。

Refactoring the Event-Driven Integration

重构事件驱动的集成

As you can see, blindly pouring events on a system makes it neither decoupled nor resilient. You may assume that this is an unrealistic example, but unfortunately, this example is based on a true story. Let’s see how the events can be adjusted to improve the design dramatically.

如您所见,盲目地向系统注入事件并不会使其解耦或具有弹性。您可能认为这是一个不真实的例子,但不幸的是,这个例子是基于真实故事的。让我们看看如何调整事件以显著改善设计。

Exposing all the domain events constituting the CRM’s data model couples the subscribers to the producer’s implementation details. The implementation coupling can be addressed by exposing either a much more restrained set of events or a different type of events.

暴露构成CRM数据模型的所有领域事件会将订阅者耦合到生产者的实现细节上。可以通过暴露一组更加受限的事件或不同类型的事件来解决实现耦合问题。

The Marketing and AdsOptimization subscribers are functionally coupled to each other by implementing the same business functionality.

Marketing和AdsOptimization订阅者通过实现相同的业务功能而在功能上相互耦合。

Both implementation and functional coupling can be tackled by encapsulating the projection logic in the producer: the CRM bounded contexts. Instead of exposing its implementation details, the CRM can follow the consumer-driven contract pattern: project the model needed by the consumers and make it a part of the bounded context’s published language—an integration-specific model, decoupled from the internal implementation model. As a result, the consumers get all the data they need and are not aware of the CRM’s implementation model.

可以通过在生产者(即CRM有界上下文)中封装投影逻辑来解决实现耦合和功能耦合问题。CRM不应暴露其实现细节,而应遵循消费者驱动的契约模式:投影出消费者所需的模型,并将其作为有界上下文发布语言的一部分——这是一个与内部实现模型解耦的特定于集成的模型。这样,消费者就可以获得他们所需的所有数据,而无需了解CRM的实现模型。

To tackle the temporal coupling between the AdsOptimization and Reporting bounded contexts, the AdsOptimization component can publish an event notification message, triggering the Reporting component to fetch the data it needs. This refactored system is shown in Figure 15-6.

为了解决AdsOptimization和Reporting有界上下文之间的时间耦合问题,AdsOptimization组件可以发布一个事件通知消息,触发Reporting组件去获取它所需的数据。重构后的系统如图15-6所示。

Figure 15-6. Refactored system

图15-6. 重构的系统

Event-Driven Design Heuristics

事件驱动设计启示

Matching types of events to the tasks at hand makes the resultant design orders of magnitude less coupled, more flexible, and fault tolerant. Let’s formulate the design heuristics behind the applied changes.

将事件类型与当前任务相匹配,可以使最终的设计在耦合度上降低几个数量级,更加灵活,并且具有容错性。让我们来总结一下这些应用变更背后的设计启示。

Assume the worst

做最坏的打算

As Andrew Grove put it, only the paranoid survive. Use this as a guiding principle when designing event-driven systems:

正如安德鲁·格鲁夫(Andrew Grove)所说,只有偏执狂才能生存。在设计事件驱动系统时,请以此作为指导原则:

  • The network is going to be slow.    网络将会很慢。
  • Servers will fail at the most inconvenient moment.    服务器将在最困难的时候出现故障。
  • Events will arrive out of order.    事件将会乱序到达。
  • Events will be duplicated.    事件将会被重复。

Most importantly, these events will occur most frequently on weekends and public holidays.

最重要的是,这些事件在周末和公共假日发生的频率最高。

The word driven in event-driven architecture means your whole system depends on successful delivery of the messages. Hence, avoid the “things will be okay” mindset like the plague. Ensure that the events are always delivered consistently, no matter what:

在事件驱动架构中,“驱动”一词意味着整个系统都依赖于消息的成功传递。因此,要像避免瘟疫一样避免“一切都会好起来的”这种心态。确保无论发生什么情况,事件都能始终如一地传递:

  • Use the outbox pattern to publish messages reliably.    使用发件箱模式可靠地发布消息。
  • When publishing messages, ensure that the subscribers will be able to deduplicate the messages and identify and reorder out-of-order messages.    在发布消息时,请确保订阅者能够去重消息,并能够识别和重新排序乱序消息。
  • Leverage the saga and process manager patterns when orchestrating cross-bounded context processes that require issuing compensating actions.    在编排需要执行补偿操作的跨有界上下文流程时,利用Saga和流程管理器模式。

Use public and private events

使用公共和私有事件

Be wary of exposing implementation details when publishing domain events, especially in event-sourced aggregates. Treat events as an inherent part of the bounded context’s public interface. Therefore, when implementing the open-host service pattern, ensure that the events are reflected in the bounded context’s published language. Patterns for transforming event-based models are discussed in Chapter 9.

在发布领域事件时,要谨慎暴露实现细节,尤其是在事件源聚合中。应将事件视为有界上下文公共接口的固有部分。因此,在实现开放主机服务模式时,请确保事件反映在有界上下文的发布语言中。第9章将讨论转换基于事件模型的模式。

When designing bounded contexts’ public interfaces, leverage the different types of events. Event-carried state transfer messages compress the implementation model into a more compact model that communicates only the information the consumers need.

在设计有界上下文的公共接口时,要利用不同类型的事件。事件携带的状态传输消息将实现模型压缩成一个更紧凑的模型,该模型仅传输消费者所需的信息。

Event notification messages can be used to further minimize the public interface.

可以使用事件通知消息来进一步缩小公共接口。

Finally, sparingly use domain events for communication with external bounded contexts. Consider designing a set of dedicated public domain events.

最后,谨慎使用领域事件与外部有界上下文进行通信。考虑设计一组专用的公共领域事件。

Evaluate consistency requirements

评估一致性需求

When designing event-driven communication, evaluate the bounded contexts’ consistency requirements as an additional heuristic for choosing the event type:

在设计事件驱动通信时,评估有界上下文的一致性需求,作为选择事件类型的另外启发:

翻译:在设计事件驱动通信时,请将有界上下文的一致性要求作为选择事件类型的额外启发式方法:

  • If the components can settle for eventually consistent data, use the event-carried state transfer message.    如果组件可以接受最终一致性数据,则使用事件携带的状态传输消息。
  • If the consumer needs to read the last write in the producer’s state, issue an event notification message, with a subsequent query to fetch the producer’s up-to-date state.     如果消费者需要读取生产者的最新写入状态,则发布一个事件通知消息,并随后进行查询以获取生产者的最新状态。

Conclusion

总结

This chapter presented event-driven architecture as an inherent aspect of designing a bounded context’s public interface. You learned the three types of events that can be used for cross-bounded context communication:

本章介绍了事件驱动架构作为设计有界上下文公共接口的固有方面。您学习了可用于跨有界上下文通信的三种事件类型:

Event notification    事件通知

       A notification that something important has happened, but requiring the consumer to query the producer for additional information explicitly.    通知发生了一些重要的事情,但是要求消费者显式地查询生产者以获得更多信息。

Event-carried state transfer    事件携带状态传输

       A message-based data replication mechanism. Each event contains a snapshot of a state that can be used to maintain a local cache of the producer’s data.    基于消息的数据复制机制。每个事件都包含状态的快照,可用于维护生产者数据的本地缓存。

Domain event    领域事件

       A message describing an event in the producer’s business domain.    描述生产者业务领域中事件的消息。

Using inappropriate types of events will derail an EDA-based system, inadvertently turning it into a big ball of mud. To choose the correct type of events for integration, evaluate the bounded contexts’ consistency requirements and be wary of exposing implementation details. Design an explicit set of public and private events. Finally, ensure that the system delivers the messages, even in the face of technical issues and outages.

使用不适当的事件类型将会破坏基于事件驱动架构(EDA)的系统,使其无意中变成一个混乱的系统。为了选择正确的集成事件类型,请评估有界上下文的一致性要求,并谨慎暴露实现细节。设计一组明确的公共和私有事件。最后,确保系统能够传递消息,即使面临技术问题和中断也是如此。

Exercises

练习

1. Which of the following statements is/are correct?    以下哪个/哪些陈述是正确的?

a. Event-driven architecture defines the events intended to travel across components’ boundaries.    事件驱动架构定义了旨在跨越组件边界的事件。

b. Event sourcing defines the events that are intended to stay within the bounded context’s boundary.    “事件溯源(Event Sourcing)”定义了旨在保留在有界上下文边界内的事件。

c. Event-driven architecture and event sourcing are different terms for the same pattern.    “事件驱动架构”和“事件溯源”是同一模式的不同术语。

d. A and B are correct.    A 和 B 是正确的。

答案:d。

2. What type of event is best suited for communicating changes in state?    哪种类型的事件最适合用于传达状态变化?

a. Event notification.    事件通知

b. Event-carried state transfer.    事件携带状态传输

c. Domain event.    领域事件

d. All event types are equally good for communicating changes in state.    所有事件类型在传达状态变化方面都同样有用。

答案:b。

3. Which bounded context integration pattern calls for explicitly defining public events?    哪种有界上下文集成模式要求明确定义公共事件?

a. Open-host service    开放主机服务

b. Anticorruption layer    防腐层

c. Shared kernelc    共享内核

d. Conformist    遵奉者

答案:a。

4. The services S1 and S2 are integrated asynchronously. S1 has to communicate data and S2 needs to be able to read the last written data in S1. Which type of event fits this integration scenario?    服务S1和S2是异步集成的。S1需要传输数据,而S2需要能够读取S1中最后写入的数据。哪种类型的事件适合这种集成场景?

a. S2 should publish event-carried state transfer events.    S2应该发布事件携带状态传输事件。

b. S2 should publish public event notifications, which will signal S1 to issue a synchronous request to get the most up-to-date information.    S2应该发布公共事件通知,这将向S1发出信号,以发出同步请求以获取最新信息。

c. S2 should publish domain events.    S2应该发布领域事件。

d. A and B.    A和B。

答案:b。


1 Hohpe, G., & Woolf, B. (2003). Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions. Boston: Addison-Wesley.    企业集成模式: 设计、建造和部署消息解决方案。波士顿: Addison-Wesley。

2 Fowler, M. (n.d.). What do you mean by “Event-Driven”? Retrieved August 12, 2021, from Martin Fowler (blog).    你说的“事件驱动”是什么意思? 检索自 Martin Fowler (blog) ,2021年8月12日。

3 Grove, A. S. (1998). Only the Paranoid Survive. London: HarperCollins Business.    《只有偏执狂才能生存》。伦敦: 哈珀柯林斯商业出版社。

posted @ 2023-05-13 09:23  菜鸟吊思  阅读(165)  评论(0)    收藏  举报