3

In the context of a web application where we handle only one command per request in a stateless fashion, do we always have to load/save aggregates as one atomic unit? Why is it recommended not to partially load/save aggregates? What are the fundamental reasons behind this rule of thumb?

Some references where I found this recommendation (but no real explanation why):

DDD: do I really need to load all objects in an aggregate?

How to work with large aggregate roots?

I could only think of caching but I think there is more. Maybe inconsistency by concurrent writers?

Notes: I have read Eric's blue book up to the end of Aggregates chapter, iterated over most of the questions in SE and SO about aggregate persistence, and watched a few DDD courses on Pluralsight, but I could not find a direct and detailed answer.

5
  • 1
    I took the freedom to remove the "list of examples" part from your question (which is often a magnet for close votes). I also added some example references, consider to add your own ones.
    – Doc Brown
    Aug 16, 2021 at 12:14
  • @DocBrown Thank you indeed sir. It looks much better now. I will try to add more references.
    – geeko
    Aug 16, 2021 at 12:57
  • How would you ensure all invariants are satisfied if you execute a command on a partially loaded Aggregate? If the command handlers ‘know’ which parts of the Aggregate are used, that’s leaking domain knowledge and error prone (have to change code in 2 places when invariants change).
    – Rik D
    Aug 16, 2021 at 16:05
  • @RikD Hi there. Thx for passing by. What I really mean is that we already know which fields are required to be loaded in order to satisfy the invariants related to the command in hand. AFAIK, commands are defined in the domain so there shouldn't be a domain logic leak. Though, you are right about changing the code in two places when changing invariants.
    – geeko
    Aug 16, 2021 at 16:12
  • How would you do it? Create multiple constructors? Make fields nullable that are actually required in some code paths? One of the core principles in OO design is “high cohesion and low coupling”; an AR that can function partially loaded doesn’t have high cohesion. Perhaps consider redesigning the AR to be more cohesive, so you don’t have a reason for partial loading.
    – Rik D
    Aug 16, 2021 at 17:23

3 Answers 3

2

There may not be a direct and detailed answer, because DDD itself is not a well defined thing. I just wanted to draw your attention to the fact, that not all interpretations of DDD end up loading/storing structures of data without context.

Even Eric Evans seems to shift his views, in essence telling us to concentrate on the "theoretical" parts of the book, like thinking in terms of "domain", language, behavior and expressing those in code.

The whole services-data structures thinking, as far as I can tell, is represented by the Vaughn Vernon wing of DDD. It is not the whole DDD community however.

Anyway, let's think about your problem a second. Why would you load a bunch of data before you even know what to do with them, or even if you know you will not need all of it? It doesn't really seem to make sense.

It gets worse however. Because you have one set of data structures (made up of aggregates, entities, value objects) without any business-context, you'll have to define a "validity" that is somehow common for all use-cases. This basically guarantees that this "model" will not be an optimal fit for any of the use-cases. You usually can't even optimize how it gets persisted, because at that time the data structures are again without any context.

The alternative is to concentrate on behavior. Load and store data from a database as needed while inside the context of a business behavior. Or execute pure SQL statements, third-party calls, etc. Do the optimal thing. This is the only way, you can implement a behavior optimally and don't have to wrestle with a generalized data structure with an all-or-nothing persistence strategy.

Is that DDD? Reading the blue book, I would say yes. Others may disagree. Again, I don't think you'll find an authoritative answer, you may have to think for yourself here and decide based on your requirements.

10
  • I am not sure how to apply this answer to the question: is your recommendation not to build aggregates at all, or to design aggregates for each use case ("behaviour") on their own, even if that means they represent overlapping data in some database?
    – Doc Brown
    Aug 19, 2021 at 8:40
  • 1
    Ok, let me be clearer: No. You don't have to save/load everything at once for each behavior. Indeed, as I argue in my answer, you should instead load/store just the things you absolutely need in your behavior. (Duh) To the question where the authoritative source on the topic is: I think there is none. Even the blue book is up to interpretation, and it seems Eric Evans himself changed his mind on some things already. Again, as I said in my answer, this whole data-centric thing comes from Vaughn Vernon as far as I can tell. Aug 19, 2021 at 8:59
  • 1
    @geeko - regarding different DDD flavors: Evans' book that originally introduced DDD focused more on analysis and modeling, and offered a set of common "tactical" patterns (values, entities, aggregates, ...) to give the reader an idea how modeling decisions can be expressed in code, but didn't require them. E.g., Evans introduces aggregates both as a way to control interdependencies, and also as a way to solve specific domain demands - but it's based on the understanding of the needs of the application, it's not a generic prescription of how to organize your classes. 1/3 Aug 19, 2021 at 22:42
  • 1
    The example he gives is concurrent editing of aspects of the same data, where locking the whole thing is undesired, and a degree of eventual consistency is acceptable. Vaughn Vernon's book is more of a how-to book - and the more prescriptive rules of aggregate design are due to him; the tone is much more focused on the tactical patterns, and is more database-centric. This is what most people's idea of DDD is. It is not wrong, but it is a particular flavor of DDD; it's not the only way to express these concepts, and may not always be a good fit. 2/3 Aug 19, 2021 at 22:42
  • 2
    Evans' conception was that aggregates help enforce invariants at runtime, in a live network of object instances existing in memory, and also provide a way to express (to developers reading the code) a transactional boundary for a small cluster of related data structures; you don't necessarily need, in your update implementation, to update the whole aggregate at once (although that can be simpler), but you need to disallow something else doing a concurrent partial update of the same aggregate as that could break the aggregate invariants (== corrupt the data). 3/3 Aug 19, 2021 at 22:42
