Как моделировать банковский перевод в CQRS

Я читаю шаблон учета и довольно любопытно о его внедрении в CQRS.

Я думаю, что AccountingTransaction является совокупным корнем, поскольку он защищает инвариант:

Нет утечек денег, это должен быть переход от одной учетной записи к другой.

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,...);
    }
}

Когда AccountingTransaction добавляется в его репозиторий. Он публикует несколько AccountingEntryBookedEvent, которые используются для обновления баланса соответствующих учетных записей на стороне запроса.

Один агрегированный корень обновлен на транзакцию db, возможная согласованность, настолько хорошая.

Но что делать, если некоторые учетные записи применяют ограничения передачи, например, не могут передать количество больше текущего баланса? Я могу использовать сторону запроса, чтобы получить баланс аккаунта, но я беспокоюсь, что данные со стороны запроса устарели.

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

Должен ли я моделировать учетную запись в командной строке? Я должен обновить не менее трех совокупных корней на транзакцию db (от/до учетной записи и учетной записи 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,...);
        }   
    }
}

Ответ 1

Существуют некоторые варианты использования, которые не позволят обеспечить конечную согласованность. CQRS - это хорошо, но данные, возможно, должны быть на 100% согласованы. CQRS не подразумевает/требует конечной согласованности.

Однако хранилище моделей транзакций/доменов будет согласованным, и баланс будет согласован в этом хранилище, поскольку он представляет текущее состояние. В этом случае транзакция должна завершиться неудачно, независимо от непоследовательной стороны запроса. Это будет несколько странный пользовательский опыт, хотя такой подход на 100% может быть лучше.

Ответ 2

Я помню бит этого, однако M Fowler использует другое значение события по сравнению с событием домена. Он использует термин "неправильный", так как мы можем распознать команду в его определении "событие". Поэтому в основном он говорит о командах, а событие домена - это то, что произошло, и оно никогда не может измениться.

Возможно, я не совсем понял, что имел в виду Фаулер, но я бы моделировал вещи по-другому, точнее как можно ближе к Домену. Мы не можем просто извлечь шаблон, который всегда можно применить к любому финансовому приложению, мелкие детали могут изменить смысл концепции.

В примере OP я бы сказал, что мы можем иметь неявную "транзакцию": нам нужна учетная запись, дебетованная с суммой и другим кредитом с одинаковой суммой. Полагаю, самый простой способ - реализовать его с помощью саги.

Debit_Account_A → Account_A_Debited → Credit_Account_B- > Account_B_Credited = завершена транзакция.

Это должно произойти за несколько секунд в большинстве секунд, и этого было бы достаточно, чтобы обновить модель чтения. Люди и браузеры работают медленнее, чем несколько секунд. И пользователь знает, чтобы нажать F5 или подождать несколько минут/часов. Я не буду беспокоиться о точности прочитанной модели.

Если транзакция явна, то у домена есть понятие транзакции, и в бизнесе действительно хранятся транзакции, которые представляют собой совершенно другую историю. Но даже в этом случае, вероятно, транзакция будет определяться несколькими идентификаторами учетных записей и некоторыми суммами и, возможно, завершенным флагом. Однако на данный момент бессмысленно продолжать, поскольку он действительно зависит от определения и использования домена.

Ответ 3

Всего два слова: Event Sourcing" с шаблоном резервирования. И, возможно, но не всегда, вам может понадобиться шаблон Sagas.