Lets assume scenario:
Users of the systemUser have their Clients (Client is always assigned to one and only one User)Users upload different Documents and a Document is always assigned to one and only one ClientOne of the business rules is that User can upload up to X Documents in total, regardless of number of Clients.
By the book, i would make User an aggregate root which would contain collection of Clients. Then each Client would have collection of Documents uploaded for that particular client. When User attempts to upload new Document for given Client, we would load Users aggregate root with all of its Clients and their Documents, and on User class i'd have method like:
boolean CanUploadDocument()
{
int numberOfDocuments = //Iterate Clients and sum up total number of their documents;
//compare to maximum allowed number of docs for User instance
return numberOfDocuments < this.maxAllowedNumberOfDocuments;
}
All well and good, but maxAllowedNumberOfDocuments can be thousands or tens of thousands and it feels like a huge overkill to load them all from db just to count & compare them.
Putting int documentsCount on User seems like breaking the rules and introducing unnecessary redundancy.
Is this the case to introduce separate aggregate root like UserQuota where we would load just count of all Documents and do the check? Or maybe a value object UserDocumentCount which service would get and call method on User object:
boolean CanUploadDocument(UserDocumentCount count)
{
//compare to maximum allowed number of docs for User instance
return count < this.maxAllowedNumberOfDocuments;
}
What is the ddd-proper & optimized way to handle this?
Having a big User aggregate is not a solution but not because of the fact that it is slow and it needs an optimization, it's because of the internal fields cohesion.
In order to protect the quota limit the User aggregate needs only the uploaded documents and nothing more. This is a sign that you have in fact two aggregates, the second being UserDocuments with its method uploadDocument. This method internally checks the quote invariant. As an optimization, you could keep a int countOfDocumentsUploadedSoFar that is used in the uploadDocument method. The two aggregates share only the same identity (the UserId).
Note: no inheritance is needed between the two aggregates.
Introducing something like UserQuota looks like a good solution. This thing is a real domain concept, it has a right to be an entity. Just now it has one propery DocumentsCount, but in time probably you will need LasDocumentUploadedTime... MaxAllowedNumberOfDocuments can be part of the quota too, it will help when this number changed and the change should be applied only for new quotas, or then quotas became more personal.
Your domain operations should touch quotas too. For example, when uploading a document you initially read appropriate quota and check it, store document, then update the quota.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With