1

I am not a DDD zealot, but I have some experience with database systems where data is modeled as aggregates of multiple records, often spanning multiple tables, so I guess the issues with this approach are mostly the same as with DDD. In short:

  • partially loading and saving (or modifying) aggregates is not "forbidden", but it makes things more complicated, and you have to be more careful for doing it correctly.

Let's start with "loading": loading aggregates partially is obviously no problem as long as the partial aggregates are exclusively used within use cases where the missing attributes are not required. For example, lets say you are developing a document management system, and your aggregates are documents with a head line and a body. You don't want to have separate aggregates "head line" and "body", since these document parts belong strongly together. Now you have a use case where you have to display a list of headlines, without the body itself, and want to avoid loading all of the body content in advance for this case. So you allow those documents to be partially loaded, only with their headlines.

Now, let's say the next use case allows a user to pick a headline and display the document's body in full. If you pass the related partial document over to this use case, the complication comes in. Either your "use case" knows the documents are partially loaded and require a "full load". This is simple, but error-prone. Ideally, the document knows it was loaded partially, and which data is missing. Then it can control the lazy-loading of the missing parts by itself. For this, it will need some access to its original data source, which can be solved by an internal reference to a repository object. This lazy-loading now can fail, and produce exceptions at a place in the program where you don't expect it. If the whole document aggregate would have been loaded in advance, this would not be necessary, and the whole program may be kept simpler (for the price of the first use case being a lot slower).

Note this was just the complication for loading. Modifying / saving an aggregate partially introduces some extra quirks:

  • as already mentioned in the comments, keeping all invariants intact may require some extra thought, or some duplicate logic

  • As you already mentioned by yourself, concurrent writers may require extra thought. Let's say there is one interactive use case which loads and saves aggregates as a whole, and uses some kind of locking strategy to avoid collisions. Imaginbe another use case, let's say some batch process, which manipulates only partially loaded aggregates (but lots of them). Then you need to make sure this does not collide with the locking strategy. Of course, relational DB systems can solve this by utilizing transactions, or your batch process cares for the locking done by the interactive process.

  • Caching can indeed become another problem: when aggregates are always loaded and saved in full, this in usually implemented in one place, with a repository which does exactly this. It is then easy to implement a caching functionality as part of the repo. However, if you additionally allow partial modifications, you have to make sure these partial modifications update or invalidate the cache as well.

In the end, this is often a performance consideration: loading and saving aggregates always as a whole makes things a lot easier and less error-prone. But for certain use cases, this may turn out to be too slow in the real-world. And when you observe such a measurable performance degredation, working with partial aggregates may become necessary (for the price of making things more complicated). That's why it is not recommended to use partial aggregates if you can avoid it, which is not always the case.

1
  • When a DDD domain model is combined with CQRS, the aggregates are only loaded for commands, which typically don’t have to be super fast. Domain events can be used to create highly optimized read models for queries. Such an architecture could be perceived as more complex on the tech side, but I would prefer that over partially loading aggregates and risking introducing bugs or more complexity on the business side of the code.
    – Rik D
    Aug 20, 2021 at 5:43
1

I think there is no definitive answer to judge if partially loading aggregates is always wrong or if it is okay in specific cases.

I usually tend to avoid partially loading for writes for the sake of simplicity. When it comes to performance considerations and splitting an aggregate would not be a good option it could be worth having a look at partially loading (or lazy loading) - being aware of the corresponding trade-offs of course.

There are two articles of Vladimir Khorikov where he eloborates on this topic in more detail which I can recommend that might help to come to a decision for your specific problem.

https://enterprisecraftsmanship.com/posts/defense-lazy-loading

https://enterprisecraftsmanship.com/posts/domain-model-purity-lazy-loading/

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.