Сложность определяет инструмент
Transaction Script организует простую операцию как последовательность шагов и должен оставлять систему только в целостном состоянии: либо изменения зафиксированы полностью, либо откатились. Даже здесь возникают ловушки. Запись в базу вместе с файлом или внешней системой превращает действие в распределённую транзакцию, а повтор неидемпотентного запроса после потерянного ответа способен применить эффект дважды. Для простой логики supporting- и generic-поддоменов это честный, экономичный выбор. Антипаттерном скрипт становится тогда, когда сложные правила core-домена размазываются по множеству процедур, начинают дублироваться и расходиться.
Active Record добавляет объектное представление данных и удобен для CRUD, отчётов и небольших сервисов, но не меняет предел простой модели. Когда переходы состояния зависят от инвариантов и дают бизнесу конкурентное отличие, участники переходят к rich domain model. Её задача — оставить в коде существенную сложность предметной области и убрать случайную сложность инфраструктуры. Чистые объекты выражают существительные, действия и правила на ubiquitous language, а хранение и транспорт не диктуют форму модели. Такой дизайн дороже, поэтому применять его повсеместно так же ошибочно, как решать core-домен набором таблиц и скриптов.
Правила должны иметь владельца
Value object определяется значениями: два одинаковых цвета или телефонных номера взаимозаменяемы, отдельный идентификатор им не нужен, а изменение создаёт новое значение. Entity, напротив, сохраняет тождество при изменении атрибутов и поэтому получает стабильный идентификатор. Специализированные типы для имени, телефона или доменного ID помещают проверку рядом со смыслом и не позволяют спутать одинаковые технические примитивы. Часть некорректных состояний тогда отсекает компилятор, а разработчику не приходится искать по системе, где строку уже проверили и можно ли доверять входным данным.
Aggregate объединяет entity и value objects, чьи инварианты должны сохраняться одной транзакцией. Внешний код меняет состояние через aggregate root, поэтому ограничения нельзя случайно обойти. Границу проводят по бизнес-правилу, а не по желанию загрузить весь связанный граф: слишком большой агрегат обновляется по любому поводу, конфликтует и становится непонятным. Другие агрегаты связываются ссылками и могут сходиться постепенно. Domain service вычисляет правило по данным нескольких агрегатов, но не превращает их в одну транзакцию. Domain events, выходящие из API агрегата, сообщают уже случившийся факт и несут сведения, необходимые его потребителям.
История как модель состояния
Обычная доменная модель хранит текущее состояние, а Event Sourcing делает источником истины упорядоченную последовательность доменных событий. Состояние агрегата восстанавливается их применением; тот же поток позволяет увидеть прошлую версию, разобрать ошибку и построить новое представление, которого не существовало во время записи. Версия потока поддерживает оптимистическую конкуренцию: при конфликте можно прочитать появившиеся события и решить, допустимо ли продолжение. Важно, что события описывают бизнес-смысл изменения. Журнал строк базы или Change Data Capture показывает, что поля поменялись, но не объясняет, почему это произошло.
Цена подхода — обучение команды, эволюция схем событий и необходимость поддерживать воспроизведение старой истории. Большие потоки ускоряют снапшотами, начиная восстановление с сохранённого состояния, а независимые агрегаты можно распределять по разделам хранилища. Неизменяемый журнал также сталкивается с удалением персональных данных: выпуск обсуждает хранение чувствительных значений отдельно, ссылки вместо копий и уничтожение ключа шифрования. Параллельная запись состояния и событий не даёт той же модели — она создаёт два источника истины и риск частичного коммита. Event Sourcing оправдан, когда история и причинность действительно являются частью продукта, а не ради модного стека.
Что стоит унести с собой
- 01Transaction Script и Active Record подходят простой бизнес-логике; их сложность начинается с распределённых эффектов, повторов и растущего числа правил.
- 02Rich domain model окупается в core-домене, где инварианты и переходы состояния важнее удобства прямой работы с таблицами.
- 03Граница агрегата совпадает с границей строгой бизнес-консистентности, а не со всем графом связанных данных.
- 04Event Sourcing хранит смысловую историю и открывает новые представления, но требует дисциплины эволюции событий, восстановления и удаления данных.
Источники
- Автоматические субтитры записи
- Запись выпуска