Skip to main content
CodeOath
← All posts

Architecture & Patterns68 min total · 17 parts

Microservices vs. Monolith: The Trade You're Actually Making

Part 17 of 17 · ~3 min

Common Mistakes Worth Remembering

  • Reaching for microservices on day one of something brand new, before load or headcount has created any real problem for it to solve — you take on every network hop, every consistency headache, and every extra deploy pipeline this reference walks through, and there's no second team around yet to actually cash in the independence they're supposed to buy you.
  • Letting two supposedly independent services quietly keep reading the same database table, the way Subscriptions briefly did off Harvest's available_quantity column — what you actually have at that point is a monolith wearing a network call as a costume, because a schema change still has to go through both teams before anyone can ship it.
  • Defaulting every cross-service call to synchronous with nothing guarding it — no timeout set, no retry policy written, no circuit breaker in front — so a dependency that's merely having a slow afternoon ends up taking down everything downstream of it instead of just itself.
  • Assuming a saga hands you the same guarantee the transaction it replaced did, and only discovering in production that one of its steps was never actually designed to be undone once something further down the chain fails.
  • Putting off distributed tracing until the first incident that actually needs it. Knowing a problem happened "somewhere in the last four services a request touched" isn't a diagnosis — it's the start of a much longer investigation than the one Furrow actually had, because the plumbing was already there before the lettuce mystery ever showed up.
  • Drawing a service boundary that sounds clean on a diagram but isn't clean in practice. Furrow's first attempt at splitting Fulfillment actually created two services — Box Packing and Delivery Routing — on the theory that packing and routing were obviously separate concerns. In practice, a packer needs to know a route's current capacity before deciding how to fill the next box, and a router needs to know what's actually been packed before it can finalize a stop — the two were calling each other synchronously, multiple times, for nearly every single order, which is exactly the tight coupling a service boundary is supposed to remove, just now paying network latency and a saga's worth of failure modes for the privilege. Three months in, the team merged them back into one Fulfillment service. The lesson that stuck: a boundary is only earning its keep if the two sides genuinely don't need to talk to each other constantly — and the only way to actually find that out, most of the time, is to draw the line, run it against real traffic for a while, and stay willing to redraw it once that traffic shows you where it doesn't hold.
  • Trying to rewrite the whole thing as services in one push instead of pulling pieces out incrementally, strangler-fig style, in a way that leaves the system shippable at every point along the way.
  • Drawing the split along technical layers — a storage layer as its own service, a messaging layer as another — rather than along what the business actually does. A shared messaging layer that every other service has to phone for every kind of outbound message still means almost no feature ships without touching several services at once, which just reintroduces the monolith's coupling and taxes it with the network on top.

Furrow's story doesn't end at "four services forever" — it ends at three, with the boundary that didn't hold redrawn once real traffic proved it wrong. That's really the whole shape of this reference: not a verdict on which side of the monolith-versus-microservices line is correct, but a way of noticing, concretely, which specific wall you're actually up against before you pay to remove it — and being just as willing to walk a boundary back as you were to draw it in the first place.