I'm in the process of writing my first CQRS application, let's say my system dispatches the following commands:
- CreateContingent (Id, Name)
- CreateTeam (Id, Name)
- AssignTeamToContingent (TeamId, ContingentId)
- CreateParticipant (Id, Name)
- AssignParticipantToTeam (ParticipantId, TeamId)
Currently, these result in identical events, just worded in the past tense (ContingentCreated, TeamCreated, etc) but they contain the same properties. (I'm not so sure that is correct and is one of my questions)
My issue lies with the read models.
I have a Contingents read model, that subscribes to ContingentCreated, and has the following methods:
List<ContingentQueries.Contingent> GetContingents();
ContingentQueries.Contingent GetContingent(System.Guid id);
ContingentQueries.Contingent GetContingent(string province);
The Contingent class returned by these looks like this:
public class Contingent
{
public Guid Id { get; internal set; }
public string Province { get; internal set; }
}
This works fine for a listing, but when I start thinking about the read model for a view of a single contingent, and all it's teams, things start to get messy. Given the methods it seems trivial:
ContingentQueries.Contingent GetContingent(System.Guid id);
ContingentQueries.Contingent GetContingent(string province);
And I create the Contingent class for these queries with a collection of teams:
public class Contingent
{
public Guid Id { get; internal set; }
public string Province { get; internal set; }
public IList<Team> Teams { get; internal set; }
}
public class Team
{
public Guid Id { get; internal set; }
public string Name { get; internal set; }
}
In theory, I should only need to subscribe to ContingentCreated, and TeamAssignedToContingent, but the TeamAssignedToContingent event only has the team and contingent ids... so I can't set the Team.Name property of this read model.
Do I also subscribe to TeamCreated? Add keep another copy of teams for use here?
Or when events are raised, should they have more information in them? Should the AddTeamToContingent command handler be querying for team information to add to the event? Which may not yet exist, because the read model might not have been updated with the team yet.
What if I wanted to show the team name and the participant that has been assigned to the team, and named the captain in this Contingent view? Do I also store the participants in the read model? This seems like a ton of duplication.
For some extra context. Participants are not necessaily part of any contingent, or a team, they could just be guests; or delegates and/or spares of a contingent. However, roles can shift. A delegate who is not a team member, could also be a spare, and due to injury be assigned to a team, however they are still a delegate. Which is why the generic "Participant" is used.
I understand I want read models to not be heavier than necessary, but I'm having difficulty with the fact that I may need data from events that I'm not subscribed to (TeamCreated), and in theory shouldn't be, but because of a future event (TeamAssignedToContingent) I do need that info, what do I do?
UPDATE: Thought about it overnight, and it seems like all the options I have thought of are bad. There must be another.
Option 1: Add more data to raised events
- Do Command Handlers start using read models that may not yet be written so they can get that data?
- What if a future subscriber to the event needs even more info? I can't add it!
Option 2: Have Event Handlers subscribe to more events?
- This results in the read model storing data it might not care about?
- Example: Storing participants in the ContingentView read model so when a person is assigned to a team, and marked as captain, we have know their name.
Option 3: Have Event Handlers query other Read Models?
- This feels like the best approach, but feels wrong. Should the Contingent view query the Participant view to get the name if and when it needs it? It does eliminate the drabacks in 1 and 2.
Options 4: ...?