Microservices are an organizational tool first, a technical one second

Splitting a system into independently deployed services pays off when several teams need to ship on different cadences without stepping on each other. If you have one team, or a handful of engineers sharing one backlog, you are taking on the costs of a distributed system - network failures, partial failure handling, distributed tracing, versioned contracts between services, and eventual consistency - without the organizational benefit that justifies them.

The costs are not hypothetical. A single database transaction becomes a coordination problem across services, which is exactly why patterns like the saga exist. Teams that adopt microservices early often spend their first year building the platform to run them instead of the product.

A useful test: if two services always change together and are always deployed together, they are one service wearing two names. You have paid the network cost for no isolation benefit.

What a modular monolith actually is

A modular monolith is one deployable unit with hard internal boundaries. Each module owns its data and exposes a small public interface; other modules call that interface, never reach into its tables. Boundaries are enforced by tooling - package visibility rules, architecture tests, separate schemas - not just good intentions in a wiki page.

Done properly, this gives you the main thing people want from microservices - the ability to reason about one area without holding the whole system in your head - while keeping a single deployment, one place to debug, and in-process calls that cannot time out.

Own your data per module

No cross-module table access. If billing needs customer data, it asks the customer module through its interface.

Enforce boundaries automatically

Architecture tests that fail the build when a module imports another module's internals keep the design honest as the team grows.

When to extract a service

Extract when a module has a concrete reason to be separate: it needs to scale on a very different profile from the rest, it has a different release cadence owned by a different team, it needs stronger isolation for security or compliance, or it is built on a different runtime. Each of those is a specific, checkable reason - not a feeling that services are more modern.

Because the boundaries already exist, extraction becomes a mechanical step: move the module, replace in-process calls with a network client, and put the data behind the new service. If you later hit the consistency problems that come with that move, our articles on choosing between a local transaction and the saga pattern cover the trade-offs.

Deciding how to structure a new product or untangle an existing one? Talk to our architects before you commit to a topology.

Key takeaways

  • Microservices mainly solve a team-scaling problem; without multiple teams needing independent release cadences, you pay distributed-systems costs for little benefit.
  • A modular monolith gives you enforced boundaries, per-module data ownership, and a single deployable - the useful part of microservices without the network.
  • Enforce module boundaries with tooling such as architecture tests and package visibility rules, not documentation alone.
  • Extract a service only for a specific reason: a different scaling profile, release cadence, isolation requirement, or runtime.