Я уже довольно давно адаптирую CQRS 1 для бедного человека, потому что мне нравится его гибкость, позволяющая хранить детализированные данные в одном хранилище данных, предоставляя большие возможности для анализа и, таким образом, увеличивая ценность для бизнеса и, при необходимости, другую для операций чтения, содержащих денормализованные данные, для повышения производительности. ,
Но, к сожалению, с самого начала я боролся с проблемой, где именно я должен разместить бизнес-логику в архитектуре этого типа.
Из того, что я понимаю, команда является средством сообщения о намерениях и сама по себе не связана с доменом. Они в основном данные (тупые - если хотите) объекты передачи. Это позволяет легко переносить команды между различными технологиями. То же самое относится к событиям как ответы на успешно завершенные события.
В типичном приложении DDD бизнес-логика находится в сущностях, объектах значений, совокупных корнях, они богаты как данными, так и поведением. Но команда не является объектом домена, поэтому ее не следует ограничивать представлениями данных в домене, поскольку это создает для них слишком большую нагрузку.
Итак, реальный вопрос: где именно логика?
Я обнаружил, что чаще всего сталкиваюсь с этой борьбой, пытаясь построить довольно сложный агрегат, который устанавливает некоторые правила относительно комбинаций его значений. Кроме того, при моделировании доменных объектов мне нравится следовать парадигме отказоустойчивости , зная, что когда объект достигает метода, он находится в допустимом состоянии.
Допустим, агрегат Car
использует два компонента:
Transmission
,Engine
,
Как Transmission
и Engine
объекты значений представлены в виде супер типов и имеют в соответствии типа суб, Automatic
и Manual
передачи, или Petrol
и Electric
двигатели соответственно.
В этой области вполне успешно жить самостоятельно созданный Transmission
, будь то Automatic
или Manual
, или любой тип Engine
. Но Car
агрегат вводит несколько новых правил, применимых только тогда , когда Transmission
и Engine
объекты используются в том же контексте. А именно:
- Когда автомобиль использует
Electric
двигатель, единственным разрешенным типом трансмиссии являетсяAutomatic
. - Когда автомобиль использует
Petrol
двигатель, он может иметь любой типTransmission
.
Я мог бы уловить это нарушение комбинации компонентов на уровне создания команды, но, как я уже говорил, из того, что я понимаю, этого не следует делать, поскольку команда тогда будет содержать бизнес-логику, которая должна быть ограничена уровнем домена.
Один из вариантов - перенести проверку бизнес-логики на сам валидатор команд, но это тоже не совсем правильно. Такое чувство, что я буду деконструировать команду, проверять ее свойства, полученные с помощью геттеров, сравнивать их в валидаторе и проверять результаты. Это кричит как нарушение закона Деметры для меня.
Отказ от упомянутой опции проверки, потому что она не кажется жизнеспособной, кажется, что нужно использовать команду и построить агрегат из нее. Но где должна существовать эта логика? Должен ли он быть внутри обработчика команд, ответственного за обработку конкретной команды? Или это должно быть в валидаторе команд (мне тоже не нравится этот подход)?
В настоящее время я использую команду и создаю агрегат из нее в ответственном обработчике команд. Но когда я сделаю это, если у меня будет валидатор команды, он вообще ничего не будет содержать, потому что, если CreateCar
команда существует, она будет содержать компоненты, которые, как я знаю, действительны в отдельных случаях, но агрегат может отличаться.
Давайте представим другой сценарий, смешивающий разные процессы проверки - создание нового пользователя с помощью CreateUser
команды.
Команда содержит Id
пользователей, которые будут созданы, и их Email
.
Система устанавливает следующие правила для адреса электронной почты пользователя:
- Должно быть уникальным,
- не должно быть пустым,
- должно содержать не более 100 символов (максимальная длина столбца в БД).
В этом случае, хотя наличие уникального электронного письма является бизнес-правилом, проверка его в совокупности не имеет большого смысла, поскольку мне нужно было бы загрузить весь набор текущих электронных писем в системе в память и проверить электронную почту в команде. против совокупности ( Eeeek! Что-то, что-то, производительность.). Из-за этого я перенесу эту проверку в валидатор команды, который примет UserRepository
зависимость и использует репозиторий, чтобы проверить, существует ли уже пользователь с адресом электронной почты, присутствующим в команде.
Когда дело доходит до этого, внезапно имеет смысл поместить и другие два правила электронной почты в валидатор команд. Но я чувствую, что правила должны действительно присутствовать в User
агрегате, и что валидатор команд должен проверять только уникальность, и если проверка прошла успешно, я должен приступить к созданию User
агрегата в CreateUserCommandHandler
и передать его в репозиторий для сохранения.
Я чувствую себя так, потому что метод сохранения репозитория, скорее всего, примет агрегат, который гарантирует, что после прохождения агрегата все инварианты будут выполнены. Когда логика (например непустота) присутствует только внутри самой команды проверки другой программист может полностью пропустить эту проверку и вызовите метод сохранения в UserRepository
с User
объектом непосредственно , что может привести к фатальной ошибке базы данных, так как электронная почта может иметь было слишком долго
Как вы лично обрабатываете эти сложные проверки и преобразования? Я в основном доволен своим решением, но мне кажется, что мне нужно подтверждение, что мои идеи и подходы не совсем глупы, чтобы быть вполне довольными выбором. Я полностью открыт для совершенно разных подходов. Если у вас есть что-то, что вы лично попробовали и работали очень хорошо для вас, я хотел бы увидеть ваше решение.
1 Работая в качестве разработчика PHP, отвечающего за создание систем RESTful, моя интерпретация CQRS немного отличается от стандартного подхода обработки асинхронных команд , например, иногда возвращает результаты от команд из-за необходимости синхронной обработки команд.
CommandDispatcher
.