18
votes

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,...);
        }   
    }
}
4
Do you want bankapplication using DDD ?TechnicalKalsa
@Singh thank you for your comment. Yes, DDD with CQRS.Yugang Zhou
I have one project about Bankapplication. Are you want it ?TechnicalKalsa
@Singh I'd like to have a look, thank you. You could send it by email([email protected]) if it is convenient for you.Yugang Zhou
@Developer Where are your answer? :(b.ben

4 Answers

4
votes

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.

4
votes

I remember bits of this, however M Fowler uses a different meaning of event compared to a domain event. He uses the 'wrong' term, as we can recognize a command in his 'event' definition. So basically he is speaking about commands, while a domain event is something that happened and it can never change.

It is possible that I didn't fully understood that Fowler was referring to, but I would model things differently, more precisely as close to the Domain as possible. We can't simply extract a pattern that can always be applied to any financial app, the minor details may change a concept's meaning.

In OP's example , I'd say that we can have a non-explicit 'transaction': we need an account debited with an amount and another credit with the same amount. The easiest way, me thinks, is to implement it via a saga.

Debit_Account_A ->Account_A_Debited -> Credit_Account_B-> Account_B_Credited = transaction completed.

This should happen in a few ms at most seconds and this would be enough to update a read model. Humans and browsers are slower than a few seconds. And a user know to hit F5 or to wait a few minutes/hours. I won't worry much about the read model accuracy.

If the transaction is explicit i.e the Domain has a Transaction notion and the business really stores transactions that's a whole different story. But even in that case, probably the Transaction would be defined by a number of accounts id and some amounts and maybe a completed flag. However, at this point is pointless to continue, because it really depends on the the Domain's definition and use cases.

1
votes

Fixed the answer

Finally my solution is having Transaction as domain model.

And project transactions to AccountBalance but I implement special projection which make sure every data consistence before publish actual event.

0
votes

Just two words: "Event Sourcing" with the Reservation Pattern. And maybe, but not always, you may need the "Sagas" pattern also.