Integration Patterns — Deep Dive

Level: Intermediate
Pre-reading: 03 · Microservices Patterns


Integration Challenges

Microservices need to communicate. How they discover, connect, and exchange data defines the system's resilience and flexibility.


API Gateway

A single entry point for all client requests. Handles cross-cutting concerns and routes to backend services.

graph TD
    M[Mobile] --> GW[API Gateway]
    W[Web] --> GW
    P[Partner] --> GW
    GW --> O[Order Service]
    GW --> U[User Service]
    GW --> PAY[Payment Service]

Gateway Responsibilities

Concern What Gateway Does
Routing Path/header/method-based routing to services
Authentication Validate JWT/API key before forwarding
Rate Limiting Per-client throttling; return 429
SSL Termination TLS at gateway; plain HTTP internally
Request Transform Add/remove headers; shape requests
Response Aggregation Combine responses from multiple services
Caching Cache GET responses
Load Balancing Distribute across service instances

Gateway Implementation Options

Tool Characteristics
Kong OSS + Enterprise; plugin ecosystem; declarative config
AWS API Gateway Serverless; Lambda integration; pay-per-request
Spring Cloud Gateway Java-native; reactive; WebFlux
Envoy High-performance proxy; often used with Istio
Nginx Proven; lightweight; config-based
Traefik Kubernetes-native; automatic discovery

Gateway Routing Example (Kong)

services:
  - name: order-service
    url: http://order-service:8080
    routes:
      - name: orders-route
        paths:
          - /api/orders

  - name: user-service
    url: http://user-service:8080
    routes:
      - name: users-route
        paths:
          - /api/users

Gateway Anti-Patterns

Anti-Pattern Problem Fix
Business logic in gateway Gateway becomes monolith Keep gateway thin; logic in services
Single gateway for all Bottleneck; single point of failure Consider BFF pattern
No rate limiting DoS vulnerability Configure per-client limits
Catch-all error handling Hides real errors Pass through meaningful errors

Backend for Frontend (BFF)

A separate gateway per client type, each tailored to that client's needs.

graph TD
    M[Mobile App] --> MBFF[Mobile BFF]
    W[Web App] --> WBFF[Web BFF]
    P[Partner API] --> PBFF[Partner BFF]
    MBFF --> O[Order Service]
    WBFF --> O
    PBFF --> O
    MBFF --> U[User Service]
    WBFF --> U

Why BFF?

Problem with Single Gateway BFF Solution
Mobile needs less data than web BFF returns optimized payloads
Different auth flows per client Each BFF handles its auth
One client needs aggregation, another doesn't BFF customizes per client
Breaking change affects all clients Change only affects one BFF

BFF Ownership

Model Description
Frontend team owns BFF Mobile team owns Mobile BFF
Fullstack team Team owns frontend + BFF
Platform team Central team; all BFFs

Frontend team ownership preferred

The team building the client understands its needs best. They should own the BFF.

BFF Trade-offs

Benefit Cost
Tailored per client More services to maintain
Independent evolution Possible logic duplication
Team autonomy Need coordination on backend changes

Service Discovery

Services need to find each other. Two approaches: client-side and server-side discovery.

Client-Side Discovery

Client queries a service registry and selects an instance.

sequenceDiagram
    participant C as Client Service
    participant R as Service Registry
    participant S1 as Service Instance 1
    participant S2 as Service Instance 2
    C->>R: Where is Order Service?
    R->>C: [instance1:8080, instance2:8080]
    C->>C: Pick instance (round-robin)
    C->>S1: Request
    S1->>C: Response
Tool Description
Eureka Netflix OSS; Spring Cloud integration
Consul HashiCorp; health checks; KV store
Zookeeper Apache; mature; complex

Server-Side Discovery

Load balancer handles discovery; client sends to fixed endpoint.

sequenceDiagram
    participant C as Client Service
    participant LB as Load Balancer
    participant R as Service Registry
    participant S1 as Service Instance 1
    C->>LB: Request to Order Service
    LB->>R: Get healthy instances
    R->>LB: [instance1, instance2]
    LB->>S1: Forward request
    S1->>LB: Response
    LB->>C: Response
Tool Description
Kubernetes DNS Built-in; Service resolves to pod IPs
AWS ALB Cloud-native; target groups
Envoy + Istio Service mesh handles routing

