Ответ
Событие следует отправлять в очередь после успешного завершения транзакции в базе данных. Это гарантирует консистентность: если транзакция откатится, событие о её успехе не будет отправлено.
Проблемный подход (отправка внутри транзакции):
using (var transaction = context.Database.BeginTransaction())
{
try
{
var order = new Order { /* ... */ };
context.Orders.Add(order);
context.SaveChanges(); // Сохраняем в БД
// ОПАСНО: событие отправляется в очередь (например, RabbitMQ)
_eventBus.Publish(new OrderCreatedEvent(order.Id));
// Если здесь произойдет сбой, транзакция откатится,
// но событие уже ушло и не может быть отозвано.
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
Корректный подход (отправка после транзакции):
using (var transaction = context.Database.BeginTransaction())
{
try
{
var order = new Order { /* ... */ };
context.Orders.Add(order);
context.SaveChanges();
transaction.Commit(); // Транзакция успешно завершена
}
catch
{
transaction.Rollback();
throw;
}
}
// Только теперь, гарантированно после фиксации транзакции, отправляем событие.
_eventBus.Publish(new OrderCreatedEvent(order.Id));
Исключение: Если брокер очередей (например, база данных, используемая как очередь) поддерживает распределённые транзакции (XA), то отправку можно выполнять внутри общей транзакции. Однако этот паттерн сложен и менее распространён в микросервисных архитектурах.
Ответ 18+ 🔞
Давай разберем эту дичь на пальцах, а то у некоторых руки из жопы растут, и они потом месяцами ищут, почему у них в системе бардак ебаный.
Смотри, есть у тебя заказ в базе. Сохранил — и сразу в очередь событие пихнул, типа «ой, всё готово, ребята, обрабатывайте». А потом бац — и транзакция откатилась по какой-то хуевой причине. И что мы имеем? В базе заказа нет, нихуя, пустота. А в очереди уже болтается событие, которое всем кричит: «Заказ создан, иди сюда!». И понеслась: другие сервисы начинают этот несуществующий заказ искать, обновлять, хуярить по нему отчёты — пиздец полный, бардак на овердохуища. Консистентность, блядь, накрылась медным тазом. Терпения ноль ебать с такими подходами.
Вот смотри на этот кусок кода — тут прямо хитрая жопа притаилась:
using (var transaction = context.Database.BeginTransaction())
{
try
{
var order = new Order { /* ... */ };
context.Orders.Add(order);
context.SaveChanges(); // Сохраняем в БД
// ОПАСНО: событие отправляется в очередь (например, RabbitMQ)
_eventBus.Publish(new OrderCreatedEvent(order.Id));
// Если здесь произойдет сбой, транзакция откатится,
// но событие уже ушло и не может быть отозвано.
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
Видишь подвох? Сохранили заказ (ну, в контексте, не в базе ещё), а потом — ёперный театр — сразу же выстрелили событием в шину. А что, если между SaveChanges() и transaction.Commit() что-то пойдёт по пизде? Например, соседний запрос накроется, или констрейнт какой ебучий сработает при коммите? Транзакция откатится, заказ испарится, а событие-то уже улетело, как хуй с горы, и его не догонишь. Получается, мы наёбываем всю систему, отправляя фейковые уведомления. Доверия к такому коду — ноль ебать.
А теперь правильный путь, без этих детских болезней:
using (var transaction = context.Database.BeginTransaction())
{
try
{
var order = new Order { /* ... */ };
context.Orders.Add(order);
context.SaveChanges();
transaction.Commit(); // Вот теперь всё, точка. Транзакция в базе закрыта.
}
catch
{
transaction.Rollback();
throw;
}
}
// Абсолютная уверенность, что в базе заказ есть. ТОЛЬКО теперь можно кричать об этом на весь мир.
_eventBus.Publish(new OrderCreatedEvent(order.Id));
Вот это — красота. Сначала твёрдо фиксируем факт в основном хранилище. Всё, база рулит. А уже потом, как верный оруженосец, бежим разносить новости. Если отправка события сломается — это, конечно, тоже пиздец, но уже другой. Хотя бы не будет ситуации «в базе нет, а в очереди есть». Можно событие переотправить, логи обработать — жить можно.
Но! Есть же, блядь, умники, которые скажут: «А вот если у нас брокер поддерживает XA, распределённые транзакции, то можно и внутри делать!». Ну да, можно. Теоретически. Это как управлять реактивным самолётом, имея права на трактор. Паттерн тот ещё, сложный, доверия ебать ноль к его стабильности в микросервисах, и обычно от него все шарахаются как чёрт от ладана. Если, конечно, ты не любитель приключений на свою жопу.
Короче, правило простое, чувак: сначала твёрдо положи в базу, потом уже голоси в очередь. Не пытайся быть умнее всех и не создавай проблем на ровном месте. А то будет тебе не хиросима, а настоящий нигерсраки в продакшене.