Aggregate polymorphism and AggregateMembers

Hi, I have a problem with the usage of @Aggregate members in combination with aggregate polymorphism. I think a simple code example illustrates this best :

The ‘parent’ aggregate

@Aggregate
@NoArgsConstructor
public class School {
    @AggregateIdentifier
    private String schoolId;

    @CommandHandler
    public School(CreateSchoolCommand command) {
        apply(new SchoolCreatedEvent(command.schoolId()));
    }

    @EventSourcingHandler
    public void handle(SchoolCreatedEvent event) {
        this.schoolId = event.schoolId();
    }
}

The subclass aggregate with aggregate member:

@Aggregate
@RequiredArgsConstructor
public class PrimarySchool extends School {
    @AggregateMember
    final Map<String, Group> groups = new HashMap<>();

    @CommandHandler
    public void handle(CreatePrimarySchoolGroupCommand command) {
        apply(new PrimarySchoolGroupCreatedEvent(command.schoolId(), command.groupId()));
    }

    @EventSourcingHandler
    public void handle(PrimarySchoolGroupCreatedEvent event) {
        this.groups.putIfAbsent(event.schoolId(), new Group(event.groupId()));
    }
}

And the definition of the Group entity :

@AllArgsConstructor
public class Group {
    @EntityId
    private String schoolId;
}

The issue I have is that this only works if I put the groups aggregate member in the parent (School) aggregate, if it is defined in the PrimarySchool aggregate, then I get the following error :

Exception in thread "main" org.axonframework.commandhandling.CommandExecutionException: Unable to access field for getting.
Caused by: AxonServerRemoteCommandHandlingException{message=An exception was thrown by the remote message handling component: Unable to access field for getting.
Caused by Can not set final java.util.Map field nl.ronob.demo.aggregate.polymorphism.domain.PrimarySchool.groups to nl.ronob.demo.aggregate.polymorphism.domain.School, errorCode='AXONIQ-4002', server='18193@pop-os'}

However, I would like the aggregate member definition in the PrimarySchool aggregate, but I cannot get my head around what I’m doing something wrong ?
(Btw, if needed I can provide a sample spring boot app to replicate this behavior)

Have you tried removing the final keyword on the groups map?

yes I did, I don’t think the actual problem is an accessibility (related to reflection) issue. To me, it looks like somehow the ‘groups’ field is set in the parent class (School) instead of PrimarySchool where it is actually defined as aggregateMember :

Caused by Can not set final java.util.Map field nl.ronob.demo.aggregate.polymorphism.domain.PrimarySchool.groups to nl.ronob.demo.aggregate.polymorphism.domain.School, errorCode='AXONIQ-4002'

Uncertain whether you’ve already figured out the issue, @robnobel, but if not I’d be curious to get such a sample project from you.
This issue is something I would want to debug, mainly due to the implementation of aggregate polymorphism in Axon Framework.
I know quite a bit on top of mind, but not specifically whether aggregate member behavior differs in polymorphic aggregates.

Hi @Steven_van_Beelen , I have not yet resolved this issue, so it would be great if you could have a look at it. You can clone the sample project from GitHub - rnsd/axon-aggregate-polymorphism .
The reproduction steps are described in the readme.

1 Like

I just had some time to investigate your sample project, @robnobel.
To be honest, I think your sample is a little off.

Axon Framework expects you to, upon construction, create the concrete polymorphic type.
So, first creating a School parent instance and later expecting it to become a PrimarySchool child instance doesn’t work.

Instead, you need to have a command handler constructing the PrimarySchool right away.
Doing so, the first event in the stream specifies we’re dealing with a PrimarySchool aggregate type, which is paramount for Axon Framework to deduce what the concrete type of the aggregate is.

With this knowledge, I changed your sample like so:

// Root class of the polymorphic aggregate
@Aggregate
public abstract class School {

    public School() {
    }

    @AggregateIdentifier
    private String schoolId;

    @EventSourcingHandler
    public void handle(SchoolCreatedEvent event) {
        this.schoolId = event.getSchoolId();
    }
}
// ...
// Child implementation of the root
@Aggregate
public class PrimarySchool extends School {

    public PrimarySchool() {
    }

    @AggregateMember
    public Map<String, Group> groups = new HashMap<>();

    @CommandHandler
    public PrimarySchool(CreateSchoolCommand command) {
        apply(new SchoolCreatedEvent(command.getSchoolId()));
    }

    @CommandHandler
    public void handle(CreatePrimarySchoolGroupCommand command) {
        apply(new PrimarySchoolGroupCreatedEvent(command.getSchoolId(), command.getGroupId()));
    }

    @EventSourcingHandler
    public void handle(PrimarySchoolGroupCreatedEvent event) {
        this.groups.putIfAbsent(event.getSchoolId(), new Group(event.getGroupId()));
    }
}

By the way, I’ve deliberately made the School (the root class) abstract.
By doing so no one can construct it, enforcing the construction of a concrete type.

Additionally, if you would add a layering like this:

abstract class BaseSchoolclass Schoolclass PrimarySchool

Wherein the Groups aggregate member resides in the PrimarySchool and the command handling constructor in the School, you would still have an exceptional scenario.
More specifically, Axon Framework throws a NoHandlerForCommandException.
This stems from the fact that the School instance is not of type PrimarySchool.

Henceforth, the command constructing an aggregate should directly build the child type you desire.

I hope all that makes things clearer for you, Rob!
If not, be sure to reach out. :+1:

1 Like

thank you for your time and effort @Steven_van_Beelen ! Very logical indeed, it is clear to me now

1 Like

Sure thing, Rob! Glad to help :slight_smile: