The microservices trap
Microservices became the default recommendation regardless of whether they were appropriate. For most teams that bought the pitch, the bill arrived 18 months later.
The conversation that always starts the same way
Most microservices conversations start the same way. A founder, a CTO, or a head of platform sits across from me and says some version of: "We're growing, so we need microservices."
They almost never need microservices. They need a refactor, a few clear module boundaries, a database that isn't being abused, and an honest conversation about who is on-call. What they were sold was an architecture. What they actually have is a hiring problem, a coupling problem, or a deploy problem wearing an architecture-shaped costume.
The promise vs. the bill
The pitch is easy to fall for, because parts of it are true:
- Independent services that can be developed, deployed, and scaled separately.
- Teams that can pick the right tool for their service.
- Failures isolated to a small part of the system.
The bill nobody quotes you up front:
- Network is not your code. Every call that used to be a function call is now a request that can time out, retry, and double-fire.
- Data has to live somewhere. Either you split your database (and lose joins, transactions, and referential integrity) or you keep one (and lose half the point of microservices).
- Schema drift is forever. Two services on two release cycles will get out of sync. You will write a contract test suite, and you will hate it.
- Observability is now a product. Tracing, log correlation, and request IDs become non-negotiable infrastructure, not a nice-to-have.
- Eventual consistency is a UX problem. Users notice when their cart is empty for three seconds after they added something to it.
Distributed systems do not multiply your throughput first. They multiply your failure modes first.
The modular monolith middle ground
Almost everything people want from microservices — clean boundaries, parallel work, the ability to reason about a chunk of the system in isolation — you can get from a well-structured monolith. With one database, one deploy, and one set of dashboards.
The pattern is straightforward:
- Bounded contexts as modules. Each module owns its tables, exposes a typed interface, and never reaches into another module's storage.
- One transaction across modules when the business needs it, instead of distributed sagas you'll re-debug for the rest of your life.
- Background work via a table-backed queue until you have a real reason to introduce a broker.
- Strict module-to-module API tests, so the boundaries are real, not vibes-based.
When you actually need to extract a service, you extract a module. The boundary is already there, the tests are already there, and the team knows what they're carving off.
When to split for real
The honest signals that justify the cost of going distributed are:
- You have more than one team that genuinely owns a slice end-to-end, including on-call.
- Two parts of the system have different SLAs. A 99.99% checkout cannot share a deploy window with a 99% recommendation engine.
- You need failure isolation. When the analytics pipeline catches fire, the storefront must keep selling.
- You have genuinely divergent scaling profiles. One workload is CPU-bound and chatty, the other is memory-heavy and quiet.
"We might want to one day" is not on this list. Neither is "the new senior engineer is used to it."
The migration trap
If you've already inherited a microservices mess, do not big-bang it back into a monolith. Use the opposite of how you got there: a strangler fig in reverse. Pick the two services that talk to each other the most. Move them into one process. Delete the inter-service calls. Repeat.
The order matters. Start with the services that share data, not the ones that share a UI. The latency wins are immediate, the operational wins compound, and you stop paying for the network as a database.
Want this kind of judgment on your project?
I read every email within one working day. Bring a project, a quote, or a system you're stuck on.
The 5 unbilled months hidden in every Contentful enterprise build
Every Contentful enterprise build I have audited carries the same five hidden months: link references, redirects, i18n, schema-driven forms, and type safety. None of them are on the SOW. All of them are on the timeline.
Why simple architecture always wins
Complex systems fail in complex ways. The most successful projects I've seen are the ones that resisted the urge to over-engineer.
The real cost of custom development
Custom software isn't just the initial price. It's maintenance, updates, and the opportunity cost of not using existing solutions.