Условные команды «создать» в архитектуре CQRS

Я упрощу свою проблему:

Мой LightsState API может принимать 2 типа входных данных: lightOn {lightId: ##} и lightOff {lightId: ##}. (ввод AMQP, но здесь неуместен)

Эти входные данные хорошо переводятся в 2 команды: TurnLightOnCmd и TurnLightOffCmd.

Эти команды создадут 2 события: LightTurnedOnEvent и LightTurnedOffEvent.

Эти события будут применены к Light Aggregate, а постоянной проекцией будет state of the light.

Все хорошо, пока здесь.

Но поскольку входных данных нет: create light, я не могу сделать из этого CreateLightCmd. Я могу вызывать CreateLightCmd только тогда, когда получаю ввод lightOn с НОВЫМ lightId для создания Light Aggregate, а затем также применяю к нему TurnLightOnCmd.

Я не уверен, как справиться с этим, а также следовать хорошим практикам CQRS. Можно ли вызвать сторону запроса со стороны команды, чтобы проверить, есть ли light exists by id, а затем, если необходимо, сначала вызвать CreateLightCmd? Или я должен сделать запрос к БД со стороны команды и оставить стороны команды и запроса разделенными? Или есть другие решения для этого?

Спасибо


person Razor    schedule 28.11.2018    source источник
comment
Я могу вызывать CreateLightCmd только тогда, когда получаю входные данные lightOn с НОВЫМ lightId для создания агрегата освещения, а затем также применяю к нему TurnLightOnCmd — это ограничение, налагаемое вашим контекстом? Это кажется ужасно сложным.   -  person guillaume31    schedule 29.11.2018
comment
Это не ограничение, но создание агрегата должно выполняться явной командой, поэтому я предложил CreateLightCmd.   -  person Razor    schedule 29.11.2018
comment
Сейчас я экспериментирую с вызовом хранилища событий напрямую, чтобы проверить уникальность lightId. Таким образом, по крайней мере стороны Command и Query разделены.   -  person Razor    schedule 29.11.2018
comment
Я имею в виду, обязательно ли клиенту заранее знать идентификатор при создании источника света, и зачем вообще соотносить TurnLightOn с CreateLight?   -  person guillaume31    schedule 30.11.2018


Ответы (2)


Можно ли вызвать сторону запроса со стороны команды, чтобы проверить, существует ли свет по идентификатору, а затем, при необходимости, сначала вызвать CreateLightCmd?

Не совсем — это вводит условия гонки, последствия которых могут вас не радовать.

Обзор: DDD+CQRS+ES по своей архитектуре очень похож на DDD. Основная идея заключалась в том, что мы сохраняем информацию в нашем устройстве хранения (также известном как «база данных»). Модель запускается в процессе, который загружает текущее состояние из базы данных, использует команду для вычисления нового состояния, а затем сохраняет это новое состояние в базе данных.

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

Но: схемы создания странные.

Когда мы пытаемся запросить историю идентификатора, который мы раньше не видели, мы получим нуль, или None, или историю без событий, или что-то в этом роде.

Сюрприз: это хорошо.

Для вашего варианта использования вам не обязательно нужен CreateLightCmd — вместо этого вы хотите создать новый LightCreatedEvent, когда вы получаете одну из других команд, которые должны неявно создавать свет.

В псевдокоде:

TurnLightOn (cmd) {
    history = getHistory(cmd.lightId)
    if (history.isEmpty) {
        history.append(LightCreatedEvent.from(cmd))
    }
    history.append(LightTurnedOnEvent.from(cmd))
    save(cmd.lightId, history)
}
person VoiceOfUnreason    schedule 29.11.2018
comment
В итоге я реализовал что-то похожее, что вы предложили. Где я проверяю в EventStore, существует ли какое-либо событие с указанным lightId, а затем вызываю CreateLightCmd или TurnLightOnCmd. - person Razor; 30.11.2018

Взгляните на основополагающий пост Уди Дахана «Не создавайте совокупные корни» [0]. Ключевые моменты:

  • Не создавайте совокупные корни
  • Всегда получайте сущность

Это означает, что когда вы вводите команду, у вас всегда должен быть агрегат с фактическим корневым объектом агрегата, инициализированным до состояния по умолчанию. В вашем случае состояние по умолчанию — «свет выключен». Вы выполняете команду на этом свете, и теперь он находится в состоянии «свет включен». Теперь вы сохраняете его в базу данных, и он создан. Если ваш домен не заботится о LightCreatedEvent, вам не нужно его моделировать.

[0] http://udidahan.com/2009/06/29/dont-create-aggregate-roots/

person CPerson    schedule 28.11.2018