Работа с нетривиальными полезными нагрузками команд и событий в 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, который вернет их "простое" значение.