← Blog
Architecture · 8 min read

Monolith, modular monolith or microservices? How to choose an app architecture

Microservices are the default answer in architecture talks and the wrong one for most products. The useful question isn't which style is modern — it's where your boundaries are and what each split will cost you.

Three options, one real question

  • Monolith — one codebase, one deploy, one database. Fast to build, easy to debug, and fine until nobody can tell which part depends on which.
  • Modular monolith — still one deploy and one database, but split into modules with explicit public interfaces and private internals. You get most of the structure of services without the network in between.
  • Microservices — modules deployed separately, each with its own data, talking over the network. Independent scaling and releases, paid for with distributed-systems complexity.

All three are about the same thing: boundaries. A monolith with clean boundaries can be split in a week. Microservices with bad boundaries are a distributed monolith — every change still touches five services, only now over HTTP.

What microservices actually cost

  • Every call can fail. A function call becomes a network request with timeouts, retries and partial failure.
  • No more joins or transactions across data. Consistency becomes your job: events, sagas, the outbox pattern, idempotent handlers.
  • Operations multiply. Deploy pipelines, dashboards, alerts, tracing and API versioning — per service.
  • People. A service without a team that owns it end-to-end decays fastest. A useful rule: no more services than teams that can own them.

When a split pays off

Split a module out when at least one of these is true:

  • It has a very different load profile — a video encoder next to a CRUD admin panel.
  • It needs a different release cadence or owner — another team ships it daily while the rest ships weekly.
  • It sits behind a different security or compliance boundary — payments, health data, personal data.
  • It needs a different runtime — model inference in Python next to a TypeScript or Kotlin backend.

“The codebase is big” and “the app is slow” are not reasons on their own. Big codebases need modules; slow apps need a profiler.

How to build a modular monolith

  1. Cut modules by business capability — accounts, billing, catalog — not by technical layer (controllers, services, repositories).
  2. Give each module one public entry point. Everything else is internal and other modules may not import it.
  3. Each table has one owner. Other modules read and write through the owner's interface, never with their own SQL. Separate schemas per module make this visible.
  4. Use in-process events for side effects. Billing publishes InvoicePaid; accounts reacts. Neither calls into the other's internals.
  5. Enforce it in CI. Import rules that fail the build are the only boundaries that survive deadlines — and AI coding agents.
src/
  modules/
    accounts/
      index.ts        # public interface: the only file others may import
      service.ts
      repository.ts
      events.ts
    billing/
      index.ts
      ...
    catalog/
  platform/           # shared plumbing: database, logging, config, HTTP
  app/                # routes and jobs that call module interfaces

Splitting later without a rewrite

When a module earns its own service, the boundary is already there. Put its public interface behind an API, route callers to it one by one (the strangler fig pattern), publish its events through an outbox table so none are lost, and finally move its schema to its own database. The rest of the system barely notices.

A quick checklist

  • Can you name every module and its owner in one minute?
  • Does the build fail when one module imports another's internals?
  • Does every table have exactly one owning module?
  • Do you have a concrete reason — load, team, compliance, runtime — for each service you run?

If the answer to the last one is “no”, merging services back into a modular monolith is a legitimate and often profitable move.

Few spots left Free AI audit