Transactions spanning more than one aggregate root

2,162 views
Skip to first unread message

@nbarnwell

unread,
Jul 5, 2012, 5:33:33 PM7/5/12
to ddd...@googlegroups.com
Hey all,

I understand the idea that aggregate roots represent a consistency boundary, and that as such transactions should not span multiple aggregates.

However, it strikes me that there is at least one scenario where this would make sense - transfers of something between two or more aggregates.

For example, transfers between bank accounts.

I could have a Account.Transfer(Account destinationAccount) (or perhaps let the Application Service coordinate calls to sourceAccount.Withdraw() and destinationAccount.Deposit()), but this still means that two ARs have outstanding changes that in theory really should be saved in a transaction. My architecture does allow for this (updates of the read store would still happen outside this transaction) but I'm aware that I'm not *supposed* to.

Am I worrying over nothing?

Many thanks in advance.

Neil.

Gabriel Schenker

unread,
Jul 5, 2012, 6:49:45 PM7/5/12
to ddd...@googlegroups.com
In reality a transfer between bank accounts is not transactional! Of course there have to be compensating actions in case the transfer fails. Did you never see in your account log (sent to you by your bank) that your account has been debited and shortly after credited (by the same amount)?

Philip Jander

unread,
Jul 5, 2012, 6:57:55 PM7/5/12
to ddd...@googlegroups.com
Hey Neil,

> I understand the idea that aggregate roots represent a consistency
> boundary, and that as such transactions should not span multiple
> aggregates.
I gather opinion on this varies somewhat.
The definition of aggregate as a consistency boundary requires that
changes to one aggregate / events stemming from one aggregate must be
persisted in one transaction.
This does not forbid storing multiple aggregates' events in one
transaction.

The fundamental question is whether a single command may *or* must not
affect multiple aggregates.
Very often it is convenient to allow one command (your transfer example)
to affect multiple aggregates. It saves you from implementing messaging,
(sagas) to mimin transactional behavior.

>
> Am I worrying over nothing?

No, there is a downside (convenience always comes with a price tag ;)).
If you take this approach, you lose the ability to easily shard across
multiple servers.
If you take the one-command-one-aggregate approach, sharding is trivial
to implement since you already have messaging in place.
Also, you move the routing/collaboration logic which would be contained
in the saga either into the command handler or in one of the
collaborating aggregates. This need not be a bad thing, but should be
considered as you take additional dependencies.
Finally, you might actually not want to execute a "business" transaction
in a technical transaction. This hides the message flow which might
contain information valuable to the business.


Cheers
Phil

@nbarnwell

unread,
Jul 6, 2012, 4:21:56 AM7/6/12
to ddd...@googlegroups.com
If I were to take the approach of using a saga, how might that work? Something like this?:
  1. Application Service does not load/invoke behaviour/save ARs, instead choosing to send a message to a SagaService to request the transfer to happen between accounts A and B.
  2. SagaService sends message to TransactionService to withdraw from account A.
  3. TransactionService responds to SagaService success/fail.
  4. If success, SagaService sends another message to TransactionService to Deposit into account B.
  5. TransactionService responds to SagaService success/fail.
  6. If success, finished. If fail, SagaService sends message to TransactionService to deposit into account A.
  7. TransactionService responds to SagaService success/fail.
  8. If success, finished. If fail, alert users via some sort of notification mechanism/service.

Philip Jander

unread,
Jul 6, 2012, 4:34:59 AM7/6/12
to ddd...@googlegroups.com

  1. Application Service does not load/invoke behaviour/save ARs, instead choosing to send a message to a SagaService to request the transfer to happen between accounts A and B.
  2. SagaService sends message to TransactionService to withdraw from account A.
  3. TransactionService responds to SagaService success/fail.
  4. If success, SagaService sends another message to TransactionService to Deposit into account B.
  5. TransactionService responds to SagaService success/fail.
  6. If success, finished. If fail, SagaService sends message to TransactionService to deposit into account A.
  7. TransactionService responds to SagaService success/fail.
  8. If success, finished. If fail, alert users via some sort of notification mechanism/service.

More or less so. I woudln't have aggregates "respond" but emit the appropriate events to which the saga is subscribed.

You can alternatively have a "funds transfer" aggregate which can send command messages to accounts and have what has been called "stateless event handlers" issue commands to that transfer aggregate based on events emitted by the accounts. This aggregate can then itself be event sourced and contain a history and logic. I choose this second method whenever the "saga" is not mere coordination but a business concept of its own right.

Cheers
Phil

Dennis Traub

unread,
Jul 7, 2012, 8:08:14 AM7/7/12
to ddd...@googlegroups.com
I think the transfer itself can be modeled as an aggregate.
  1. Client sends command: RequestMoneyTransfer(sourceAccountId, destinationAccountId, amount, ...)
  2. Command handler creates new Transfer
  3. Transfer emits event: MoneyTransferRequested(sourceId, destinationId, amount, ...)
  4. MoneyTransferSaga consumes event from 3 and sends new command: WithdrawFromAccount(sourceId, ...)
  5. Source account emits either success or failure event
  6. Saga consumes event from 5 and in case of success sends new command: DepositToAccount(destinationId, ...)
  7. Destination account emits either success or failure event
  8. Saga consumes event from 7 and terminates itself case of success.
Reply all
Reply to author
Forward
0 new messages