Architecture
rest-service writes an order and relays it to email-service
without a direct call and without the producer ever writing to a broker. This page traces
that path end-to-end — the request, the transactional write, the CDC relay, the
idempotency check, and the trace that survives all of it — and then shows how it's
actually scheduled across a Kind cluster.
One HTTP request produces one transaction. Everything after that — the Kafka message,
the email, the trace — is a side effect CDC discovers later, not a call
rest-service makes directly.
rest-service never calls Kafka, and email-service
never calls rest-service — Debezium discovers the Outbox row by tailing
Postgres's write-ahead log and republishes it as a plain Kafka message (ADR 0001). The
idempotency check (ADR 0006) and the trace handoff (ADR 0005) are the two
concerns riding alongside that one transaction.
What each piece in the diagram above actually is.
Spring Boot · 2 replicas · NodePort 30081
Writes the Order and Outbox row in one transaction; owns the Idempotency-Key check against Redis.
StatefulSet · single instance
wal_level=logical for Debezium. Holds Order and Outbox — the Outbox table has no reader but the CDC connector.
Deployment · single instance · no persistence
Caches Idempotency-Key → orderId. Losing it on restart just reopens the dedup window.
KafkaConnect · single instance
Tails the Postgres WAL via pgoutput and republishes each Outbox row with the Outbox Event Router SMT.
Strimzi · 3 dual-role broker/controller pods
Replication factor 3, one broker per worker node. Single topic: outbox.event.order.
Spring Boot · 2 replicas · NodePort 30082
Consumes the topic and logs a confirmation, throttled by a blocking resilience4j rate limiter — lag growth under load is the intended effect.
single binary · local storage
Receives OTLP spans from both services and derives Grafana's service graph.
Prometheus Operator · NodePort 30390 / 30300
Scrapes broker, Connect, and app metrics. Grafana ships two pre-provisioned dashboards.
single instance · NodePort 30080
Browse topics, consumer lag, and connector status.
Built on Kind: 1 control-plane + 3 worker nodes. Pod anti-affinity is declared for exactly
three things — rest-service, email-service, and the Kafka
broker pool — everything else is a single instance the scheduler places freely.
Once ./up.sh finishes, every service is reachable at $NODE_IP:<port>.
| Service | Port |
|---|---|
| rest-service | :30081 |
| email-service | :30082 |
| kafka-ui | :30080 |
| grafana | :30300 |
| prometheus | :30390 |
| postgres (debug) | :30432 |