Kubernetes Service Discovery

apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order
  ports:
    - port: 80
      targetPort: 8080
---
# Client calls: http://order-service/api/orders
# Kubernetes DNS resolves to pod IPs

Discovery Comparison

Aspect Client-Side Server-Side
Client complexity Higher Lower
Load balancer Not needed Required
Language agnostic No (needs library) Yes
Kubernetes native No Yes

Gateway Aggregation

Gateway fetches from multiple services and combines responses.

sequenceDiagram
    participant C as Client
    participant GW as Gateway
    participant OS as Order Service
    participant US as User Service
    participant PS as Product Service
    C->>GW: GET /dashboard
    par Parallel calls
        GW->>OS: GET /orders
        GW->>US: GET /user
        GW->>PS: GET /recommendations
    end
    OS->>GW: Orders data
    US->>GW: User data
    PS->>GW: Recommendations
    GW->>GW: Aggregate
    GW->>C: Combined response

Aggregation Considerations

Concern Solution
Partial failure Return partial data; indicate failures
Latency Parallel calls; timeouts
Complexity Keep aggregation simple; consider BFF
Caching Cache individual responses

When to Aggregate at Gateway vs Client

Gateway Aggregation Client Aggregation
Mobile clients (minimize round trips) Web apps with good connectivity
Complex joins across services Simple parallel fetches
Caching benefits Dynamic composition needs

Gateway Offloading

Push cross-cutting concerns to the gateway so services stay focused on business logic.

Concern Gateway Implementation
Authentication Validate JWT; reject invalid
Authorization Check scopes/roles for route
Rate limiting Token bucket per client
Logging Access logs; correlation IDs
Metrics Request count, latency per route
Compression gzip responses
CORS Handle preflight; set headers

Offloading Example (Kong)

plugins:
  - name: jwt
    config:
      claims_to_verify:
        - exp
        - iss
  - name: rate-limiting
    config:
      minute: 100
      policy: local
  - name: correlation-id
    config:
      header_name: X-Request-ID
      generator: uuid

Anti-Corruption Layer (ACL)

A translation layer protecting your services from external or legacy APIs.

graph LR
    subgraph Your Service
        DS[Domain Logic]
        ACL[ACL Adapter]
    end
    EXT[External API]
    DS --> ACL
    ACL --> EXT

ACL Implementation

// External API returns inconsistent format
public class LegacyInventoryResponse {
    private String sku_code;
    private int qty_avail;
    private String loc_id;
}

// Your clean domain model
public record StockLevel(
    ProductId productId,
    int availableQuantity,
    WarehouseId warehouseId
) {}

// ACL translates
public class InventoryAcl {
    private final LegacyInventoryClient client;

    public StockLevel getStockLevel(ProductId productId) {
        LegacyInventoryResponse response = client.getStock(productId.value());
        return new StockLevel(
            productId,
            response.getQty_avail(),
            new WarehouseId(response.getLoc_id())
        );
    }
}

When to Use ACL

Scenario ACL Value
Integrating with legacy Isolate legacy model from domain
Third-party APIs Translate to your vocabulary
External partner APIs Handle format changes in one place
Migration Adapter to old system during transition

Integration Pattern Selection

Scenario Recommended Pattern
Single public API API Gateway
Multiple client types BFF per client
Kubernetes environment Server-side discovery (DNS)
Spring Cloud apps Client-side discovery (Eureka)
Dashboard with multiple data sources Gateway aggregation
Integrating with legacy/external Anti-Corruption Layer

When should you use BFF vs a single API Gateway?

Use BFF when clients have significantly different needs: different data shapes, different auth flows, different aggregation patterns. Examples: mobile needs minimal payload; web needs rich data; partner API needs stable contract. Use single gateway when clients are similar or when team doesn't want to maintain multiple gateways.

Why is client-side discovery falling out of favor?

Kubernetes provides server-side discovery natively via DNS and kube-proxy. Services just call http://service-name. No library needed, language-agnostic. Client-side discovery (Eureka) requires libraries, adds complexity, and doesn't integrate as seamlessly with K8s.

How do you handle partial failures in gateway aggregation?

(1) Set timeouts for each downstream call. (2) Use circuit breakers to fail fast. (3) Return partial data with indication of what failed. (4) Have fallbacks (cached data, default values). (5) Design clients to handle incomplete responses gracefully.