The microservices conversation generates more heat than light. One camp treats services as the only serious way to build software, the other treats the monolith as a badge of pragmatism. Both positions skip the only question that matters: what does your team size, traffic, and domain complexity actually require right now? Architecture is a response to constraints, not an identity.
Start with the modular monolith
For almost every new product, the right starting point is a single deployable application with clean internal module boundaries. You get one codebase to reason about, one deployment, local function calls instead of network hops, and transactions that just work. A well-structured monolith is not technical debt. It is the architecture that lets a small team move fast while the domain is still being discovered. Premature service decomposition is one of the most expensive mistakes dressed up as best practice.
What microservices actually cost
Splitting into services trades in-process simplicity for distributed-systems complexity. Now a single user action may cross several network boundaries, each of which can fail, retry, or time out. You inherit eventual consistency, distributed tracing, service discovery, and a deployment surface that multiplies. These are solvable problems, but solving them consumes engineering capacity that a small team usually needs for the product itself.
The signals that justify splitting
Reach for services when you have concrete pressure, not aspiration. Different parts of the system need to scale independently. Separate teams keep colliding in the same deployment and need autonomy. One component has genuinely different reliability or compliance requirements. When these forces are real, the operational cost of services buys you something. When they are hypothetical, it just buys you complexity.
Conway's law is not optional
Your architecture will come to mirror your communication structure whether you plan for it or not. Two teams will struggle to share one tightly coupled service, and one team will struggle to coordinate ten. Choose boundaries that match how your people are actually organised, and revisit them when the organisation changes. Boundaries drawn on a whiteboard that ignore team structure tend to dissolve in practice.
Decompose along the domain, not the layer
When you do split, draw boundaries around business capabilities rather than technical layers. A service that owns "billing" end to end is far healthier than a "database service" and a separate "API service" that must change together for every feature. Capability-aligned services can evolve independently, which is the entire point. Many teams find that event-driven patterns are what make those boundaries genuinely decoupled rather than distributed-monolith in disguise.
The migration path that works
When a team does outgrow its monolith, the failure mode is trying to rewrite the whole thing into services at once, which stalls feature work for months and frequently collapses under its own ambition. The path that works is incremental. Keep the monolith running, identify the one capability under the most pressure, and extract just that into a service while everything else stays put. Each extraction teaches you something about your real boundaries and delivers value on its own, so the effort is never an all-or-nothing bet. This is only feasible if the monolith had clean internal modules to begin with, which is the strongest argument for investing in those seams early even when you have no immediate plan to split. A well-structured monolith is not the opposite of microservices; it is the on-ramp to them.
A pragmatic default
Begin with a modular monolith, keep the module seams clean so extraction is cheap later, and split a service out only when a real constraint demands it. This path keeps you fast early and gives you a clear, low-drama route to services when you have earned the need. If you want help designing that evolution, our cloud and DevOps team does this regularly, and you can compare it with our take on serverless versus containers for the runtime decision.