Работа с нетривиальными полезными нагрузками команд и событий в Axon

Всякий раз, когда я смотрю на Axon Bank, я начинаю задаваться вопросом, должен ли я следовать набору правил разработки событий и команд.

В Axon Bank события и команды состоят исключительно из примитивов. В своих приложениях я стараюсь максимально избегать примитивного использования, главным образом для создания выразительного домена и обеспечения безопасности типов везде, где я могу их получить.

Сам Axon поставляется с некоторыми ссылками DDD, но независимо от того, какие документы я просматриваю, ни один пример не использует составные объекты как часть полезных данных событий / команд.

Что меня смущает. Имеется встроенная поддержка полномасштабной сериализации xml и json, способной не только иметь несколько пар ключ-значение.

Я понимаю, что особенно события, как правило, являются небольшими и простыми структурами, поскольку они отражают только постепенные изменения состояния, но всегда будет некоторый разрыв между сложной моделью предметной области и событием (входом).


В моем домене я мог бы иметь несколько классов, таких как OverdraftLimit, CurrentBalance, Deposit а также AccountIdentifier,

Теперь есть два возможных способа разработки событий и команд:

1. Примитивы и экстенсивное преобразование

  • Относитесь к событиям как к необработанным данным с красивым ярлыком
  • Преобразование необработанных данных в мощные объекты, как только они "входят" в приложение
  • При создании событий просто лишить их снова.

    public class BankAccountcreatedEvent {
        private final String accountIdentifier;
        private final int overdraftLimt;
    
        // ...
    }
    

    И еще где-то:

    public void on (BankAccountCreatedEvent event) {
        this.accountIdentifier = AccountIentifier.fromString(event.getAccountIdentifier());
        this.overdraftLimit = new OverdraftLimit(event.getOverdraftLimit());
    }
    

Плюсы:

  • Простой API команды / события, который не имеет каких-либо странных зависимостей
  • Делает распределение проще
  • Аутстакеры понадобятся только в том случае, если реальная структура событий изменится и, следовательно, может быть легко предвидена

Минусы:

  • Нужно написать и поддерживать огромный конверсионный слой
  • Разделение событий / команд и остальной части предметной модели по техническим причинам приводит к новому искусственному контекстуальному разрыву

2. Выразительные полезные нагрузки

  • Используйте сложные типы непосредственно в качестве атрибутов

    public class BankAccountCreatedEvent {
        private final BankAccountIdentifier bankAccountIdentifier;
        private final OverdraftLimit overdraftLimit;
    
        //..
    }
    

Плюсы:

  • Меньше писать, легче читать
  • Держите вместе то, что естественно принадлежит друг другу

Минусы:

  • Доменная логика косвенно влияет на структуру событий, обновление будет требоваться чаще и будет менее предсказуемым.

Мне нужно второе мнение. Есть ли рекомендуемый способ?

1 ответ

Решение

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

Лично я не против примитивов в Событиях. Тем не менее, я понимаю ценность использования явных объектов значений для определенных полей, так как они позволяют выразить "математику", связанную с каждым из них. В случае идентификаторов они предотвращают "перепутывание", когда идентификатор используется для случайной попытки идентифицировать объект другого типа.

В конце концов, это не так важно. С помощью нескольких простых аннотаций Джексона вы можете перевести эти объекты-значения в простое значение в JSON. Проверьте @JsonValue, например.

public class BankAccountCreatedEvent {
    private final BankAccountIdentifier bankAccountIdentifier;
    private final OverdraftLimit overdraftLimit;

    //..
}

будет отображаться в:

{
    "bankAccountIdentifier": "abcdef1234",
    "overdraftLimit" : 1000
}

Если оба класса BankAccountIdentifier и OverdraftLimit будут иметь аннотированный метод @JsonValue, который вернет их "простое" значение.

Другие вопросы по тегам