4

I'm developing my first application using DDD and event sourcing.

From what I understand, aggregate encapsulates specific domain functionality with one or more associated invariants. The invariants are translated into consistency constraints - at any point in time the state of the aggregate must be consistent (invariants preserved).

So far so good.

Most tutorials that I saw suggest that aggregate roots should be obtained in this manner:

UserAggregate user = mUsersRepository.getUser(userId);

At this point I become confused.

Let's assume that each call to UsersRepository returns a new instance of UserAggregate ("approach 1"). This can be beneficial because it eliminates a need to ensure thread safety (in code). However, if multiple UserAggregate objects will be used simultaneously, these objects might get out of sync with each other (while staying consistent individually). I get a feeling that having several "non-synchronized" instances of the same aggregate can lead to all kinds of nasty bugs, but I can't really be sure about it.

Now let's assume that each call to UsersRepository returns the same UserAggregate object ("approach 2"). Then the instantiation and caching of UserAggregate inside UserRepository need to be made thread safe. In addition, the caching must rely on weak references; otherwise, the entire users database will eventually be hold in memory. My primary concern, however, is that in this scenario UserAggregate itself must become thread safe. It looks like it will be a major PITA to ensure thread safety of all aggregate roots. It can also become a performance issue. Not mentioning the risk associated with multi-threading bugs...

If that would be all, I would probably go with "approach 2" because, at least, I know how to make it safe. However, there is this notion of optimistic locking based on aggregate version that (in my understanding) applies to "approach 1".

If I understand it correctly, each aggregate gets a version associated with it. On each update of aggregate's state the version is checked, and, if the version in DB is not the same as the version of the cached aggregate, the update fails. Then the aggregate state can be synced from database and the update retried. This seems to address my fears related to inconsistency between different instances of the aggregate. However, it feels like a lot of work - each method that updates the aggregate can fail, therefore the code that uses it must be capable of retrying the operation.

My questions are:

  1. Is my understanding (summarized above) correct?
  2. Are there additional issues associated with approach 1/2 that I don't see?
  3. Are there additional approaches that I don't see?
  4. Is there a standard approach that DDD community uses?
4
UserAggregate user = mUsersRepository.getUser(userId);

At this point I become confused.

Yeah, that's a bit of a tangle.

However, if multiple UserAggregate objects will be used simultaneously, these objects might get out of sync with each other (while staying consistent individually). I get a feeling that having several "non-synchronized" instances of the same aggregate can lead to all kinds of nasty bugs, but I can't really be sure about it.

There are two concerns.

The first is that operations that read the state of the aggregate may be reading an old copy. For the most part, the literature recognizes that this simply means that reads are "eventually consistent". It's analogous to working with mutable resources on the web -- Alice POST's a change to the resource, but Bob doesn't see that change immediately because he's got a cached copy.

The CQRS pattern goes so far as to take support for queries (reads) out of the model entirely -- the role of your aggregate roots is to ensure that consistency is maintained during writes.

The second is that if we have multiple writers (two threads, possibly in different processes, possibly on different hosts) performing a write concurrently, we can end up with a split brain; "the" aggregate in two different states depending on where you look, and we may lose edits when one gets saved on top of the other.

That's the problem that is "real" and needs to be addressed.

One answer is to restrict yourself to a single writer. The LMAX architecture is one example of this - all commands are linearized by publishing them into a queue, and "the" writer then consumes the queue and manages all of the writes. So you solve the problem of conflicts by not having any -- or more correctly you accept a leader election problem in exchange for the conflict problem.

With multiple writers; the usual answer is to recognize that the database (be it an RDBMS, or a No-SQL thing, or a file system) is the book of record -- that's what you are going to use as "truth" if the power shuts down. Therefore, your writers are sharing the database, and you need to enforce write integrity there.

I only know of two answers - your writers race to grab a lock, and the winner gets write authority until the lock is released, or your writers race to compare and swap.

If I understand it correctly, each aggregate gets a version associated with it. On each update of aggregate's state the version is checked, and, if the version in DB is not the same as the version of the cached aggregate, the update fails.

This is "compare and swap", at least logically. You may need to play games with version numbers, or stream positions, or the like, to actually get a working implementation, but logically they are all variations of "change the current state from the one located there to the one located here". Conditional PUT, as it were.

However, it feels like a lot of work - each method that updates the aggregate can fail, therefore the code that uses it must be capable of retrying the operation.

Here's the "good" news -- in a distributed system, you needed to worry about that problem anyway. The database might not be available, it might be overloaded, the filesystem might be full, etc. It's work, but it's not extra work.

Is there a standard approach that DDD community uses?

Most folks who are doing event sourcing tend toward using compare and swap, especially when using NoSql event stores that support idempotent writes.

But my feeling is that ES is still a relative minority in the space; it's less clear to me which approach the folks saving aggregate state are using.

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.