Изредка подключаемая система CQRS
Проблема:
Два сотрудника (A & B) выходят в автономный режим одновременно, редактируя клиента № 123, скажем, версию № 20, и в то время как автономный режим продолжают вносить изменения...
Сценарии:
1 - Два сотрудника редактируют клиента № 123 и вносят изменения в один или несколько идентичных атрибутов.
2 - Два сотрудника редактируют клиента № 123, но НЕ вносят одинаковые изменения (они пересекаются, не касаясь друг друга).
... они оба возвращаются в онлайн, сначала добавляется сотрудник А, тем самым изменяя клиента на версию № 21, затем сотрудник Б, все еще на версии № 20
Вопросы:
Какие изменения мы оставляем в сценарии 1?
Можем ли мы сделать слияние в сценарии 2, как?
Контекст:
1 - CQRS + система стилей источников событий
2 - Использование Event Sourcing Db в качестве очереди
3 - Возможная последовательность на модели чтения
4 - RESTful API
РЕДАКТИРОВАТЬ-1: Разъяснения, основанные на ответах до сих пор:
Чтобы выполнить мелкозернистое объединение, мне понадобится одна команда для каждого поля в форме, например?
Выше, мелкозернистые команды для ChangeName, ChangeSupplier, ChangeDescription и т. Д., Каждая из которых имеет свою собственную временную метку, позволит автоматически объединить в событии A & B оба измененных ChangedName?
Редактирование-2: Отслеживание на основе использования определенного хранилища событий:
Кажется, что я буду использовать @GetEventStore для сохранения своих потоков событий.
Они используют оптимистический параллелизм следующим образом:
Каждое событие в потоке увеличивает версию потока на 1
Операторы записи могут указывать ожидаемую версию, используя заголовок ES-ExpectedVersion на устройствах записи.
-1 указывает, что поток не должен существовать
0 и выше указывает версию потока
Запись не будет выполнена, если поток не соответствует версии, либо вы повторите попытку с новым ожидаемым номером версии, либо повторно обработали поведение и решили, что все в порядке, если вы того пожелаете.
Если не указана ожидаемая версия ES, оптимистическое управление параллелизмом отключено
В этом контексте оптимистический параллелизм основан не только на идентификаторе сообщения, но и на событии #
4 ответа
Если я правильно понимаю ваш проектный рисунок, то иногда подключенные пользователи ставят команды в очередь, т. Е. Запросы на изменение, и когда пользователь повторно подключается, команды в очереди отправляются вместе; существует только одна база данных (которую запрашивают обработчики команд для загрузки самых последних версий их агрегатов); только модель представления синхронизируется с клиентами.
В этой настройке Сценарий 2 тривиально автоматически объединяется вашим проектом, если вы выбираете команды мудро, читайте: сделать их детализированными: для каждого возможного изменения выберите одну команду. Затем при повторном подключении клиента команды обрабатываются в любом порядке, но, поскольку они влияют только на несвязанные поля, проблем нет:
- Клиент на v20.
- A не в сети, редактирует изменения в устаревшей модели v20.
- B не в сети, редактирует изменения в устаревшей модели v20.
- А приходит в онлайн, партия отправляет в очередь
ChangeName
команда, Клиент v20 загружается и сохраняется как v21. - B приходит в онлайн, партия отправляет в очередь
ChangeAddress
команда, Клиент v21 загружается и сохраняется как v22. - База данных содержит пользователя с его правильными именем и адресом, как и ожидалось.
В сценарии 1 с этой настройкой оба сотрудника будут перезаписывать изменения других сотрудников:
- Клиент на v20.
- A не в сети, редактирует изменения в устаревшей модели v20.
- B не в сети, редактирует изменения в устаревшей модели v20.
- А приходит в онлайн, партия отправляет в очередь
ChangeName
команда "Джон Доу", Клиент v20 загружается и сохраняется как v21 с именем "Джон Доу" - B приходит в онлайн, партия отправляет в очередь
ChangeName
По команде "Жанна д'Арк" Клиент v21 (с именем "Джон Доу") загружается и сохраняется как v22 (с именем "Жанна д'Арк"). - База данных содержит пользователя с именем "Joan d'Arc".
Если B выходит в сеть раньше, чем A, то наоборот:
- Клиент на v20.
- A не в сети, редактирует изменения в устаревшей модели v20.
- B не в сети, редактирует изменения в устаревшей модели v20.
- B приходит в онлайн, партия отправляет в очередь
ChangeName
По команде "Жанна д'Арк" Клиент v20 загружается и сохраняется как v21 (с именем "Жанна д'Арк"). - А приходит в онлайн, партия отправляет в очередь
ChangeName
По команде "Джон Доу", Клиент v21 загружается и сохраняется как v22 с именем "Джон Доу". - База данных содержит пользователя с именем "Джон Доу".
Есть два способа включить обнаружение конфликта:
- Проверьте, является ли дата создания команды (т. Е. Время изменения сотрудников) после даты последнего изменения
Customer
, Это отключит функцию автоматического слияния в Сценарии 2, но даст вам полное обнаружение конфликтов при одновременных изменениях. - Проверьте, является ли дата создания команды (т. Е. Время изменения сотрудников) после даты последнего изменения отдельного поля
Customer
это изменится. Это оставит автоматическое слияние сценария 2 без изменений, но даст вам автоматическое обнаружение конфликта в сценарии 1.
И то, и другое легко реализовать с помощью источника событий (поскольку временные метки отдельных событий в потоке событий, вероятно, известны).
Что касается вашего вопроса: "Какие изменения мы оставляем в сценарии 1?" - это зависит от вашего бизнеса и его требований.
РЕДАКТИРОВАТЬ-1: Чтобы ответить на уточняющий вопрос:
Да, вам понадобится одна команда для каждого поля (или группы полей соответственно), которую можно изменить индивидуально.
Относительно вашего макета: то, что вы показываете, представляет собой типичный пользовательский интерфейс "CRUD", то есть несколько полей формы и, например, одну кнопку "Сохранить". CQRS обычно и естественным образом комбинируется с "основанным на задачах" пользовательским интерфейсом, где, скажем, Status
поле будет отображаться (только для чтения), и если пользователь хочет изменить статус, один щелкнет, скажем, кнопку "Изменить статус", которая открывает диалоговое окно / новое окно или другой элемент пользовательского интерфейса, где можно изменить статус (в веб-системах редактирование на месте также распространено). Если вы выполняете пользовательский интерфейс "на основе задач", где каждая задача влияет только на небольшое подмножество всех полей, то точные команды для ChangeName, ChangeSupplier и т. Д. Естественным образом подходят.
Вот общий обзор некоторых решений:
Сценарий 1
Кто-то должен решить, предпочтительно человек. Вы должны спросить пользователя или показать, что есть конфликт.
Dropbox решает эту проблему, выбирая более поздний файл и сохраняя файл file.conflict в том же каталоге, который пользователь может удалить или использовать.
Сценарий 2
Сохраняйте исходные данные и смотрите, какие поля действительно изменились. Затем вы можете применить изменения сотрудника 1, а затем изменения сотрудника 2, не наступая ни на какие пальцы.
Сценарий 3 (только когда изменения вступают в силу в разное время)
Сообщите второму пользователю, что произошли изменения, когда он был отключен. Попробуйте Сценарий 2 и покажите второму пользователю новый результат (потому что это может изменить его входные данные). Затем спросите его, хочет ли он сохранить свои изменения, сначала изменить их или выбросить.
В этом случае, возможно, вы можете использовать концепцию "агрегатного корня" для Предмета, который работает на CEP Engine (Сложный обработчик событий) для выполнения этих сложных операций.
Аарон, где события действительно конфликтуют, т. Е. В сценарии 1, я бы ожидал, что будет выдано какое-то исключение параллелизма.
Второй сценарий гораздо интереснее. Предполагая, что ваши команды и события достаточно хорошо определены, то есть не являются оболочкой для CRUD, вы сможете проверить, действительно ли конфликты произошли после совершения вашей команды. Для этой цели я использую реестр конфликтов параллелизма. По сути, когда я обнаруживаю потенциальный конфликт, я беру события, которые были зафиксированы с той версии, которая у меня сейчас есть, и прошу реестр проверить, действительно ли конфликтует какой-либо из них.
Если вы хотите увидеть пример кода и немного подробнее об этом, я соберу пост, в котором изложен мой подход. Взгляните на это здесь: обработка проблем параллелизма в системах cqrs es
Надеюсь это поможет!