Published on : Apr 01, 2026

What Are Microservices?: How They Work and Why They Matter

Microservices: Understanding Architecture, Working, and Importance.

7 Minutes Read
Gradient

Palash Somani

Principal Engineer Dream11

what is Microservices

Why Microservices Are the Backbone of Modern Software

String (4).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.


What are Microservices?

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.

Microservices in a Single Picture

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

How Microservices Work

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 milliseconds

Each service did its own job. No service had to know the internal implementation of any other service. They communicated only through defined interfaces.


Monolithic vs Microservices Architecture

The Monolith Problem Illustrated

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.

Core Components of Microservices

1. API Gateway

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)

2. Service Registry and Discovery

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

3. Load Balancer

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.

4. Message Broker (Event Bus)

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

5. Database per Service

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.

6. Circuit Breaker

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.


Key Design Patterns

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


Building a Microservice: Code Examples

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");
    }
}

Service Communication

Microservices communicate in two main ways: synchronous (request/response) and asynchronous (events).

Synchronous: HTTP/REST

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()

Asynchronous: Message Queue (Kafka / RabbitMQ)

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()

Microservices Deployment (Docker and Kubernetes)

Docker: Package Each Service as a Container

# 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: Orchestrate at Scale

# 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:5001

Kubernetes 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.


Complexity and Performance

Latency: The Network Tax

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.

Performance Table

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


Real-World Applications

  1. 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.

  2. 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.

  3. 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

Advantages and Disadvantages

Advantages

  • 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.

Disadvantages

  • 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


.Conclusion

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

FREQUENTLY ASKED QUESTIONS

Monolith is one large tightly coupled unit. Microservices break it into small independent services, each with its own codebase, database, and deployment.
Start with a monolith. Switch to microservices only when different parts need different scaling or multiple large teams need to work independently.
A single entry point for all client requests that routes them to the correct microservice.
It stops a failing service from crashing others. If a service keeps failing, it returns a fallback response instead of waiting for a timeout every time.
Docker packages a microservice and all its dependencies into a portable container that runs the same on any machine.
A pattern for handling transactions across multiple services. If one step fails, compensating transactions undo the previous steps.
A strategy to gradually replace a monolith by extracting one feature at a time into microservices until the monolith is fully retired.