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
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:
- Is my understanding (summarized above) correct?
- Are there additional issues associated with approach 1/2 that I don't see?
- Are there additional approaches that I don't see?
- Is there a standard approach that DDD community uses?