When We Stopped Using Microservices (And When We Kept Them)

When We Stopped Using Microservices (And When We Kept Them) — T-Square engineering blog

TL;DR — Modular monolith first. Microservices when a service has fundamentally different scaling characteristics or a fundamentally different team owning it. Almost everything else is operations cost we did not need to take on.

Modular monolith first — microservices only for clear gainSplitting buys you nothing unless one of four conditions is true. — /split or stay? Modular monolith DEFAULT ✓ one deploy ✓ refactors that work ✓ type safety across modules ✓ transactional consistency ✓ lower ops overhead Microservices when specific conditions → different scaling shape → different team ownership → different tech stack → compliance isolation not: “easier later”
Splitting buys you nothing unless one of four conditions is true.

The microservices vs monolith debate is supposed to be settled by now. It is not, because the default keeps drifting toward microservices for cargo-cult reasons. Our position has shifted in the other direction: we ship modular monoliths by default and reach for separate services only when we have a concrete reason.

What the modular monolith gives us

  • One deployment, one log stream, one DB connection pool to manage
  • Cross-module refactors that work the way refactors should
  • Type safety across module boundaries (in TypeScript, Go, Rust)
  • Transactional consistency without saga gymnastics
  • Lower operational overhead — one Postgres, one app, one health check

When microservices actually win

  1. Different scaling characteristics. A video transcoder needs different machines than your API. Splitting is the only sane option.
  2. Different team ownership. When two teams need to deploy independently with different release cadences. (Not “the same team has two services”. That is overhead.)
  3. Different technology requirements. An ML inference service in Python alongside a Node API. Process boundary is the right boundary.
  4. Compliance or data isolation. A service that handles regulated data isolated by network and access policy.

When microservices lose

  • “Easier to scale later” — premature; you can split a modular monolith when scale demands it
  • “Independent deployment” — your team is small enough that one deploy is fine
  • “Right tool for the job” — usually means a 4-developer team operating 12 services. Not viable.
  • “Bounded contexts” — bounded contexts can live as modules in one app

Where we changed our mind

We have walked back from microservices on three projects. Each one was a modular monolith underneath; we just stopped pretending the network boundary added value. Deployment, observability and incident response all got easier. Performance got better because the network layer dropped out of the hot path.

Frequently asked questions

Modular monolith — how modular?

Strict module boundaries enforced at the language level. No cross-module imports outside declared interfaces. ESLint rules or language-level boundaries (Go internal packages, Rust visibility) make this enforceable.

When did you regret going to microservices?

Twice. Both times in projects under 50k DAU where we expected to scale and did not. Operations cost was the killer, not performance.

Working on something similar?

T-Square architects, builds and operates production systems for learning, AI and custom software products. Talk to a senior engineer for a second opinion.

— /more

Keep reading