Start with a modular monolith
One codebase, one deploy — but with clean internal boundaries between modules. It's the fastest way to build now, and it keeps the option to split a service out later, once you actually know where the seams are.
Choose this when
- One team, or an early-stage product still changing shape
- Everything scales together and ops is a small crew
- You want to move fast and keep your options open
Trade-offs
- Needs discipline to keep module boundaries from blurring
- Everything deploys together — one bad change can hold up the rest
- Very different scaling needs eventually push you to split a part out
Keep the monolith, split off one service
Leave the bulk as a monolith and extract only the part with a real, specific reason to be separate — the piece that scales differently or that a separate team owns. One seam, not twenty.
Choose this when
- One part has very different load or hardware needs
- Or a specific team needs to own and deploy one area independently
- And you have the ops muscle to run more than one service
Trade-offs
- Now you have a network boundary to design, version, and monitor
- The temptation is to keep splitting — resist it without a new reason
Go with microservices
Several teams, a settled domain, and real platform muscle — this is the situation microservices were actually built for. Split along team and domain lines so each team can ship on its own schedule.
Choose this when
- Several teams that are stepping on each other in one pipeline
- Stable, well-understood domain boundaries to split along
- A platform team and mature CI/CD, monitoring, and on-call
Trade-offs
- Big operational cost: networking, deploys, tracing, failure handling
- Distributed systems are hard — partial failures and data consistency become daily work
- Wrong boundaries are expensive to move once services are live