Microservices Architecture
Microservices decompose an application into independently deployable services, each owning a bounded context, data store, and release cadence.
Introduction
Microservices decompose an application into independently deployable services, each owning a bounded context, data store, and release cadence. Netflix, Uber, and Amazon popularized the pattern for organizational scale as much as technical scale — small teams ship small services without coordinating monolith releases.
The cost is operational complexity: network latency, partial failures, distributed tracing, contract testing, and eventual consistency across service boundaries. HLD interviews expect you to name these costs honestly while designing service boundaries, APIs, and data ownership.
This lesson covers decomposition strategies, synchronous vs asynchronous integration, and the observability stack that makes microservices operable.
Understanding the topic
Key concepts
- Each service owns its data — no shared tables across services (database per service ideal).
- Communication: sync REST/gRPC for queries; async events for decoupled side effects.
- API Gateway aggregates mobile-friendly responses; services stay granular internally.
- Independent scaling: scale search service separately from checkout.
- Failure isolation: circuit breakers prevent cascade; bulkheads limit blast radius.
- Requires mature DevOps: CI/CD per service, container orchestration, centralized logging.
flowchart TBClient --> GW[API Gateway]GW --> U[User Service]GW --> O[Order Service]GW --> P[Payment Service]O --> MQ[Kafka]P --> MQ
Internal architecture
Architecture overview
flowchart TBClient --> GW[API Gateway]GW --> U[User Service]GW --> O[Order Service]GW --> P[Payment Service]O --> MQ[Kafka]P --> MQ
Step-by-step explanation
- Clients → CDN/LB → API Gateway (auth, rate limit, routing).
- Domain services: User, Catalog, Cart, Order, Payment, Notification — each with private DB.
- Order service publishes OrderPlaced event to Kafka; Notification and Analytics consume.
- Synchronous calls only on critical path; prefer event-driven for non-blocking steps.
- Service mesh (optional) handles mTLS, retries, metrics between pods.
- Centralized identity (OAuth2/JWT), distributed tracing (OpenTelemetry), SLO dashboards per service.
Informative example
Order service publishes domain events — decouples payment capture from email and analytics:
@Servicepublic class OrderService {private final OrderRepository repo;private final KafkaTemplate<String, OrderPlacedEvent> kafka;public OrderService(OrderRepository repo, KafkaTemplate<String, OrderPlacedEvent> kafka) {this.repo = repo;this.kafka = kafka;}@Transactionalpublic Order create(CreateOrderRequest req) {Order order = repo.save(Order.from(req));kafka.send("order.placed", order.id(), new OrderPlacedEvent(order.id(), req.userId(), order.total()));return order;}}public record OrderPlacedEvent(String orderId, String userId, BigDecimal total) {}@Componentpublic class NotificationConsumer {@KafkaListener(topics = "order.placed", groupId = "notification")public void onOrderPlaced(OrderPlacedEvent event) {// send email asynchronously — failure retries without blocking checkout}}
State data ownership: Order DB holds orders only. Payment service owns charges; integrate via events or sagas, not cross-DB joins.
Real-world use
Real-world use cases
- Global e-commerce with teams per domain (catalog, pricing, fulfillment).
- Ride-hailing: matching, pricing, maps, payments evolve on different release trains.
- Banking: account service isolated from PCI-scoped payment card service.
- Social platform: feed, graph, media transcoding scale independently.
Best practices
- Decompose by business capability (bounded context), not technical layer.
- Design idempotent consumers and compensating transactions for sagas.
- Version APIs; avoid breaking consumers — contract tests in CI.
- Limit synchronous call chains to 2–3 hops; prefer events for fan-out.
- Standardize platform concerns: auth, logging format, health checks.
- Start with a monolith extract — strangler fig pattern — not big-bang rewrite.
Common mistakes
- Shared database across microservices — creates hidden coupling.
- Chatty fine-grained services causing latency and operational nightmares.
- Distributed monolith: must deploy all services together due to tight coupling.
- No correlation IDs — impossible to debug cross-service failures.
- Ignoring data consistency — expecting ACID across services without saga design.
Advanced interview questions
Q1BeginnerWhat defines a microservice?
Q2BeginnerMain downside of microservices?
Q3IntermediateSync vs async between services?
Q4IntermediateHow avoid distributed monolith?
Q5AdvancedSplit e-commerce monolith — what services first?
Summary
Microservices trade simplicity for independent scale and team velocity. Database-per-service and event-driven integration are core patterns. API Gateway and observability are non-optional platform layers. Design for failure: circuit breakers, idempotency, sagas. Extract gradually; avoid premature microservice adoption. SOA and event-driven lessons deepen integration choices next.