← Back to the section

When you start a new project, one of the first questions is how to split the system up. Should you run everything as a single application or break it into independent services right away? The answer depends heavily on the team, the deadlines, and the size of the task.

In this article we'll look at three options: the layered monolith, the modular monolith, and microservices. For each one we'll cover when it fits, where its weak spots are, and how to tell it's time to change.

Layered monolith: one process, one deploy

The simplest option. The whole application is one process: it holds the API, the business logic, and the database access. It deploys as a single file and is debugged in one place.

diagram

This approach works well when:

  • The load is small and predictable (up to 100–300 requests per second).
  • The team is small — 1–3 developers.
  • You need to launch the product quickly.
  • The domain is narrow: a notification service, a simple product catalog, a prototype.

What it gives you: fast development, easy debugging, transactions out of the box, minimal infrastructure.

Where it lets you down: as the codebase grows without clear boundaries, different parts of the system start getting in each other's way. A change in one place unexpectedly breaks another. This is called a "Big Ball of Mud" — and it's the main risk of a monolith without discipline.

Signs it's time to change something:

  • Different parts of the system change at different speeds, yet releases still have to be coordinated.
  • The team has grown into 2+ independent groups.
  • The codebase has passed 50,000 lines and it's becoming hard to find your way around.

Modular monolith: boundaries inside a single application

An intermediate option between a monolith and microservices. Physically it's still one process and one deploy — but inside, it has clearly separated modules with their own areas of responsibility. Each module works with its own slice of data and talks to the others through events or explicit interfaces, rather than through direct calls into someone else's code.

diagram

It's a good fit when:

  • The product is potentially large, but you need a fast start.
  • The team is 5–7 people and growth is expected.
  • You want to keep the option to split into services later without rewriting everything from scratch.

What it gives you: no network latency (everything is in one process), simpler transactions than in microservices, and a structure that's already ready to grow.

Where it lets you down: the boundaries between modules are easy to violate — especially when people are in a hurry. Another module's model starts leaking to its neighbor through "shared" classes. All of this has to be tracked manually in reviews. Another problem: a large monolith is heavier in the IDE and during builds.

Signs it's time to split into services:

  • A specific module has become a load bottleneck and needs to be scaled separately.
  • The teams have diverged and want to deploy independently.
  • You need to hand off a specific module to an outside team.
  • The codebase has passed 200,000 lines and there are three or more teams.

Microservices: independent services with separate databases

Each service is a separate process with its own database. Services communicate over HTTP or a message queue. They deploy, scale, and fail independently of each other.

diagram

Justified when:

  • The project is large, deadlines aren't pressing, and you have a strong team and a solid DevOps setup.
  • Different parts of the system evolve with different load and reliability requirements.
  • Several independent teams are involved.
  • The load is high and uneven — some services need to scale, others don't.

What it gives you: independent releases and scaling, failure isolation (a downed service doesn't take everything down), and different technologies in different services.

Where it lets you down: network latency, distributed transactions through Saga, complex request tracing, high infrastructure demands (gateway, contract tests, observability). This is the most expensive option in terms of development and maintenance costs.

When you definitely shouldn't:

  • A small team or no experience with this kind of architecture.
  • The domain hasn't stabilized yet — you'll have to constantly rework the service boundaries.
  • The launch deadline was "yesterday".

More on what you need to build around microservices: structural patterns and distributed patterns.

How to choose: a simple checklist

One point for each "Yes":

QuestionYes
Deadlines aren't pressing?
High load (>250 RPS) or critical peaks?
Five or more domains that change independently?
Independent releases needed across different teams?
Strict availability requirements (SLA 99.95%+ or p95 < 100 ms)?
The system will live and evolve for several years?

Total:

  • 0–1 points → layered monolith
  • 2–3 points → modular monolith
  • 4–6 points → microservices
diagram

The main rule

Don't choose microservices because "that's what all the big companies do". Big companies arrived at this after the monolith stopped keeping up. For a small team with an early-stage product, a monolith is a sensible choice, not a compromise.

If the domain grows, you can always migrate via the Strangler Fig pattern: gradually carve parts out of the monolith into separate services without stopping the system.

In short

  • Layered monolith — a simple start, minimal infrastructure; good for small teams and a narrow domain.
  • Modular monolith — boundaries inside a single application; lets you grow and later split into services painlessly.
  • Microservices — independent processes and databases; justified under high load, with several teams and mature infrastructure.
  • The main risk of a monolith is blurred boundaries (Big Ball of Mud). The main risk of microservices is complexity the team isn't ready to carry.
  • Don't choose an architecture for future growth that may never happen. Start simple and migrate once the monolith starts getting in the way.