Когда отправлять событие в очередь: перед транзакцией или после?

«Когда отправлять событие в очередь: перед транзакцией или после?» — вопрос из категории Архитектура, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Событие следует отправлять в очередь после успешного завершения транзакции в базе данных. Это гарантирует консистентность: если транзакция откатится, событие о её успехе не будет отправлено.

Проблемный подход (отправка внутри транзакции):

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), то отправку можно выполнять внутри общей транзакции. Однако этот паттерн сложен и менее распространён в микросервисных архитектурах.