Пейджер

🌍 Привет мир! 👋🏻

🌍 Привет мир! 👋🏻

Бэкэндеры читаем, фронты не расстраиваемся и тоже читаем, а вдруг пригодится.
И сегодня весьма важная тема из жизни небольших (как правило небольших), но гордых микросервисов. Поговорим про Transactional Outbox & Inbox patterns, эти штучки не позволят вам потерять сообщения между сервисами, а как следствие сделают ваш сон — спокойным🙂!

😮‍💨 Мотивация

Простенький пример, есть два сервиса:

- Сервис заказов
- Сервис оплаты

Пользователь оформляет заказ, от нас нужно два действия:

- Сохранить заказ в БД.
- Отправить событие во внешний сервис оплаты.

Возможная проблема:

🚨 Заказ сохранен, но событие по какой-то причине не отправилось
🚨 Или наоборот, событие ушло, но сервис упал и заказ в базу не сохранился

И в этот момент начинается школа жизни, пофикси или умри (не спи)

Вы могли подумать, да че там, я вот синхронно все гоняю и зарплату получаю 💵НО надёжность таких решений стремится к нулю, при падении одного сервиса теряются все события.

А еще можно так гонять данные:

Distributed Transactions — медленные и тяжелые подходы, легко приводят к блокировкам и зависаниям всей системы (old school привет).

Отправка сообщений напрямую в брокеры (Kafka, RabbitMQ, NATS) без промежуточного хранилища — в случае падения сервиса до отправки сообщения, возникает рассинхронизация данных.

🔴 Outbox

Идея заключается в том, что вы отправляете сообщения другим сервисам не напрямую, а через специальную таблицу Outbox.

⚙️ Как работает:

✏️ Выполняется бизнес-операция, сразу после этого необходимо добавить исходящее событие в таблицу Outbox внутри текущей транзакции.
✏️ Отдельный модуль (worker, job, CDC) регулярно читает таблицу Outbox и отправляет эти события в нужные очереди, сервисы или брокеры (Kafka, RabbitMQ и т.д.).
✏️ После успешной отправки сообщение помечается как отправленное, или как завершенное, но с какой-то ошибкой.

Если транзакция по каким-то причинам откатилась, сообщение в outbox тоже не сохранится, и другие сервисы не получат некорректное событие. В итоге добиваемся атомарности и консистентности данных.

🔴 Inbox

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

👀 Как выглядит на практике:

Допустим, есть сервис заказов, который отправляет событие о новом заказе.

Outbox (сервис заказов):
// Cохранение заказа и события
await transaction(async (tx) => {
  await tx.save(order);
  await tx.save(outboxTable, {
    eventType: 'ORDER_CREATED',
    payload: order
  });
});

// Воркер отправки событий
async function processOutbox() {
  const events = await outboxTable.getUnprocessed();

  for (const event of events) {
    await eventBus.publish(event.eventType, event.payload);
    await outboxTable.markProcessed(event.id);
  }
}


Inbox (сервис оплаты):
// Получение и сохранение события

app.post('/events', async (req, res) => {
  await inboxTable.insert({
    eventType: req.body.type,
    payload: req.body.payload
  });
  res.status(200).send('Accepted');
});

// Обработка Inbox
async function processInbox() {
  const events = await inboxTable.getUnprocessed();

  for (const event of events) {
    try {
      await handlePayment(event.payload);
      await inboxTable.markProcessed(event.id);
    } catch (err) {
      console.error(err);
    }
  }
}


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

🔖 Советы:

✏️ Необходимо регулярно удалять или архивировать старые события из inbox и outbox, чтобы не падала производительность и не происходил чрезмерный рост таблиц.
✏️ Если работаете с брокером сообщений, не забывайте использовать (DLE) про это я уже писал здесь. Это позволит сохранить события и проанализировать ошибки позже, не теряя информацию.

💬 Делитесь своим мнением в комментариях👇! Если вам понравилась статья, не забудьте поставить лайк! 👍

#PATTERNS #ARCHITECTURE
Медиа 1
Хотите больше таких постов?
Подпишитесь на канал и читайте продолжение в Telegram.
Подписаться на @ivanchikovitclub Открыть пост в Telegram