Skip to main content

Service Communication and API Gateway

Understanding communication patterns, API Gateway architecture, and building stateless services for microservices systems.


Table of Contents

  1. Communication Patterns
  2. Stateless Services
  3. API Gateway Pattern
  4. Service Mesh
  5. Synchronous Communication
  6. Asynchronous Communication
  7. Event-Driven Architecture
  8. Resilience Patterns

Communication Patterns

Direct Service-to-Service

Services communicate directly with each other without intermediaries. Each service knows about and calls other services directly.

Advantages:

  • Simple to understand
  • Low latency (no intermediary hops)
  • Clear dependencies

Disadvantages:

  • Tight coupling between services
  • Hard to change service locations
  • No central control or monitoring
  • Each service implements cross-cutting concerns

Use for internal service-to-service communication within trusted boundaries.

API Gateway Pattern

A single entry point for external clients. Gateway routes requests to appropriate backend services.

Advantages:

  • Single entry point for clients
  • Centralized cross-cutting concerns (auth, logging, rate limiting)
  • Backend services hidden from clients
  • Protocol translation (REST to gRPC)

Disadvantages:

  • Single point of failure
  • Potential bottleneck
  • Additional network hop
  • Complexity in gateway logic

Essential for client-facing APIs in microservices.

Service Mesh

Infrastructure layer handling service-to-service communication. Provides observability, security, and reliability features.

Advantages:

  • Consistent observability across services
  • Automatic retries and circuit breaking
  • Mutual TLS for security
  • Traffic management (canary, blue-green)

Disadvantages:

  • Operational complexity
  • Resource overhead (sidecar proxies)
  • Learning curve
  • Debugging complexity

Consider when you have many services and need sophisticated traffic management.

Backend for Frontend (BFF)

Separate backends for different client types (web, mobile, IoT). Each BFF tailored to specific client needs.

Advantages:

  • Optimized for each client type
  • Reduces over-fetching and under-fetching
  • Client teams can own their BFF

Disadvantages:

  • Code duplication across BFFs
  • More services to maintain
  • Potential consistency issues

Useful when clients have significantly different needs.


Stateless Services

What Is Statelessness

A stateless service doesn't retain client session state between requests. Each request contains all information needed to process it.

State lives in:

  • Client (cookies, local storage)
  • External stores (database, cache)
  • Request payload (tokens)

Not in service memory.

Why Stateless Matters

Scalability - Any instance can handle any request. No session affinity needed. Easy to add/remove instances.

Resilience - Instance failures don't lose state. Requests can be retried on different instances.

Simplicity - No session synchronization. No sticky sessions. Load balancing is straightforward.

Deployment - Rolling updates and restarts don't affect users. No session migration needed.

Making Services Stateless

Externalize Sessions - Store session data in Redis, Memcached, or database. All instances access shared session store.

Use Tokens - JWT tokens contain session data. Services validate tokens without maintaining state.

Idempotent Operations - Design operations to be safely retried. Use idempotency keys for non-idempotent operations (payments, orders).

Pass State in Requests - Include necessary context in each request. Don't rely on previous requests.

Managing Stateful Requirements

Some requirements appear to need state but can be handled statelessly:

Shopping Carts - Store in Redis with user ID key or send cart contents with each request.

Multi-Step Workflows - Use workflow engine (Temporal, Cadence) or store workflow state in database.

WebSocket Connections - Stateful by nature. Use sticky sessions or message broker for cross-instance communication.

File Uploads - Stream directly to object storage. Don't buffer in service memory.

Caching in Stateless Services

Local caching appears stateful but is acceptable when:

  • Cache invalidation is handled properly
  • Stale data is acceptable
  • Cache is populated on demand

Distributed cache (Redis) is preferable when consistency matters.

Configuration as State

Configuration is a form of state. Handle by:

  • External configuration service
  • Environment variables
  • Configuration files mounted in containers
  • Feature flags from central service

Don't hardcode configuration. Services should be configurable without code changes.


API Gateway Pattern

Gateway Responsibilities

Request Routing - Direct requests to appropriate backend services based on path, headers, or other criteria.

Protocol Translation - Convert between external protocols (HTTP REST) and internal protocols (gRPC, GraphQL).

Authentication and Authorization - Verify client identity and permissions before routing to services.

Rate Limiting - Enforce rate limits per client, preventing abuse and ensuring fair usage.

Request/Response Transformation - Modify requests and responses (add headers, filter fields, aggregate responses).

Caching - Cache responses at the gateway level to reduce backend load.

Monitoring and Logging - Centralized logging and metrics for all API traffic.

SSL Termination - Handle TLS encryption/decryption at the gateway.

Implementation Approaches

Kong - Open-source API gateway with plugin ecosystem. Good for general-purpose API management.

AWS API Gateway - Managed service integrating with AWS ecosystem. Easy setup but vendor lock-in.

Nginx/OpenResty - High-performance reverse proxy with Lua scripting. Flexible but requires more configuration.

Envoy - Modern proxy designed for cloud-native applications. Often used in service meshes.

Traefik - Cloud-native gateway with automatic service discovery. Good for Kubernetes.

