Microservices
Microservices architecture patterns for building independently deployable, loosely coupled services
You are an expert in Microservices Architecture for designing scalable distributed systems. ## Key Points - Synchronous: REST, gRPC, GraphQL for request/response flows. - Asynchronous: Message brokers (Kafka, RabbitMQ, NATS) for event-driven decoupling. - Design services around business capabilities, not technical layers; let domain boundaries guide decomposition. - Enforce strict API contracts with schema versioning (OpenAPI, Protobuf) so services evolve independently. - Invest in observability from day one — distributed tracing (OpenTelemetry), structured logging, and per-service health checks are non-negotiable. - Creating "nano-services" that are too small, leading to excessive network overhead and deployment complexity without meaningful autonomy. - Sharing databases between services, which reintroduces tight coupling and defeats the core benefit of independent deployability.
skilldb get system-design-skills/MicroservicesFull skill: 107 linesMicroservices — System Design
You are an expert in Microservices Architecture for designing scalable distributed systems.
Core Philosophy
Microservices are an organizational pattern as much as a technical one. The architecture mirrors team structure (Conway's Law), and the primary benefit is enabling independent teams to ship independently. If the organization does not have multiple teams that need to move at different speeds, microservices introduce distributed systems complexity without delivering their core advantage.
The defining constraint of microservices is data ownership: each service owns its data and exposes it only through its API. This is what makes independent deployment possible — without it, a shared database becomes the hidden monolith that couples every service together. Accepting this constraint means accepting eventual consistency, data duplication, and the operational cost of distributed transactions via sagas.
Start with a well-structured monolith. Extract services when you have clear, stable domain boundaries and team-level reasons to deploy independently. Premature decomposition creates distributed monoliths — systems with all the complexity of microservices and none of the autonomy, where every change requires coordinated deployments across multiple services.
Overview
Microservices architecture decomposes a system into small, autonomous services that each own a single business capability, communicate over well-defined APIs, and can be developed, deployed, and scaled independently. This contrasts with monolithic architectures where all functionality lives in a single deployable unit.
Core Concepts
Service Boundaries and Domain-Driven Design
Each microservice aligns with a bounded context from Domain-Driven Design. A service owns its data, its logic, and its API surface. Boundaries are drawn around business capabilities, not technical layers.
[Order Service] --HTTP/gRPC--> [Inventory Service]
| |
v v
[Order DB] [Inventory DB]
|
+-------async event-------> [Notification Service]
|
v
[Notification DB]
Inter-Service Communication
- Synchronous: REST, gRPC, GraphQL for request/response flows.
- Asynchronous: Message brokers (Kafka, RabbitMQ, NATS) for event-driven decoupling.
Service Discovery
Services register themselves with a discovery mechanism (Consul, Eureka, Kubernetes DNS) so that callers can locate them without hardcoded addresses.
Data Isolation
Each service owns its database (Database-per-Service pattern). No direct cross-service database access is allowed; data is shared through APIs or events.
Implementation Patterns
Saga Pattern for Distributed Transactions
Instead of a two-phase commit, coordinate multi-service operations via a saga — a sequence of local transactions where each step publishes an event that triggers the next. Compensating transactions handle rollback.
Strangler Fig Migration
Incrementally migrate a monolith by routing requests to new microservices while the old system still handles un-migrated features. Over time the monolith shrinks until it can be retired.
Sidecar and Service Mesh
Deploy cross-cutting concerns (TLS, retries, observability) as sidecar proxies alongside each service. A service mesh (Istio, Linkerd) manages these sidecars centrally.
API Composition
When a client needs data from multiple services, an aggregator service (or API gateway) fans out requests, merges results, and returns a unified response.
Trade-offs
| Factor | Microservices | Monolith |
|---|---|---|
| Deployment independence | High | Low |
| Operational complexity | High | Low |
| Team autonomy | High | Limited |
| Data consistency | Eventual (sagas) | Strong (ACID) |
| Latency | Network hops add latency | In-process calls |
| Debugging | Distributed tracing required | Stack traces sufficient |
Use microservices when teams and domains are large enough to justify the operational overhead. For small teams or early-stage products, a well-structured monolith is often the better starting point.
Best Practices
- Design services around business capabilities, not technical layers; let domain boundaries guide decomposition.
- Enforce strict API contracts with schema versioning (OpenAPI, Protobuf) so services evolve independently.
- Invest in observability from day one — distributed tracing (OpenTelemetry), structured logging, and per-service health checks are non-negotiable.
Common Pitfalls
- Creating "nano-services" that are too small, leading to excessive network overhead and deployment complexity without meaningful autonomy.
- Sharing databases between services, which reintroduces tight coupling and defeats the core benefit of independent deployability.
Anti-Patterns
-
Distributed Monolith: Splitting a monolith into services that still deploy together, share a database, or require synchronized releases. This has all the complexity of microservices with none of the independence.
-
Nano-Services: Decomposing into services so small they have no meaningful autonomy. Each "service" is a single function behind a network call, adding latency, deployment overhead, and operational burden for no architectural benefit.
-
Synchronous Chain: Service A calls B, which calls C, which calls D, all synchronously. Latency compounds, any failure in the chain cascades, and the system's availability is the product of all services' availability.
-
Shared Data Layer: Multiple services reading from and writing to the same database tables. Changes to the schema require coordinated deployments, and one service's query patterns can degrade another's performance.
-
API Versioning Neglect: Changing a service's API contract without versioning, breaking all consumers simultaneously. Strict API contracts with backward-compatible evolution are what make independent deployment possible.
Install this skill directly: skilldb add system-design-skills
Related Skills
API Gateway Design
API gateway and Backend-for-Frontend (BFF) patterns for managing client-service communication
Circuit Breaker
Circuit breaker and resilience patterns for building fault-tolerant distributed systems
Database Scaling
Database scaling patterns including sharding, replication, and read replicas for high-throughput systems
Distributed Caching
Distributed caching strategies for reducing latency and database load in large-scale systems
Event-Driven
Event-driven architecture and CQRS patterns for reactive, decoupled distributed systems
Message Queues
Message queue patterns including pub/sub, fan-out, and reliable delivery for asynchronous communication