聚合标识符的分布式使用

distributed usage of aggregateidentifier

我想知道在另一个(extensipn)聚合中跨服务使用聚合标识符是否会被认为是不好的做法,它们都围绕同一个可识别实体旋转。

我目前遇到的问题是我们想将一些逻辑(如果您愿意的话,有界上下文)拆分为与最初创建聚合的服务不同的服务。

总的来说,这似乎可行,因为当我在第二个服务中发送命令时,它会被拾取并更新其状态。由于我可以使用 EventSourcingHandler 也可以使用在其他服务中创建的事件来操纵其状态,因此我从第一个服务聚合应用的源获取状态信息。

我担心快照机制会对我不利,但显然只要我确保聚合 "type" 名称不相同,单独存储快照就足够聪明了。

到目前为止,一切顺利,唯一让我感到奇怪的是第二个聚合没有(需要)初始构造函数 CommandHandler,因为 creation 已完成在第一个集合中。

那么,我是否反对 axon 框架打算使用聚合的方式,或者这是一个可行的用例?

@Aggregate
@Getter
@NoArgsConstructor
public class Foo {

  @AggregateIdentifier
  private String fooIdentifier;

  @CommandHandler
  public Foo(CreateFooCommand command) {
    apply(FooCreatedEvent.builder()
      .fooIdentifier(command.getFooIdentifier())
      .build());
  }

  @EventSourcingHandler
  public void on(FooCreatedEvent event) {
    this.fooIdentifier = event.getFooIdentifier();
  }

}


@Aggregate
@Getter
@NoArgsConstructor
public class Bar {

  @AggregateIdentifier
  private String fooIdentifier;

  private String barProperty;

  @CommandHandler
  public void on(UpdateBarCommand command) {

    apply(BarUpdatedEvent.builder()
      .fooIdentifier(this.fooIdentifier)
      .barProperty(command.getBarProperty())
      .build());
  }

  @EventSourcingHandler
  public void on(FooCreatedEvent event) {
    this.fooIdentifier = event.getFooIdentifier();
  }

  @EventSourcingHandler
  public void on(BarUpdatedEvent event) {
    this.barProperty = event.getBarProperty();
  }

}

我尝试拆分的原因是我们希望将基本逻辑(聚合的创建,在本例中为车辆)与发生的逻辑分开,并在不同的有界上下文和单独的微服务中处理(从建筑工地转移到建筑工地)。由于我无法为同一聚合标识符但不同聚合类型两次发布创建事件(构造函数中的 CommandHandler,序列 0),因此我无法完全分离这两种状态。

所以我现在唯一的选择就是我上面介绍的,或者使用第二个聚合的创建来设置不同的 aggregateId,但也在内部添加第一个聚合的 aggregateId 以允许事件发布第一个作为参考Id的aggregateId信息。为了完成这项工作,我必须保留一个投影来在两个标识符之间来回映射,这看起来也不太好。

提前致谢, 拉尔斯·卡申

Lars 提出的解决方案非常有趣。不能说我曾经将聚合逻辑拆分成这样一个庄园,一个服务创建它,另一个服务加载相同的事件,以它自己的形式重新创建该状态。

So, am I going against the way axon framework intends aggregates to be used, or is this a viable use case?

老实说,我认为这不是预期用途。与其说是因为 Axon,不如说是因为您使用的术语“限界上下文”。在上下文之间,您应该非常有意识地分享,因为术语(无处不在的语言)因上下文而异。您的活动本质上是该语言的一部分,因此我通常不建议与其他服务共享整个聚合流。

您所说的这些服务是否真正属于不同的限界上下文,我现在无法推断,因为我不是您的领域专家。如果它们确实属于同一个上下文,那么共享事件就完全没问题了。那么我仍然不会根据相同的事件重新创建不同的聚合。那么,让我添加另一个可能有帮助的概念。

我从你的描述中得到的是,你有一个叫做 Vehicle 聚合的东西,它可以转换不同的状态。 Polymorphic Aggregate 难道不是您正在寻找的解决方案吗?这样你就可以拥有一个包含所有基础知识的 parent Vehicle 集合,以及必要时更具体的实现?尽管如此,这可能并不完全适合您的解决方案,根据您的描述我不确定这一点。

所以,我要添加第三个我认为值得强调的指针:

Since I cannot publish a creation event (CommandHandler in the constructor, sequence 0) for the same aggregate identifier but different aggregate type twice, I could not separate the two states completely.

这一行表明你想在不同的聚合之间重用聚合标识符,这也出现在问题的标题中。正如您所指出的,[聚合标识符,序列号] 对需要是唯一的。因此,为不同类型的聚合重用聚合标识符不是一种选择。但是请注意,Axon 将使用聚合标识符 class 的 toString 方法来填写聚合标识符字段。如果您因此调整 toString() 方法以包含聚合类型,您将能够保持唯一性要求并仍然重用您的聚合标识符。

例如,包含 UUIDVehicleId class 的 toString 方法通常会输出:

  • 684ec9f4-b9f8-11ea-b3de-0242ac130004

但是如果您更改 toString 以包含聚合类型,您将得到:

  • VehichleId[684ec9f4-b9f8-11ea-b3de-0242ac130004]

最后,我想主要分享三点:

  1. Axon Framework 无意重用聚合流来重新创建不同的聚合类型。
  2. 多态聚合可能是解决您遇到的情况的一种方法。
  3. [aggregateId, seqNo] 唯一性要求可以重复使用 aggregateId,只要 toString 方法将 append/prepend 聚合类型作为结果。

我希望这对您的旅程有所帮助拉斯。如果您觉得遗漏了什么或者我没有正确理解您的问题,请告诉我。