Ответ
Transactional Outbox (иногда называемый Inbox для потребителя) — это архитектурный паттерн, гарантирующий точно-однократную (exactly-once) или хотя-бы-однократную (at-least-once) доставку сообщений/событий в распределённых системах. Он решает проблему атомарности обновления базы данных и отправки сообщения в брокер (например, RabbitMQ, Kafka).
Проблема: В микросервисной архитектуре типична операция: "сохранить заказ в БД И отправить событие OrderCreated". Если после сохранения в БД происходит сбой до отправки события, система становится несогласованной.
Решение Transactional Outbox:
- Атомарная запись: Сообщение сохраняется в специальную таблицу
Outboxв той же транзакции, что и основное бизнес-состояние. - Фоновая доставка: Отдельный фоновый процесс (Outbox Processor или Relay) периодически опрашивает таблицу
Outbox, отправляет новые сообщения в брокер и помечает их как отправленные.
Пример реализации на C# (Entity Framework Core):
// 1. Модель сообщения в outbox
public class OutboxMessage
{
public Guid Id { get; set; } // Для идемпотентности
public DateTime OccurredOn { get; set; }
public string Type { get; set; } // "OrderCreated"
public string Payload { get; set; } // JSON данных события
public DateTime? ProcessedDate { get; set; }
}
// 2. Использование в сервисе заказов
public async Task PlaceOrder(OrderDetails details)
{
using var transaction = await _dbContext.Database.BeginTransactionAsync();
try
{
// Сохраняем бизнес-объект
var order = new Order { ... };
_dbContext.Orders.Add(order);
await _dbContext.SaveChangesAsync();
// АТОМАРНО сохраняем событие в ту же БД
var outboxMessage = new OutboxMessage
{
Id = Guid.NewGuid(),
OccurredOn = DateTime.UtcNow,
Type = "OrderCreated",
Payload = JsonSerializer.Serialize(new
{
OrderId = order.Id,
Total = order.Total
})
};
_dbContext.OutboxMessages.Add(outboxMessage);
await _dbContext.SaveChangesAsync(); // Одна транзакция!
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
// Фоновый сервис (например, Hangfire job или IHostedService)
// позже заберёт и отправит это сообщение.
}
Преимущества:
- Гарантированная доставка: Событие не потеряется даже при падении сервиса сразу после коммита транзакции.
- Согласованность: Бизнес-данные и факт генерации события всегда синхронизированы.
- Производительность: Основная операция не блокируется на сетевой вызов к брокеру.
Варианты: Для повышения производительности опроса таблицы Outbox можно использовать механизмы, отслеживающие изменения в БД (CDC), например, Debezium или SQL Server Change Tracking.