Loading...
Loading...
Bounded contexts, independent deploys and the price you pay in discovery, observability and distributed transactions
The gatekeeper now terminates encryption once, reuses a small pool of warm upstream sockets, and overwrites forged client headers at the edge. Behind that door, the food-ordering app is still one codebase holding users, menus, orders, payments, and notifications. The payments team wants to ship a fraud check. To do it, they redeploy everything, including the menu code they’ve never read. The deploy breaks search. Friday night, the whole app is down because of a feature most users never touch.
That knot has a name: a monolith. One deployable, one database, one blast radius. Microservices are what happens when teams cut the knot into pieces on purpose, each business capability becomes its own small app, with its own database, its own deploys, and its own team that can break it without breaking yours.
One deploy moves everything, so one bug can sink everything.
Four deploys, four databases, four blast radii, so payments can burn without touching menus.
Because every split has a price, and beginners pay it without knowing. Each service needs its own database, so a question that used to be one SQL join is now three network calls stitched together in code. Each deploy needs pipelines, monitoring, and tracing across service boundaries. Debugging goes from “read the stack trace” to “reconstruct what happened across six machines from six log files.”
The industry learned this the hard way, and the consensus is blunt: start as a monolith. Split when a specific pain forces you: one team blocked by another’s deploys, one feature needing wildly different scale, one boundary that’s obvious in hindsight. Premature microservices is a distributed monolith: all of the pain, none of the payoff.
“Payments” is a service. “The file that also sends emails sometimes” is not. If you can’t name it in two words, the boundary is wrong.
The payments team deploys Friday at 5pm without asking menus for permission. If deploys still move in lockstep, you just have a slow monolith.
No shared database. Orders asks Payments through an API like any stranger would, which is exactly what keeps them independent.
A team small enough to agree over lunch runs it end to end. Conway’s law, the observation that systems mirror the communication of the teams building them, applies here: the org chart becomes the architecture, so draw it deliberately.
Search in Rust, billing in Java, glue in Python. Freedom helps, until five stacks need five kinds of on-call expertise.
Recommendations can burn all day as long as checkout survives. Isolation is the entire point, design for it, don’t assume it.
Split services still need each other, checkout must tell inventory, tell payments, tell email. Three patterns cover nearly everything, and the choice is about how long the caller can afford to wait. Synchronous calls, where the caller blocks until the answer arrives, are the REST and RPC styles the next pages compare; the queue and event styles below let the caller move on:
Ask a direct question, stand there until answered. Simplest to reason about, and the caller freezes if the answer never comes.
Leave the task in a shared inbox and walk away. Someone picks it up when they can, seconds or minutes later.
Announce what happened without naming recipients. Email, analytics, and fraud check all hear “OrderCreated” and react independently.
One checkout request fans out to inventory, pricing, fraud, and payments. Each answers in 30ms at p50, the typical answer, and 200ms at p99, the slowest 1% of answers. Your checkout latency is not the average , it is the slowest answer you wait for, so p50 stays near 40ms while p99 lands near 200ms plus stitching overhead. Add a fifth dependency at the same profile and p99 barely moves but the probability that at least one of the five hits its bad tail climbs: with five independent 1%-bad calls, roughly 5% of checkouts feel the tail. Depth multiplies it further, three sequential layers of two calls each stack their tails end to end.
Availability multiplies the same gloomy way. Five dependencies at 99.9% monthly availability each (about 43 minutes of downtime apiece) combine to roughly 99.5% for the caller, over three and a half hours of user-visible trouble, unless failures isolate. This is the quantitative case for the patterns later modules teach: deadlines under a second per hop, bulkheads, walls that stop one slow dependency from consuming every thread, and graceful degradation where recommendations may fail but checkout must not.
Start as a modular monolith, one deploy with clean internal boundaries, and split along team and failure boundaries when deploys or blast radius force it: large retailers' famous sale-day and outage postmortems all trace back to isolating what must survive independently. Every boundary drawn converts a compile-time error into a runtime failure with its own latency, versioning, and observability bill, so only pay it where independent shipping or independent survival is worth the operations. And when services own their data, resist the urge to join across them: fetch twice and merge in code, maintain a read-optimized view fed by events, or accept eventual consistency through a saga, a sequence of local transactions where each step publishes an event triggering the next, with compensating steps on failure. A shared-database read for convenience re-creates the monolith's coupling with the network's failure modes, which is the worst of both architectures.
Before giving each service its own store, teams split the code but keep one shared database, so every service still joins the same tables. It fails on arithmetic and coupling. A question that was one 5ms join becomes three network calls at 30ms typical each plus stitching, near 100ms before any business logic, and every deploy still coordinates on one schema, so the Friday-night blast radius never shrinks. The shared database re-creates the monolith's coupling with the network's failure modes, which is the worst of both architectures: distributed latency with centralized risk.
Count the new costs honestly: network calls where function calls used to be (slower, flakier), transactions that can’t span services (goodbye easy consistency), testing that needs the whole zoo running, and observability you must build before you need it. Microservices don’t remove complexity. They move it from code into operations, worth it at scale, miserable prematurely.
And once services talk, the fan-out math sets the stakes: five dependencies at 1% bad each means roughly 5% of checkouts feel the tail, and five 99.9% dependencies combine to roughly 99.5% for the caller. Holding that story together takes one trace ID across six services in a fleet where boxes appear and vanish hourly. Hardcoded addresses don’t survive that world, so something has to keep the phone book.