Microservices / Data Consistency

    Transactional Outbox Pattern

    Rozwiązanie problemu dual write — atomowo zapisz event z danymi, opublikuj na Kafka przez Debezium CDC, zapewnij exactly-once przez Inbox Pattern.

    Outbox + Tx
    Dual write fix
    Debezium WAL
    CDC bez pollingu
    Inbox Pattern
    Exactly-once
    Spring Modulith
    Spring wbudowane

    6 wzorców zapewnienia spójności danych

    Od prostego Outbox po Event Sourcing i Kafka Transactions — każde podejście oferuje inny trade-off między złożonością a gwarancjami.

    Wzorzec Gwarancja Latencja Overhead Kiedy
    Transactional Outbox At-least-once publish Sub-second (CDC) / Sekundy (polling) Dodatkowa tabela + relay Mikrousługi z DB + message broker
    Inbox Pattern Exactly-once consume Brak dodatkowej Dodatkowa tabela At-least-once broker, krytyczne operacje
    Debezium CDC At-least-once capture Sub-second (WAL) Kafka Connect cluster PostgreSQL/MySQL -> Kafka pipelines
    Event Sourcing Exactly-once (wbudowane) Sub-second Zmiana architektury Nowe systemy, audit log wymagany
    Kafka Transactions Exactly-once end-to-end Wyższa (2-phase) Złożoność + niższy throughput Kafka Streams, krytyczne pipelines
    Temporal Workflows Durable execution Zależy od workflow Temporal Server Long-running, multi-step workflows

    Często zadawane pytania

    Co to jest Transactional Outbox Pattern i jaki problem rozwiązuje?

    Problem dual write: aplikacja musi zapisać dane do DB i wysłać event do message broker (Kafka, RabbitMQ). Jeśli zapis do DB powiedzie się ale broker jest niedostępny — event zgubiony. Jeśli broker dostępny ale DB fail — niespójność. Rozwiązanie — Transactional Outbox: 1. Zamiast wysyłać event bezpośrednio — zapisz event do tabeli 'outbox' w tej samej transakcji co główne dane. 2. Osobny Message Relay czyta z outbox i publikuje do brokera. 3. Po potwierdzeniu przez broker — oznacz event jako published. Gwarancja: event jest zapisany atomowo z główną zmianą. Nie ma dual write. At-least-once delivery (event może być opublikowany wielokrotnie — wymaga idempotentnych konsumentów). Outbox table schema: id, aggregate_type, aggregate_id, event_type, payload, created_at, published_at. Message Relay implementacje: Polling Publisher: prosty, odpytuje outbox co N sekund. Wada: latencja i obciążenie DB. Change Data Capture (CDC): efektywniejsze, używa log DB do wykrywania zmian. Debezium: open-source CDC connector dla PostgreSQL, MySQL, MongoDB, SQL Server. Czyta transaction log (WAL w PostgreSQL) — zero overhead na aplikacji.

    Debezium CDC — jak działa Change Data Capture?

    Debezium: open-source platforma CDC (Confluent/Red Hat). Działa jako Kafka Connect connector. Czyta Write-Ahead Log (WAL) PostgreSQL, binlog MySQL, oplog MongoDB. Żadnego odpytywania — event-driven, sub-second latencja. Architektura: Debezium Connector (Kafka Connect plugin) -> DB transaction log -> Kafka topic. Konfiguracja PostgreSQL: wal_level = logical. Replikacja slot dla Debezium. Konfiguracja connectora: database.hostname, port, user, password. database.server.name — prefix dla topic names. table.include.list — które tabele śledzić. Kafka topics: {server-name}.{schema}.{table} per tabela. Każda zmiana (INSERT/UPDATE/DELETE) jako event. Format eventu: before (stare dane) i after (nowe dane). Outbox Event Router (Debezium SMT): Single Message Transform który routuje eventy z outbox tabeli. Routing na podstawie aggregate_type i event_type. Envelope: after.event_type -> Kafka topic name. Zalety CDC vs Polling: Zero overhead na aplikacji (czyta log). Real-time (sub-second latencja). Łapie wszystkie zmiany (nawet bezpośrednie SQL). Wady CDC: złożoność setup. Zmiany schematu DB mogą zepsuć CDC. Wymaga PostgreSQL logical replication. Zarządzanie replication slots (nie mogą rosnąć bez końca).

    Inbox Pattern — jak zapewnić exactly-once processing?

    Inbox Pattern: odpowiednik Outbox dla consumerów. Problem: consumer dostaje wiadomość z brokera, przetwarza ją i zapisuje do DB. Jeśli aplikacja crashuje po zapisie DB ale przed commit offset — wiadomość zostanie przetworzona ponownie (at-least-once). Rozwiązanie: Inbox table — przed przetworzeniem zapisz wiadomość do inbox tabeli (w tej samej transakcji). Sprawdź czy message_id już w inbox — jeśli tak, pomiń (already processed). Inbox table schema: message_id (PK), topic, partition, offset, payload, processed_at. Gwarantuje exactly-once processing (idempotent consumer przez DB check). Wada: dodatkowy DB write per message, wzrost tabeli. Cleanup: usuń stare inbox entries (soft delete lub TTL). Idempotency Key: alternatywa do inbox — consumer generuje idempotency key (np. message_id) i sprawdza w Redis lub DB przed przetworzeniem. Pattern Transactional Outbox + Inbox razem: Producer używa Outbox (atomowy zapis + publikacja). Consumer używa Inbox (exactly-once processing). Razem = end-to-end exactly-once w distributed systemie (przy at-least-once brokerze). Kafka z transakcjami: Kafka producers z enable.idempotence = true. Kafka transactions (beginTransaction, commitTransaction). Exactly-once streams z Kafka Streams. Ale: droższe, niższy throughput, złożoniejsze.

    Jak zaimplementować Outbox Pattern w Springu?

    Spring Boot Outbox implementacja: Entity: Order (główna encja). OutboxEvent (tabela outbox). OrderService: @Transactional. 1. save(order). 2. outboxRepository.save(new OutboxEvent(order)). Jeden commit = oba zapisy atomowo. Polling Publisher (prosty): @Scheduled(fixedDelay = 1000). Pobierz niepublikowane eventy. Publish na Kafka. Oznacz jako published. Problem: race condition przy wielu instancjach. Rozwiązanie: SELECT FOR UPDATE SKIP LOCKED — pobierz i zablokuj. Jednocześnie tylko jedna instancja przetwarza. Spring Modulith Outbox: Spring Modulith 1.1+ ma wbudowane Event Publication Registry. @ApplicationModuleListener — automatycznie przechowuje eventy. Automatyczny retry. Transactional Domain Events. Debezium + Spring: nie wymaga Polling Publisher. Debezium czyta z WAL. Dodaj connector konfigurację do Kafka Connect. Outbox Event Router SMT automatycznie routuje. Axon Framework: Event Sourcing + CQRS + Saga + Outbox wbudowane. Axon Server jako event store i message broker. Produkcyjne przykłady: Eventuate Tram (Chris Richardson) — biblioteka do Saga + Outbox dla Java/Spring. Outboxer (Python). Transactional Outbox dla Go. Inbox/Outbox dla .NET (MassTransit).

    Jakie są alternatywy dla Outbox Pattern?

    Alternatywa 1 — Event Sourcing zamiast Outbox: zamiast osobnej outbox tabeli — aplikacja zapisuje eventy jako primary store. Event Store = outbox. Eventy czytane przez projektory i publishowane do brokera. EventStoreDB, Axon Server, Marten (PostgreSQL as event store). Wada: zmiana paradigmatu — duże refaktoryzacje istniejących aplikacji. Alternatywa 2 — Idempotent Producer: accept dual write. Przy failurze — retry z tym samym idempotency key. Kafka enable.idempotence = true — broker odrzuca duplikaty per producer session. Wada: nie gwarantuje spójności przy crash przed wysłaniem. Alternatywa 3 — Saga Orchestration z Temporal: Temporal persystuje stan workflow. Retry automatyczny przy failurze. Brak potrzeby osobnego outbox — Temporal zarządza durable execution. Wada: nowy komponent (Temporal Server), zmiana architektury. Alternatywa 4 — Kafka jako transaction log: traktuj Kafka jako source of truth. Aplikacja zapisuje tylko do Kafka (log-centric architecture). Brak DB jako primary store. Wada: trudne zapytania ad-hoc, streaming-first architecture. Wybór: prostość + istniejąca DB -> Outbox + Debezium. Event-driven new system -> Event Sourcing. Złożone long-running workflows -> Temporal. Kafka-native architecture -> Log-centric.

    Czytaj dalej

    Powiązane artykuły

    Kontakt

    Skontaktuj się z nami

    Porozmawiajmy o Twoim projekcie. Bezpłatna wycena w ciągu 24 godzin.

    Wyślij zapytanie

    Bezpłatna wycena w 24h
    Bez zobowiązań
    Indywidualne podejście
    Ekspresowa realizacja

    Telefon

    +48 790 814 814

    Pon-Pt: 9:00 - 18:00

    Email

    adam@fotz.pl

    Odpowiadamy w ciągu 24h

    Adres

    Plac Wolności 16

    61-739 Poznań

    Godziny pracy

    Pon - Pt9:00 - 18:00
    Sob - NdzZamknięte

    Wolisz porozmawiać?

    Zadzwoń teraz i porozmawiaj z naszym specjalistą o Twoim projekcie.

    Zadzwoń teraz