Ответ
Transactional Outbox — это паттерн надежной доставки сообщений, который решает проблему двойной записи (dual write). Проблема возникает, когда нужно атомарно изменить состояние в базе данных и опубликовать событие в брокере сообщений (например, Kafka или RabbitMQ).
Если делать это последовательно, система становится ненадежной:
- Запись в БД успешна, а отправка в брокер — нет. Результат: состояние изменилось, но никто об этом не узнал.
- Отправка в брокер успешна, а запись в БД — нет. Результат: разослано ложное событие.
Как работает паттерн:
- В той же базе данных, где хранятся основные данные, создается специальная таблица
outbox
(почтовый ящик). - Изменение данных и запись события в таблицу
outbox
происходят в рамках одной транзакции БД. - Отдельный асинхронный процесс (называемый Relay или Publisher) периодически опрашивает таблицу
outbox
. - Обнаружив новые события, Relay пытается доставить их в брокер сообщений.
- После успешной доставки Relay помечает событие в
outbox
как обработанное (или удаляет его).
Пример на Go:
func CreateOrder(db *sql.DB, order Order) error {
tx, err := db.Begin()
if err != nil { return err }
defer tx.Rollback() // Безопасный откат по умолчанию
// 1. Сохраняем заказ в основной таблице
_, err = tx.Exec("INSERT INTO orders...", order.ID, order.Details)
if err != nil { return err }
// 2. Сериализуем событие и записываем его в outbox-таблицу
eventPayload, _ := json.Marshal(map[string]interface{}{"order_id": order.ID})
_, err = tx.Exec(
"INSERT INTO outbox (event_type, payload) VALUES (?, ?)",
"OrderCreated",
eventPayload,
)
if err != nil { return err }
// 3. Коммитим транзакцию. Только после этого изменения станут видны
return tx.Commit()
}
Преимущества:
- Атомарность и консистентность: Гарантирует, что событие будет создано тогда и только тогда, когда основная операция с данными успешна.
- Отказоустойчивость: Если брокер сообщений временно недоступен, события остаются в
outbox
и будут отправлены позже, когда связь восстановится. Потеря сообщений исключена. - Разделение ответственностей: Основной сервис отвечает только за бизнес-логику, а доставкой сообщений занимается отдельный компонент (Relay).