Microservices
How to decompose a system into small, autonomous services that can be deployed independently, aligned with business domains.
What problem it solves
As a monolithic application grows, problems emerge that slow the team down:
- Risky deployments: A small change requires redeploying the entire application
- Inefficient scaling: To scale one feature, you have to scale the whole monolith
- Blocked teams: Multiple teams work on the same codebase and block each other
- Homogeneous technology: The entire system must use the same language, framework, and database
- Growing build times: Compiling and testing the whole monolith takes longer and longer
Monolith:
βββββββββββββββββββββββββββββββββββ
β Application β
β βββββββ βββββββ ββββββββββββ β
β βUsersβ βProdsβ β Orders β β
β βββββββ βββββββ ββββββββββββ β
β ββββββββββββββββββββββββββββββββ
β β Single database ββ
β ββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββ
One deploy = the whole system
One failure = the whole system
How it works
The microservices architecture decomposes the system into small, independent services, each responsible for a specific business capability. Each service:
- Has its own database (Database per Service)
- Is deployed independently
- Communicates with other services through APIs or events
- Is owned by a specific team
Microservices:
ββββββββββββ ββββββββββββ ββββββββββββ
β Users β β Products β β Orders β
β Service β β Service β β Service β
β ββββββ β β ββββββ β β ββββββ β
β β DB β β β β DB β β β β DB β β
β ββββββ β β ββββββ β β ββββββ β
ββββββββββββ ββββββββββββ ββββββββββββ
β β β
βββββββββ Event Bus ββββββββββ
Decomposition principles
The key lies in how to divide the system. The most effective criteria are:
| Criterion | Description | Example |
|---|---|---|
| By business domain | Each service represents a DDD bounded context | User Service, Order Service, Payment Service |
| By business capability | Each service implements an organizational capability | Billing, Inventory, Notifications |
| By subdomain | Aligned with the business subdomains | Core (orders), Supporting (reports), Generic (auth) |
Communication between services
Microservices communicate in two main ways:
Synchronous (request-response):
Order Service ββHTTP/gRPCβββΊ Product Service
"Is product X in stock?"
β "Yes, 50 units"
Asynchronous (events):
Order Service ββeventβββΊ Event Bus βββΊ Inventory Service
"OrderCreated" "Reserve stock"
βββΊ Notification Service
"Send email"
Database per Service
Each microservice owns its data. No other service accesses its database directly:
β
Correct:
Order Service ββAPIβββΊ User Service
"Give me the name of user 123"
β Incorrect:
Order Service ββSQLβββΊ User DB
"SELECT name FROM users WHERE id=123"
This guarantees encapsulation and lets each service choose the most suitable storage technology (SQL, NoSQL, cache, etc.).
Advantages
- Independent deployment: Each service is deployed without affecting the others
- Granular scaling: Scale only the services that need it
- Team autonomy: Each team owns its service end-to-end
- Technology diversity: Each service can use the most suitable language and framework
- Resilience: A failure in one service does not necessarily bring down the whole system
- Time to market: Small teams can deliver faster
Trade-offs / Disadvantages
- Distributed complexity: Debugging, testing, and tracing are significantly harder
- Eventual consistency: Distributed transactions are complex (they require Sagas, Outbox)
- Network latency: Communication between services adds latency
- Operational overhead: More services = more deployments, more monitoring, more infrastructure
- Data duplication: Each service may need copies of other servicesβ data
- Organizational complexity: Requires mature teams with a DevOps culture
When to use
- Large systems with multiple development teams
- When different parts of the system have very different scaling requirements
- When you need to deploy features independently and frequently
- Organizations with autonomous teams aligned with business domains
- Systems where resilience and availability are critical
When to avoid
- Small teams (fewer than 8-10 developers) where the overhead is not justified
- Systems with a simple domain that does not require decomposition
- When you donβt have mature CI/CD, monitoring, and orchestration infrastructure
- Prototypes or MVPs where development speed is the priority
- When you canβt define clear bounded contexts (a sign that the domain is not well understood)
Common technologies and implementations
| Category | Options |
|---|---|
| Synchronous communication | REST, gRPC, GraphQL |
| Asynchronous communication | Kafka, RabbitMQ, Amazon SQS, NATS |
| Orchestration | Kubernetes, Docker Swarm, Amazon ECS |
| Service Discovery | Consul, Eureka, DNS-based (Kubernetes) |
| Observability | Jaeger, Zipkin, OpenTelemetry, Prometheus |
| API Gateway | Kong, Traefik, AWS API Gateway |
Relationship with other patterns
- DDD (Domain-Driven Design): Provides the criteria for defining the boundaries of each service
- API Gateway: A single entry point for external clients
- Saga Pattern: Handles transactions that span multiple services
- Event-Driven Architecture: Asynchronous communication pattern between services
- Circuit Breaker: Protects services from cascading failures
Next steps
Microservices are a powerful but demanding architectural style. To understand how to handle asynchronous communication between them, explore Event-Driven Architecture. To protect communication with external systems, review the Anti-Corruption Layer.