Друкарня від WE.UA

Як гарантувати доставку подій у мікросервісах? Реальний підхід через Transactional Outbox у Spring Boot 🚀

Уявіть: користувач оформляє замовлення. Ви успішно зберігаєте його в базу, але повідомлення іншим сервісам не доходить через мережевий збій...
В результаті: продавець не знає про нове замовлення, рахунок не виставлений, а клієнт незадоволений.

Создать мем "плачущий кот, грустный кот мем, кот плачет мем" - Картинки -  Meme-arsenal.com
Це ти до Transactional Outbox 😸

Як це виправити раз і назавжди? Відповідь — Transactional Outbox.

Що таке Transactional Outbox?

Це простий і надійний принцип:

• В тій самій транзакції, де ми оновлюємо основні дані, ми також записуємо подію у спеціальну таблицю Outbox.

• А потім окремий процес читає ці події і відправляє їх у брокер (Kafka, RabbitMQ тощо).

Так ми гарантуємо: або все успішно, або нічого.

Реалізація

1. Створюємо таблицю для подій

CREATE TABLE outbox_event (
    id UUID PRIMARY KEY,
    order_id VARCHAR(255) NOT NULL,
    status VARCHAR(255) NOT NULL,
    payload JSONB NOT NULL,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL,
    processed BOOLEAN NOT NULL DEFAULT false
);

2. Пишемо Entity для OutboxEvent

import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.UUID;

@Entity
@Table(name = "outbox_event")
public class OutboxEvent {

    @Id
    private UUID id;

    @Column(name = "order_id", nullable = false)
    private String orderId;

    @Column(nullable = false)
    private String status;

    @Column(columnDefinition = "jsonb", nullable = false)
    private String payload;

    @Column(name = "created_at", nullable = false, updatable = false)
    private LocalDateTime createdAt;

    @Column(name = "updated_at", nullable = false)
    private LocalDateTime updatedAt;

    @Column(nullable = false)
    private Boolean processed = false;

    // Getters and Setters

}

3. Пишемо репозиторій для OutboxEvent

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.UUID;

public interface OutboxEventRepository extends JpaRepository<OutboxEvent, UUID> {

    @Query(value = """
        SELECT * FROM outbox_event
        WHERE processed = false
        ORDER BY created_at
        FOR UPDATE SKIP LOCKED
        LIMIT :batchSize
        """, nativeQuery = true)
    List<OutboxEvent> findUnprocessedEventsForUpdate(@Param("batchSize") int batchSize);
}

Ти напевно здивований чому саме такий запит? Це на той випадок якщо твій сервіс задеплоєн на декілька інстансів, та необхідно уникнути конфліктів при читанні подій паралельно кількома інстансами. Завдяки SKIP LOCKED конкуренція між інстансами вирішується автоматично.

✅ Можна масштабувати горизонтально без страху дублювання або втрати повідомлень.

4. Запис основних даних і події в одній транзакції

@Transactional
public void createOrder(Order order) {
    orderRepository.save(order);

    OutboxEvent event = new OutboxEvent(
        UUID.randomUUID(),
        order.getId(),
        "OrderCreated",
        serialize(order)
    );

    outboxEventRepository.save(event);
}

5. Публікація подій за допомогою Scheduler

@Scheduled(fixedDelay = 5000)
@Transactional
public void publishOutboxEvents() {
    List<OutboxEvent> events = outboxEventRepository.findUnprocessedEventsForUpdate(10);

    for (OutboxEvent event : events) {
        messagePublisher.publish(event.getStatus(), event.getPayload());
        event.setProcessed(true);
    }
}

Практичні поради для продакшена

  • 🛡 Ідемпотентність: обробники подій мають бути ідемпотентними.

  • 🔁 Retry: якщо брокер тимчасово недоступний — передбачити повторні спроби.

  • 🧹 Очищення Outbox: налаштувати регулярне видалення або архівацію старих подій.

  • 🏎 Батчинг: обробляти події пачками для кращої продуктивності.

Transactional Outbox — це надійний фундамент для побудови консистентних, масштабованих і відмовостійких мікросервісів.

