Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to model bank transfer in CQRS

I'm reading Accounting Pattern and quite curious about implementing it in CQRS.

I think AccountingTransaction is an aggregate root as it protects the invariant:

No money leaks, it should be transfer from one account to another.

public class AccountingTransaction {
    private String sequence;
    private AccountId from;
    private AccountId to;
    private MonetaryAmount quantity;
    private DateTime whenCharged;

    public AccountingTransaction(...) {
        raise(new AccountingEntryBookedEvent(sequence, from, quantity.negate(),...);
        raise(new AccountingEntryBookedEvent(sequence, to, quantity,...);
    }
}

When the AccountingTransaction is added to its repository. It publishes several AccountingEntryBookedEvent which are used to update the balance of corresponding accounts on the query side.

One aggregate root updated per db transaction, eventual consistency, so far so good.

But what if some accounts apply transfer constraints, such as cannot transfer quantity more that current balance? I can use the query side to get the account's balance, but I'm worried that data from query side is stale.

public class TransferApplication {
    public void transfer(...) {
        AccountReadModel from = accountQuery.findBy(fromId);
        AccountReadModel to = accountQuery.findBy(toId);
        if (from.balance() > quantity) {
            //create txn
        }
    }
}

Should I model the account in the command side? I have to update at least three aggregate roots per db transaction(from/to account and account txn).

public class TransferApplication {
    public void transfer(...) {
        Account from = accountRepository.findBy(fromId);
        Account to = accountRepository.findBy(toId);
        Transaction txn = new Transaction(from, to, quantity);
        //unit or work locks and updates all three aggregates
    }
}

public class AccountingTransaction {
    public AccountingTransaction(...) {
        if (from.permit(quantity) {
            from.debit(quantity);
            to.credit(quantity);
            raise(new TransactionCreatedEvent(sequence, from, to, quantity,...);
        }   
    }
}
like image 563
Yugang Zhou Avatar asked May 04 '14 06:05

Yugang Zhou


1 Answers

There are some use cases that will not allow for eventual consistency. CQRS is fine but the data may need to be 100% consistent. CQRS does not imply/require eventual consistency.

However, the transactional/domain model store will be consistent and the balance will be consistent in that store as it represents the current state. In this case the transaction should fail anyway, irrespective of an inconsistent query side. This will be a somewhat weird user experience though so a 100% consistent approach may be better.

like image 105
Eben Roux Avatar answered Oct 17 '22 03:10

Eben Roux