In Microservice-Architekturen müssen Datenänderungen oft an mehrere Dienste propagiert werden — ein neuer Kunde, eine aktualisierte Bestellung, eine stornierte Buchung. Anstatt jeden Dienst aktiv die Datenbank pollen zu lassen, nutzt Change Data Capture (CDC) die Write-Ahead-Logs der Datenbank selbst als Event-Quelle. Debezium ist die führende Open-Source-CDC-Plattform, die Datenbanken wie PostgreSQL, MySQL, MongoDB und SQL Server in Echtzeit an Apache Kafka streamt.

Das CDC-Prinzip

Traditionelle Synchronisationsansätze pollen in regelmäßigen Abständen die Datenbank:

Dienst A --> [Poll: SELECT * FROM bestellungen WHERE updated > ?] --> Datenbank
Code-Sprache: CSS (css)

Das belastet die Datenbank und liefert Änderungen nur mit Verzögerung. Debezium hingegen liest das Write-Ahead-Log (WAL) der Datenbank:

PostgreSQL WAL --> Debezium Connector --> Kafka Topic --> Consumer 1, 2, 3

Jede INSERT-, UPDATE- und DELETE-Operation wird sofort als Kafka-Event publiziert — ohne die Source-Datenbank zusätzlich zu belasten.

Architektur

Debezium setzt auf Kafka Connect als Laufzeitumgebung:

┌──────────┐     ┌─────────────────┐     ┌─────────┐
│PostgreSQL│────▶│Debezium Connector│────▶│  Kafka   │
│  (WAL)   │     │  (Kafka Connect) │     │  Topic   │
└──────────┘     └─────────────────┘     └────┬────┘
                                              │
                         ┌────────────────────┼─────────────┐
                         ▼                    ▼             ▼
                   Suchindex-         Analyse-         Audit-
                   Aktualisierung     Pipeline         Service

PostgreSQL-Connector konfigurieren

Der Debezium-Connector wird als JSON-Konfiguration an Kafka Connect übergeben:

{
  "name": "bestellungen-connector",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "plugin.name": "pgoutput",
    "database.hostname": "localhost",
    "database.port": "5432",
    "database.user": "debezium",
    "database.password": "geheim",
    "database.dbname": "shop",
    "topic.prefix": "shop_cdc",
    "table.include.list": "public.bestellungen",
    "publication.autocreate.mode": "filtered"
  }
}
Code-Sprache: JSON / JSON mit Kommentaren (json)

PostgreSQL benötigt dafür wal_level = logical in der postgresql.conf.

Events verarbeiten in Java

Jeder Datenbank-Change wird als Kafka-Event mit einer spezifischen Struktur publiziert:

{
  "before": null,
  "after": {
    "id": 42,
    "kunde_id": 100,
    "betrag": 99.90,
    "status": "NEU"
  },
  "op": "c",
  "ts_ms": 1718123456000
}
Code-Sprache: JSON / JSON mit Kommentaren (json)

Der op-Wert steht für c (create), u (update), d (delete) oder r (snapshot). In Java verarbeitet man die Events mit einem Kafka-Streams-Topologie oder einem einfachen Consumer:

import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.consumer.ConsumerRecords;

import java.time.Duration;
import java.util.List;
import java.util.Properties;

public class BestellungsConsumer {

    public static void main(String[] args) {
        var props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "bestellungs-service");
        props.put("key.deserializer",
            "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer",
            "org.apache.kafka.common.serialization.StringDeserializer");

        try (var consumer = new KafkaConsumer<String, String>(props)) {
            consumer.subscribe(List.of("shop_cdc.public.bestellungen"));

            while (true) {
                ConsumerRecords<String, String> records =
                    consumer.poll(Duration.ofMillis(100));
                records.forEach(record -> {
                    System.out.printf("Topic: %s, Key: %s, Value: %s%n",
                        record.topic(), record.key(), record.value());
                });
                consumer.commitSync();
            }
        }
    }
}
Code-Sprache: JavaScript (javascript)

Das Outbox-Pattern

Ein verbreitetes Muster in Kombination mit CDC ist das Outbox-Pattern: Statt eine Nachricht direkt in Kafka zu publizieren, schreibt der Service ein Event in eine Outbox-Tabelle — innerhalb derselben Datenbank-Transaktion. Debezium streamt die Outbox-Tabelle dann nach Kafka:

<em>-- Transaktionale Outbox-Tabelle</em>
CREATE TABLE outbox (
    id UUID PRIMARY KEY,
    aggregatetype VARCHAR(255) NOT NULL,
    aggregateid VARCHAR(255) NOT NULL,
    type VARCHAR(255) NOT NULL,
    payload JSONB NOT NULL,
    timestamp TIMESTAMP DEFAULT NOW()
);
Code-Sprache: PHP (php)
<em>// Innerhalb einer @Transactional-Methode</em>
bestellungsRepo.save(bestellung);
outboxRepo.save(new OutboxEvent(
    UUID.randomUUID(),
    "Bestellung",
    bestellung.getId().toString(),
    "BestellungErstellt",
    toJson(bestellung)
));

<em>// Debezium erwartet den Outbox Event Router SMT:</em>
<em>// transforms=outbox</em>
<em>// transforms.outbox.type=io.debezium.transforms.outbox.EventRouter</em>
Code-Sprache: JavaScript (javascript)

So ist garantiert, dass die Bestellung und das dazugehörige Event atomar persistiert werden — ohne verteilte Transaktionen (2PC).

Embedded Debezium Engine

Für Anwendungen, die kein vollständiges Kafka-Cluster betreiben wollen, bietet Debezium eine Embedded Engine:

var engine = DebeziumEngine.create(Connect.class)
    .using(props)
    .notifying(record -> {
        <em>// Event-Verarbeitung direkt in der JVM</em>
        System.out.println("Change: " + record.key() + " -> " + record.value());
    })
    .build();

ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(engine);
Code-Sprache: JavaScript (javascript)

Die Embedded Engine läuft im selben Java-Prozess und verzichtet auf Kafka Connect — ideal für Tests und Single-Service-Szenarien.

Fazit

Debezium bringt CDC in die Java-Welt und entkoppelt Datenänderungen von ihrer Verarbeitung. In Kombination mit dem Outbox-Pattern entstehen zuverlässige, transaktionssichere Event-getriebene Architekturen. Für Java-Entwickler, die mit Microservices und asynchroner Kommunikation arbeiten, ist Debezium ein unverzichtbares Werkzeug im Werkzeugkasten.