ArchitectureMicroservices Architecture
Microservices decompose a large application into small, independently deployable services that each own a specific business domain. They enable teams to move faster — at the cost of distributed systems complexity.
Monolith vs. Microservices
Monolith
All functionality lives in one deployable unit.
Pros:
- Simple to develop and debug initially
- No network overhead between components
- Atomic transactions across all data
- Easy to test end-to-end
Cons:
- Scales as one unit (can't scale individual components)
- Deployment of any change requires deploying the whole app
- Large codebase becomes hard to understand and modify
- A bug in one module can crash everything
Microservices
Functionality split into independently deployable services.
Pros:
- Independent deployment — deploy only what changed
- Independent scaling — scale only what needs it
- Technology flexibility — each service can use the best tool for its job
- Fault isolation — one service failing doesn't necessarily kill others
- Team autonomy — small teams own specific services end-to-end
Cons:
- Network latency between services
- Distributed transactions are hard
- Operational complexity (service discovery, monitoring, tracing)
- Data consistency challenges
- Much harder to test end-to-end
Service Boundaries (Domain-Driven Design)
Good microservice boundaries follow business domains. The "strangler fig" approach: identify bounded contexts in your domain and make each a service.
Example — e-commerce:
- User Service (auth, profiles)
- Product Service (catalog, inventory)
- Order Service (order lifecycle)
- Payment Service (payment processing)
- Notification Service (emails, SMS)
- Search Service (full-text search)
Signs of bad boundaries:
- Services that always deploy together (should be merged)
- Services that need synchronous calls to 5+ other services per request (chatty interfaces, too fine-grained)
- Shared databases between services (breaks independence)
Inter-Service Communication
Synchronous (Request-Response)
- REST/HTTP: Simple, widely understood. Adds latency per hop.
- gRPC: Faster, type-safe. Better for internal services.
Problem: If Service A calls B which calls C, a failure in C can cascade through the whole chain.
Asynchronous (Event-Driven)
- Services communicate via events/messages (Kafka, RabbitMQ, SQS)
- Service A publishes an event; B and C consume it independently
- Decouples services; much better for fault tolerance
- Harder to trace and reason about
Service Discovery
With many services and dynamic scaling, how does Service A find Service B?
- Client-side discovery: Service A queries a registry (Consul, etcd, Zookeeper) to find B's address
- Server-side discovery: A load balancer or service mesh (Istio, Linkerd) handles routing transparently
- Kubernetes uses its own built-in DNS-based service discovery
Distributed Tracing
A single user request may touch 10 services. When it's slow or fails, how do you debug?
Distributed tracing (Jaeger, Zipkin, AWS X-Ray) assigns a trace ID to each request. Every service logs its span (duration, errors) with that ID. You can reconstruct the full request path.
The Database Per Service Rule
Each microservice should own its data. No shared databases. This enables true independence but requires event-driven patterns for cross-service data needs.
When NOT to Use Microservices
Microservices add complexity. For most early-stage products:
- Start with a monolith
- Identify actual bottlenecks
- Extract services where needed (the "Strangler Fig" pattern)
Premature microservices is one of the most common architecture mistakes.
Interview Tips
- Don't default to microservices — justify the decision. Mention the complexity trade-offs
- Know the key challenges: service discovery, distributed tracing, data consistency, circuit breaking
- The API Gateway pattern is almost always needed: a single entry point that routes to services, handles auth, rate limiting
- Mention event-driven communication for loose coupling — Kafka or SQS between services is often better than synchronous REST calls