Microservices Architecture: Principles, Patterns, and Trade-offs
Learn the principles of microservices architecture, including service decomposition strategies, inter-service communication, data management, and the trade-offs involved.
Why Microservices?
Microservices architecture structures an application as a collection of small, autonomous services. Each service is self-contained, implements a single business capability, and can be deployed independently.
Historical context: Microservices evolved from service-oriented architecture (SOA). They can be seen as "SOA done right" — avoiding the complexity of enterprise service buses (ESBs) by using simple, lightweight protocols.
Monolith vs Microservices
Monolithic Architecture
All components in a single deployable unit.
Microservices Architecture
Each service runs independently with its own database.
Key Principles of Microservices
1. Single Responsibility Principle
Each service does one thing well — a single business capability.
2. Decentralized Data Management
Each service owns its database. This is the most important and controversial principle.
3. Smart Endpoints, Dumb Pipes
Services communicate via simple protocols (HTTP, message queues), not through an ESB.
4. Design for Failure
Every service can fail. Build resilience with retries, circuit breakers, and fallbacks.
5. Infrastructure Automation
Use CI/CD, container orchestration (Kubernetes), and infrastructure as code.
Don't start with microservices: If you're building something new or small, start with a modular monolith. Split into microservices when you have a proven need (team scaling, independent deployment, technology diversity).
Service Decomposition Strategies
By Business Capability
Group services by what the business does, not by technical layers.
| Business Capability | Service |
|---|---|
| Customer management | Customer Service |
| Order management | Order Service |
| Payment processing | Payment Service |
| Product catalog | Catalog Service |
| Delivery management | Delivery Service |
By Subdomain (Domain-Driven Design)
Use bounded contexts from your domain model to define service boundaries.
Avoiding Common Mistakes
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Nanoservices | Too many tiny services | Combine related functionality |
| Shared database | Tight coupling, deployment issues | Per-service databases |
| Distributed monolith | Pretending microservices but all deploy together | Consolidate or truly decouple |
| Chatty services | Too many network calls between services | Use batch operations, caching, or event sourcing |
Inter-Service Communication
Synchronous: REST/gRPC
# REST call between services
import httpx
async def get_user(user_id: str):
async with httpx.AsyncClient() as client:
response = await client.get(f"http://user-service/users/{user_id}")
return response.json()
Asynchronous: Message Queues
Data Management Patterns
Database per Service
Saga Pattern (Distributed Transactions)
Instead of ACID transactions across services, use a sequence of local transactions with compensating actions.
API Composition
Combine data from multiple services at the API gateway or a dedicated BFF (Backend for Frontend).
Operational Complexity
| Concern | Monolith | Microservices |
|---|---|---|
| Deployment | Single deploy | Multiple deploys |
| Testing | Easier | Complex (integration testing) |
| Monitoring | Simple | Distributed tracing, metrics |
| Debugging | Stack trace | Correlate across services |
| Database migrations | Simple | Coordinate across services |
Essential Tools for Microservices
- Service mesh: Istio, Linkerd (mTLS, traffic management)
- API gateway: Kong, AWS API Gateway (routing, auth, rate limiting)
- Distributed tracing: Jaeger, Zipkin, AWS X-Ray
- Container orchestration: Kubernetes, Docker Swarm
- Service discovery: Consul, Eureka, Kubernetes DNS
Start with these minimums:
- Containerization (Docker)
- Orchestration (Kubernetes or managed k8s)
- Centralized logging
- Distributed tracing
- Health checks and graceful shutdown
When to Use Microservices
| Use Case | Recommendation |
|---|---|
| Small team, new project | Mod monolith |
| Large team, complex domain | Microservices |
| Need independent scaling | Microservices |
| Different tech stacks needed | Microservices |
| Fast iteration required | Microservices |
| Simple CRUD app | Monolith |
What to Remember for Interviews
- Core principle: Each service owns its data; never share databases
- Trade-offs: More operational complexity for independent deployment and scaling
- Start simple: Don't start with microservices unless you have a proven need
- Communication: Use synchronous (REST, gRPC) for request-response, async (events) for decoupled flows
- Data consistency: Use sagas for distributed transactions, eventual consistency is often acceptable
Practice: Draw a microservices architecture for a familiar application (e.g., Netflix, Uber, Twitter). Identify the services, their boundaries, and how they communicate.