1
votes

I was wondering whether it would be considered bad practice to use an aggregate identifier across a service in another (extensipn) aggregate which shares that they are both revolving about the same identifiable entity.

The problem I am currently having is that we want to split some logic (bounded context if you so will) into a different service as the one originally creating the aggregate.

In general, this seems to work, as when I send a Command within the second service, it is picked up and updates its state. As I can use EventSourcingHandler to also use Events created in the other service to manipulate its state, I get state information from a source applied by the first services aggregate.

I was worried that the snapshot mechanism would work against me, but apparently it is smart enough to store snapshots separately as long as I make sure the aggregate "type" name is not the same.

So far, so good, the only thing that's a smell for me is that the second aggregate does not have (needs) an initial constructor CommandHandler, as the creation is done in the first aggregate.

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

@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();
  }

}

The case for why I tried to split is that we wanted to separate the base logic (creation of the aggregate, in this case a vehicle) from the logic that happens and is handled in a different bounded context and separate microservice (transfers from and to a construction site). 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.

So my only options right now would be what I presented above, or use the creation of the second aggregate to set a different aggregateId, but also add internally the aggregateId of the first aggregate to allow for events to be published with the aggregateId information of the first as a reference Id. To make this work I would have to keep a projection to map back and forth between the two identifiers, which also does not look too good.

Thanks in advance, Lars Karschen

1

1 Answers

0
votes

Very interesting solution you've come up with Lars. Cannot say I have ever split the Aggregate logic in such a manor that one service creates it and another loads the same events to recreate that state in it's own form.

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

To be honest, I don't think this would be the intended usage. Not so much because of Axon, but more because of the term Bounded Context you are using. Between contexts, you should share very consciously, as terms (the ubiquitous language) differs per context. Your events are essentially part of that languages, so sharing the entirety of an aggregate's stream with another service would not be something I'd suggest normally.

Whether these services you are talking about truly belong to distinct Bounded Contexts is not something I can deduce right now, as I am not your domain expert. If they do belong to the same context, sharing the events is perfectly fine. Then still I wouldn't recreate a different aggregate based on the same events. So, let me add another concept which might help.

What I take from your description, is that you have something called a Vehicle aggregate which transitions different states. Wouldn't a Polymorphic Aggregate be the solution you are looking for? That way you can have a parent Vehicle aggregate with all the basics, and more specific implementations when necessary? Still, this might not fit your solution completely, something I am uncertain about given your description.

So, I am going to add a third pointer which I think is valuable to highlight:

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.

This line suggests you want to reuse the Aggregate Identifier between different Aggregates, something which comes back in the question's title too. As you've noted, [aggregate identifier , sequence number] pairs need to be unique. Hence, reusing an aggregate identifier for a different type of aggregate is not an option. Know however that Axon will use the toString method of your aggregate identifier class to fill in the aggregate identifier field. If you would thus adjust the toString() method to include the aggregate type, you'd be able to keep the uniqueness requirement and still reuse your aggregate identifier.

For example, the toString method of a VehicleId class containing a UUID would normally output this:

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

But if you change the toString to include the aggregate type, you would get this:

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

Concluding, I think there are three main points I'd like to share:

  1. Axon Framework did not intent to reuse Aggregate Streams to recreate distinct Aggregate types.
  2. Polymoprhic Aggregates might be a means to resolve the scenario you have.
  3. The [aggregateId, seqNo] uniqueness requirement can reuse an aggregateId as long is the toString method would append/prepend the aggregate type to the result.

I hope this helps you on your journey Lars. Please let me know of you feel something is missing or if I didn't grasp your question correctly.