HLD · Architecture
Microservices Architecture
Decompose a system into independently deployable services with clear boundaries.
1. Monolith vs microservices
| Monolith | Microservices |
|---|---|
| Single codebase & deploy | Many services, independent deploy |
| Simple debugging, shared DB | Team autonomy, polyglot stacks |
| Scale entire app together | Scale hot services only |
| Good for early stage / small teams | Good for large orgs with clear domains |
Interview line: “Start with a monolith when it meets the need. Extract a service when measured scaling needs or team boundaries justify the extra operational cost.”
2. Domain-driven service boundaries
Split by business capability, not technical layer.
- User Service — auth, profiles
- Order Service — checkout, order state
- Payment Service — charges, refunds
- Notification Service — email, push
Bad split: a "Database service" plus an "API service" — this creates chatty coupling without clear ownership.
3. Communication patterns
Synchronous (HTTP/gRPC)
The Order service calls the Payment service and waits for a response. This is easy to follow, but a slow Payment service can make the Order service slow too.
Mitigation: Use timeouts, bounded retries only for safe operations, and a circuit breaker.
Asynchronous (message queue)
Order placed → event to queue → Notification sends email. Loose coupling; harder to trace.
4. Circuit breaker pattern
States: Closed (normal) → Open (fail fast after N errors) → Half-open (trial request).
Example: Recommendation service down — product page loads without recommendations instead of hanging 30s.
5. Data ownership
Each service owns its database. No shared tables across services — access via API/events only.
Example violation: Order service directly JOINs the users table in Payment DB — creates tight coupling.
5b. Walkthrough — order fails when payment is down
- User clicks Pay → Order service calls Payment service (sync HTTP).
- Payment DB is slow → Order waits 30s → user sees timeout.
- Fix: 2s timeout + circuit breaker. After 5 failures, open circuit → fail fast with "try again".
- Async option: Order records a
PENDING_PAYMENTstate and publishes an event. Payment processes it and emits a success or failure event. Use this only if the product accepts an asynchronous checkout flow.
6. Distributed tracing & observability
One user request spans 5 services. Use correlation ID (trace ID) in headers; tools: Jaeger, Zipkin, OpenTelemetry.
Concrete example: If checkout takes four seconds, one trace can show 50 ms in Order, 3.8 seconds in Payment, and 150 ms elsewhere. The team can investigate the actual slow service instead of guessing.
7. Example: Netflix-style decomposition
- Monolith streaming app → extract billing, encoding, recommendations
- Encoding pipeline async via queue (heavy GPU workers)
- API gateway + BFF (backend-for-frontend) per client (TV vs mobile)
- Each team owns SLA for their service
8. Challenges (say these in interviews)
- Network latency & partial failures
- Distributed transactions → sagas (see Saga note)
- DevOps overhead: CI/CD per service, K8s, service mesh
- Testing integration across services
Quick revision
- Split by domain, not by layer; each service owns its data.
- Sync: HTTP/gRPC + timeout + circuit breaker.
- Async: Events/queues for decoupling.
- Don't microservice too early — operational tax is real.
- Observability: Trace IDs across services mandatory.
- Trade-off: Independent deployment adds network and operational failure modes.