Согласованность DomainEventPublisher

Только что прочитав эффективный составной дизайн Вона Вернона, я интересно узнать о сбоях, связанных с публикацией событий.

В данном примере на странице 9 (страница 3 PDF) мы вызываем DomainEventPublisher.publish(). Публикуемое событие позволяет другим агрегатам выполнять свое поведение.

Мне интересно: что произойдет, если DomainEventPublisher.publish() выйдет из строя? Что произойдет, если DomainEventPublisher.publish() завершится успешно, но транзакция завершится неудачно?

Как реализации обрабатывают эти два случая?


person Arnaud Le Blanc    schedule 21.11.2018    source источник
comment
Автор отвечает на это в следующем абзаце. What happens if the subscriber experiences concurrency contention with another client, causing its modification to fail? The modification can be retried if the subscriber does not acknowledge success to the messaging mechanism. The message will be redelivered, a new transaction started, a new attempt made to execute the necessary command, and a corresponding commit. This retry process can continue until consistency is achieved, or until a retry limit is...   -  person Constantin Galbenu    schedule 21.11.2018
comment
@ConstantinGalbenu: Нет, я говорил о публикации события, а не об его обработке :)   -  person Arnaud Le Blanc    schedule 21.11.2018
comment
этот пример не очень устойчив/пригоден для использования в реальном мире. Наиболее надежными шаблонами являются шаблоны из источника событий, когда события вообще не публикуются, а извлекаются из хранилища событий, а потребитель событий (модель чтения или сага) отслеживает, обработаны ли события.   -  person Constantin Galbenu    schedule 21.11.2018
comment
Решение @plalx кажется мне довольно устойчивым. Вы знаете о каких-либо проблемах с ним?   -  person Arnaud Le Blanc    schedule 21.11.2018
comment
он использует транзакции; это делает Aggregate зависимым от инфраструктуры (что можно смягчить, вместо этого возвращая события)   -  person Constantin Galbenu    schedule 21.11.2018
comment
@ConstantinGalbenu Независимо от того, возвращаете ли вы их или используете статический синхронный DomainEventPublisher с привязкой к потоку, вы в значительной степени получаете тот же точный результат с той же возможностью тестирования. Шаблон действительно использует транзакции базы данных, но домен никак не знает об этом. Я не очень понимаю вашу точку зрения.   -  person plalx    schedule 21.11.2018
comment
@plalx Агрегат делает IO вызов, и мне это не нравится. Я держу свои Агрегаты в чистоте, без побочных эффектов, фактически без зависимости от Инфраструктуры или каких-либо других Компонентов.   -  person Constantin Galbenu    schedule 21.11.2018
comment
@ConstantinGalbenu Ну, они не выполняют вызов ввода-вывода, если нет зарегистрированного обработчика, который выполняет ввод-вывод, а это означает, что все может быть протестировано без какого-либо ввода-вывода. Сами AR не зависят от компонентов инфраструктуры или каких-либо других пакетов.   -  person plalx    schedule 21.11.2018
comment
@plalx, ​​но они есть, это DomainEventPublisher   -  person Constantin Galbenu    schedule 21.11.2018
comment
@ConstantinGalbenu DomainEventPublisher — это компонент домена, который находится в домене и выполняет публикацию событий в памяти без каких-либо операций ввода-вывода. Вам может не нравиться идея статического класса, но, в конце концов, он не добавляет никаких ограничений или практических недостатков по сравнению с возвратом событий или их записью в коллекцию на AR. Единственная проблема, которую я вижу, заключается в том, что если в DomainEventPublisher есть ошибка, то тесты могут завершиться неудачей, но я не вижу в этом проблемы, учитывая, что это не тот компонент, который все равно когда-либо изменится.   -  person plalx    schedule 21.11.2018
comment
@plalx DomainEventPublisher — это инфраструктура, которая находится в домене. Агрегаты не должны зависеть от инфраструктуры. Саги или модели чтения могут зависеть от инфраструктуры, потому что им это необходимо, а агрегаты — нет (потому что есть другие способы сделать это). Это мое личное мнение.   -  person Constantin Galbenu    schedule 21.11.2018
comment
@ConstantinGalbenu Sagas и Readmodels не будут зависеть от DomainEventPublisher, они будут зависеть от надежной инфраструктуры обмена сообщениями, которая отличается от DomainEventPublisher. Это просто реализация публикации-подписки в памяти, которая имеет интерфейс, управляемый UL, вспомогательный класс. Я думаю, вы могли бы концептуально рассматривать это как инфраструктуру, но это просто разделение волос, учитывая, что на самом деле это не оказывает никакого негативного влияния на гибкость модели и системы (по крайней мере, не то, о чем я могу думать прямо сейчас). Это не похоже на то, если бы AR зависел от NServiceBus.   -  person plalx    schedule 21.11.2018
comment
@plalx - это постоянство (база данных), достигнутое в вызове DomainEventPublisher.publish()?   -  person Constantin Galbenu    schedule 21.11.2018
comment
@ConstantinGalbenu Нет, если для этого нет подписчика, которого вы бы зарегистрировали только в контексте работающего приложения. Что касается DomainEventPublisher, то он публикует события в списке подписчиков в памяти. Вы можете посмотреть пример реализации здесь.   -  person plalx    schedule 21.11.2018
comment
@plalx буду, спасибо   -  person Constantin Galbenu    schedule 21.11.2018
comment
Граница транзакции должна быть только ОДНОЙ совокупностью. Вам не нужна транзакция, которая подразумевает два или более агрегатов. Иногда вам нужна транзакция для сохранения бизнес-изменений, сделанных агрегатом, потому что диапазон бизнес-изменений изменяется в нескольких элементах в постоянстве; то есть две или более таблиц в реляционной базе данных.   -  person jlvaquero    schedule 22.11.2018
comment
@jlvaquero Я не совсем уверен, насколько ваш комментарий уместен в приведенном выше обсуждении? Ваш комментарий был ответом на что именно? Спасибо!   -  person plalx    schedule 23.11.2018


