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
DfMonolithic Architecture
A monolithic architecture deploys the entire application as a single unit. All components share the same process, memory, and database. It is simpler to develop, test, and deploy initially but becomes harder to scale and modify as the system grows.
DfMicroservices Architecture
A microservices architecture decomposes a system into small, independent services, each owning its data and business logic. Services communicate via lightweight protocols (HTTP, gRPC, messaging). Each service can be developed, deployed, and scaled independently.
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) |
Start with a monolith. The "Monolith First" approach (Martin Fowler) recommends building a well-structured monolith first, then extracting services when you have clear domain boundaries and the organizational capability to operate them.
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:
DfBounded Context
A bounded context is a linguistic and organizational boundary within which a particular domain model applies. Each microservice should correspond to a bounded context, owning its domain model and data.
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
The "Two Pizza Rule" (Amazon): A team should be small enough that it can be fed with two pizzas (6-8 people). This maps well to microservice team size.
Service Discovery
In a dynamic environment where services scale up and down, services need to find each other.
DfService Discovery
Service discovery is the mechanism by which services locate the network addresses of other services. It solves the problem of dynamic service instances in a microservices architecture where instances are constantly being created and destroyed.
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.
DfAPI Gateway
An API Gateway is a server that acts as a single entry point for a set of microservices. It handles cross-cutting concerns such as authentication, rate limiting, request routing, protocol translation, and response aggregation.
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 |
Use an API Gateway for external-facing APIs. For internal service-to-service communication, use a service mesh (Istio, Linkerd) instead of routing through a gateway. This avoids the gateway becoming a bottleneck.
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.
Key Takeaways:
- Start with a monolith; extract microservices when you have clear domain boundaries and organizational maturity
- Decompose by business capability or bounded context (DDD)
- Service discovery enables dynamic service location in elastic environments
- API Gateway centralizes cross-cutting concerns for external clients
- Service mesh handles internal service-to-service communication
- Follow the Twelve-Factor App methodology for building cloud-native services
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.