System Design - Architecture
Microservices
Microservices decompose a system into small, independently deployable services that communicate over well-defined APIs. This guide covers when to use microservices, how to decompose a system, and the infrastructure patterns that make them work.
- Decomposition - Splitting by business capability or subdomain
- Service Discovery - How services find each other in a dynamic environment
- API Gateway - Single entry point for external clients
Do not start with microservices. Start with a monolith and extract services when you have clear boundaries.
Monolith vs Microservices
When to Use Microservices
| Factor | Monolith | Microservices |
|---|---|---|
| Team size | Small (< 10 engineers) | Large (multiple teams) |
| Domain complexity | Simple, well-understood | Complex, multiple bounded contexts |
| Deployment frequency | Low to moderate | High (multiple times per day) |
| Scaling needs | Scale as a unit | Scale individual components |
| Technology diversity | Single stack | Polyglot (best tool per service) |
| Organizational maturity | Low to moderate | High (DevOps culture required) |
Decomposition Strategies
By Business Capability
Decompose along organizational lines: User Management, Order Processing, Payment, Notification.
By Subdomain (DDD)
Apply Domain-Driven Design to identify bounded contexts:
Decomposition Principles
- Single Responsibility: Each service does one thing well
- High Cohesion: Related functionality lives together
- Low Coupling: Services interact through well-defined APIs
- Data Ownership: Each service owns its data (no shared databases)
- Team Autonomy: Each service is owned by a small, autonomous team
Service Discovery
In a dynamic environment where services scale up and down, services need to find each other.
Discovery Patterns
| Pattern | Description | Example |
|---|---|---|
| Client-side | Client queries registry, load balances itself | Netflix Eureka |
| Server-side | Load balancer handles discovery | AWS ELB, Kubernetes Service |
| DNS-based | Services registered as DNS entries | Consul, CoreDNS |
API Gateway
An API Gateway provides a single entry point for external clients.
API Gateway Responsibilities
| Responsibility | Description |
|---|---|
| Request routing | Route external requests to appropriate internal services |
| Authentication | Verify client identity (JWT, OAuth) |
| Rate limiting | Protect services from abuse |
| Load balancing | Distribute requests across service instances |
| Request aggregation | Combine multiple service responses into one |
| Protocol translation | Convert external protocols (HTTP) to internal (gRPC) |
| Response caching | Cache responses to reduce backend load |
| Logging and monitoring | Centralized observability |
API Gateway vs Direct Communication
| Pattern | Pros | Cons |
|---|---|---|
| Gateway | Cross-cutting concerns centralized, simplified client | Single point of failure, added latency |
| Direct | No gateway overhead, direct service communication | Cross-cutting concerns duplicated |
Inter-Service Communication
Synchronous Communication
| Protocol | Description | Use Case |
|---|---|---|
| REST/HTTP | Standard, widely supported | Simple APIs, external clients |
| gRPC | High-performance, contract-first | Internal service communication |
Asynchronous Communication
| Pattern | Description | Use Case |
|---|---|---|
| Message queue | Point-to-point task distribution | Background jobs, work queues |
| Event streaming | Pub/sub event distribution | Event sourcing, data pipelines |
| Event bus | Decoupled event notification | Domain events, choreography |
The Twelve-Factor App
The Twelve-Factor App methodology provides best practices for building microservices:
- Codebase: One codebase in version control, many deploys
- Dependencies: Explicitly declare and isolate dependencies
- Config: Store config in the environment
- Backing services: Treat backing services as attached resources
- Build, release, run: Strictly separate build and run stages
- Processes: Execute the app as one or more stateless processes
- Port binding: Export services via port binding
- Concurrency: Scale out via the process model
- Disposability: Maximize robustness with fast startup and graceful shutdown
- Dev/prod parity: Keep environments as similar as possible
- Logs: Treat logs as event streams
- Admin processes: Run admin/management tasks as one-off processes
Practice Exercises
-
Design: You are tasked with decomposing a monolithic e-commerce application into microservices. Identify the bounded contexts and define service boundaries. What data does each service own?
-
Trade-offs: Compare REST and gRPC for inter-service communication in a microservices architecture. When would you choose each?
-
Architecture: Design an API Gateway for a system with 20 microservices. How do you handle authentication, rate limiting, and request aggregation without creating a bottleneck?
-
Migration: Your team has a large monolith serving 50M users. Outline a strategy for incrementally extracting microservices without disrupting production traffic.
What to Learn Next
-> CAP Theorem Consistency models, availability, and partition tolerance.
-> Load Balancing Algorithms, health checks, and L4 vs L7.
-> Message Queues Kafka, RabbitMQ, event-driven architecture.
-> API Design REST, GraphQL, gRPC, versioning, and rate limiting.
-> Databases SQL vs NoSQL, indexing, replication, and sharding.
-> Scalability Fundamentals Vertical vs horizontal scaling and capacity planning.