Microservices & API Design
Microservices architectures decouple systems into independent, scalable units, but introduce significant distributed systems complexity. You must understand how they communicate, fail, and maintain consistency.
API Paradigms
| Paradigm | Protocol | Format | Best For |
|---|---|---|---|
| REST | HTTP/1.1 | JSON | Public APIs, CRUD apps, standard web clients. |
| gRPC | HTTP/2 | Protobuf (Binary) | Internal microservice-to-microservice communication. High performance, strongly typed. |
| GraphQL | HTTP | JSON | Complex UIs where clients need to specify exactly what data they want. Prevents over-fetching. |
| Webhooks | HTTP | JSON | Server-to-server event notifications (e.g. Stripe payment succeeded). |
REST API Best Practices
- Resource-Oriented: Use nouns, not verbs.
GET /users/123/ordersnotPOST /getUserOrders. - HTTP Methods:
GET- Retrieve (Safe, Idempotent)POST- Create (Not idempotent)PUT- Replace entirely (Idempotent)PATCH- Partial update (Usually idempotent)DELETE- Remove (Idempotent)
- Status Codes: 200 (OK), 201 (Created), 204 (No Content), 400 (Bad Request), 401 (Unauthorized - identity), 403 (Forbidden - permissions), 404 (Not Found), 429 (Too Many Requests), 500 (Internal Server Error).
- Versioning: Either URI (
/v1/users) or Header (Accept: application/vnd.company.v1+json). - Pagination: Use Cursor-based pagination (
?cursor=xyz) for massive datasets. Offset pagination (?page=5&limit=20) gets extremely slow on deep pages.
Managing Data in Microservices
The golden rule of microservices: Database per service. Services should NEVER share a database. If Service A needs Service B's data, it must call Service B's API.
Distributed Transactions (The Saga Pattern)
In a monolith, you use ACID transactions. In microservices, a workflow might span 3 services (e.g., Order Service → Inventory Service → Payment Service). If Payment fails, you must rollback Order and Inventory.
- Choreography: Event-driven. Order Service emits "OrderCreated". Inventory listens, reserves stock, emits "StockReserved". If payment fails, it emits "PaymentFailed", and Inventory/Order listen and run compensating transactions to undo the work.
- Orchestration: A central "Saga Orchestrator" service commands the other services and handles the rollback logic explicitly.
CQRS (Command Query Responsibility Segregation)
Separating the read model from the write model. Writes go to a primary DB (Command). An async event bus updates a secondary DB optimized for querying (e.g. Elasticsearch or a flattened NoSQL table). Reads hit the secondary DB.
Event Sourcing
Instead of storing current state, store every state-changing event as an append-only log. The current state is derived by replaying the events. (Think of a bank ledger: you don't just store "Balance=$500", you store "+$600", "-$100").
Inter-Service Communication & Resilience
Networks fail. Services crash. Your system must survive.
Sync vs Async
- Synchronous (REST/gRPC): Service A calls Service B and waits. Danger: If B is slow, A blocks. If B is down, A fails. Creates cascading failures.
- Asynchronous (Message Broker): Service A publishes to Kafka/RabbitMQ. Service B consumes it. Benefit: High availability, decoupling, smooths out traffic spikes.
Resilience Patterns
- Timeouts: NEVER make a network call without a strict timeout.
- Retries with Exponential Backoff: If a call fails, retry after 1s, then 2s, then 4s, 8s. Add Jitter (randomness) so all failing clients don't retry at the exact same millisecond.
- Circuit Breaker: If Service B fails 10 times in a row, "trip" the circuit breaker. Service A immediately returns an error or fallback value without calling B, giving B time to recover.
- Bulkhead: Isolate resources. If the Payment Service is slow, don't let it consume all the threads in the API Gateway, leaving none for the Profile Service.
- Idempotency: Ensure that if a request is retried (due to a network blip), the operation only happens once. (e.g., Pass an
Idempotency-Keyheader; server checks if it has processed it before).
Microservices Infrastructure
API Gateway
The single entry point for clients. Handles Cross-Cutting Concerns: SSL termination, Authentication (JWT validation), Rate Limiting, Request Routing.
Service Discovery
How does Service A find Service B when IPs change constantly in Kubernetes? A Service Registry (like Consul or K8s internal DNS) keeps track of healthy instances.
Observability (The Three Pillars)
- Logs: Must be structured (JSON) and centralized (ELK stack).
- Metrics: Time-series data (Prometheus/Grafana). Track RED metrics (Rate, Errors, Duration).
- Distributed Tracing: (OpenTelemetry, Jaeger). A
trace_idis generated at the API Gateway and passed in HTTP headers to every downstream service. Allows you to visualize the entire path of a request across 10 different microservices.
Authentication (JWT vs OAuth)
JWT (JSON Web Token): Stateless. Contains header.payload.signature. The signature prevents tampering. Downside: Cannot be easily revoked before expiration unless you maintain a blacklist in Redis.
OAuth 2.0: An authorization framework.
Authorization Code Flow: Client gets a code from Auth Server, exchanges it for an Access Token on the backend. Highly secure.
Client Credentials Flow: Machine-to-machine authentication.