Notes from production.
Ten years of building, written down.
How I design, migrate and debug distributed systems — with interactive diagrams you can poke at, and a meme at the end of most posts, drawn in code.
Ten years of building: the patterns that keep repeating
SMB commerce, an e-commerce marketplace, a travel platform, an AI startup, cloud infrastructure — different domains, the same lessons. A map of everything I've written, and what I'd tell my 2015 self.
Built in the open
Projects I built, and what they taught me. Write-ups of my open-source projects: the design, the trade-offs, the code.
Building an annotation-driven MCP server in Java
Exposing a Java method to an AI agent should take one annotation, not a protocol implementation. How I built java-mcp-server on Spring Boot 3, and what I'd change next.

2026GraphQL Federation with Java subgraphs, without the N+1
Three Spring Boot services, one graph, one query. How entity resolution, DataLoaders and resolver timing work in a federated Java setup — and the N+1 I left in on purpose to find.

2026One dependency for logs, traces and metrics: writing a Spring Boot starter
Every service wires observability slightly differently, and the differences show up during incidents. How I packaged logging, tracing and metrics into one Spring Boot starter — and three gaps a code review of my own starter found.
2026YT Studio AI: wiring Vision, NLP and LLMs into a creator tool
Score a thumbnail and title before you upload. How I combined deterministic Google Cloud APIs with Gemini and Claude in a Spring Boot + React app, why facts go in before opinions, and what I'd change.
Three small tools, three lessons
A vaccine-slot checker from 2021, a WebSocket chat on a free tier that no longer exists, and a Docker setup that streams five videos to YouTube at once. Small projects, each with one lesson that applies to big systems.
Reliability
When the network lies, retries pile up, and queues back up. Making distributed systems behave when networks, retries and queues misbehave.
Idempotency keys: making retries safe for money
A request that times out may still have succeeded. If the client retries a refund, you pay twice. How idempotency keys work, the edge cases that bite, and a Spring + Postgres implementation.
Retries without a retry storm: timeouts, backoff, jitter and circuit breakers
Retries turn short failures into successes — and, done carelessly, turn a one-minute blip into a long outage. The four tools that keep retries helpful, with Resilience4j code.
The transactional outbox: stop losing events between your database and Kafka
"Save to the database, then publish to Kafka" loses events whenever the process dies in between. The outbox pattern fixes it with one extra table — here's how to build it, relay it and live with its trade-offs.
Kafka consumers in production: ordering, poison messages and lag
Most Kafka incidents are consumer incidents. Partition keys and ordering, at-least-once processing, poison messages and dead-letter topics, rebalances, and the one metric to alert on.
Change at scale
Changing systems while they keep running. Changing running systems without downtime: migrations, monolith breakups, workflows.
Zero-downtime schema and data migrations with expand–contract
Renaming one column without downtime takes five deploys, and that's fine. The expand–contract pattern step by step: dual writes, batched backfills, verification, switching reads, and rolling back at every stage.
The strangler fig in practice: carving a service out of a monolith
Big-bang rewrites stall; carving out one capability at a time works. How to pick the first slice, route traffic at the edge, prove the new path with shadow traffic, move data ownership — and know when to stop.
Durable workflows vs. a queue and a cron job
If your job has more than three steps and must survive restarts, you're writing a workflow engine by hand. What queue + cron + status table turns into, what a durable workflow engine like Temporal gives you, and when it's overkill.
Model business workflows as state machines
Many 'weird bugs' in order, refund and tenant systems are illegal state transitions. Make states and transitions explicit, enforce them in the database with a conditional write, and keep an audit trail.
Choose a database by access pattern, not by hype
List your queries first; the database choice mostly falls out. A practical guide to relational, key-value, document, search, graph and warehouse stores — with one real query for each — and when to use two.
Staff-level judgment
The half of the job that isn’t code. The non-code half of senior engineering: debugging, design docs, debt, platforms.
Debugging production: a method, not a hunch
The fastest production debuggers aren't the ones who know the most. They stop the bleeding first, build a timeline, ask what changed, and narrow the search by halves. A repeatable method for incidents.
Design docs that get approved
A design doc is a decision record, not an essay. A one-page structure that gets to a decision quickly: context, goals and non-goals, options with trade-offs, the decision, risks and rollout — plus how to run the review.
Tech debt as a portfolio: how to get it paid down
"We need a refactor sprint" rarely gets approved; "this costs us two days every release" does. How to classify debt, measure its interest, pick what to pay first, and bundle paydown with work the business already wants.
Self-serve platforms: turn your team from a queue into a product
If every request to your team becomes a ticket, your team is the bottleneck. How to find the requests worth automating, build guardrails so non-experts can run them safely, and measure whether self-serve actually worked.
AI-assisted engineering: what actually stuck
Claude Code, Cursor and MCP servers changed how I write tests, review code and explore unfamiliar systems — and changed nothing about who makes design decisions. Where AI tools help, where they don't, and the guardrails that make them safe on a team.