Everything was going according to plan.
We had modeled the entire system. The architecture was solid. CQRS, fully event-sourced, clean boundaries between read and write sides. The team was confident. We could see the finish line.
Then came the Mike Tyson moment.
âEverybody has a plan until they get punched in the mouth.â
The Punch
Our business expert walked in with those three dreaded words: âOne more thing.â
We need templating.
Not just simple templating - users need to upload their own templates and fill them with⌠basically everything from the project. Customer data, addresses, project details, status information. Everything.
For a second my heart sank. I´ve been there before.
The Trap Every Developer Falls Into
Hereâs the thing about templating requirements - they expose a fundamental tension in your architecture.
Templates need access to {customer.email}, {billing.address}, {project.status} - they need to reach into every corner of your system. And if you approach this wrong, you introduce the mother of all coupling.
The worst thing you could do? Just read from every table you have to fill those templates.
Itâs so tempting. The data is right there. Your read side has nicely populated relational tables with all the information you need. Just query them, fill the template, done.
And that´s exactly what was suggested by one developer. âNo problem, we have all data..â
But the moment you do that, everything is glued together like concrete.
When we change the structure for the dashboard slice for a new UI feature, the templates break mysteriouslyâŚ
Your project just failed. You maybe just didnât realize it yet.
Why The âEasyâ Solution Kills Your Architecture
We had built a fully CQRS-enabled system. The backend takes write requests and asynchronously populates relational tables used by the client. Write side and read side are completely decoupled.
Those tables on the read side? Theyâre tailor-made for specific slices - the dashboard UI, the reporting screen, the admin panel. Each one is owned by a specific feature, optimized for a specific purpose.
When the developer on our team saw all these populated tables, their instinct was immediate: âPerfect, letâs just query these for the template data.â
I had to stop them.
âWe canât touch those tables. Theyâre owned by specific UI slices. If we start querying them for templating, weâve just coupled our template system to UI concerns. The whole architecture collapses.â
Iâd explained CQRS separation before. Iâd gotten the nods, the âyes, yesâ acknowledgments. But this time was different.
This time, with a real feature, a real deadline, and those tables RIGHT THERE - so easy to just query - something clicked.
âOh my god, you are right! I get itâ
The Solution: Treat Every Variable As A Mini-Slice
Hereâs how we solved it in just four hours.
We had one concrete first use case: a user needed to create a prefilled onboarding document - a PDF sent to clients by mail, filled with customer data, addresses, and account information.
Instead of querying existing tables, we built dedicated read models. One for every template variable.
Letâs say we have a {client.address} variable. We create one read model that listens to:
- ClientAdded
- ClientDeleted
- AddressAttached
The read model doesnât just store the data - it delivers a pre-rendered string ready to drop into the document. When the template engine hits {client.address}, it gets â123 Main St, Berlin, 10115â - formatted, ready to go.
The template renderer? Dead simple. Just string replacement. No logic, no formatting rules, no decisions.
The Beautiful Part: Infinite Extensibility
What happens when they come back and say âactually, we need the address formatted differently for international clientsâ?
Then itâs just another read model. Clearly laid out in the Event Model, verified using Given / When / Thens.. Business as usual. We always work like that.
{client.address.international} - new read model, listening to the same events, different formatting logic. Done.
No refactoring. No risk of breaking existing templates. No database schema changes. No coupling.
Right now, most of our variables read directly from the event stream. But if performance becomes an issue for a specific variable, we can easily change that one to precompute data and store it as JSON for rendering.
Each variable can evolve independently based on its own needs.
Why This Converted A CRUD Believer
We´ve built this whole solution in just four hours. Modeling the âTemplate-Variable-Slicesâ, and then just implement them one by one.
In the meantime - one developer implemented the Rendering Slice - just take those Read Models and apply them to a template.
They saw the pattern:
- Every variable is its own mini-slice
- Each read model has clear given-when-then behavior
- Adding a new variable follows the exact same pattern
It wasnât magic. It was teachable, repeatable, and predictable. As always.
They could see exactly how to add the next variable without me holding their hand.
Thatâs the real power of event sourcing - being able to answer tomorrowâs questions today. Because we captured all data as events, we can easily access it to build anything tomorrow we didnât know we needed today.
These Requirements Will Come
Templating is just one example. But requirements like this - the ones you couldnât plan for, the ones that need access to everything - they will come. Dashboards, Reporting.. you name it.
This is when your architecture truly proves itself.
Not when everything goes according to plan. But when you get punched in the mouth, and youâre still delivering.
Join the Agentic Engineer Program
Apply Spec-Driven Development Hands-On - Event Modeling, Event Sourcing, and AI Engineering with autonomous agents.
Book a Call Today
Want to talk through how Event Modeling could work for your team or project? Letâs have a quick, no-pressure conversation.