Custom Gateway - Build on Express, Fastify, or Go. Maximum flexibility but more maintenance.

Authentication Strategies

API Keys - Simple but not secure for sensitive operations. Use for read-only public APIs.

OAuth 2.0 - Industry standard for delegated authorization. Complex but comprehensive.

JWT Tokens - Self-contained tokens with claims. Services validate without external calls.

Mutual TLS - Certificate-based authentication. Strong security for service-to-service communication.

Session-Based - Traditional session cookies. Works but limits scalability.

Gateway Anti-Patterns

Business Logic in Gateway - Gateway should route and enforce policy, not contain business logic.

Tight Coupling to Services - Gateway shouldn't know service internals. Use generic routing rules.

Single Gateway Bottleneck - Run multiple gateway instances behind load balancer. Scale horizontally.

Excessive Transformation - Avoid complex request/response manipulation. Let services return appropriate formats.

Synchronous Aggregation - Calling multiple services synchronously increases latency. Use BFF or client-side aggregation.

Gateway Security

Input Validation - Validate and sanitize all inputs at gateway. Prevent injection attacks.

Rate Limiting - Per-client rate limits prevent abuse and DDoS attacks.

IP Whitelisting - Restrict access to known IP ranges when appropriate.

Request Size Limits - Prevent memory exhaustion from large payloads.

Timeout Enforcement - Set timeouts to prevent hanging requests.

Security Headers - Add headers like HSTS, CSP, X-Frame-Options.

Gateway Patterns for Resilience

Circuit Breaker - Stop routing to failing services. Return cached responses or errors immediately.

Retry Logic - Retry failed requests with exponential backoff. Only for idempotent operations.

Timeout Management - Set aggressive timeouts at gateway. Don't let slow services affect others.

Fallback Responses - Return cached or default responses when services are unavailable.

Health Checking - Monitor backend service health. Route only to healthy instances.


Service Mesh

Service Mesh Architecture

Service mesh provides infrastructure layer for service-to-service communication:

Data Plane - Sidecar proxies deployed alongside each service. Handle actual traffic.

Control Plane - Manages and configures proxies. Provides centralized control and monitoring.

Istio - Feature-rich, integrates well with Kubernetes. Complex but powerful.

Linkerd - Lightweight, easier to operate. Good for simpler use cases.

Consul Connect - From HashiCorp. Integrates with Consul service discovery.

AWS App Mesh - Managed service mesh for AWS environments.

Service Mesh Benefits

Automatic Observability - Distributed tracing, metrics, and logging without code changes.

Security - Mutual TLS between services automatically. No application code changes.

Traffic Management - Sophisticated routing (canary, blue-green, A/B testing) without application changes.

Resilience - Circuit breaking, retries, timeouts configured centrally.

Service Discovery - Automatic service discovery and load balancing.

When to Use Service Mesh

Consider service mesh when you have:

  • Many microservices (20+)
  • Complex traffic management requirements
  • Strong security requirements (mTLS everywhere)
  • Need for detailed observability
  • Multiple teams deploying services

Don't use service mesh for:

  • Small number of services
  • Simple architectures
  • Teams without operational maturity
  • Cost-sensitive projects

Service Mesh Trade-offs

Advantages:

  • Consistent cross-cutting concerns
  • Language-agnostic (works with any language)
  • Centralized configuration

Disadvantages:

  • Resource overhead (CPU, memory for proxies)
  • Operational complexity
  • Debugging difficulty
  • Learning curve
  • Latency overhead from proxy hops

Synchronous Communication

REST APIs

HTTP REST is the most common synchronous communication pattern.

Advantages:

  • Universal support
  • Human-readable
  • Cacheable
  • Well-understood

Disadvantages:

  • Verbose (JSON overhead)
  • Schema not enforced
  • Versioning complexity

Best Practices:

  • Use standard HTTP methods correctly (GET, POST, PUT, DELETE)
  • Proper status codes
  • HATEOAS for discoverability
  • API versioning strategy
  • Comprehensive documentation

gRPC

Binary protocol using Protocol Buffers. Efficient for service-to-service communication.

Advantages:

  • Efficient binary format
  • Strong typing with .proto files
  • Streaming support
  • Multiple languages

Disadvantages:

  • Not human-readable
  • Limited browser support
  • More complex debugging

When to Use:

  • Internal service-to-service communication
  • High-performance requirements
  • Strong typing desired
  • Streaming needed

GraphQL

Query language for APIs. Clients request exactly the data they need.

Advantages:

  • No over-fetching or under-fetching
  • Single endpoint
  • Strong typing
  • Self-documenting

Disadvantages:

  • Complex queries can be expensive
  • Caching is harder
  • N+1 query problem
  • Learning curve

When to Use:

  • Complex data relationships
  • Multiple client types with different needs
  • Frontend teams need flexibility

Request-Reply Challenges

Timeouts - Set appropriate timeouts. Too short causes premature failures. Too long wastes resources.

Retries - Only retry idempotent operations. Use exponential backoff with jitter.

Circuit Breaking - Stop calling failing services. Return errors immediately or use fallbacks.

