1. Overview
1.概述
In this article, we’ll be looking into how Axon supports Aggregates with multiple entities.
在本文中,我们将研究Axon如何支持具有多个实体的聚合体。
We consider this article to be an expansion of our main guide on Axon. As such, we’ll utilize both Axon Framework and Axon Server again. We’ll use the former in the code of this article, and the latter is the Event Store and Message Router.
我们认为这篇文章是我们关于Axon的主要指南的扩展。因此,我们将再次利用Axon Framework和Axon Server。我们将在本文的代码中使用前者,而后者是事件存储和消息路由器。
As this is an expansion, let’s elaborate a bit on the Order domain that we presented in the base article.
由于这是一个扩展,让我们对我们在基础文章中介绍的Order域进行一下阐述。
2. Aggregates and Entities
2.集合体和实体
The Aggregates and Entities that Axon supports stem from Domain-Driven Design. Prior to diving into code, let’s first establish what an entity is within this context:
Axon支持的聚合体和实体源自域驱动设计。在深入学习代码之前,我们首先确定在此背景下实体是什么。
- An object that is not fundamentally defined by its attributes, but rather by a thread of continuity and identity
An entity is thus identifiable, but not through the attributes it contains. Furthermore, changes occur on the entity, as it maintains a thread of continuity.
因此,一个实体是可识别的,但不是通过它所包含的属性。此外,变化发生在实体上,因为它保持着一线的连续性。
Knowing this, we can take the following step, by sharing what an Aggregate means in this context (distilled from Domain-Driven Design: Tackling Complexity in the Heart of Software):
了解了这一点,我们就可以采取以下措施,分享在这种情况下聚合的含义(从域驱动设计中提炼出来。应对软件核心的复杂性)。
- An Aggregate is a group of associated objects acting as a single unit to data changes
- References regarding the Aggregate are restricted towards a single member, the Aggregate Root
- A set of consistency rules apply within the Aggregate boundary
As the first point dictates, an Aggregate is not a single thing, but a group of objects. Objects can be value objects but, more importantly, they can also be entities. Axon supports modeling the aggregate as a group of associated objects rather than a single object, as we’ll see later on.
正如第一点所决定的,一个聚合体不是一个单一的东西,而是一组对象。对象可以是价值对象但更重要的是,它们也可以是实体。Axon支持将聚合体建模为一组相关的对象,而不是单一的对象,我们将在后面看到。
3. Order Service API: Commands and Events
3.订单服务API 命令和事件
As we’re dealing with a message-driven application, we start with defining new commands when expanding the Aggregate to contain multiple entities.
由于我们要处理的是一个消息驱动的应用程序,所以在扩展聚合体以包含多个实体时,我们首先要定义新的命令。
Our Order domain currently contains an OrderAggregate. A logical concept to include in this Aggregate is the OrderLine entity. An order line refers to a specific product that is being ordered, including the total number of product entries.
我们的Order域目前包含一个OrderAggregate。包含在这个聚合体中的一个逻辑概念是OrderLine实体。一个订单行指的是正在订购的特定产品,包括产品条目的总数。
Knowing this, we can expand the command API – which consisted of a PlaceOrderCommand, ConfirmOrderCommand, and ShipOrderCommand – with three additional operations:
了解到这一点,我们可以用三个额外的操作来扩展命令API–它包括PlaceOrderCommand、ConfirmOrderCommand,和ShipOrderCommand。
- Adding a product
- Incrementing the number of products for an order line
- Decrementing the number of products for an order line
These operations translate to the classes AddProductCommand, IncrementProductCountCommand, and DecrementProductCountCommand:
这些操作转化为AddProductCommand、IncrementProductCountCommand和DecrementProductCountCommand类。
public class AddProductCommand {
@TargetAggregateIdentifier
private final String orderId;
private final String productId;
// default constructor, getters, equals/hashCode and toString
}
public class IncrementProductCountCommand {
@TargetAggregateIdentifier
private final String orderId;
private final String productId;
// default constructor, getters, equals/hashCode and toString
}
public class DecrementProductCountCommand {
@TargetAggregateIdentifier
private final String orderId;
private final String productId;
// default constructor, getters, equals/hashCode and toString
}
The TargetAggregateIdentifier is still present on the orderId, since the OrderAggregate remains the Aggregate within the system.
TargetAggregateIdentifier仍然存在于orderId上,因为OrderAggregate仍然是系统中的聚合。
Remember from the definition, the entity also has an identity. This is why the productId is part of the command. Later in this article, we’ll show how these fields refer to an exact entity.
记得从定义中,实体也有一个身份。这就是为什么productId是命令的一部分。在本文的后面,我们将展示这些字段是如何指代一个确切的实体的。
Events will be published as a result of command handling, notifying that something relevant has happened. So, the event API should also be expanded as a result of the new command API.
事件将作为命令处理的结果被发布,通知相关的事情已经发生。所以,事件API也应该作为新的命令API的结果而被扩展。
Let’s looks at the POJOs that reflect the enhanced thread of continuity — ProductAddedEvent, ProductCountIncrementedEvent, ProductCountDecrementedEvent, and ProductRemovedEvent:
让我们看看反映增强的连续性线程的POJO – ProductAddedEvent, ProductCountIncrementedEvent, ProductCountDecrementedEvent, 和ProductRemovedEvent。
public class ProductAddedEvent {
private final String orderId;
private final String productId;
// default constructor, getters, equals/hashCode and toString
}
public class ProductCountIncrementedEvent {
private final String orderId;
private final String productId;
// default constructor, getters, equals/hashCode and toString
}
public class ProductCountDecrementedEvent {
private final String orderId;
private final String productId;
// default constructor, getters, equals/hashCode and toString
}
public class ProductRemovedEvent {
private final String orderId;
private final String productId;
// default constructor, getters, equals/hashCode and toString
}
4. Aggregates and Entities: Implementation
4.集合体和实体 实施
The new API dictates that we can add a product and increment or decrement its count. As this occurs per product added to the Order, we need to define distinct order lines allowing these operations. This signals the requirement to add an OrderLine entity that is part of the OrderAggregate.
新的API规定,我们可以添加一个产品并增加或减少其数量。由于这发生在添加到Order的每个产品上,我们需要定义不同的OrderLine,允许这些操作。这表明需要添加一个OrderLine实体,它是OrderAggregate的一部分。
Axon doesn’t know, without guidance, if an object is an entity in an Aggregate. We should place the AggregateMember annotation on a field or method exposing the entity to mark it as such.
在没有指导的情况下,Axon不知道一个对象是否是一个聚合体中的实体。我们应该将AggregateMember注解放在暴露实体的字段或方法上,以将其标记为实体。。
We can use this annotation for single objects, collections of objects, and maps. In the Order domain, we’re better off using a map of the OrderLine entity on the OrderAggregate.
我们可以对单个对象、对象的集合和地图使用这个注解。在Order领域,我们最好在OrderAggregate上使用OrderLine实体的映射。
4.1. Aggregate Adjustments
4.1.总数调整
Knowing this, let’s enhance the OrderAggregate:
了解了这一点,让我们加强OrderAggregate。
@Aggregate
public class OrderAggregate {
@AggregateIdentifier
private String orderId;
private boolean orderConfirmed;
@AggregateMember
private Map<String, OrderLine> orderLines;
@CommandHandler
public void handle(AddProductCommand command) {
if (orderConfirmed) {
throw new OrderAlreadyConfirmedException(orderId);
}
String productId = command.getProductId();
if (orderLines.containsKey(productId)) {
throw new DuplicateOrderLineException(productId);
}
AggregateLifecycle.apply(new ProductAddedEvent(orderId, productId));
}
// previous command- and event sourcing handlers left out for conciseness
@EventSourcingHandler
public void on(OrderPlacedEvent event) {
this.orderId = event.getOrderId();
this.orderConfirmed = false;
this.orderLines = new HashMap<>();
}
@EventSourcingHandler
public void on(ProductAddedEvent event) {
String productId = event.getProductId();
this.orderLines.put(productId, new OrderLine(productId));
}
@EventSourcingHandler
public void on(ProductRemovedEvent event) {
this.orderLines.remove(event.getProductId());
}
}
Marking the orderLines field with the AggregateMember annotation tells Axon it’s part of the domain model. Doing this allows us to add CommandHandler and EventSourcingHandler annotated methods in the OrderLine object, just as in the Aggregate.
用AggregateMember注解来标记orderLines字段,告诉Axon它是领域模型的一部分。这样做允许我们在OrderLine对象中添加CommandHandler和EventSourcingHandler注解的方法,就像在 Aggregate.一样。
As the OrderAggregate holds the OrderLine entities, it’s in charge of adding and removing the products and, thus, the respective OrderLines. The application uses Event Sourcing, so there’s a ProductAddedEvent and ProductRemovedEvent EventSourcingHandler that respectively add and remove an OrderLine.
由于OrderAggregate持有OrderLine实体,它负责添加和删除产品,从而负责各自的OrderLines。该应用程序使用事件源,所以有一个ProductAddedEvent和ProductRemovedEvent EventSourcingHandler,分别添加和删除一个OrderLine。
The OrderAggregate decides when to add a product or decline the addition since it holds the OrderLines. This ownership dictates that the AddProductCommand command handler lies within the OrderAggregate.
OrderAggregate决定何时添加产品或拒绝添加,因为它持有OrderLines。这种所有权决定了AddProductCommand命令处理程序位于OrderAggregate中。
A successful addition is notified through publishing the ProductAddedEvent. Unsuccessful addition follows from throwing the DuplicateOrderLineException, if the product is already present, and an OrderAlreadyConfirmedException if the OrderAggregate has already been confirmed.
成功的添加会通过发布ProductAddedEvent来通知。如果产品已经存在,不成功的添加会抛出DuplicateOrderLineException;如果OrderAggregate已经被确认,则抛出OrderAlreadyConfirmedException。
Lastly, we set the orderLines map in the OrderPlacedEvent handler because it’s the first event in the OrderAggregate‘s event stream. We can set the field globally in the OrderAggregate or in a private constructor, but this would mean state changes are no longer the sole domain of the event sourcing handlers.
最后,我们在OrderPlacedEvent处理程序中设置orderLines映射,因为它是OrderAggregate的事件流中的第一个事件。我们可以在OrderAggregate或私有构造函数中全局设置该字段,但这意味着状态变化不再是事件源处理程序的唯一领域。
4.2. Entity Introduction
4.2.实体介绍
With our updated OrderAggregate, we can start taking a look at the OrderLine:
有了我们更新的OrderAggregate,我们可以开始看一下OrderLine。
public class OrderLine {
@EntityId
private final String productId;
private Integer count;
private boolean orderConfirmed;
public OrderLine(String productId) {
this.productId = productId;
this.count = 1;
}
@CommandHandler
public void handle(IncrementProductCountCommand command) {
if (orderConfirmed) {
throw new OrderAlreadyConfirmedException(orderId);
}
apply(new ProductCountIncrementedEvent(command.getOrderId(), productId));
}
@CommandHandler
public void handle(DecrementProductCountCommand command) {
if (orderConfirmed) {
throw new OrderAlreadyConfirmedException(orderId);
}
if (count <= 1) {
apply(new ProductRemovedEvent(command.getOrderId(), productId));
} else {
apply(new ProductCountDecrementedEvent(command.getOrderId(), productId));
}
}
@EventSourcingHandler
public void on(ProductCountIncrementedEvent event) {
this.count++;
}
@EventSourcingHandler
public void on(ProductCountDecrementedEvent event) {
this.count--;
}
@EventSourcingHandler
public void on(OrderConfirmedEvent event) {
this.orderConfirmed = true;
}
}
The OrderLine should be identifiable, as is defined in section 2. The entity is identifiable through the productId field, which we marked with the EntityId annotation.
OrderLine应该是可识别的,正如第2节所定义的那样。该实体可通过productId字段来识别,我们用EntityId注解来标记。
Marking a field with the EntityId annotation tells Axon which field identifies the entity instance inside an aggregate.
用EntityId注解标记一个字段,可以告诉Axon哪个字段是聚合中实体实例的标识。
Since the OrderLine reflects a product that is being ordered, it’s in charge of handling the IncrementProductCountCommand and DecrementProductCountCommand. We can use the CommandHandler annotation inside an entity to directly route these commands to the appropriate entity.
由于OrderLine反映了正在订购的产品,它负责处理IncrementProductCountCommand和DecrementProductCountCommand。我们可以在实体内使用CommandHandler注解来直接将这些命令路由到适当的实体。
As Event Sourcing is used, the state of the OrderLine needs to be set based on events. The OrderLine can simply include the EventSourcingHandler annotation for the events it requires to set the state, similar to the OrderAggregate.
由于使用了事件源,OrderLine的状态需要根据事件来设置。OrderLine可以简单地包括EventSourcingHandler注解,用于它需要设置状态的事件,与OrderAggregate类似。
Routing a command to the correct OrderLine instance is done by using the EntityId annotated field. To be routed correctly, the name of the annotated field should be identical to one of the fields contained in the command. In this sample, that’s reflected by the productId field present in the commands and in the entity.
将命令路由到正确的OrderLine实例是通过使用EntityId注释字段完成的。为了正确路由,注释字段的名称应与命令中包含的字段之一相同。在这个例子中,这体现在命令和实体中的productId字段上。
Correct command routing makes the EntityId a hard requirement whenever the entity is stored in a collection or map. This requirement is loosened to a recommendation if only a single instance of an aggregate member is defined.
正确的命令路由使EntityId成为硬性要求,只要实体被存储在集合或地图中。如果只定义了一个集合成员的单个实例,那么这个要求就会被放宽为一个建议。
We should adjust the routingKey value of the EntityId annotation whenever the name in the command differs from the annotated field. The routingKey value should reflect an existing field on the command to allow command routing to be successful.
只要命令中的名称与注释的字段不同,我们就应该调整EntityId注释的routingKey值。routingKey值应反映命令中的现有字段,以使命令路由成功。
Let’s explain it through an example:
让我们通过一个例子来解释它。
public class IncrementProductCountCommand { @TargetAggregateIdentifier private final String orderId; private final String productId; // default constructor, getters, equals/hashCode and toString }
... public class OrderLine { @EntityId(routingKey = "productId") private final String orderLineId; private Integer count; private boolean orderConfirmed; // constructor, command and event sourcing handlers }
The IncrementProductCountCommand has stayed the same, containing the orderId aggregate identifier and productId entity identifier. In the OrderLine entity, the identifier is now called orderLineId.
IncrementProductCountCommand保持不变,包含orderId聚合标识符和productId实体标识符。在OrderLine实体中,该标识符现在被称为orderLineId。
Since there’s no field called orderLineId in the IncrementProductCountCommand, this would break the automatic command routing based on the field name.
由于在IncrementProductCountCommand中没有名为orderLineId的字段,这将破坏基于字段名的自动命令路由。
Hence, the routingKey field on the EntityId annotation should reflect the name of a field in the command to maintain this routing ability.
因此,EntityId注解上的routingKey字段应反映命令中的字段名,以保持这种路由能力。
5. Conclusion
5.总结
In this article, we’ve looked at what it means for an aggregate to contain multiple entities and how Axon Framework supports this concept.
在这篇文章中,我们已经看了一个聚合体包含多个实体的含义,以及Axon Framework如何支持这个概念。
We’ve enhanced the Order application to allow Order Lines as separate entities to belong to the OrderAggregate.
我们增强了订单应用程序,允许订单行作为单独的实体,属于OrderAggregate。
Axon’s aggregate modeling support provides the AggregateMember annotation, enabling users to mark objects to be entities of a given aggregate. Doing so allows command routing towards an entity directly, as well as keeping event sourcing support in place.
Axon的聚合建模支持提供了AggregateMember注解,使用户能够将对象标记为特定聚合的实体。这样做允许直接向实体发送命令,并保持对事件源的支持。
The implementation of all these examples and the code snippets can be found over on GitHub.
所有这些例子的实现和代码片段都可以在GitHub上找到over。
For any additional questions on this topic, also check out Discuss AxonIQ.
如有关于此主题的任何其他问题,也请查看讨论AxonIQ。