Як гарантувати доставку подій у мікросервісах? Реальний підхід через 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
Борат одобряє 😉
Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Сергій Барабаш
Сергій Барабаш@sbarabash

82Прочитань
1Автори
2Читачі
На Друкарні з 30 березня

Вам також сподобається

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

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

Вам також сподобається