I’ll preface this question by saying I am relatively new to DDD so I may be making some fundamental mistakes here!
I working on a project which involves the concepts of Accounts and Transactions (in the financial sense). An Account can have many Transactions entered against it.
It seems to me that Account and Transaction are both Entities, and that Account is an Aggregate root containing Transactions since a Transaction cannot exist without the Account.
However when I come to apply this in code I immediately hit a problem. In many situations it’s not especially useful for me to have a list of every Transaction in an Account at all times. I am interested in being able to do things like calculating Account balance and enforcing invariants such as a credit limit, but I also want to be able to easily work with a subset of Transactions (eg displaying those which fall within a date range).
In the latter case, if I was using a TransactionRepository
I could efficiently access just those objects I need without loading the whole (potentially very large) list. However this would allow things other than the Account to work with Transactions which means I have broken the concept of Account as aggregate root.
How do people handle this kind of situation? Do you just accept the memory and performance implications of loading a potentially huge number of children for an aggregate root?
I’d recommend being careful with the “can’t exist without” rule. This talks to the concept of composition in UML/OO design and may have been one of the prescribed approaches to designing Aggregates in the original DDD blue book (not sure about that) but has largely been revised since. It might be a better idea to see your aggregates from a transactional consistency boundary perspective.
The idea is to neither make your aggregates too large, where you would have performance problems such as the one you point out, nor too small, because some invariants would inevitably span multiple aggregates – causing aggregate locking and concurrency problems.
The right aggregate size would ideally match the contours of what you modify in a given business transaction, no more and no less. In your example, if there are not many domain invariants that span over multiple financial transactions, making Transaction
an Aggregate Root in its own right might well be the best solution.
2
tl;dr – break the rules if you need to. DDD can’t solve all problems; in fact the object ideas it gives are good advice and a good start, but really bad choices for some business problems. Consider it a hint for how to do things.
For the issue of loading all children (transaction) with the parent (account) – Looks like you’ve run into the n+1 problem (something to google) which many ORMs have solved.
You can solve it by lazy loading the children (transaction) – only when needed.
But is sounds like you already know that by mentioning that you can use a TransactionRepository to solve the problem.
To ‘conceal’ that data so that only Account can use it, you’d need to not even store it where anybody else would no how to deserialize it, like a public relational table. You could have it stored with the ‘document’ of account in a document DB. Either way if somebody tried hard enough they could still see the data. And ‘work’ with it. And when you’re not looking, they will!
So you could set up permissions, but then, you have to run ‘account’ as a separate process.
What you really realize here is that DDD and pure use of the object model will sometimes back you into a corner. Truthfully, of course, you don’t have to use the ‘composition’/aggregate root to benefit from the design principles of DDD. It’s just one thing you can use when you have a situation that fits in its constraints.
Somebody may say ‘don’t early optimize’. Here in this case, though, you know the answer – there will be enough transactions to bog down a method that will keep them all forever with the account.
The real answer is to begin standing up SOA. At my workplace we watched the Udi Dahan ‘Distributed computing’ videos and bought nServiceBus (just our choice). Make a service for accounts – with its own process, message queues, access to a relation database only it can see, and … viola, you could hardcode SQL statements in the program and even throw in a couple of Cobol transaction scripts (kidding of course) but seriously have more separation of concerns than the smartest OO/Java snob could ever dream of.
I’d recommend modeling it well anyway; you can just have the benefits of aggregate root here without the problems by treating the service as a mini-bounded countext.
This has a drawback, of course. You can not just RPC (webservice, SOAP, or REST) in and out of services and between them or you get an SOA antipattern called ‘the knot’ due to temporal coupling. You have to use the inversion of communication pattern, a.k.a. ‘Pub-Sub’ which it just like event handlers and event raisers, but (1) between processes (which you can put on separate machines if they are getting overloaded on one).
the real issue is you don’t want a service that needs to get data from another service to ‘block’ or wait – you have to fire and forget the message and let a handler elsewhere in your program pick it up to complete processing. This means you have to do your logic differently. nServicebus automates the ‘saga’ pattern to help with some of this, but in the end, you have to develop a different coding style. You can still do it all, just have to do it differently!
The book “SOA Patterns” by Arnon Rotem-Gal-Oz answers many questions about this. Including the usage of ‘active service pattern’ to periodically replicate data from outside services to your own when the need arises (many RPC would have been needed, or link is unreliable/not in the publish/subscribe ecosystem).
Just to preview, UIs do have to RPC into services. Reports are generated from a reporting database that’s fed by the services’ databases. Some people say reports are not needed and that the problem should be solved another way. Be skeptical of that talk.
In the end though, not all things can properly be classified into a single service. The world does not run on ravioli code! So you will have to break rules. Even if you would never have to, new devs to the project will do it when you leave it. But don’t worry, if you do what you can, the 85% that follows the rules will make a program that’s far more maintainable.
Wow, that was long.
1