Microservices Best Practices

Microservices have been the default architectural answer for over a decade, and the industry has now had long enough to see the failure modes. The teams that adopted them for the right reasons report independent deployability, team autonomy, and targeted scaling. The teams that adopted them by default report a distributed monolith — all the operational cost of distributed systems combined with all the coupling of the architecture they were trying to escape. The best practices below are what separate the two outcomes. None of them are about the technology; all of them are about the discipline applied to deciding whether, where, and how.
Every microservice you create takes on permanent operational cost: deployment automation, observability, on-call ownership, security policy, and FinOps discipline. The first best practice is to adopt microservices only where they pay that cost back — in independent deployability, fault isolation, or team autonomy. A well-structured modular monolith is often the more honest answer, especially for a single coherent product built by a small or mid-size team. The right question at the start of any project is "should we even do this?", not "how many services?"
The single most consequential decision in any microservices design is where the service boundaries fall. Bad boundaries produce a distributed monolith no matter how good the implementation; good boundaries make the implementation feel obvious. Work boundaries-first:
- Model your business domains as they actually are — not the historical accident of who reports to whom.
- Use Domain-Driven Design to identify aggregate boundaries where it applies, and distinguish your core domain from supporting and generic subdomains — they deserve different levels of architectural attention.
- Aim for one team, one service, clear scope. A service without a single clear owner is a future coordination problem.
- Document why each boundary sits where it does (see practice 9), so next quarter's debate doesn't relitigate the same trade-off.
The network introduces failure modes a monolith never has. Consistency, availability, partition tolerance, latency budgets, retry semantics, idempotency, and observability all have to be designed deliberately — at the architecture stage, not after the first production incident. Decide, per interaction, what your consistency and latency requirements actually are, and make idempotency and retry behaviour explicit rather than emergent.
Inter-service communication is where architectural decisions get most expensive if they're wrong. Best practice:
- Choose synchronous vs asynchronous boundaries by what the domain needs, not by what's currently in vogue.
- Use event-driven cores with explicit ordering guarantees only where the domain requires them.
- Match eventing infrastructure to the workload (Kafka, RabbitMQ, NATS, cloud-native streams) with a documented reason for the choice.
- Version API contracts so a service can evolve without breaking downstream consumers.
- Contain the blast radius of any single failure with backpressure and circuit breakers.
The rule that holds across all of it: don't add infrastructure that earns less than the complexity it brings.
Data is where most decomposition efforts underestimate themselves. Best practice is to make data ownership follow the service boundary:
- Database-per-service where it genuinely matches the domain — and a consciously shared database where extraction would add cost without benefit.
- CQRS / read-write separation only where read and write workloads diverge enough to justify the complexity.
- Event sourcing for domains where the history of state is the source of truth.
- Saga patterns for cross-service workflows that need transactional behaviour without distributed transactions.
- A real data-migration strategy that doesn't freeze the business while boundaries change.
Identity, observability, error handling, retries, and security should be inherited by every new service, not reimplemented in each. Provide service templates and paved roads so a new service starts with the right defaults, shared libraries or sidecars for cross-cutting concerns, and distributed tracing that works across service boundaries — not just within each service. When adding a service is a configuration step rather than a re-architecture, you've done this right.
A microservices estate the team can't operate calmly is a failed architecture, no matter how clean the diagrams. Design for operability from the start: SLOs and error budgets per service to drive priorities, runbooks for the likely failure modes (cascading failures, hot keys, regional degradation), observability tuned for distributed systems, safe deployment automation with automated rollback and canaries, and on-call patterns that don't burn out the people running dozens of services.
If you're moving off a monolith, the migration strategy matters at least as much as the destination. Big-bang rewrites fail the overwhelming majority of the time: the business can't absorb a multi-quarter freeze, and the rewrite rarely finishes on schedule. The best-practice alternative:
- Strangler fig — a routing layer in front of the monolith, with new functionality and extracted services behind it. The monolith shrinks over quarters, not weeks, and rollback is always available because the old code still runs.
- Vertical migration slices — a bounded use case cut end-to-end through UI, API, service, and data, validating the whole pipeline before extracting more.
- Parallel running — old and new side by side, with automated comparison, until confidence is high enough to cut over.
- Selective extraction — extract only the components that genuinely need independent scaling or deployment (high-traffic payments, compute-heavy inference) and leave the rest. A mixed architecture is often the right end state, not just a waypoint.
For a deeper decision framework, see From Monolith to Microservices: A Decision Framework for CTOs.
The anti-patterns to avoid
- The distributed monolith. Operational cost of services, coupling of a monolith. It happens when service boundaries don't match domain boundaries, so every feature touches multiple services and requires coordinated deployment. The defence is practice 2: get boundaries right before extracting anything.
- Microservices everywhere. Extracting by default rather than by need produces a sprawling estate no team can operate calmly.
- Freezing product work to migrate. Any plan that requires stopping the roadmap is the wrong plan.
- Deferring operability. Observability, SLOs, and runbooks designed after the first incident are always more expensive than designing them up front.
Microservices best-practices checklist
F. A. Q.
Getting service boundaries right (aligned to business domains), designing distributed-systems trade-offs deliberately, giving each service its own data where the domain warrants it, building cross-cutting concerns once, and migrating incrementally with the strangler-fig pattern. Almost every microservices failure traces back to skipping one of these.
Not by default. Microservices become the right answer when different parts of your system have genuinely different load profiles, deployment cadences, ownership boundaries, or security requirements. A coherent product built by a small or mid-size team is often best served by a well-structured modular monolith.
The wrong question. The right one is whether each service has a clear owner, a coherent domain, and a strong reason to exist as its own deployable unit. If those three hold, the count doesn't matter much; if they don't, the count won't save you.
A system with the operational cost of microservices but the coupling of a monolith — services that can't be deployed independently because every change ripples across several of them. It's the most common failed-microservices outcome, and it comes from boundaries that don't match the domain.
Yes — and it's often both the right migration state and the right end state. A core monolith for stable, tightly-coupled logic, with extracted services around it for the parts that genuinely need independence. The strangler-fig pattern produces this naturally.