Допустим, вы подходите к DDD с помощью EventSourcing.
Мы все знаем, что события неизменны, и их никогда не следует удалять из журнала событий. Но что, если поток логически «неверен»? Не тот классический случай, когда «я добавил деньги, мне не нужно было добавлять их, поэтому создайте компенсационное событие, чтобы вывести их».
Я не говорю о исключениях времени выполнения , но логических исключениях , которые вы можете найти в потоке событий, потому что кодеры допустили ошибки в средствах записи событий.
Вопрос
Как вы «воспроизводите» поток событий, если в программном обеспечении, которое его записало, были ошибки, нарушавшие логику домена?
Оууу ... Мы все знаем, что "этого не должно никогда не происходило" и "увольнять кодировщиков, которые писали эти авторы событий" и так далее ...
Но давайте предположим, что поток событий равен именно там, и вы перестраиваете проекции , воспроизводя всего потока. Просто могло бы случиться, и вам сказали перестроить проекции из существующего потока событий.
И неожиданно, когда воспроизводит поток событий, вы обнаруживаете "несвязные" события, которые не соответствуют ни текущим бизнес-правилам, ни тем правилам, которые существовали тогда.
Пример 1
У вас есть следующие события:
# TimeStamp Event Data
------------------------------------------------------
1 03/jul car.created { id: 4444, color: blue }
2 14/jul car.delivered { id: 4444, to: Alice }
3 18/jul car.created { id: 5555, color: blue }
4 22/jul car.created { id: 5566, color: orange }
5 25/jul car.created { id: 5577, color: blue }
26 июля кто-то спрашивает: «Сколько у вас синих автомобилей в наличии?».
Кристально чистый: 2 единицы (идентификаторы 5555
и 5577
).
Причина: Единица 4444
была продана. Единица 5566
оранжевого цвета.
Но что, если у вас есть эта глючная последовательность?
# TimeStamp Event Data
------------------------------------------------------
1 03/jul car.created { id: 4444, color: blue }
2 14/jul car.delivered { id: 4444, to: Alice }
3 18/jul car.created { id: 5555, color: blue }
4 22/jul car.created { id: 5566, color: orange }
5 23/jul car.created { id: 5555, color: red }
6 25/jul car.created { id: 5577, color: blue }
Конечно, событие 5 никогда не должно было произойти, вы не можете создать один и тот же юнит 2 раза.
После исследования экспертов домена ... вы обнаружите, что событие 5 неверно. Должно быть написано «car.repainted», но программное обеспечение содержит ошибки и написано «car.created».
Вопрос к примеру 1:
- Вы бы добавили новые события с номерами 7 и более с меткой времени «сразу после» события 5, чтобы сделать какую-то компенсацию? Какие события вы бы написали?
- Вы бы добавили новые события с номерами 7 и более, с отметкой времени «непосредственно перед» событием 5, чтобы сделать своего рода сигнал для проигрывателя «эй, игнорируйте следующее создание»? Какие события вы бы написали?
- Не могли бы вы переписать ваши "проигрыватели", чтобы они могли интерпретировать, что "все до 25 / июл", что означает "двойное создание", означает "car.repainted" и перезапускать проигрыватели, чтобы восстановить агрегаты?
- Будете ли вы нарушать золотые правила и «трогать» историю? На самом деле это не «история», потому что событие «5» на самом деле не произошло. Можем ли мы потрогать его тогда?
Пример 2
Давайте предположим, что склад с вилочным погрузчиком забирает вещи с полок. Склад содержит 2 вертикальных коридора, 2 горизонтальных коридора и 1 диагональный коридор.
Все коридоры являются двунаправленными, за исключением левого вертикального, который имеет какие-то ступеньки или что-то еще, и вилочный погрузчик может двигаться только от А к С, но не наоборот; кроме того, за исключением горизонтальной плоскости, которая также имеет ступени, и погрузчик может двигаться только от D к C и никогда от C к D.
После покупки машины вы начинаете каждый день в точке А, так как там есть входная дверь на склад. Независимо от того, как для этого примера в конце дня погрузчик просто исчезает, все равно.
Команды могут быть:
purchase()
start()
goRight()
goLeft()
goUp()
goDown()
cross()
События могут быть:
purchased
started
wentRight
wentLeft
wentUp
wentDown
crossed
Это возможная диаграмма состояния агрегата вилочного погрузчика:
Предположим, что вы воспроизводите события совокупности, и вы нашли их:
# TimeStamp Event
----------------------------------------------
1 12/jul 10:00 purchased
2 14/jul 09:00 started
3 14/jul 11:00 wentDown
4 14/jul 12:00 crossed
5 14/jul 14:00 wentDown
6 23/jul 09:00 started
7 23/jul 10:00 wentRight
8 23/jul 13:00 crossed
Кто-то спрашивает: «Где сейчас грузоподъемник? Вы можете легко сказать« С ».
Причина: независимо от того, что произошло до 6
, потому что событие 6
сбрасывается в положение A
, событие 7
движется в направлении B
, событие 8
движется в направлении C
.
Но что, если у вас последовательность продолжается вот так?
# TimeStamp Event
----------------------------------------------
[...]
6 23/jul 09:00 started
7 23/jul 10:00 wentRight
8 23/jul 13:00 crossed
9 23/jul 15:00 wentUp
10 23/jul 16:00 wentRight
11 27/jul 09:00 started
12 27/jul 11:00 wentDown
Какой-то эксперт в области спрашивает вас: «Привет, парень, ты сказал нам, что аутсорсинг был волшебным: где был вилочный погрузчик 23 июля в 18:00?»
Мы все знаем, что лифт не может «перепрыгнуть» через лестницу, поэтому мы все знаем, что событие 9 никогда не может произойти.
Так что наши «переигровщики» не могут делать ничего другого, что вызывает исключение. Но уже записана последовательность событий.
Тема здесь не «как написать хорошую последовательность», а «что делать, когда вы сталкиваетесь с последовательностью с исключениями».
Вопросы для примера 2:
- Вы бы написали компенсирующее событие? Как? Который? Когда?
- Переписали бы историю? (некрасиво, если у вас миллионы событий)
- Как бы вы обработали это исключение с точки зрения доменного события replayers ?