What Are Microservices?: How They Work and Why They Matter
Microservices: Understanding Architecture, Working, and Importance.
.png&w=3840&q=75)
Microservices: Understanding Architecture, Working, and Importance.
.png&w=3840&q=75)
.png)
In 2008, Netflix suffered a major database failure that took their entire platform offline for four days. One single point of failure brought down everything: search, playback, billing, and recommendations all went dark at the same time because they were all part of one giant application.
In 2009, Netflix started rebuilding. By 2011, they had migrated to a completely different architecture where each function (search, recommendations, streaming, billing, user accounts) ran as a completely separate, independent service. Today Netflix runs on hundreds of microservices. A failure in the recommendation engine does not stop you from watching a video. A bug in the billing service does not crash search.
Amazon made the same shift in the early 2000s. Jeff Bezos famously sent a company-wide mandate: all teams must expose their functionality through APIs, and all communication between services must go through those APIs. No exceptions. This decree is what turned Amazon from a website into a platform that could power AWS, Alexa, and Prime simultaneously.
Spotify, Uber, Airbnb, and PayPal all followed the same path. They all started monolithic, hit a scaling wall, and broke their applications into microservices to survive growth.
Understanding microservices is no longer optional for software engineers. It is the dominant architectural pattern for any application that needs to scale.
Microservices are an architectural style where a large application is broken into a collection of small, independent services. Each service handles one specific business function, runs in its own process, communicates with other services through APIs, and can be developed, deployed, and scaled independently.
Break that definition apart:
Small and focused: Each microservice does one thing well. A Payment Service handles payments. A Search Service handles search. Nothing more
Independent: Each service has its own codebase, its own database, and its own deployment pipeline. A change to the payment service does not require redeploying the search service
Communicates via APIs: Services talk to each other through well-defined interfaces (HTTP/REST, gRPC, or message queues), not shared memory or internal function calls
Independently scalable: If search traffic spikes, you scale only the search service, not the entire application
Key Analogy: Think of a microservices application like a modern restaurant kitchen. There is a separate chef for appetisers, one for main courses, one for desserts, and one for drinks. Each chef works independently at their own station with their own tools and ingredients. When the restaurant gets busy on Friday night, you can hire an extra dessert chef without touching the appetiser station.
Client (Browser / Mobile App)
|
v
[API Gateway] ← Single entry point for all requests
/ | \\ \\
v v v v
[User] [Pay] [Search] [Order] ← Independent microservices
| | | |
DB1 DB2 DB3 DB4 ← Each service owns its own database
Let us trace a single user action through a microservices system. When a user places an order on an e-commerce platform:
Step 1: Browser sends request to API Gateway
POST /orders
Step 2: API Gateway authenticates the request (via Auth Service)
and routes it to the Order Service
Step 3: Order Service receives the request
Calls Inventory Service: "Is item #4521 in stock?"
Inventory Service: "Yes, 12 units available"
Step 4: Order Service calls Payment Service
"Charge $49.99 to card ending in 4242"
Payment Service: "Charge successful, txn_id: TX89123"
Step 5: Order Service saves order to its own database
Publishes event: "ORDER_PLACED" to message broker
Step 6: Notification Service listens for ORDER_PLACED event
Sends confirmation email to customer
Step 7: Warehouse Service listens for ORDER_PLACED event
Creates a pick-and-pack task
Step 8: API Gateway returns order confirmation to browser
Total time: ~200-400 millisecondsEach service did its own job. No service had to know the internal implementation of any other service. They communicated only through defined interfaces.
Monolithic application:
┌────────────────────────────────────────┐
│ UI + Auth + Orders + Payments + │
│ Search + Notifications + Inventory │
│ (all in one) │
└────────────────────────────────────────┘
|
v
One deployment
One database
One point of failure
Change billing? Redeploy everything.
Search traffic spike? Scale everything.
Microservices:
[Auth] [Orders] [Payments] [Search] [Notify]
| | | | |
DB1 DB2 DB3 DB4 DB5
Change billing? Redeploy only [Payments].
Search traffic spike? Scale only [Search].
Payments service crashes? Others keep running.
The single entry point for all external requests. Routes requests to the correct service, handles authentication, rate limiting, and logging.
Without Gateway: With Gateway:
Client → Auth Service Client → [API Gateway] → Auth Service
Client → Order Service → Order Service
Client → Payment Service → Payment Service
(Client manages routing) (Gateway manages routing)
A directory that tracks all running service instances and their network addresses. When one service needs to call another, it asks the registry instead of hardcoding an address.
Popular tools: Eureka (Spring Cloud), Consul, Zookeeper
Distributes incoming requests across multiple instances of the same service. If you run 5 instances of the Order Service, the load balancer routes each request to the least busy instance.
Enables asynchronous communication between services. Service A publishes an event. Service B listens and reacts. Neither needs to know about the other directly.
Popular tools: Apache Kafka, RabbitMQ, Amazon SQS
Each microservice owns its own database. This is critical for independence: if the Order Service and Payment Service share one database, a schema change for orders can break payments.
Prevents a failing service from crashing all services that call it. If Service A calls Service B and B keeps failing, the circuit breaker “trips” and starts returning a fallback response immediately rather than waiting for B to time out.
Pattern | What It Does | When to Use |
|---|---|---|
API Gateway | Single entry point, routes requests | Always: required for any microservices system |
Saga | Manages distributed transactions across services | When a business operation spans multiple services |
Circuit Breaker | Stops calling a failing service, returns fallback | When downstream services can be unreliable |
Strangler Fig | Gradually replace monolith with microservices | When migrating an existing monolith |
Sidecar | Attach helper (logging, security) to each service | For cross-cutting concerns without code changes |
Event Sourcing | Store events instead of current state | When you need full audit trail of changes |
CQRS | Separate read and write models | When read and write workloads have very different needs |
Python (Flask) — User Service
from flask import Flask, jsonify, request app = Flask(__name__) # Each microservice has its own in-memory (or real) database users_db = { 1: {"id": 1, "name": "Alice", "email": "alice@example.com"}, 2: {"id": 2, "name": "Bob", "email": "bob@example.com"}, } @app.route('/users/<int:user_id>', methods=['GET']) def get_user(user_id): """Single responsibility: handle user lookups only.""" user = users_db.get(user_id) if user: return jsonify(user), 200 return jsonify({"error": "User not found"}), 404 @app.route('/users', methods=['POST']) def create_user(): data = request.json user_id = max(users_db.keys()) + 1 users_db[user_id] = {"id": user_id, **data} return jsonify(users_db[user_id]), 201 @app.route('/health', methods=['GET']) def health(): """Health check endpoint — Kubernetes uses this.""" return jsonify({"status": "healthy"}), 200 if __name__ == '__main__': app.run(port=5001) # User Service runs on its own port
Node.js (Express) — Order Service
const express = require('express'); const axios = require('axios'); // To call other microservices const app = express(); app.use(express.json()); const orders = []; app.post('/orders', async (req, res) => { const { user_id, product_id, quantity } = req.body; // Call User Service to verify the user exists const userRes = await axios.get(`http://user-service:5001/users/${user_id}`); if (!userRes.data) { return res.status(400).json({ error: 'Invalid user' }); } // Create order (in a real system: call Inventory + Payment services too) const order = { id: orders.length + 1, user_id, product_id, quantity, status: 'PLACED' }; orders.push(order); // Publish event (other services listen and react asynchronously) console.log(`Event published: ORDER_PLACED${order.id}`); res.status(201).json(order); }); app.listen(5002, () => console.log('Order Service running on port 5002'));
Java (Spring Boot) — Payment Service
@RestController @RequestMapping("/payments") public class PaymentController { @PostMapping public ResponseEntity<Map<String, Object>> processPayment( @RequestBody Map<String, Object> request) { // Single responsibility: handle payment processing only String orderId = (String) request.get("order_id"); Double amount = (Double) request.get("amount"); // Process payment logic (simplified) Map<String, Object> result = new HashMap<>(); result.put("transaction_id", "TXN_" + System.currentTimeMillis()); result.put("order_id", orderId); result.put("amount", amount); result.put("status", "SUCCESS"); return ResponseEntity.ok(result); } @GetMapping("/health") public Map<String, String> health() { return Map.of("status", "healthy"); } }
Microservices communicate in two main ways: synchronous (request/response) and asynchronous (events).
import requests
def get_user_details(user_id):
"""
Synchronous call: wait for the response before continuing.
Good for: real-time data needed immediately (user profile on page load).
Bad for: long-running processes (sending emails, processing payments).
"""
response = requests.get(f"<http://user-service:5001/users/{user_id}>")
return response.json()import json import pika # RabbitMQ client def publish_event(event_type, data): """ Asynchronous: publish and forget. Other services listen and react. Good for: notifications, analytics, warehouse tasks. The Order Service does NOT wait for the Notification Service. """ connection = pika.BlockingConnection(pika.ConnectionParameters('rabbitmq')) channel = connection.channel() channel.queue_declare(queue='order_events') message = json.dumps({"event": event_type, "data": data}) channel.basic_publish(exchange='', routing_key='order_events', body=message) connection.close() def listen_for_orders(): """Notification Service: listens for ORDER_PLACED events.""" connection = pika.BlockingConnection(pika.ConnectionParameters('rabbitmq')) channel = connection.channel() channel.queue_declare(queue='order_events') def callback(ch, method, properties, body): event = json.loads(body) if event['event'] == 'ORDER_PLACED': print(f"Sending confirmation email for order{event['data']['order_id']}") channel.basic_consume(queue='order_events', on_message_callback=callback, auto_ack=True) channel.start_consuming()
# Dockerfile for User Service
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5001
CMD ["python", "app.py"]# docker-compose.yml: run all services locally
version:'3'
services:
user-service:
build: ./user-service
ports:
-"5001:5001"
order-service:
build: ./order-service
ports:
-"5002:5002"
depends_on:
- user-service
payment-service:
build: ./payment-service
ports:
-"5003:5003"
rabbitmq:
image: rabbitmq:3-management
ports:
-"5672:5672"# Kubernetes deployment for User Service
# Run 3 replicas, self-heal if any crash
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas:3 # Run 3 copies for high availability
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
-name: user-service
image: myapp/user-service:1.0
ports:
-containerPort:5001
readinessProbe:
httpGet:
path: /health # Kubernetes checks this endpoint
port:5001Kubernetes self-healing: If one of the 3 User Service pods crashes, Kubernetes automatically starts a replacement within seconds to maintain the desired state of 3 replicas. No human intervention needed.
Every service-to-service call goes over the network. In a monolith, a function call takes nanoseconds. In microservices, an HTTP call takes milliseconds.
Monolith: function call
orderService.processPayment() → ~0.001ms (in-memory)
Microservices: HTTP call
POST <http://payment-service:5003/payments> → ~2-20ms (network)
For a page load that makes 5 service calls:
Best case: 5 × 2ms = 10ms (parallel calls)
Worst case: 5 × 20ms = 100ms (sequential calls)
Solution: Make independent calls in PARALLEL, not sequential.Operation | Monolith | Microservices | Notes |
|---|---|---|---|
Internal function call | O(1), nanoseconds | O(1) + network, milliseconds | Network overhead per call |
Horizontal scaling | Scale entire app | Scale individual service | Microservices more efficient |
Deployment time | Minutes (full rebuild) | Seconds (one service) | Faster iteration |
Failure recovery | Entire app restarts | One service restarts | Better resilience |
Database query | Shared DB, possible contention | Isolated DB per service | Less contention |
Netflix (600+ microservices): Each function (streaming, search, recommendations, billing, user accounts, device management, content encoding) is a separate service. Netflix deploys hundreds of times per day without any downtime.
Amazon: Order management, payment, inventory, recommendations, and shipping are all independent services. During Black Friday, only the services under high load (orders, payments) are scaled up, not the entire platform.
Uber: Microservices handle driver matching, pricing (surge calculation), payments, maps, notifications, and trip history independently. A bug in the surge pricing algorithm does not prevent drivers from accepting trips.
# Simplified Netflix architecture (conceptual) NETFLIX_SERVICES = { "auth-service": "Handles login and token validation", "profile-service": "Manages user profiles and preferences", "catalog-service": "Stores metadata for all titles", "recommendation-service": "Generates personalised suggestions", "search-service": "Full-text search across catalogue", "playback-service": "Manages video stream delivery", "billing-service": "Handles subscription and payments", "notification-service": "Sends emails and push notifications", "analytics-service": "Tracks viewing behaviour for recommendations", } # Each service: independent team, independent database, independent deployment # When recommendation-service has a bug → billing, playback, search still work
Independent deployability: Each service can be deployed, updated, or rolled back without touching any other service. Teams can ship multiple times per day. A bug fix in the payment service does not require redeploying the entire application.
Independent scalability: If the search service handles 10x more traffic than the payment service, you scale only search. In a monolith, you must scale everything, wasting resources and increasing cost.
Technology flexibility: The user service can be Python, the recommendation service can be Go, and the payment service can be Java. Each team picks the best tool for their specific problem without being locked into a single stack.
Fault isolation: When one service fails, the rest continue operating. A properly designed microservices system degrades gracefully: if recommendations are down, users can still search and watch content.
Parallel development: Independent teams work on independent services simultaneously without blocking each other. A large engineering team can work at full speed instead of waiting for merges and shared code changes.
Distributed systems complexity: A monolith has one process. Microservices have dozens or hundreds, each a potential failure point. Network partitions, service timeouts, and partial failures require explicit handling that simply does not exist in a monolith.
Network latency overhead: Every service call crosses the network. A page that requires data from five services incurs five network round-trips. Without careful design (parallel calls, caching, smart API gateway aggregation), this latency compounds.
Data consistency challenges: Without a shared database, maintaining consistency across services requires distributed transactions (Saga pattern) or eventual consistency, both significantly more complex than a simple database transaction.
Operational overhead: You need container orchestration (Kubernetes), service discovery, distributed tracing, centralised logging, and monitoring dashboards. A monolith needs none of this. Microservices require a mature DevOps infrastructure before they become manageable.
Testing complexity: Testing a monolith means running one application. Testing microservices requires setting up the entire ecosystem of dependent services (or mocking them), which is significantly more complex to set up correctly
Microservices are the architectural pattern that enables modern software to scale.
Here is the full recap:
Microservices break a large application into small, independent services where each service handles one business function, owns its own database, and communicates through APIs.
The core shift from monoliths: independent deployment, independent scaling, and fault isolation. One service failing does not bring down the rest.
Key components: API Gateway (single entry point), Service Registry (service discovery), Message Broker (async communication), Circuit Breaker (fault tolerance), and Database per Service (data isolation).
Services communicate synchronously (HTTP/REST for real-time needs) or asynchronously (message queues for background tasks).
Docker packages each service as a portable container. Kubernetes orchestrates containers at scale with self-healing and auto-scaling.
The main trade-off: microservices give you scalability, resilience, and speed of development at the cost of operational complexity and distributed systems challenges.
Netflix, Amazon, Uber, Spotify, and Airbnb all switched to microservices after hitting the scaling limits of monolithic architecture. For high-traffic applications, microservices are the standard.
FAQ