Aggregate inheritance (subtypes)

We have the following problem: Two different types of customer aggregates (subtypes) with common attributes and specific ones, different invariants and common command handlers and specific ones.

  • How could the common commands be routed to the proper aggregate?
  • Does aggregate roots support subtyping mapping?

Hi Victor,

unfortunately, inheritance on the Aggregate Root level is not fully supported yet. We’ve done some prototyping to find how we could implement subtype mapping, but we haven’t been able to find an elegant solution, yet. It is possible to use polymorphism, but the command handlers need to be declared on the abstract supertype, for now.

Polymorphism is possible to some extent, but requires you to implement a command handler on a static method that returns a specific instance based on the command. You cannot do this in constructors. Additionally, you’ll need to configure a AggregateFactory that builds a specific instance of the aggregate based on the event that was published.

There is an issue to track progress. It’s been open for a while, waiting for an elegant way to implement this.
https://github.com/AxonFramework/AxonFramework/issues/314

Cheers,

For commands and events involved in the initialisation of the aggregate it is not a problem. For example, CreatePersonalCurrentAccount and PersonalCurrentAccountCreated are quite different than CreateBusinessAccount and BusinessAccountCreated, those command and events require different attributes and are forwarded to different aggregates. I would prefer to handle those commands on static methods on the aggregates, process those events in the constructors (allowing me to set some final values in the aggregate) and return a list of events from command handlers on the aggregates (Removing the dependency on AggregateLifecycle.), but this is another discussion

The problem arrives when, for example, one wants to manage the balance in both accounts the same way (same interface) with different invariants. At the moment the routing relationship is one command to one aggregate type. Imagine the transfer saga that has to identify the actual target aggregate before emitting the commands and every aggregate must has specific commands. We solve that in a projection base on the creation events, but the code is becoming a mess every time we add a new command or a new type (sub-type) of account with different invariants.

Any ideas to improved our solution? Any workaround?

Hi Viktor,

while polymorphism on the Aggregate Root level isn’t currently fully supported in Axon, that doesn’t mean you can’t use polymorphism at all. The only limitation Axon currently has, is that Command handlers need to be defined on the “declared types” within your aggregate.

For command handlers updating an existing instance, you’d need to have (abstract) methods on the parent class for each command handler. Different implementations can implement different behavior. You cannot have a constructor on a parent abstract class. However, you can have a static method on your parent class that makes the decision which implementation to build.

Lastly, you’ll also need to define an AggregateFactory that decides which implementation should be constructed when sourcing an instance from events. This decision must be made based on the first event. If you’re using Spring Autoconfiguration, the easiest way is to provide an AggregateConfiguration bean, instead of relying on @Aggregate on the class to autowire everything.

For example:

@Bean
public AggregateConfiguration<ParentAggregate> parentAggregateConfiguration() {
    return AggregateConfigurer.defaultConfiguration(ParentAggregate.class)
                              .configureAggregateFactory(c -> new AbstractAggregateFactory<ParentAggregate>(ParentAggregate.class) {
                                  @Override
                                  protected ParentAggregate doCreateAggregate(String s, DomainEventMessage domainEventMessage) {
                                      if (SomeFirstType.class.isAssignableFrom(domainEventMessage.getPayloadType())) {
                                          return new SomeFirstTypeOfAggregate();
                                      } else if (SomeOtherType.class.isAssignableFrom(domainEventMessage.getPayloadType())) {
                                          return new SomeOtherTypeOfAggregate();
                                      }
                                      throw new IllegalArgumentException("Don't know what card that is");
                                  }
                              });
}

Hope this helps.

Cheers,