Vincent
Vincent

Reputation: 65

Maintain reference between aggregates

I'm trying to wrap my head around how to maintain id references between two aggregates, eg. when an event happens on either side that affects the relationship, the other side is updated as well in an eventual consistent manner.

I have two aggregates, one for "Team" and one for "Event", in the context of a festival with the following code:

@Aggregate
public class Event {
    @AggregateIdentifier
    private EventId eventId;
    private Set<TeamId> teams; // List of associated teams

... protected constructor, getters/setters and command handlers ...
}
@Aggregate
public class Team {
    @AggregateIdentifier
    private TeamId teamId;
    private EventId eventId; // Owning event

... protected constructor, getters/setters and command handlers ...
}

A Team must always be associated to an event (through the eventId). An event contains a list of associated teams (through the team id set). When a team is created (CreateTeamCommand) on the Team aggregate, I would like the TeamId set on the Event aggregate to be updated with the team id of the newly created team. If the command "DeleteEventCommand" on the Event aggregate is executed, all teams associated to the event should also be deleted. If a team is moved from one event to another event (MoveTeamToEventCommand) on the Team aggregate, the eventId on the Team aggregate should be updated but the TeamId should be removed from the old Event aggregate and be added to the new Event aggregate.

My current idea was to create a saga where I would run SagaLifecycle.associateWith for both the eventId on the Event aggregate and the teamId on the Team aggregate with a @StartSaga on the "CreateTeamCommand" (essentially the first time the relationship starts) and then have an event handler for every event that affects the relationship. My main issue with this solution is:

1: It would mean I would have a unique saga for each possible combination of team and event. Could this cause trouble performance wise if it was scaled to eg. 1mil events with each event having 50 teams? (This is unrealistic for this scenario but relevant for a general solution to maintain relationships between aggregates).

2: It would require I had custom commands and event handlers dedicated to handle the update of teams in team list of the Event aggregate as the resulting events should not be processed in the saga to avoid an infinite loop of updating references.

Thank you for reading this small story and I hope someone can either confirm that I'm on the right track or point me in the direction of a proper solution.

Upvotes: 3

Views: 220

Answers (1)

tugberk
tugberk

Reputation: 58444

An event contains a list of associated teams (through the team id set).

If you mean "An event aggregate" here by "An event", I don't believe your event aggregate needs team ids. If you think it does, it would be great to understand your reasoning on this.

What I think you need is though your read side to know about this. Your read model for a single "Event" can listen on CreateTeamCommand and MoveTeamToEventCommand as well as all other "Event" related events, and build up the projection accordingly. Remember, don't design your aggregates with querying concerns in mind.

If the command "DeleteEventCommand" on the Event aggregate is executed, all teams associated to the event should also be deleted.

A few things here:

  • Again, your read side can listen on this event, and update the projections accordingly.
  • You can also start performing validation on relevant command handlers for the Team aggregate to check whether the Event exists or not before performing the operations. This won't have exact sync, but will cover for most cases (see "How can I verify that a customer ID really exists when I place an order?" section here).
  • If you really want to delete the associated Team aggregates off the back of a DeleteEventCommand event, you need to handle this inside a Saga as there is no way for you to be able to perform this in an atomic way w/o leaking the data storage system specifics into your domain model. So, you need certain retry and idempotency needs here, where a saga can give you. It's not exactly what you are suggesting here but related fact is that a single command can't act on a set of aggregates, see "How can I update a set of aggregates with a single command?" section here.

Upvotes: 2

Related Questions