The monolith
Everything you have built so far lives in a single application: one process that is compiled, tested and deployed as a block. That is a monolith. The layers (controller → service → repository) are inside the same deployment and call each other with simple function calls.
The monolith has great virtues: it is simple to develop, debug and deploy, and an internal call is instant. For most projects it is the right choice. Its limits appear when the team and the code grow a lot: everything deploys together (a small change forces redeploying everything) and you cannot scale only the part that needs it.
Microservices
The alternative is microservices: instead of one big application, many small, independent services, each responsible for a part of the domain (users, payments, catalog...). Each service:
- Has its own deployment and its own lifecycle (it is released on its own).
- Usually has its own database (they do not share tables).
- Communicates with the others over the network: HTTP/gRPC for direct calls, or asynchronous messages through a queue or a bus.
This way, the payments team deploys whenever it wants without touching the catalog team, and you can scale only the saturated service. The price is heavy operational complexity: the network can fail, you must coordinate deployments, observe many services and live without transactions spanning several of them.
Event-driven architecture
Coupling services with direct HTTP calls creates rigid dependencies: if A calls B, A needs to know that B exists and wait for it to respond. Event-driven architecture flips it around: instead of calling, a service publishes an event ("OrderCreated") on an event bus, and other services subscribe to the events that interest them. It is the publish/subscribe pattern (pub/sub).
Orders ──(publishes "OrderCreated")──▶ Event bus
├──▶ Billing (subscribed)
└──▶ Email (subscribed)
The publisher does not know who is listening: to add a new reaction (notify logistics) you just subscribe another service, without touching Orders. In exchange you give up immediate consistency: subscribers react a bit later, so the system lives in eventual consistency (everything ends up consistent, but not at the same instant).
Sagas at a high level. When an operation spans several services (reserve stock, charge, ship) there is no global transaction. It is modeled as a saga: a sequence of steps where, if one fails, compensation actions run to undo the previous ones. In the choreography variant, there is no central coordinator: each service reacts to the others' events and publishes its own.
When to make the jump?
| Advantages | Drawbacks | |
|---|---|---|
| Monolith | Simple, fast to develop and deploy | Deploys and scales as a block |
| Microservices | Independent scaling and deployment, team autonomy | Operational complexity, network, eventual consistency |
It is not a choice of "better or worse", but of trade-off. Most systems start as a well-structured layered monolith and only extract microservices when the pain of joint deployment and scaling justifies it.