Updating multiple aggregates using JOliver EventStore
Asked Answered
M

3

5

I'm having a question regarding updates to multiple aggregates in a single transaction using JOliver's Event Store. As I understand, every aggregate should have its own event stream. Now, while many command handlers will only load a single aggregate and only update that aggregate (i.e. save events for those aggregates), I can imagine that there will be command handlers which need to update multiple aggregates. And of course, I would like to do that in a transactional way.

However, I don't see how I could do that with the Event Store. Storing events is done by calling CommitChanges() on an event stream. If we're having multiple aggregates to update, will have multiple event streams and thus multiple calls to CommitChanges(). The only way to make that transactional is to wrap it in a TransactionScope, but that does not make much sense, since the underlying storage technology might not support transactions. So I end up with this code, which is definitely not what I am looking for:

        Guid aggregateGuid1 = Guid.NewGuid();
        Guid aggregateGuid2 = Guid.NewGuid();
        Guid commitGuid = Guid.NewGuid();

        var stream = store.OpenStream(aggregateGuid1, 0, int.MaxValue);
        stream.Add(new EventMessage() { Body = new MonitorDisabled { MonitorGuid = aggregateGuid1, User = "A" } });
        stream.CommitChanges(commitGuid);

        stream = store.OpenStream(aggregateGuid2, 0, int.MaxValue);
        stream.Add(new EventMessage() { Body = new MonitorEnabled { MonitorGuid = aggregateGuid2, User = "B" } });
        // Can't commit twice with the same commit id, what if fails after first one? No way for the store to know it had to write the second part of the commit.
        stream.CommitChanges(commitGuid);

This makes me feel I'm completely missing something on how the Event Store should be used. Could anybody help me out here? Thanks a lot!

Misanthropy answered 15/11, 2011 at 21:12 Comment(0)
B
8

An Aggregate defines a transaction boundary.

If you need to perform cross-aggregate transactions you should review your aggregates and maybe redesign them.

In cases where an operation ( command ) affects more than one aggregate, and you are sure that your aggregates are well design and map to real consistency boundaries in your domain, eventual consitency might be what you are looking for. Just send a command to each aggregate, and have two transactions , one for each of them. If you don't feel eventual consistency is right for your case than i'm afraid it's back to the drawing board.

Bald answered 16/11, 2011 at 13:21 Comment(4)
Hmm, let's take a classic example: bank transactions. An account is an aggregate and I have to transfer money from one account to the other. This means I have to store an event like MoneySent on one account and an event MoneyReceived on the other account. This seams a legitimate scenario for a transactional update on 2 aggregates to me. What am I missing?Misanthropy
Some more reading tells me this case should be implemented using compensating actions. I'll have to do some more reading on that. Being able to write a single transaction on 2 aggregates seems to be a much cleaner solution (when you control both accounts in your domain).Misanthropy
With DDD and ES/CQRS you will gain something and lose something. It's the CAP Theorem You gain partitioning, scalability, high availability, etc, but you lose consistency. Well you don't actually lose it, you just get it delayed. Assuming the gains overweight the losses you are left with finding solutions for the cases like you describe, and as you already found out there are usually commonly accepted solutions like compensating actions.Bald
One other thing is that you better be sure that the gains overweight the loses: it makes no sens to build a highly scalable system that will be used by less that 100 people.Bald
J
3

I can't speak for John Oliver, but I think the answer is "don't". Aggregates are transaction boundaries. If you need to coordinate the commit of several aggregates, you need an explicit coordination process, like a saga, that will do and if necessary undo the relevant events.

Jesuitism answered 16/11, 2011 at 13:3 Comment(2)
Now, suppose we're in the hosting business and we're managing servers in rooms in racks in data centers. And each server is monitored by a set of monitors, let's call that a monitor configuration. Now, we want to disable monitors during maintenance. And, we can do maintenance on servers, racks, rooms or complete data centers. The monitor configuration is a natural aggregate for monitors for a server, we want to raise MonitorDisabled events on the monitor configuration aggregate when we put a rack in maintenance. Where do we define the TX boundary? The server, the rack, the room, ...?Misanthropy
If you have transcriptional boundaries that cross aggregate boundaries, you're doing it wrong. By definition an aggregate is a transactional boundary and your model should reflect that.Smudge
C
0

Sometimes it can be easier to take advantage of distributed transactions in these scenarios if your infrastructure supports them.

TransactionScope with EventStore:

The EventStore by default suppresses any ambient transaction created by NServiceBus before committing changes to the database. However if you are using a queue and a database that support distributed transactions (MSMQ, SQL Server, Raven etc) then you can change the EventStore's TransactionScopeOption to Required. This will ensure that the EventStore enlists in the ambient transaction, of which will become distributed using the MSDTC, and the message queue and database will be kept in-sync. - NES Documentation

Cross document transactions with RavenDB:

You’ll pry transactions from my dead, cold, broken hands - Ayende

Cathrin answered 29/11, 2011 at 16:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.