Microservices Communication

Production-grade guide to microservices communication covering architecture patterns, implementation strategies, testing approaches, and operational best practices for enterprise engineering teams.

Microservices Communication is the practice of connecting independent services over a network, enabling distributed systems to collaborate through well-defined interfaces. It matters when services need to exchange data, coordinate workflows, or react to events across a shared infrastructure—whether in a cloud-native application, a multi-region deployment, or a system with high availability and scalability demands. Communication patterns determine latency, reliability, error handling, and observability across the system.

Request-Response Communication

Services exchange data synchronously using HTTP, typically with JSON payloads. The client sends a request and waits for a response, making it ideal for user-facing operations and orchestration.

HTTP Client Configuration

A typical request-response cycle involves setting timeouts, retries, and serialization. The client must handle network failures, serialization errors, and inconsistent responses.

{
  "http": {
    "timeout": "30s",
    "retries": {
      "max": 3,
      "backoff": "exponential",
      "initial_delay": "100ms",
      "max_delay": "5s"
    },
    "serialization": "json",
    "compression": "gzip",
    "headers": {
      "Accept": "application/json",
      "Content-Type": "application/json",
      "X-Request-ID": "uuid"
    },
    "auth": {
      "type": "bearer",
      "token_source": "file",
      "path": "/etc/secrets/jwt-token"
    }
  }
}

A service calling /api/orders/123 may receive:

HTTP/1.1 200 OK
Content-Type: application/json
X-Request-ID: a3f8c2d1-9e4b-4d2a-8c3f-2e9d1a4b8c3f
Content-Length: 214

{
  "id": 123,
  "customer_id": 45,
  "total": 259.99,
  "status": "shipped",
  "created_at": "2023-09-15T12:00:00Z"
}

Common Failure Modes

Idempotency and Retry Safety

Idempotency is essential in request-response communication, especially for stateful operations like order creation.

POST /api/orders HTTP/1.1
X-Request-ID: a3f8c2d1-9e4b-4d2a-8c3f-2e9d1a4b8c3f
X-Idempotency-Key: 1234567890
Content-Type: application/json

{
  "customer_id": 45,
  "items": [
    { "product_id": 101, "quantity": 2 }
  ]
}

The service must use X-Idempotency-Key as a primary key to deduplicate requests. If the same key arrives twice, the service returns the same result without creating a duplicate order. The client may retry the request up to three times, knowing that the operation is idempotent.

Event-Driven Communication

Services react to events asynchronously through message brokers or event streams. This pattern decouples producers and consumers, enabling scalability and resilience.

Message Broker Configuration

A message broker such as Apache Kafka or RabbitMQ supports event-driven communication. Messages are published to topics or exchanges and consumed by one or more services.

{
  "kafka": {
    "brokers": [
      "kafka-1.prod.example.com:9092",
      "kafka-2.prod.example.com:9092"
    ],
    "topic": "order.created",
    "partition_count": 8,
    "replication_factor": 3,
    "retention": "7d",
    "compression": "lz4"
  }
}

A producer sends an event:

{
  "event_id": "evt-1a2b3c",
  "event_type": "order.created",
  "timestamp": "2023-09-15T12:00:00Z",
  "payload": {
    "order_id": 123,
    "customer_id": 45,
    "total": 259.99
  }
}

A consumer polls the topic, processes the event, and commits the offset.

Consumer Group and Offset Management

Consumers form a group to share the load of processing messages. Each consumer maintains an offset in the __consumer_offsets topic, tracking the last processed message.

A consumer may fail while processing a message. Upon restart, it resumes from the last committed offset. The broker tracks the consumer group state, ensuring no message is lost or duplicated.

Eventual Consistency and Idempotent Processing

Event-driven systems are eventually consistent. A service may receive an event, process it, and update its state, but the change may not be immediately visible to other services.

To handle duplicate events or partial failures, consumers must implement idempotent processing.

def process_order_created(event):
    order_id = event["payload"]["order_id"]
    if db.is_order_processed(order_id):
        return  # Already processed; skip

    db.update_order_status(order_id, "confirmed")
    send_confirmation_email(order_id)
    db.mark_order_processed(order_id)
    db.commit()

If the same event arrives twice, the service skips the second processing. The order_id is used as a deduplication key.

Event Schema Evolution

Events may change over time. A service may publish a new version of order.created, adding a shipping_method field:

{
  "event_type": "order.created.v2",
  "payload": {
    "order_id": 123,
    "customer_id": 45,
    "total": 259.99,
    "shipping_method": "express"
  }
}

Consumers must be backward compatible. A consumer expecting order.created must handle order.created.v2, reading the new field and using defaults for missing ones.

Failure Modes

Service Discovery and Load Balancing