Правильна імплементація дозволяє тобі не боятись втрати події навіть у найскладніших розподілених середовищах.

Sacha Baron Cohen offers to pay tourists' mankini fines | The Times of  Israel
Борат одобряє 😉

Статті про вітчизняний бізнес та цікавих людей:

  • Як побудована програма Meest China Academy

    Курс про товарний бізнес охоплює різні етапи роботи: від пошуку ідеї до перевірки товару, логістики та масштабування. Програма Meest China Academy містить 17 модулів, які послідовно розкривають ці теми без зведення всього навчання до однієї універсальної поради.

    Теми цього довгочиту:

    Meest
  • Які технології використані в Айфон 17

    Айфон 17 поєднує OLED-дисплей із частотою до 120 Гц, чип A19, дві камери Fusion 48 Мп і швидке заряджання через USB-C. У COMFY можна купити Айфон 17 з накопичувачем на 256 або 512 ГБ, вибравши конфігурацію відповідно до обсягу фото, відео та застосунків

    Теми цього довгочиту:

    Iphone 17
  • Як вибрати вживаний iPhone: коротке керівництво для розумної покупки

    Вторинний ринок смартфонів Apple активно зростає, і питання, як вибрати вживаний iPhone, стає актуальним для дедалі більшої кількості користувачів. Правильний підхід дозволяє отримати бу iPhone з актуальною iOS і доброю камерою за помітно нижчу ціну, ніж у нових пристроїв.

    Теми цього довгочиту:

    Iphone
  • Як подолати жіночу безплідність за допомогою донорських яйцеклітин

    Діагноз "безпліддя" рідко звучить як вирок одразу. Для багатьох жінок саме в цей момент вперше звучить словосполучення "донорські яйцеклітини" — і це часто не кінець шляху до материнства, а якраз новий, реальний шанс.

    Теми цього довгочиту:

    Донорство
  • Як заспокоїти зубний біль і чому самолікування може призвести до операції?

    Раптовий біль у зубі може призвести до порушення сну та ускладненого вживання їжі. Люди часто приймають таблетки, роблять полоскання або лікуються народними засобами. В таких випадках не варто відкладати візит до стоматолога, адже можуть з’явитись ускладнення.

    Теми цього довгочиту:

    Стоматологія
Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Сергій Барабаш
Сергій Барабаш@sbarabash

5Довгочити
759Перегляди
13Підписники
На Друкарні з 30 березня 2024

Більше від автора

Це також може зацікавити:

  • Java. Що я б запитував на технічному інтерв’ю?

    Десь вискочив OutOfMemory. Як взнати, що є причиною? Чи достатньо буде треддампу (в нас є декілька потоків, аутофмеморі міг викинутися в іншому потоці, який просто десь створив новий об'єкт, але реальна проблема в іншому потоці)?

    Теми цього довгочиту:

    Java
  • Exactly Once Delivery Semantics в Apache Kafka

    У статті розбираємо, як працює exactly-once delivery в Apache Kafka, які проблеми виникають у мікросервісній архітектурі та чому на практиці підхід At-Least-Once + Idempotent Consumer часто простіший і ефективніший за повноцінний exactly-once.

    Теми цього довгочиту:

    Kafka

Коментарі (0)

Підтримайте автора першим.
Напишіть коментар!

Це також може зацікавити:

  • Java. Що я б запитував на технічному інтерв’ю?

    Десь вискочив OutOfMemory. Як взнати, що є причиною? Чи достатньо буде треддампу (в нас є декілька потоків, аутофмеморі міг викинутися в іншому потоці, який просто десь створив новий об'єкт, але реальна проблема в іншому потоці)?

    Теми цього довгочиту:

    Java
  • Exactly Once Delivery Semantics в Apache Kafka

    У статті розбираємо, як працює exactly-once delivery в Apache Kafka, які проблеми виникають у мікросервісній архітектурі та чому на практиці підхід At-Least-Once + Idempotent Consumer часто простіший і ефективніший за повноцінний exactly-once.

    Теми цього довгочиту:

    Kafka