Here is the problem: We have a project where users interact with missions. But every mission is part of a campaign and every campaign resides in an account. So to me seems logical to make the account an aggregate root (since a mission cannot exist without a campaign and a campaign cannot exists without an account).
The account, campaign and mission have some sort of budget that must be available for the mission to be executed by users. So i thought something like this:
class Account{
Budget Budget;
List<Campaign> Campaigns;
// Account stuff
}
class Campaign{
Budget Budget;
List<Mission> Missions;
// Campaign stuff
}
class Mission{
Budget Budget;
double Value;
// Mission stuff
}
Which make sense for the domain logic, but i'm having troubles to allow users work directly with missions, since they don't care about the campaign or the account. There are also multiple accounts and i need to provide the user with all the missions they can execute. Loading all these entities gets heavy and complicated so i thought about reverting everything and make the mission like this:
class Mission{
Budget AccountBudget;
Budget CampaignBudget
Budget MissionBudget
Double Value;
// All mixed stuff that i need
}
So i'll just have a mission repository and it gets easier to work with all the missions and users, but when a mission gets executed i need to update the account and campaign budget for all missions. This structure also makes less sense to me regarding the domain logic. So, if i go with the first solution, how i avoid performance troubles and make it work for a user to get all available missions? And if i go with the second solution, does it make sense?
So to me seems logical to make the account an aggregate root (since a mission cannot exist without a campaign and a campaign cannot exists without an account).
That doesn't seem to be a particularly good heuristic, in practice; carving the domain into aggregates is more about behavior than it is about structure. Do you need to know the details of the account to change the mission? Do you need to know the details of the mission(s) to change the account?
i'm having troubles to allow users work directly with missions, since they don't care about the campaign or the account.
That's a big hint that missions are, perhaps, a separate aggregate; this is especially the case if you expect many users to be manipulating their missions at the same time without stepping on each other's toes.
when a mission gets executed i need to update the account and campaign budget for all missions.
Yes, but does that need to happen immediately, or just soon?
You should probably review Greg Young's talk on warehouse systems. The basic idea was that the domain model didn't try to prevent the users from doing things; instead, it was focused on creating "exception reports" if the model suspected there might be a problem.
In your example, that might look like users working directly with the mission budgets, with a separate aggregate that asynchronously observes the changes to the mission budgets and updates the campaign and account budgets as required.
Another view of the same basic idea is Udi Dahan's essay Race Conditions Don't Exist
A microsecond difference in timing shouldn’t make a difference to core business behaviors.
You have many users updating mission budgets; this implies that they are collaboratively interacting with the account budget. So you should be thinking about designs that allow this collaboration to happen without the user contention.
My guess is that you are ultimately going to find yourself with a Mission
structure like
class Mission{
AccountId accountId;
CampaignId campaignId;
MissionId missionId;
Budget Budget;
// Mission stuff
}
Do you have any suggestion about a design to allow users collaboration on a shared resource or some sources to look up?
I don't feel like there's a lot out there. You might be able to get some ideas from Greg Young's talk on occasionally connected systems.
There is an alternative to treating the messages from the outside world as commands; you can consider them to be events: UserDecided
, UserSubmittedForm
.... So the outside world is the book of record, and your aggregate acts like a downstream event processor
Alice said X
Bob said !X (contradicting Alice)
... and therefore (other consequences)
where other consequences might be to escalate to a human being, or that the two decisions cancel each other out, or....
You are still updating your book of record, so it's still a "command" in the CQS sense of the word handle(Event)
, but you can decouple "capturing the event" from "acting on the event"