I have been organizing books I read before. This one is Microservices Patterns.

Book cover of Microservices Patterns

The original is Microservices Patterns, by Chris Richardson.

My reason for opening it was very specific. I needed to do Contract Testing at the time — services talking to each other through contracts, and I needed a way to verify both sides matched without spinning up the whole environment. A senior architect at work said this book covers it, go read it. So I did. Contract testing is in there, chapters nine and ten. But after finishing the whole thing, what stayed in my head wasn't those two chapters. It was Saga and Outbox — and I read the book first, then later built two systems at work that used them: one pushing management commands down to Android devices, one an alerting system for 100K+ devices. That order made the gap between "understanding it" and "building it" very easy to see.

What is this book about? Everything that goes wrong after you split a system into microservices. The author runs a site called microservices.io with all 44 patterns listed. The site is fine for the table of contents, but the book is worth reading for the reasoning.

One thing I quite like about it: the author never sells microservices. He says up front that microservices trade operational complexity for deployment independence, and if your org isn't big enough to need that trade, don't split. The whole book runs on one example, a food delivery platform called FTGO, walking through the patterns one by one, spelling out the cost of each. The last chapter is about migrating off a monolith gradually — not a rewrite.

The real core of the book, I think, is a single constraint: Database per Service. Every service owns its database, nobody else touches it. Once that constraint lands, all the trouble starts. In a monolith, cross-module consistency was one database transaction. Now the data is spread across several databases, and ACID is gone. Everything hard in the later chapters — Saga, Event Sourcing, CQRS — is cleanup for this one consequence.

I always knew distributed transactions were hard, but that "hard" had honestly stayed fuzzy for me — I knew things would break, I just couldn't say what would break, or why. Chapter four was the first time anyone laid it out for me.

For a transaction that spans services, the instinct is two-phase commit: everyone votes, and only when everyone says yes do you commit together. The problem is that it makes every participant hold locks and wait for each other — one slow node stalls all of them, and the more participants the more fragile it gets. And things like Kafka don't support it anyway.

Saga's approach is to break one big transaction into a relay of local transactions. Each service does its step, commits its own database, and passes the baton. If a step fails midway, you walk back and cancel out what the earlier steps did with reverse operations — money already charged gets refunded. The book calls these compensating transactions. The price is isolation: every step's commit is immediately visible to the world, so others can see the intermediate state before the Saga finishes. ACID minus the I. The book's fix is to guard at the application layer — say, a PENDING status field acting as a lock, so anyone who sees PENDING knows this row isn't settled yet.

Later I built a real Saga. Our system issues a command, drops it into Kafka, the executor picks it up, calls Google's API, pushes the policy to the device, and the outcome gets reported back. The skeleton is exactly the relay from the book. But once the skeleton stood, everything left was stuff the book doesn't have.

What ate the most time was this: a command goes out and the result doesn't come right back. Sometimes it goes through a third party's asynchronous API, and the receipt flies back much later — while you have hundreds of commands in flight. Whose receipt is this? The book has the term correlation ID, filed under observability. But here it isn't an observability problem, it's whether the Saga can close the loop at all. I had to catch the third party's async replies and translate them back into my own outcome events before I could tie them back to the original command. That part took me a long time.

Then ordering. Two commands to the same device, executed in the wrong order, is an incident. The fix is using the device as the partition key, so one device's commands land in the same Kafka partition and stay ordered for free. The book barely touches this. And message versions — a message arrives with the wrong schema version, swallow or reject? I reject, because silently swallowing a message you don't understand is burying a landmine in your data.

There is also a kind of pit the book will never cover, because it isn't a design problem. I fixed a goroutine leak where a lock wasn't released when the context got cancelled. Books don't teach that. On-call does.

The Outbox story is funnier, because this time I genuinely tried to follow the book — and couldn't.

The alerting problem is a classic: record an alert, send a notification. Recording is the database; sending is another system. Record first, send second, crash in between — the data exists, the notification never went out, and the customer misses an alert they should have gotten. This is dual write. The book's answer is the Transactional Outbox: treat "the notification to send" as data too, and store it in the same database transaction as the business data. One transaction, so it can't half-succeed. Something else drains the outbox and does the sending afterwards. Reading it, I thought: good, that's the one.

Then I sat down to build it and realized my alerts are consumed from upstream Kafka. My outbox write is a standalone insert — there is no "business state" committing alongside it in the same transaction. The textbook Outbox's entire guarantee rests on "same transaction," and I simply don't have that transaction. What I built is something that looks like an Outbox but doesn't satisfy its premise.

Afterwards I left one line in my notes, for future me:

Never say same-transaction commit.

One slip of the tongue — calling it the textbook Outbox — and anyone who knows the pattern asks "where's your transaction boundary" and I'm caught. So I honestly call it something else: a durable record plus idempotent at-least-once delivery. The only source of truth is that outbox row; sending immediately after writing is just a latency optimization, and if the send dies or the process crashes, polling picks it up. At-least-once means duplicates, so you block them layer by layer — a dedup key at the source, a database unique constraint across replicas, idempotency downstream. For alerts like lost-contact that fire every polling round, an hour bucket collapses same-device same-hour into one entry, or the alerts flood themselves.

None of this is in the book. But flip it around: without the book, I couldn't even have made the call that "what I built is not an Outbox" — you have to know what the standard version looks like before you can see where yours differs.

So for me this book is a map, not a blueprint. It hands you the pits and the names of the fixes, so you know the direction before you start and can describe what you're doing. But the things that actually cost me the most time — tying receipts back to commands, keeping order, staying idempotent — are each a single dot on the map, and some aren't drawn at all.

Is it still worth reading in the AI era? AI knows all 44 patterns better than I do. Ask it about Saga, ask it about Outbox, and it explains them more neatly than the book. But when I was building the alerting system, if I had asked it "is my Outbox right," it would have assured me that an Outbox must commit in the same transaction — it doesn't know my data comes in from Kafka, doesn't know I don't have that transaction. It knows the pattern. It can't see that my system doesn't meet the pattern's premise, because it doesn't have my system.

A book can give you the names and the direction. What your own system looks like, you still have to walk it yourself.