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.