Периодически связанная система CQRS

Проблема:

Два сотрудника (A и B) одновременно выходят в автономном режиме при редактировании клиента № 123, скажем, версии №20, а в автономном режиме продолжают вносить изменения...

Сценарии:

1 - Два сотрудника редактируют клиента № 123 и вносят изменения в один или несколько идентичных атрибутов.

2 - Два сотрудника редактируют клиента № 123, но НЕ делают те же изменения (они пересекают друг друга, не касаясь).

... они затем возвращаются в режиме онлайн, первый сотрудник A добавляет, тем самым меняя клиента на версию № 21, а затем сотрудник B, все еще на версии №20

Вопросы:

Какие изменения мы сохраняем в сценарии 1?

Можно ли выполнить слияние в сценарии 2, как?

Контекст:

1 - система CQRS + система поиска событий

2 - Использовать Event Sourcing Db в качестве очереди

3 - Конечная согласованность в модели чтения

4 - API RESTful

diagram for the visually inclined; it's mash-up of a MS diagram and a few things changed

EDIT-1: Разъяснения, основанные на ответах до сих пор:

Чтобы выполнить оштукатуренное слияние, мне нужно будет иметь одну команду для каждого поля в форме, например?

enter image description here

Выше, мелкозернистые команды для ChangeName, ChangeSupplier, ChangeDescription и т.д., каждая со своей собственной меткой времени, позволит автоматически слить в событиях A и B оба обновленных ChangedName?

Редактировать-2: следить за использованием определенного хранилища событий:

Кажется, что я буду использовать @GetEventStore для сохранения моих потоков событий.

Они используют Optimistic Concurrency следующим образом:

  • Каждое событие в потоке увеличивает версию потока на 1

  • Writes может указывать ожидаемую версию, используя заголовок ES-ExpectedVersion для авторов

    • -1 указывает, что поток еще не существует

    • 0 и выше указывает версию потока

    • Ошибки не будут выполняться, если поток не находится в версии, вы либо повторите попытку с новым ожидаемым номером версии, либо обработали поведение, и решили, что это нормально, если вы это сделаете.

  • Если версия ES-Expected Version не указана, оптимизированное управление Concurrency отключено

  • В этом контексте Оптимистичный Concurrency основан не только на идентификаторе сообщения, но также на событии #

Ответ 1

Если я правильно понимаю ваш дизайн-рисунок, тогда команды, связанные с временным подключением пользователей, то есть запросы на изменение, и когда пользователь повторно соединяет команды в очереди, отправляются вместе; существует только один орган базы данных (который обработчики команд запрашивают для загрузки самых последних версий своих агрегитов); для клиентов синхронизируется только модель представления.

В этой настройке Сценарий 2 трижды автоматически объединяется по вашему дизайну, если вы правильно выбираете свои команды, читайте: сделайте их мелкозернистыми: для всех возможных измените, выберите одну команду. Затем, при повторном соединении клиента, команды обрабатываются в любом порядке, но поскольку они влияют только на поля disjunct, нет проблем:

  • Клиент находится на уровне v20.
  • A отключен, редактирует изменения против устаревшей модели v20.
  • B отключен, редактирует изменения в сравнении с устаревшей моделью v20.
  • A выходит онлайн, пакет отправляет команду в очередь ChangeName, Клиент v20 загружается и сохраняется как v21.
  • B выходит онлайн, пакет отправляет команду в очередь ChangeAddress, клиент v21 загружается и сохраняется как v22.
  • В базе данных есть пользователь с правильным именем и адресом, как и ожидалось.

В Сценарий 1 с этой настройкой оба сотрудника перезапишут изменения других сотрудников:

  • Клиент находится на уровне v20.
  • A отключен, редактирует изменения против устаревшей модели v20.
  • B отключен, редактирует изменения в сравнении с устаревшей моделью v20.
  • A отправляется онлайн, пакет отправляет команду queue ChangeName в "John Doe" , клиент v20 загружается и сохраняется как v21 с именем "John Doe"
  • B выходит онлайн, пакет отправляет команду queue ChangeName в "Joan d'Arc" , клиент v21 (с именем "John Doe" ) загружается и сохраняется как v22 (с именем "Joan d'Arc" ).
  • База данных содержит пользователя с именем "Joan d'Arc" .