Cascading Failures - One slow service can cascade to others. Use timeouts and circuit breakers.


Asynchronous Communication

Message Queues

Messages sent to queues. Consumers process messages asynchronously.

Advantages:

  • Decoupling (publisher doesn't know consumers)
  • Buffering (handle traffic spikes)
  • Retry logic built-in
  • Guaranteed delivery

Disadvantages:

  • Eventual consistency
  • Debugging complexity
  • Message ordering challenges
  • Duplicate handling

Technologies:

  • RabbitMQ (feature-rich, complex)
  • Apache Kafka (high-throughput, log-based)
  • AWS SQS (managed, simple)
  • Google Pub/Sub (managed, scalable)
  • Azure Service Bus (managed, enterprise features)

Publish-Subscribe

Publishers emit events. Multiple subscribers receive events independently.

Use Cases:

  • Broadcasting notifications
  • Data replication
  • Triggering workflows
  • Analytics and monitoring

Patterns:

  • Topic-based (subscribe to topics)
  • Content-based (filter by content)
  • Fanout (all subscribers receive all messages)

Work Queues

Tasks distributed across workers. Each message processed by one worker.

Use Cases:

  • Background processing
  • Task distribution
  • Load leveling

Considerations:

  • Idempotency (messages may be delivered multiple times)
  • Error handling (dead letter queues)
  • Ordering (usually not guaranteed)
  • Monitoring (queue depth, processing time)

Message Patterns

Fire and Forget - Sender doesn't wait for acknowledgment. Fast but no delivery guarantee.

Request-Response - Sender waits for response. Use correlation IDs to match responses.

Saga Pattern - Coordinate transactions across services with compensating actions.

Event Sourcing - Store events, derive state from events. Complete audit trail.

Asynchronous Trade-offs

Advantages:

  • Better scalability and resilience
  • Loose coupling
  • Natural load leveling

Disadvantages:

  • Complexity
  • Eventual consistency
  • Debugging difficulty
  • Operational overhead

Event-Driven Architecture

Domain Events

Events representing business occurrences:

  • OrderPlaced
  • PaymentProcessed
  • ItemShipped
  • UserRegistered

Events are past tense—they happened. They're facts, not commands.

Event Sourcing

Store events as the source of truth. Current state derived by replaying events.

Advantages:

  • Complete audit trail
  • Time travel (replay to any point)
  • Event-driven analytics
  • Easy to add new views

Disadvantages:

  • Complexity
  • Event schema evolution challenges
  • Storage growth
  • Eventual consistency

CQRS (Command Query Responsibility Segregation)

Separate models for writes (commands) and reads (queries).

Command Side:

  • Handles writes
  • Validates business rules
  • Emits events

Query Side:

  • Handles reads
  • Optimized for queries
  • Eventually consistent with command side

When to Use:

  • Complex domain logic
  • Different read/write performance needs
  • Event sourcing

When to Avoid:

  • Simple CRUD applications
  • Strong consistency required
  • Small teams without experience

Event Schemas

Define event schemas carefully:

  • Include event type
  • Include timestamp
  • Include relevant data (avoid requiring lookups)
  • Version schemas
  • Use standard formats (JSON, Protobuf)

Schema Evolution:

  • Add optional fields (backward compatible)
  • Don't remove fields (use deprecation)
  • Version events when breaking changes needed

Eventual Consistency

In event-driven systems, changes propagate asynchronously. System is eventually consistent but not immediately.

Handling Eventual Consistency:

  • Design UI to show pending state
  • Use optimistic updates
  • Implement idempotency
  • Communicate delays to users

Resilience Patterns

Circuit Breaker

Stop calling failing services. Three states:

Closed - Normal operation. Requests pass through.

Open - Too many failures. Requests immediately fail without calling service.

Half-Open - Testing if service recovered. Limited requests pass through.

Configuration:

  • Failure threshold to open circuit
  • Timeout before trying half-open
  • Success threshold to close circuit

Retry with Exponential Backoff

Retry failed operations with increasing delays:

  • First retry: 1 second
  • Second retry: 2 seconds
  • Third retry: 4 seconds
  • Fourth retry: 8 seconds

Add jitter (randomization) to prevent thundering herd.

Only retry transient failures (network errors, timeouts). Don't retry validation errors.

Timeout Strategy

Set timeouts at multiple levels:

  • Connection timeout (how long to establish connection)
  • Request timeout (total time for request/response)
  • Gateway timeout (overall timeout including retries)

Timeouts should be realistic but aggressive. Better to fail fast than hang.

Bulkhead Pattern

Isolate resources to prevent cascading failures:

  • Separate thread pools per service
  • Separate connection pools per service
  • Separate circuit breakers per service

Failure in one service doesn't exhaust shared resources.

Fallback Strategies

When service unavailable:

  • Return cached data
  • Return default values
  • Degrade functionality gracefully
  • Return error message with retry guidance

Design fallbacks during normal development, not during incidents.

Health Checks

Implement health check endpoints:

  • Liveness (is service alive?)
  • Readiness (can service handle requests?)
  • Dependency checks (are dependencies available?)

Load balancers and orchestrators use health checks to route traffic.