Смешивание DDD и Event Sourcing
Я не могу придумать концепцию смешения DDD с ES. Я считаю события частью домена. Учитывая, что нет проблем с публикацией их из репозитория во внешний мир и сохранением моделей в чистоте и простоте. Но кроме этого должна быть возможность переиграть их обратно на конкретный агрегат. Здесь моя проблема возникает. Я хотел бы сохранить свои доменные модели чистыми и простыми объектами, которые остаются независимыми от lib/framework.
Чтобы применить прошлые события к агрегату, агрегат должен знать, что он является частью структуры ES (поэтому он не должен оставаться чистым доменным объектом). Поскольку основная задача агрегата состоит в том, чтобы включить некоторые бизнес-инварианты, которые могут эволюционировать со временем, невозможно применять старые события с использованием агрегатного API. Например, есть совокупный пост с дочерними сущностями Комментарии. Сегодняшняя публикация позволяет добавлять 10 комментариев, а метод addCommnet() защищает это правило. Но это не всегда так. Год назад пользователю было разрешено добавить до 20 комментариев. Таким образом, применение прошлых событий может не соответствовать текущим правилам.
Бродвей (популярная библиотека PHP CQRS) решает эту проблему, применяя события без предварительной проверки. Метод addCommnet() просто проверяет его на соответствие нашим инвариантам, а затем обрабатывает события приложения. Событие Applyinig само по себе не выполняет дальнейшую проверку. Это здорово, но я воспринимаю это как высокий уровень интеграции в моих моделях предметной области. Действительно ли моя модель предметной области должна знать что-либо об инфаструктуре (которая является ES-стилем сохранения данных)?
РЕДАКТИРОВАТЬ: Чтобы сформулировать проблему с простейшими словами: есть ли возможность избавиться от всех этих методов applyXXX() из агрегата?
EDIT2: я написал (немного хакерский) PoC этой идеи с PHP - github
4 ответа
Я не могу придумать концепцию смешения DDD с CQRS.
Судя по всему, вы не можете полностью разобраться в смеси DDD и источников событий. CQRS и Event Sourcing - это отдельные идеи (которые хорошо сочетаются).
Сегодняшняя публикация позволяет добавлять 10 комментариев, а метод addCommnet() защищает это правило. Но это не всегда так. Год назад пользователю было разрешено добавить до 20 комментариев. Таким образом, применение прошлых событий может не соответствовать текущим правилам.
Это абсолютно верно. Однако обратите внимание, что верно также и то, что если у вас была публикация, не связанная с событиями, с 15 комментариями, и вы пытаетесь составить "правило" теперь, когда разрешено только 10 комментариев, у вас все еще есть проблема.
Мой ответ на эту загадку (в обоих стилях) заключается в том, что вам нужно немного отличаться от понимания ответственных обязанностей.
Ответственность за модель предметной области лежит на поведении; он описывает, какие состояния достижимы из текущего состояния. Модель предметной области не должна ограничивать вас в плохом состоянии, она должна предотвращать превращение хороших состояний в плохие.
В первой версии можно сказать, что состояние Post
включает в себя TwentyList of Comments
где TwentyList - это (неожиданно) контейнер, который может содержать до 20 идентификаторов комментариев.
Во второй версии, где мы хотим сохранить ограничение в 10 комментариев, мы не меняем TwentyList
к TenList
, потому что это дает нам головную боль обратной совместимости. Вместо этого мы изменили правило домена, сказав, что "к записи с 10 или более комментариями нельзя добавлять комментарии". Схема данных не изменяется, и нежелательные состояния все еще представимы, но разрешенные переходы состояний сильно ограничены.
По иронии судьбы, хорошей книгой, которую можно почитать, чтобы узнать больше, является версия Грега Янга в системе источников событий. На высоком уровне урок заключается в том, что управление версиями событий - это просто управление версиями сообщений, а состояние - это просто сообщение, оставленное предыдущей моделью для текущей модели.
Типы значений не об ограничениях правил, а о семантических ограничениях.
Имейте в виду, что сроки очень разные; поведение о настоящем и следующем, а состояния о прошлом. Предполагается, что состояния должны длиться намного дольше, чем поведение (с соответствующими инвестициями в проектный капитал).
Действительно ли моей доменной модели нужно что-то знать об инфраструктуре (в стиле ES для сохранения данных)?
Нет, модель предметной области не должна знать об инфраструктуре.
Но события не инфраструктура - они государственные. Журнал AddComment
а также RemoveComment
события это состояние, как список Comment
Записи гос.
Наиболее общая форма "поведения" - это функция, которая принимает текущее состояние в качестве входных данных и генерирует события в качестве выходных данных.
List<Event> act(State currentState);
как мы всегда можем на внешнем уровне, взять события (которые представляют собой неразрушающее представление состояния и построить из них состояние).
State act(State currentState) {
List<Event> changes = act(currentState)
State nextState = currentState.apply(changes)
return nextState
}
List<Event> act(List<Event> history) {
State initialState = new State();
State currentState = initialState.apply(changes)
return act(currentState)
}
State act(List<Event> history) {
// Writing this out long hand to drive home the point
// we could of course call act: List<Event> -> State
// to avoid duplication.
List<Event> changes = act(history)
State initialState = new State()
State currentState = initialState.apply(history)
State nextState = currentState.apply(changes)
return nextState;
}
Дело в том, что вы можете реализовать поведение в самом общем случае, добавить несколько адаптеров, а затем позволить сантехнику выбрать, какая реализация является наиболее подходящей.
Опять же, разделение обязанностей - это ваша путеводная звезда: заявите, что управляет тем, что есть, поведение, которое управляет тем, какие изменения разрешены, и сантехника / инфраструктура - все это отдельные проблемы.
Проще говоря: я ищу возможность избавиться от многих методов applyXXX() (или похожих в языках с методами перегрузки) из моего агрегата
applyXXX
это просто функция, которая принимает State
и Event
в качестве аргументов и возвращает новый State
, Вы можете использовать любое правописание и область видимости, которую вы хотите для этого.
Отказ от ответственности: я парень из CQRS.
Бродвей (популярная библиотека PHP CQRS) решает эту проблему, применяя события без предварительной проверки.
Так работает каждый CQRS Aggregate, события не проверяются, потому что они отражают факты, которые уже произошли в прошлом. Это означает, что применение event
не бросает exceptions
, Когда-либо.
Чтобы применить прошлые события к агрегату, агрегат должен знать, что он является частью структуры ES (поэтому он не должен оставаться чистым доменным объектом).
Нет, это не так. Он должен знать о своих прошлых событиях. Это хорошо.
Сегодняшняя публикация позволяет добавлять 10 комментариев, а метод addCommnet() защищает это правило. Но это не всегда так. Год назад пользователю было разрешено добавить до 20 комментариев. Таким образом, применение прошлых событий может не соответствовать текущим правилам.
Что держит тебя aggregate
игнорировать это событие или интерпретировать иначе, чем год назад?! Этот конкретный случай должен заставить вас задуматься о мощи CQRS: записи имеют другую логику, чем чтения. Вы применяете события к агрегату, чтобы проверить будущие команды, которые приходят к нему (сторона записи / команды). Отображение этих 20 событий обрабатывается другой логикой (сторона чтения / запроса).
Здесь моя проблема возникает. Я хотел бы сохранить свои доменные модели чистыми и простыми объектами, которые остаются независимыми от lib/framework.
CQRS позволяет поддерживать ваши агрегаты в чистоте (без побочных эффектов), без зависимости от какой-либо библиотеки и просто. Я делаю это, используя стиль, представленный http://cqrs.nu/, путем выдачи событий. Это означает, что агрегированные методы обработчиков команд фактически являются генераторами.
Прочитанные модели также могут быть очень простыми, обычными неизменяемыми объектами PHP. Только средство чтения модели чтения имеет зависимость от постоянства, но это можно инвертировать с помощью interface
,
Мой ответ очень короткий. На самом деле, вы боретесь с источником событий, а не с CQRS.
Если обработка какого-либо события со временем меняется, у вас есть два сценария:
- Вы исправляете ошибку, и ваш обработчик должен вести себя по-другому. В этом случае вы просто приступаете к изменению.
- У тебя есть новое намерение. У тебя на самом деле новая обработка. Это означает, что на самом деле это другое событие. В этом случае у вас есть новое событие и новый обработчик.
Эти сценарии не имеют отношения к языкам программирования и средам. Event-Sourcing в целом гораздо больше о бизнесе, чем о любой технологии.
Я бы поддержал книгу рекомендации Грега.
Я думаю, что ваша проблема в том, что вы хотите проверять события, когда они применяются, но применение и проверка - это два разных этапа совокупного действия. Когда вы добавляете комментарий методом addComment(событие), вы должны проверить логику, и этот метод генерирует событие, когда вы отвечаете на событие, эта логика больше не проверяется. Прошлое событие не может быть изменено, и если ваш агрегат выдает исключение с событием ответа, что-то не так с вашим агрегатом. Вот как я понимаю твою проблему.