Both are valid. The right choice depends on team size, change rate and operational maturity — not on fashion.
flowchart TB
subgraph Mono["Monolith"]
M["catalog + cart + orders + payments"]
end
subgraph Micro["Microservices"]
GW["API gateway"] --> CAT["catalog"]
GW --> CART["cart"]
GW --> ORD["orders"]
ORD -->|"event"| PAY["payments"]
end
%% step M: One codebase, one build, one deployment and usually one database.
%% step GW: Clients call a gateway that routes to the right service.
%% step CAT: Each service owns its data and can be deployed on its own schedule.
%% step ORD: Services talk over HTTP/gRPC or events; the network is now part of every feature.
%% step PAY: A failing payments service should not take catalog browsing down with it.
Monolith
Good: simple to build, test, deploy and debug; one transaction across features; low latency (function calls).
Drawbacks:
- Every change redeploys everything — slow for frequent releases by many teams.
- Scaling means scaling everything, even if only search is busy.
- One bad component (memory leak, crash) can take the whole application down.
- Harder for many teams to work in parallel; technology choices are locked in.
Microservices
Good: services fail independently; each team owns, deploys and scales its service; each can pick suitable tools.
Costs: network calls fail and add latency; distributed data and transactions (sagas); much more CI/CD, monitoring and tracing; versioned APIs.
Loose coupling
Services should know as little as possible about each other's internals: talk through stable APIs or events, own their data (no shared database tables), and tolerate the other side being slow or down. Tight coupling turns microservices into a distributed monolith — all the costs, none of the benefits.
A sensible path
- Start with a modular monolith: clear modules, one deployment.
- Extract a service when a module has a different scaling need, release pace or team.
- Use the strangler pattern (module 5) to move piece by piece.
Traps: splitting a 5-person product into 30 services; services sharing one database schema; synchronous chains of five calls per request (latency and availability multiply); no tracing.