Aggregate factories in Axon 3

861 views
Skip to first unread message

Steven Grimm

unread,
Feb 1, 2017, 4:54:41 AM2/1/17
to Axon Framework Users
First, the actual question: Is there an example of how to use aggregate
factories with an aggregate class hierarchy in Axon 3?


Now the details. Context is that I have a few related aggregate root
classes with some of the command handlers defined in the superclass
(RequestAggregate) and others in the subclasses
(RegisterRequestAggregate, etc.)

In Axon 2, I registered a single factory, attached it to a repository,
and registered a command handler for each subclass, like so:

<bean id="requestAggregateFactory"
class="com.xyz.RequestAggregateFactory" />

<axon:event-sourcing-repository id="requestAggregateRepository"

aggregate-factory="requestAggregateFactory"
event-bus="eventBus"
event-store="eventStore" />

<axon:aggregate-command-handler
aggregate-type="com.xyz.CancelRequestAggregate"
repository="requestAggregateRepository"
command-bus="commandBus" />
<axon:aggregate-command-handler
aggregate-type="com.xyz.RegisterRequestAggregate"
repository="requestAggregateRepository"
command-bus="commandBus" />
<axon:aggregate-command-handler
aggregate-type="com.xyz.ValidateRequestAggregate"
repository="requestAggregateRepository"
command-bus="commandBus" />

In Axon 3, I try to configure the factory and the aggregates (I'm
letting autoconfiguration build the repository for me):

configurer.configureAggregate(
AggregateConfigurer.defaultConfiguration(RequestAggregate.class)
.configureAggregateFactory(c -> new
RequestAggregateFactory()));
configurer.configureAggregate(CancelRequestAggregate.class);
configurer.configureAggregate(RegisterRequestAggregate.class);
configurer.configureAggregate(ValidateRequestAggregate.class);

And this sort of works in that the factory gets called to instantiate
the aggregate when the initial creation command is dispatched. But none
of the subclass command handler methods are recognized; if I try to send
a subclass-specific command, Axon throws an exception saying it can't
find the command handler. Commands whose handlers are on the superclass
work fine.

The problem seems to be that when the configuration is initialized, the
AbstractRepository constructor calls ModelInspector.inspectAggregate()
and passes it the superclass (as returned by the getType() method on the
factory bean) rather than one of the subclasses. So the aggregate model
only contains the superclass command handler methods.

It doesn't work to modify the factory to return a subclass from
getType() because then the factory will claim it's a producer of, say,
CancelRequestAggregate instances but will return a
RegisterRequestAggregate instead if you pass it a particular kind of
command.

I tried declaring the factory bean with a bunch of aliases so it would
be associated with all the aggregates by autoconfiguration, but that
ended up behaving the same as the explicit call above.

I also tried getting rid of the factory and seeing if autoconfiguration
would figure it out, but that ends up not working either; I get an
IncompatibleAggregateException because commands end up getting routed to
the wrong subclasses.

Hopefully there's an example I can look at to see how this is supposed
to be done; I expect I'm failing to understand something fundamental. I
only found one Axon JUnit test that uses aggregate factories and it
doesn't use a class hierarchy, just a single aggregate class.

-Steve

Allard Buijze

unread,
Feb 6, 2017, 5:21:41 AM2/6/17
to Axon Framework Users
Hi Steven,

at the moment, polymorphism isn't supported to the extent that you can define @CommandHandlers on specific implementations only. It's still on our wishlist and we have a few ideas on how to implement this, but it's not a trivial problem to solve.

For now, the only supported way is to define the commands on the (abstract) super class of the aggregate and put the annotations on those methods. The subclasses can override the methods to provide the implementation-specific options.

Alternatively, use 'external' CommandHandlers for the implementation specific commands: in a "regular" bean, load the aggregate from the repository, do an instanceof check and invoke a method after casting the aggregate. In Axon 3, you don't get the instance itself from the Repository, but a wrapper. Use aggregate.execute(ar -> ...), or aggregate.invoke(ar ->...) to execute logic on the aggregate root. This (also) ensures that apply() applies events to the correct aggregate instance.

The AggregateFactory is responsible for instantiating the correct instance of an aggregate, which it needs to do based on the first event. The ensures that the implementation returned from the repository

Hope this helps.
Cheers,

Allard

--
You received this message because you are subscribed to the Google Groups "Axon Framework Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to axonframewor...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Allard Buijze

unread,
Mar 7, 2018, 11:23:57 AM3/7/18
to Axon Framework Users
This topic has been lingering for a while. It was never forgotten, but just part of a backlog that had some, let's say, higher priority issues on it. But it's time to address it.

The problem I'm primarily facing is the API design for this. Both using Spring Boot autoconfiguration and the AggregateConfigurer, it should be fairly easy (technically) to manage polymorphism. The main thing Axon needs to do, is find all possible implementations for an aggregate class, and register them. Preferably, this would also include the (type of) event that defines an aggregate is of that specific type. In that case, Axon will automatically know which instance to create when sourcing it from events.

Basically, I see globally two options:

1. Define the possible implementations (and their creation event type) in the abstract parent class. On one hand, this creates coupling of a superclass to its implementing classes (which is not 'pure' in OO terms), but on the other hand, it creates a good overview.

Example:

@Aggregate(implementations = {@PolymorphicAggregate(eventType = SomeImplementationCreatedEvent.class, aggregateType = SomeImplementation.class})
public abstract class SomeAggregateSuperClass {
...
}

2. Each subclass declares itself as a specialized implementation. 

@Aggregate
public abstract class SomeAggregateSuperClass {
....
}

@PolymorphicAggregate(initialEvent = SomeImplementationCreatedEvent.class)
public class SomeImplementation extends SomeAggregateSuperclass {
}

The super class doesn't need to be abstract. Basically, that type would be created if the creation event is anything else than the ones specified on the subclasses. Also, the initial event isn't necessary if the Aggregate isn't event sourced. In that case, it should have proper JPA annotations to indicate how implementations are stored and retrieved.

I'm hoping to be able to include this in 3.2, although we're on the brink of releasing that version.
Any feedback is welcome.

Cheers,

Allard

On Monday, 6 February 2017 11:21:41 UTC+1, Allard Buijze wrote:
Hi Steven,

at the moment, polymorphism isn't supported to the extent that you can define @CommandHandlers on specific implementations only. It's still on our wishlist and we have a few ideas on how to implement this, but it's not a trivial problem to solve.

For now, the only supported way is to define the commands on the (abstract) super class of the aggregate and put the annotations on those methods. The subclasses can override the methods to provide the implementation-specific options.

Alternatively, use 'external' CommandHandlers for the implementation specific commands: in a "regular" bean, load the aggregate from the repository, do an instanceof check and invoke a method after casting the aggregate. In Axon 3, you don't get the instance itself from the Repository, but a wrapper. Use aggregate.execute(ar -> ...), or aggregate.invoke(ar ->...) to execute logic on the aggregate root. This (also) ensures that apply() applies events to the correct aggregate instance.

The AggregateFactory is responsible for instantiating the correct instance of an aggregate, which it needs to do based on the first event. The ensures that the implementation returned from the repository

Hope this helps.
Cheers,

Allard

Reply all
Reply to author
Forward
0 new messages