arrow_back All work

Case Study 02

A reusable event-publishing framework

Abstracting Kafka + Debezium CDC into a framework so teams publish domain events reliably without re-inventing outbox plumbing.

My role

Sole designer & author (graduation project)

Context

Albert Heijn · Feb–Jul 2025

Stack

KotlinSpring BootApache KafkaDebeziumDDD

Context

In an event-driven microservices estate, services need to publish domain events when their state changes, and those events have to be reliable: never lost, never published for a change that was later rolled back. The standard answer is the transactional outbox pattern, which isn't hard to get right once. The catch was that every team was solving it on its own, some correctly and some subtly wrong.

This was my graduation project: design and build a reusable framework that turns reliable event publishing into a solved, drop-in concern instead of bespoke plumbing per service.

The problem

The obvious approach, write to the database and then publish to Kafka, has a dual-write failure mode: the transaction commits but the publish fails (or vice versa), and now the event stream disagrees with the source of truth. Each team solved this independently, which meant:

Options considered

Application-level outbox relay

Each service writes to an outbox table in the same transaction as its business change, and a relay process reads the table and publishes to Kafka. Correct, but every service still owns the relay, the polling, and the failure handling, so the duplication problem stays.

Publish directly, accept eventual reconciliation

Skip the outbox and reconcile drift after the fact. Rejected: reconciliation is harder to reason about than prevention, and it weakens the very guarantee the framework exists to provide.

Change-Data-Capture via Debezium, abstracted into a framework

Let the database's commit log be the source of events: Debezium captures committed changes from the outbox and streams them to Kafka, so a change is published if and only if it committed. Wrap the outbox contract, serialization, and topic conventions in a framework teams adopt rather than rebuild.

The decision, and why

I built the framework around CDC with Debezium over a transactional outbox, packaged for Kotlin / Spring Boot services and designed with DDD boundaries so events map cleanly to domain concepts. The reasons:

Honest attribution

This one is cleanly mine: sole designer and author, end to end, as my graduation project. The thesis was graded 8.5, and the framework's reuse across the estate was estimated to save on the order of 16 developer-years of duplicated outbox work. That's a projection, but a grounded one given the number of services that would otherwise build this themselves.

Outcome

Reliable event publishing became a drop-in concern. Instead of each team designing, building, and debugging its own outbox-and-relay, they depend on a single framework that closes the dual-write hole for them. The headline estimate of ~16 developer-years saved comes from the duplicated effort it removes across the microservices estate.

Reflection

The shift that interested me was one of framing. The valuable output wasn't the code that publishes one event; it was removing a decision from every future service. Designing for adoption (a small contract, sensible defaults, DDD-aligned boundaries) mattered more than any single technical trick. This is the project that pointed me most clearly toward platform and solution-architecture work.

Outcome

Estimated ~16 developer-years saved org-wide. Thesis graded 8.5.

More case studies Get in touch arrow_outward