Free · Hands-on · Java + Apache Kafka

Learn Event-Driven Architecture
by watching it happen.

A real, runnable system — five Java services trading events through Kafka to fulfil an order — built so you learn EDA by breaking things and watching them recover, not just by reading definitions.

5
Java services
10
Hands-on labs
4
Learning levels
~2h
Total hands-on time

What is Event-Driven Architecture?

The short version: instead of services calling each other and waiting for an answer, services publish facts about things that already happened — and whoever's interested reacts, on their own time.

In a typical request/response system, Service A calls Service B directly and blocks until it gets an answer. If B is slow or down, A's request fails right then — the caller has to decide what to do about it.

In an event-driven system, Service A publishes an event — "OrderCreated" — to a durable, ordered log (that's what Kafka is) and moves on. It doesn't know or care who's listening. Service B reacts whenever it gets to it, even if that's 20 seconds later because it was temporarily offline. Nothing is lost — the message just waits.

That one shift — from "call and wait" to "publish and move on" — is what gives event-driven systems their defining properties: services fail and recover independently, scale independently, and can be added or removed without anyone else's code changing.

Request/response vs. events

Coupling Caller knows the callee exists and calls it by name.
Events Publisher doesn't know — or care — who's listening.
Failure Callee down → the call fails right now.
Events Consumer down → the event waits. Nothing is lost.
Scaling Add more callers → the callee needs to handle more load, now.
Events Add more consumers → they just read the log at their own pace.

A real saga: order → payment → inventory → shipping

No central coordinator. Each service reacts only to events on the topics it subscribes to, and publishes its own — including two compensating paths when something downstream fails.

order-service saga entry point payment-service Kafka transactions inventory-service idempotent consumer shipping-service retry + DLQ OrderCreated PaymentCompleted InventoryReserved OrderShipped → saga complete PaymentFailed → OrderCancelled InventoryFailed → PaymentRefunded

This is choreography, not orchestration — there's no coordinator telling services what to do next, just each one reacting to the events it subscribes to.

Set up the environment

Everything runs in Docker. No IDE required to see it working, though you'll want one for the labs that ask you to edit code.

Prerequisites

Java 25+, Maven, and Docker with Docker Compose. That's the whole list.

Start a local Kafka broker

The project doesn't bundle its own broker — it joins one over an external Docker network. A minimal single-node KRaft broker (no ZooKeeper) plus a UI, in its own folder:

# kafka-local/docker-compose.yml
services:
  kafka:
    image: apache/kafka:latest
    container_name: kafka
    ports: ["29092:29092"]
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093,PLAINTEXT_HOST://:29092
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
      CLUSTER_ID: 4L6g3nShT-eMCtK--X86sw
  kafka-ui:
    image: provectuslabs/kafka-ui:latest
    ports: ["8080:8080"]
    environment:
      KAFKA_CLUSTERS_0_NAME: local
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
cd kafka-local && docker compose up -d

Clone the project

git clone https://github.com/nitinramachandran/EventDrivenArchitecture.git
cd EventDrivenArchitecture

Build and start the saga

Joins the Kafka network from step 2 and brings up all four always-on services.

docker compose build
docker compose up -d
docker compose logs -f    # watch it happen — a new order every ~4s

Watch it visually

Open localhost:8080 (Kafka UI) to browse topics, partitions, and consumer groups alongside the logs — then head into the labs below.

10 labs, 4 levels, about 2 hours

Each level has a clear "you'll walk away able to…" outcome and builds on the vocabulary from the one before it. Work through them in order the first time — after that, the repo's HANDS_ON_LABS.md doubles as a reference you can jump into anywhere.

01

Foundations

Labs 0–2·~25 min

Explain what makes EDA different from request/response, read a raw Kafka event envelope, and prove a consumer being offline doesn't lose data.

  • Get oriented — watch the saga run live
  • Read the raw log — the event envelope
  • Consumer groups, offsets, lag
02

Delivery guarantees

Labs 3–4·~30 min

Explain at-least-once vs. exactly-once precisely, make a handler safe against duplicates, and use Kafka transactions correctly.

  • Idempotent consumer — catch a live duplicate
  • Exactly-once semantics + producer fencing
03

Transactions & failure isolation

Labs 5–6·~25 min

Design a choreographed saga with compensating actions, and isolate a poison message without blocking the partition behind it.

  • Saga compensation, forced on demand
  • Dead-letter queue — inspect and replay
04

Event sourcing & resilience

Labs 7–9·~30 min

Tell event sourcing apart from event notification, see how partitions bound scaling, and watch the system survive a broker outage.

  • Event sourcing vs. notification
  • Partition rebalancing
  • Broker failure and recovery

Concepts worth being able to explain cold

The six ideas this lab is built to make you not just recognize, but have actually watched happen.

N/S

Event notification vs. event sourcing

One style signals a change; the other is the system of record, with state derived purely by replaying the log.

Idempotent consumers

At-least-once delivery is the default, not an edge case — see a duplicate arrive live, and the dedupe pattern that makes it safe.

Exactly-once semantics

What Kafka transactions actually guarantee (the Kafka-to-Kafka hop), and why that's not the same as "exactly once end to end."

Saga pattern + compensation

How a distributed "transaction" works without two-phase commit — each step commits independently, failures trigger explicit undo actions.

Dead-letter handling

Why a poison message needs its own topic instead of blocking everything behind it — and how to inspect and safely replay one.

Partitioning & scale

Parallelism inside a consumer group is capped at partition count — watch a 4th instance sit completely idle to see why.

Clone it and run Lab 0 in the next five minutes

Free, open, and built to be broken on purpose — that's how the labs work.

git clone https://github.com/nitinramachandran/EventDrivenArchitecture.git
cd EventDrivenArchitecture
docker compose build && docker compose up -d
docker compose logs -f
people have visited this tutorial

Send a short message

Found a bug, have a question, or just want to say hi? Leave a note below — 60 words max.

0 / 60 words