Microservices get proposed for two very different reasons. Sometimes a system has a genuine scaling or ownership problem that justifies splitting it. More often, the proposal shows up because the current monolith feels difficult to work in, and "microservices" sounds like an architecture rather than a symptom of unclear boundaries.
Start with the monolith, not the diagram
A monolith with clear internal boundaries is easier to operate, test and reason about than a set of services with unclear ones. Splitting a system before its domain boundaries are settled just moves the same coupling problems across a network, where they're harder to see and more expensive to fix. I've found it far more productive to design a monolith as if it might be split later — clear module boundaries, no shared mutable state between domains, explicit interfaces between them — and only extract a service once one of those modules has a real, independent reason to exist on its own.
A domain boundary is a business boundary
The boundaries that hold up over time are the ones that map to how the business actually thinks about its own domains, not the ones that map to a diagram drawn during a single planning session. Billing, identity, catalog and fulfillment are different domains because they change for different reasons, at different speeds, owned by different people. A service boundary drawn around a technical layer — "the API layer," "the database layer" — tends to fall apart quickly, because technical layers don't have a single reason to change; business domains do.
What actually justifies a service split
- A part of the system needs to scale independently of the rest, and that difference is significant enough to matter operationally.
- A team needs to own and deploy a domain independently, without waiting on unrelated release cycles.
- The domain has a genuinely different data model, workload pattern or compliance requirement from the rest of the system.
- The boundary has already proven stable inside the monolith — extracting an unstable boundary just relocates the instability.
Communication patterns matter more than transport
Whether two services talk over REST, a queue, or an event stream matters far less than whether the contract between them is explicit and versioned. A service boundary without a stable contract just replaces in-process coupling with network coupling — you still can't change one side without coordinating the other, except now a deploy that used to be safe requires coordinating two teams and a schema migration. Ownership of the contract needs to be as clear as ownership of the service itself.
The cost nobody puts on the roadmap
Every additional service adds operational surface: another deployment pipeline, another set of logs and alerts, another point where a network call can fail in ways a function call never could. That cost is real and ongoing, and it's rarely weighed against the problem the split was supposed to solve. The question worth asking before any service boundary is drawn isn't "could this be its own service" — almost anything could — but "does the cost of running it separately pay for itself in what we get back." Most of the time, in my experience, a well-organized monolith answers that question just as well, for a lot longer than teams expect.