captain speakingLadies and gentlemen, this is your captain speaking. Welcome aboard. Please be advised that this aircraft is being rebuilt during the flight. Sit back and relax — our cabin crew is ready to ASSIST™ you. Cabin crew, arm doors and cross-check.
The usual story
The terrifying part is getting there.
No maintenance window long enough. No big-bang rewrite. No moment to stop the world.
in production · every day · today
events / day
commands / day
queries / day
services
instances
processing groups
business users
customers a day
Twelve intermediate architectures. Every one of them had to fly.
The browser was the backend.
illustrative · the browser, orchestratinglist = awaitget(
"/appointments")
for (a of list)
a.customer = awaitget(
`/sessions/${a.id}`)
awaitpost("/session")
awaitpost("/appointment")
// …and if the second one fails?
The browser wasn’t really a frontend. It had become the integration platform.
the NO_TRANSACTION dragon
One business action, several CRUD calls — nothing held them together.
Phase 0 · Control the chaos
Browser-as-Backend
Starting state · before the web BFF (2021)
captain speakingLadies and gentlemen, the CRUD swamp is now being left behind. Some turbulence is to be expected — please keep your seatbelts fastened while the seatbelt sign is illuminated. Should help be needed, the crew is standing by to ASSIST™ you.
Put a backendin the middle.
Mar 2022 · an entire service methodreturnaccountsConnector
.postPreRegister(…);
It wasn’t the new architecture yet. It was the place where the new architecture could start.
BFF: not “best friends forever”.Backend for Frontend — a protocol stereotype service.
the NO_TRANSACTION dragon
The same CRUD calls through the new door: still no transaction, and the third can still get lost.
Phase 1 · Control the chaos
Gateway BFF
Git · Nov 2021 BFF initial commit (14fe5c3de) · Mar 2022 “BFF for WebFE” (633555af7)
CRUD starts producing events.
REST → service → JPA commit
@PostPersist→CREATED@PostUpdate→MODIFIED@PostRemove→DELETEDafterCommit() → SNS
First our events described what changed. Later they started describing why.
the OUT_OF_ORDER dragon
Ordering mattered within two weeks: FIFO message groups, May 2022.
Phase 2 · Control the chaos
CRUD + Change Events
Git · May 2022 · f4ada88e0 · “Send SNS notifications when an appointment is created/updated/deleted”
We built projectionsbefore we calledthem projections.
before fan-out at request time:
config → resources → appointments
→ a customer session per appointment
after one query:
/projections/appointments/search
Read models before Event Sourcing. CQRS doesn’t require Event Sourcing — the write side was still CRUD.
the READ_MODEL dragon
Read models bring their own problems: projection deadlocks, May 2024.
captain speakingLadies and gentlemen, the chaos has been left behind and cruising altitude is being reached. Local state and messaging can be seen on your left. Cabin crew, you are released to ASSIST™ with the in-flight service.
JPA afterCommit→Axon command → aggregate → event↘ SNS, still running
each path behind its own feature flag
Yes, there were aggregates. Their replay restored exactly one field: the ID.
the FILTER_CONFIG dragon
Every release shipped new filter config; every rollback had to undo it. We never found a forward/backward-compatible way.
Phase 5 · Local state & messaging
Axon Message Bus
Git · Jul – Aug 2023 · d45e981a1, fe6355276, d67519a83 “Replace SNS/SQS with Axon”
From technical APIsto domain conversations.
beforePATCH key-drop document { …fields… }
worsePUT { the whole record } → diff it
afterSetParkingAccessCodeUploadKeyDropMediaRemoveKeyDropMediawhereno URL — the handlers register
We stopped making services talk HTTP to each other. We started making them speak the domain language.
the GUESS_THE_INTENT dragon
A PATCH says which fields changed. A PUT overwrites the whole record: diff old and new, then guess the intent.
Phase 6 · How the system speaks
Command & Query
Git · Mar – Nov 2024 · 8ce2a442d, 22d65d389, 96967f0de, d18d9db6a
captain speakingLadies and gentlemen, for the next stretch a new language will be spoken on board: domain conversations. Please refer to the safety card in the seat pocket in front of you; translations will be provided by ASSIST™.
Every change, told twice.
POST / PUT / PATCH / DELETE→command → domain event+ CDC event, emitted explicitly
JPA afterCommit→ retired, service by service
the same change, twice · on purpose
Every event processor moves from CDC to domain events on its own — still today.
the TOLD_TWICE dragon
150+ processing groups to migrate, one by one — still in progress.
Phase 7 · How the system speaks
Dual Stream
Git · Aug 2023 – Nov 2024 · d67519a83, f9df230d2, d18d9db6a
Intent goes in.State changes come out.
Feb 2024 · the notification pathprojection
→ query update
→Axon subscription query→WebSocket BFF→ browser
Finally out of the 1:1 request–response paradigm.
Progressively, everything moves to WebSocket.
the LONG_POLLING dragon
Long polling for notifications: long requests hurt Apdex, and it was ugly.
Phase 8 · How the system speaks
Split Front Door
Git · Dec 2023 24d559197 (scaffold) · Feb 2024 9f12aa68f, 611d3e3bd (subscriptions)
Every deployable gets an explicit job.
Feb 2024 · one image, two rolesspring profile:commandspring profile:query
We stopped treating every deployment unit as a unique snowflake.
Spring profiles: not elegant — just easy.
the HALF_AND_HALF dragon
HTTP and Axon, both ways, in one service: deadlocks. Duplicated services: inconsistent code.
Phase 9 · Make it explicit
Building Blocks
Git · Feb 2024 5dc5bdc20 · Apr 2024 9aaab10f8 · Oct 2025 ace89af40 · Jul 2026 0a5a8fb82
captain speakingLadies and gentlemen, our approach to Event Sourcing has been initiated. Tray tables are to be stowed and seat backs returned to the upright position — the crew will ASSIST™ you. Cabin crew, prepare for landing.
Projections patched by hand over the years no longer match their events.
Phase 10 · Make it explicit
Event-Sourced Core
Git · Apr 2025 c5932fced · Aug 2025 69606607e
You can’t replaywhat was never written.
projection: rows from before the events
event store: starts later
import batch →…Imported events// ≈ 20 million of them
replay → the whole story
First we wrote the history down. Then we could replay it.
the WIPE_AND_REPLAY dragon
A replay would have quietly dropped everything older than the first event.
Phase 11 · Make it explicit
Full-History Event Store
Git · Jan 2026 · a727e8da0 · import support for pre-Axon data
The payoff
The behaviour is already there. It is the event stream.
Once meaningful state changes are first-class events, a new capability is often just a new reader.
Every event processor has its own cassette — a copy of an Axon context, at its own place. Each one replays on its own.
R: real event names from the codebase · L: their CDC twins, illustrative · the herd: illustrative
captain speakingLadies and gentlemen, our destination has been reached — but no one is permitted to disembark. Please remain seated with your seatbelt fastened: the rebuild is being continued by ASSIST™. Cabin crew, doors remain armed.
Reality check
It was never a straight line.
Different parts of the system moved at different speeds. The phases overlapped — the order is a story, not a schedule.
Not a project, a process: driven by the business, changing as new needs arise — never an ivory tower.
Each dot is a selected commit anchoring a phase · Git author dates, not deployment dates · phase 0 predates this repository
Closing
None of these wasthe target architecture.Every one of themwas a production architecture.
We never migrated the application to Event Sourcing. We continuously evolved it into an event-sourced system.
captain speakingLadies and gentlemen, thank you for flying with us. This aircraft is being kept in service. Should you need anything, our crew will be happy to ASSIST™ you. Cabin crew, disarm doors and cross-check.
01 / 17v238 · c03d8dc · 2026-10-28 07:30 UTC
The End
Navigation
→ · Space · PgDn · ▶
Next (▶: the “Press → for next level” button, bottom right)
← · PgUp
Previous
Home · End
First · last slide
O
Spatial overview (click a slide to go there)
N
Speaker notes
H
Notes long · short (short: one hint per paragraph) — in the notes panel and the presenter view
R
Replay this slide’s animation
RRR
…and the walking animal starts over (rehearsals)
M
Light · dark mode
0
The map (slide 0): back to the start — clock and flyer rewound; Next takes off at slide 01
1 – 17
Go to that slide — two digits typed quickly: 1 then 4 is slide 14
S
Restart the 30-minute clock — asks first: dragon yes, unicorn no; S again closes
P
Pause / resume the 30-minute clock (the radar blinks while paused)
F
Fullscreen
V
Presenter view in a second window: now, next, notes, clock — the two windows follow each other