Если B входит в сеть до A, то это наоборот:

  • Клиент находится на уровне v20.
  • A отключен, редактирует изменения против устаревшей модели v20.
  • B отключен, редактирует изменения в сравнении с устаревшей моделью v20.
  • B выходит онлайн, пакет отправляет команду queue ChangeName в "Joan d'Arc" , клиент v20 загружается и сохраняется как v21 (с именем "Joan d'Arc" ).
  • A приходит в сеть, пакет отправляет команду queue ChangeName в "John Doe" , клиент v21 загружается и сохраняется как v22 с именем "John Doe" .
  • База данных содержит пользователя с именем "John Doe" .

Существует два способа разрешения обнаружения конфликтов:

  • Проверьте, не указана ли дата создания команды (т.е. время изменения сотрудников) после последней даты изменения Customer. Это отключит функцию автоматического слияния сценария 2, но даст вам полное обнаружение конфликтов против одновременных изменений.
  • Проверьте, не изменилась ли дата создания команды (т.е. время изменения сотрудников) после последней даты изменения отдельного поля Customer. Это оставит автоматическое слияние сценария 2 без изменений, но даст вам автоматическое обнаружение конфликтов в сценарии 1.

Оба легко реализуются с использованием источников событий (поскольку, вероятно, известны временные метки отдельных событий в потоке событий).

Что касается вашего вопроса "Кто изменения, мы сохраняем в сценарии 1?" - это зависит от вашего бизнес-домена и его требований.

EDIT-1: ответить на вопрос о разъяснении:

Да, вам понадобится одна команда для каждого поля (или группы полей, соответственно), которые могут быть изменены индивидуально.

Что касается вашего макета: то, что вы показываете, является типичным пользовательским интерфейсом "CRUD", то есть несколькими полями формы и, например, одной кнопкой "Сохранить". CQRS обычно и естественно сочетается с "пользовательским интерфейсом на основе задачи", где будет отображаться поле Status (только для чтения), и если пользователь хочет изменить статус, один щелчок, скажем, Кнопка "Изменить статус", которая открывает диалоговое окно/новое окно или другой элемент пользовательского интерфейса, в котором можно изменить статус (в веб-системах также возможно редактирование на месте). Если вы работаете с пользовательским интерфейсом на основе задачи, где каждая задача влияет только на небольшое подмножество всех полей, то мелкозернистые команды для ChangeName, ChangeSupplier и т.д. Естественны.

Ответ 2

Вот общий обзор некоторых решений:

Сценарий 1

Кто-то должен решить, желательно человек. Вы должны спросить пользователя или показать, что есть конфликт.

Dropbox решает это, выбирая более поздний файл и сохраняя файл file.conflict в том же каталоге, который пользователь может удалить или использовать.

Сценарий 2

Сохраните исходные данные и посмотрите, какие поля действительно изменились. Затем вы можете применить изменения 1 сотрудника, а затем изменения 2 сотрудника, не наступая на любые пальцы.

Сценарий 3 (Только когда изменения вступают в онлайн в разное время)

Пусть второй пользователь знает, что были изменения, когда они были в автономном режиме. Попробуйте Сценарий 2 и покажите второму пользователю новый результат (потому что это может изменить его ввод). Затем спросите его, хочет ли он сохранить свои изменения, сначала изменить их или выбросить.

Ответ 3

Аарон, где события действительно конфликтуют, т.е. в сценарии 1, тогда я ожидал бы исключения concurrency какого-то рода.

Второй сценарий гораздо интереснее. Предполагая, что ваши команды и события достаточно хорошо определены, т.е. Не обертка для CRUD, тогда вы сможете проверить, действительно ли конфликтуют события, произошедшие с момента выдачи вашей команды. Для этой цели я использую реестр конфликтов concurrency. По сути, когда я обнаруживаю потенциальный конфликт, я захватываю события, которые были совершены с версии, которую я сейчас имею, и попросите реестр проверить, действительно ли кто-то из них конфликтует.

Если вы хотите увидеть пример кода и немного подробнее об этом, я собрал сообщение с изложением моего подхода. Взгляните на это здесь: обработка concurrency проблем в системах cqrs es

Надеюсь, это поможет!

Ответ 4

В этом случае, возможно, вы можете использовать концепцию "совокупный корень", для элемента, который работает на CEP Engine (Complex Process Process Engine) для выполнения этих сложных операций.