Microservices Design Principles
Good microservices design rests on a small set of recurring principles: each service should have a single, well-bounded responsibility; services should be loosely coupled so a change inside one doesn't ripple into others; the API contract between services should be explicit and versioned; and failures should be isolated so one service's outage degrades, rather than crashes, the whole system. These principles matter more than any specific technology choice, a poorly bounded service written in the trendiest framework is still a poorly bounded service.
Cricket analogy: A well-run team assigns the death-overs specialist bowler one clear job, closing out the innings, rather than making them also open the batting; unclear responsibility produces a player, or a service, that's mediocre at everything.
Loose Coupling and API Contracts
Loose coupling means order-service can be redeployed, or even rewritten in a different language, without any change required in payment-service, as long as the API contract between them stays the same. This is achieved by hiding implementation details (database schema, internal class structure) behind a stable, versioned API, and by avoiding shared code libraries that couple services to the same release schedule for anything beyond simple, rarely-changing DTOs. Consumer-driven contract testing, where each consuming service defines the exact shape of the response it depends on, catches breaking changes before they reach production.
Cricket analogy: As long as the toss-and-playing-conditions rulebook stays the same, two countries' boards can each run their domestic leagues however they like internally; the shared 'contract' (the rules) is what enables independence, like an API contract.
Failure Isolation
In a distributed system, some service will eventually be slow or down; the design goal is to contain that failure rather than let it cascade. Techniques include timeouts on every network call (never wait forever for inventory-service to respond), circuit breakers that stop calling a failing service after a threshold of errors and fail fast instead, and bulkheads that give each downstream dependency its own connection pool or thread pool so a slow payment-service can't exhaust the thread pool that checkout-service needs to serve unrelated requests.
Cricket analogy: A team doesn't let one poor over from an out-of-form bowler ruin the whole innings' momentum, the captain sets a threshold and takes them off after two costly overs, similar to a circuit breaker tripping after repeated failures.
// Timeout + circuit breaker around a call to payment-service
const breaker = new CircuitBreaker(callPaymentService, {
timeout: 3000, // fail fast after 3s
errorThresholdPercentage: 50,
resetTimeout: 10000, // try again after 10s
});
breaker.fallback(() => ({
status: 'QUEUED_FOR_RETRY',
message: 'Payment service unavailable, order queued.',
}));
async function chargeCustomer(order) {
return breaker.fire(order);
}Without timeouts and circuit breakers, a single slow downstream service can exhaust the calling service's thread pool or connection pool, taking down an otherwise-healthy service purely because it was waiting on someone else. This cascading-failure pattern has caused several well-documented major outages.
- Each microservice should have a single, well-bounded responsibility, not a mix of unrelated concerns.
- Loose coupling means one service's internals can change freely as long as its API contract stays stable.
- Avoid sharing code libraries or databases in ways that force services onto the same release schedule.
- Consumer-driven contract testing catches breaking API changes before they reach production.
- Every network call between services needs a timeout to avoid waiting indefinitely.
- Circuit breakers fail fast after repeated errors instead of letting failures cascade.
- Bulkheads isolate each dependency's resource pool so one slow service can't starve unrelated requests.
Practice what you learned
1. What does 'loose coupling' mean in a microservices context?
2. What does a circuit breaker do when a downstream service keeps failing?
3. Why does each network call between services need a timeout?
4. What is the purpose of a bulkhead pattern?
Was this page helpful?
You May Also Like
What Are Microservices?
An introduction to microservices architecture: small, independently deployable services that communicate over the network to form a larger application.
Domain-Driven Design Basics
An introduction to Domain-Driven Design concepts, bounded contexts, ubiquitous language, and aggregates, and how they inform microservice boundaries.
Monolith vs Microservices
A practical comparison of monolithic and microservices architectures across deployment, scaling, data management, and team structure.
Related Reading
Related Study Notes in Software Engineering
Browse all study notesTesting & TDD Study Notes
Software Testing · 30 topics
Software EngineeringDesign Patterns Study Notes
Software Design · 30 topics
Software EngineeringSoftware Engineering Study Notes
Python · 40 topics
Software EngineeringGit & Version Control Study Notes
Bash · 40 topics
Software EngineeringSystem Design Study Notes
Architecture · 40 topics