Ответы (2)


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

После того, как события были записаны на диск транзакционно, вы можете поместить их в очередь сообщений для асинхронной доставки.

Есть ли другие известные способы сделать это?

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

person plalx    schedule 21.11.2018
comment
Спасибо. Мне нравится это решение. Есть ли другие известные способы сделать это? - person Arnaud Le Blanc; 21.11.2018

Что произойдет, если DomainEventPublisher.publish() завершится успешно, но транзакция завершится неудачно?

В данном случае я против подхода Вернона. Я предпочитаю возвращать события в сервис приложений. Таким образом, я могу сохранить изменения, выполненные агрегатом, с помощью транзакции (если это необходимо) и, если все в порядке, я опубликую событие. Это также помогает поддерживать чистоту и чистоту бизнес-уровня.

В нескольких словах; если транзакция терпит неудачу, то событие не возникает.

Что произойдет, если произойдет сбой DomainEventPublisher.publish()?

Событие предметной области по бизнес-правилам никогда не завершается неудачей, потому что это уведомление о том, что произошло. Если агрегат сказал «Да» операции и возвращает событие, выражающее бизнес-изменения; тогда ничто в мире не должно говорить, что эта операция не может быть сделана или должна быть отменена.

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

person jlvaquero    schedule 21.11.2018
comment
Спасибо. Я думаю, что мой вопрос был недостаточно ясен о вещах, которые могут дать сбой: во-первых, мы можем не опубликовать событие (например, система обмена сообщениями временно не работает). Во-вторых, после публикации события текущая транзакция может не зафиксироваться; в результате мы опубликовали событие, но на самом деле этого не произошло, так как транзакция не прошла. - person Arnaud Le Blanc; 21.11.2018
comment
Но я отвечаю, что 2 вещи. Что вы не понимаете в моем ответе, что заставляет вас думать, что я неправильно понял ваш вопрос? - person jlvaquero; 21.11.2018
comment
Я говорю об публикации событий, а не об их обработке :) - person Arnaud Le Blanc; 21.11.2018
comment
И мой ответ заключается в том, что изменение стратегии Вернона позволяет вам публиковать события, когда транзакция фиксируется, или не публиковать их, когда транзакция терпит неудачу. - person jlvaquero; 22.11.2018
comment
"I can persist the changes performed by the aggregate using a transacion (if needed) and, if everything is Ok, I will publish the event" Это не имеет смысла, данные события и состояние AR должны храниться атомарно. То, как вы только что описали, допускает очень непоследовательную систему. Что делать, если транзакция фиксируется, но публикация события не удалась? - person plalx; 23.11.2018
comment
@plalx это называется согласованностью в конечном итоге. Это большое модное слово. Я удивлен, что ты этого не знаешь. Поищите в гугле. Теперь я понимаю, почему все, что я говорю, не имеет смысла ни для вас, ни для ОП. Мы почти говорим на разных языках. - person jlvaquero; 23.11.2018
comment
@jlvaquero Я очень хорошо осведомлен о возможной согласованности и сагах / менеджерах процессов. Это не то, что вы описываете. Вы говорите, что вы можете создавать и фиксировать события только ПОСЛЕ успешной транзакции. Это неправильно, просто и ясно. События должны ВСЕГДА сохраняться в той же транзакции, что и транзакция, в которой было изменено состояние AR. - person plalx; 23.11.2018
comment
@plalx Нет. События являются побочными эффектами. Совокупность является границей транзакции, и как только совокупные изменения сохраняются, согласованность ограниченного контекста находится в хорошем состоянии. Другие BC в конечном итоге будут соответствовать событиям. Транзакция не должна охватывать более одного БК. - person jlvaquero; 24.11.2018
comment
@jlvaquero Я не думаю, что ты понимаешь, о чем мы говорим. Мы говорим, что события должны быть СОХРАНЕНЫ в той же транзакции, что и состояние AR. Это не имеет ничего общего с публикацией событий в инфраструктуре обмена сообщениями. - person plalx; 24.11.2018
comment
@plalx I вы сохраняете события в постоянстве, вы выполняете поиск событий (кстати, в OP не упоминается источник событий), поэтому вам не нужно хранить состояние AR, потому что бизнес-состояние уже выражено событиями. Мой ответ заключается в том, как я думаю, что все должно быть сделано в архитектуре без источников событий. Чтобы избежать потери событий в случае сбоя публикации, я использую механизм, предоставляемый NServiceBus, но вы всегда можете сделать это вручную. Если публикация не удалась, ваш BC находится в согласованном состоянии, и вам просто нужно повторно опубликовать событие, когда сбой будет устранен. - person jlvaquero; 26.11.2018