CodeOath
← All posts
Architecture & Patterns68 min total · 17 parts

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

Part 4 of 17 · ~3 min

What Splitting Actually Costs You

None of the above is free, and Furrow finds out roughly what it costs in the first month after the split, mostly in this order:

  • What used to be a function call is now a request over the wire. HarvestLot.reserve(lot_id, quantity) used to return in under a millisecond because it never left the process. Once Harvest is its own service, that same lookup is an HTTP round trip that costs fifty milliseconds on an ordinary day — and on a bad one, during the Wednesday-night upload rush, it stalls past its own timeout, or Harvest simply isn't reachable for a few seconds mid-deploy. A piece of code that used to just work, every time, now has to carry its own retry policy, its own timeout, and a real answer to the question "what does checkout actually do when Harvest goes quiet."
  • Cross-service transactions stop existing. The old monolith could reserve a lot and charge a card in one database transaction and let Postgres guarantee both happened or neither did. Split across two services with two databases, that guarantee is simply gone. What replaces it — the Saga pattern — is the subject of the next chapter, and it is not a drop-in substitute; it's a different, weaker guarantee that has to be designed for on purpose.
  • Debugging a request means finding it across services first. A subscriber emails support saying her box arrived without the lettuce the app said it would have. In the monolith, that's one stack trace or one set of log lines in one place. Split apart, that same mystery might live in Harvest's allocation logic, Fulfillment's packing list, or a substitution rule that fired somewhere in between — and finding out which one needs infrastructure the monolith never required. (The observability chapter below tells this exact story in full.)
  • Four services is four times the operational surface. Four things to deploy, four things to monitor, four container images to keep patched, four on-call rotations to eventually argue about. None of this was a line item when Furrow was one app.
  • Testing gets a new, harder failure mode. The monolith's integration tests spun up one process and exercised real code paths across Subscriptions, Harvest, and Billing directly. Actually exercising how two of them behave together now means either spinning both up side by side, which is slow, or building a convincing stand-in for one — and a defect that only appears when two particular service releases meet in a particular way is a whole class of problem the single-process monolith never had room to produce in the first place.

Common mistake: hearing this list, deciding it sounds manageable, and splitting anyway before any of these problems is actually being felt. Furrow's Fulfillment lead pushed to split Harvest out six months before the team actually did it, on the theory that it would be "cleaner." It would have been — and it also would have meant carrying every cost on this list for a wall the team hadn't hit yet. The team that waited for a real Wednesday-night database pileup to force the question got the same architecture with a much clearer, much more defensible reason for every piece of it.