Pull to refresh

Comments 15

Насколько я понял, получается, что мы сначала отправляем событие, получаем OK, что оно опубликовано, а уже потом сохраняем в БД запись, так?

А если сохранение в БД отвалится, а событие уже улетело? По-моему, более правильным подходом является отправка событий через @TransactionalEventListener(BEFORE_COMMIT) и если отправка не удалась, то и транзакцию ещё не поздно зафейлить.

Не так, агрегат содержит в себе событие и при сохранении агрегата сохраняется и он сам и событие. И все это происходит в рамках одной транзакции, атомарно. А затем уже выполняется публикация.

Что касается варианта с TransactionalEventListener(BEFORE_COMMIT) то может быть ситуация, когда уже событие опубликовалось, а транзакция по какой-либо причине завершилась с ошибкой, что привело к ее откату и опять же может возникнуть несогласованное состояние. Плюс сам брокер может быть какое-то время недоступен, получается из-за его недоступности будет блокироваться и выполнение самой транзакции. А если публикация будет асинхронной опять же нет достоверной уверенности а прошла ли она корректно.
Ваш вариант с TransactionalEventListener можно задействовать, чтобы не публиковать событие, а выполнять их сохранение с Transactional(propagation = Propagation.SUPPORTS) тогда и сущность и событие сохранятся в рамках одной транзакции.

К слову, есть другая интересная аннотация DomainEvents, которая фактически автоматом публикует доменные события, она может быть использована вместе с Transactional для метода save у репозитория, при сохранение самой сущности и с EventListener и Propagation.SUPPORTS для сохранения в рамках одной транзакции самого события. Такое решение будет максимально близким к тому, о чем говорилось в статье.

Если бизнес-данные и события хранятся в разных базах данных и их нельзя поместить в одну транзакцию - как в этом случае построить алгоритм публикации события?

При каких обстоятельствах может возникнуть такая ситуация? Агрегатом владеет какой-то микросервис, у этого микросервиса имеется база данных. И весь этот алгоритм применяется в рамках этой базы данных. Сохраняется агрегат и событие в одну и ту же базу данных.

Абсолютно стандартная ситуация - продуктовая бд на оракле, а бд на которой можно реализовать Transactional outbox pattern на постгре.

Оракл - продуктовая бд заказчика и к ней имеем доступ в рамках бизнес-логики системы. Добавление новых схем и таблиц в неё невозможно. Постгре - внутренняя бд нашей системы и с ней можно делать всё что угодно.

Гарантированно атомарный - никак.

С костылями и приседаниями

Обмазывать сущности состоянием и, сохраняя бизнес-сущность, проставлять им статус "сохранено без события". При сохранении события ставить ей статус "сохранено", а у сущности менять статус на "сохранено с событием". После успешной публикации, менять статус события на "опубликовано".

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

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

напоминает шаблон Сага

Не напоминает, а она и есть. :)

может быть использована локальная транзация

А какой в этом смысл? Как транзакции между двумя БД синхронизовать будете?

Идея была все же не в разных базах и синхронизации между ними транзакций, а в использование одной базы и транзакции, для сохранения агрегата и события. Заботы о распределенных транзакциях в данной статье нет. Transactional outbox pattern решает проблему согласованного состояния базы данных и публикации доменного события с гарантией минимум один раз.

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

Сохранение в бд и агрегата, и события в принципе понятно.
А тема вычитки из бд и отправки в очередь, например, в Kafka, раскрыта не полностью.
Как при отправке в Kafka гарантировать, что события по одному субъекту отправляются в том же порядке, что и были сохранены? И желательно не в один поток

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

Автор молодец. Ту же самую связку реализовал на C# через EF Core + библиотека SAP как функционал по сохранению и публикации событий. Всё также делается атомарно в рамках одной транзакции и сразу же публикуется после коммита. После публикации статус события меняется.

Sign up to leave a comment.

Articles