Cross-aggregate transactions in Event Store

Hi there,

I was wondering if it’s possible to create cross-aggregate transactions in Event Store. For example, if a user of a bank system wants to transfer money to someone else’s account. I can imagine that such an operation is implemented in the following way: A MoneySent event with the negative amount of money is added to the sender’s stream, and a MoneyRecieved event is added to the recipient’s stream. Is it possible to create those two events with a single transactional request? I saw that ES supports batch writes but I’m not sure if they will work since those two events won’t be part of the same stream.

Another possible implementation will be to create a single event representing the money transfer and references the sender and recipient. This approach won’t have the transaction issue but seems a little bit wrong to me, mainly because I can’t see which of the streams, the new event should be added to.

What would be the right approach in this situation? And what is the usual approach in event sourcing when it comes to similar issues?

Thanks,

Stan

Hi Stanislav,

Is it possible to create those two events with a single transactional request? I saw that ES supports batch writes but I’m not sure if they will work since those two events won’t be part of the same stream.

Nope and I as you saw, batch writes targets only one single stream at the time.

What would be the right approach in this situation? And what is the usual approach in event sourcing when it comes to similar issues?

I won’t call it THE right approach but rather one I used numerous times and hasn’t failed me yet: Compensating Events.

For every event X that does something, you should have an event X’ doing the opposite. When projecting a state for your stream, you will be able to correct and also track (if you want) the hiccups that occurred.

Stanislav,

We recently had a similar problem, and after some soul searching we came to the conclusion that our design was the problem (the fact we thought we needed to perform two cross aggregate writes).

The short description of how we changed the design is as follows:

  1. Money is ringfenced in Account 1

  2. Command to move money into Account 2 is performed

  3. Account 1 gets informed (eventual consistency) that the ring fenced funds was successfully transferred

  4. If for whatever reason the funds do not get moved, the ring fenced amount simply drops off Account 1 (i.e. becomes available again)

Time to implement a process manager (AKA Saga)

Almost certainly the need to do this indicates the aggregate boundaries are wrong. The consistency boundary in this kind of use case is usually the Transfer itself, not the Accounts between which it applies.

Re sagas, let’s attempt to keep this list using the correct name for that pattern: process managers, unless talking about the actual saga pattern.

Thanks,

James

You could also use a Two-phase commit mechanism. For every MoneySent / MoneyReceived event with a correlation id A, you will have a Commit event mentioning the correlation id A. It allows to carry out validation for example. When projecting a customer statement, you take into account only committed transactions. As a bonus, you can also list incoming expenses/transfers (as not committed) for free.