Services dynamically locate and communicate with each other using service discovery, enabling horizontal scaling and dynamic routing.

Dynamic Service Registration

A service registers itself with a service registry at startup:

POST /registry/services HTTP/1.1
Host: service-registry.prod.example.com
Content-Type: application/json

{
  "name": "order-service",
  "version": "1.2.0",
  "address": "10.1.2.3",
  "port": 8080,
  "health_check": {
    "path": "/health",
    "interval": "10s",
    "timeout": "5s"
  },
  "tags": [ "prod", "api", "order" ]
}

The registry responds with a 201 Created and a unique service_id.

Service Lookup and Routing

A client queries the registry for a service and receives a list of instances:

GET /registry/services/order-service HTTP/1.1
Host: service-registry.prod.example.com

HTTP/1.1 200 OK
Content-Type: application/json

[
  {
    "id": "svc-1",
    "name": "order-service",
    "address": "10.1.2.3",
    "port": 8080,
    "health": "healthy",
    "tags": [ "prod", "api" ]
  },
  {
    "id": "svc-2",
    "name": "order-service",
    "address": "10.1.2.4",
    "port": 8080,
    "health": "healthy",
    "tags": [ "prod", "api" ]
  }
]

The client uses this list to route requests, often with round-robin, least-connections, or consistent hashing.

Health Checks and Load Balancer Integration

The registry runs health checks on each instance. A service is marked unhealthy if it fails to respond within 5s to a /health request.

The load balancer uses health status to route traffic:

Silent Failures

Observability and Monitoring

Communication between services must be observable. Logs, metrics, and traces enable debugging, alerting, and performance tuning.

Distributed Tracing

Each request spans multiple services. A trace ID is passed through the system.

GET /api/orders/123 HTTP/1.1
X-Trace-ID: 4d8f2a1c-b3d7-4e5a-8f2c-1a3b5c7d9e1f
X-Baggage: user_id=1234,session_id=abcd

Each service logs the trace ID and records its processing time.

A trace may look like:

Trace ID: 4d8f2a1c-b3d7-4e5a-8f2c-1a3b5c7d9e1f
├── [12:00:00] user-service: GET /api/users/45
│   └── Duration: 120ms
├── [12:00:01] order-service: GET /api/orders/123
│   └── Duration: 450ms
├── [12:00:02] payment-service: POST /api/payments
│   └── Duration: 680ms
└── [12:00:03] email-service: SEND order-confirmation
    └── Duration: 110ms

Error Propagation

Errors are propagated through the communication stack. A 500 Internal Server Error in payment-service results in a 503 Service Unavailable in order-service.

The order-service logs:

{
  "error": {
    "type": "PaymentServiceError",
    "message": "Payment failed: Insufficient funds",
    "code": 402,
    "details": {
      "payment_id": "pay-12345",
      "amount": 259.99,
      "currency": "USD"
    }
  },
  "trace_id": "4d8f2a1c-b3d7-4e5a-8f2c-1a3b5c7d9e1f",
  "timestamp": "2023-09-15T12:00:03Z"
}

Metrics and Alerting

Key metrics include:

An alert triggers when order-service has a 5xx error rate above 5% for 5 minutes.

Fault Tolerance and Resilience Patterns

Communication failures must be handled gracefully. Services must recover from timeouts, network partitions, and partial failures.

Retry Strategies

A client implements retry with jitter:

{
  "retries": {
    "strategy": "exponential_with_jitter",
    "base_delay": "100ms",
    "multiplier": 2,
    "max_delay": "5s",
    "jitter": "0.2"
  }
}

Circuit Breaker Integration

A circuit breaker monitors the health of a downstream service. When failures exceed a threshold, it opens the circuit, bypassing the service.

{
  "circuit_breaker": {
    "threshold": 5,
    "timeout": "10s",
    "failure_ratio": 0.5,
    "half_open_attempts": 3,
    "reset_timeout": "2m"
  }
}

When the circuit is open, subsequent requests fail immediately with a 503 Service Unavailable and a X-Circuit-Breaker: open header.

Bulkhead Isolation

A service uses a bulkhead pattern to isolate resource pools for different clients. A request to order-service uses a dedicated thread pool, separate from payment-service.

{
  "bulkheads": {
    "order_service": {
      "max_concurrent": 100,
      "queue_size": 200,
      "timeout": "30s"
    },
    "payment_service": {
      "max_concurrent": 50,
      "queue_size": 100,
      "timeout": "60s"
    }
  }
}

If order_service is overwhelmed, payment_service requests are queued but continue processing.

Summary of Key Trade-offs

Each communication pattern is not a standalone solution but a part of a larger resilience strategy. The choice of protocol, message format, retry policy, and tracing mechanism shapes the behavior and reliability of the entire system.

This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.