Skip to content

Microservices Example

Learn how to test microservices architectures with service-to-service communication, API gateway routing, and header propagation.

Overview

This example simulates a microservices setup with:

  • API Gateway: Routes requests to backend services
  • User Service: Mock service for user management
  • Order Service: Mock service for order processing
  • Payment Service: Mock service for payment processing

Use Case

Perfect for:

  • Microservices architecture testing
  • API gateway validation
  • Service mesh testing
  • Service-to-service communication validation
  • Load balancer testing
  • Integration testing across multiple services

Architecture

Client → Nginx (API Gateway) → Backend Services (Mocked)
                               ├── user-service
                               ├── order-service
                               └── payment-service

API Routes

PathUpstream ServiceDescription
/users/*user-serviceUser management operations
/orders/*order-serviceOrder processing
/payments/*payment-servicePayment handling
/healthgatewayGateway health check

File Structure

microservices/
├── Dockerfile
├── nginx.conf
└── .httptests/
    ├── config.yml
    └── test.json

Configuration Files

Dockerfile

dockerfile
FROM nginx:alpine
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

nginx.conf

nginx
events {
    worker_connections 1024;
}

http {
    server {
        listen 80;
        server_name _;

        # Health check
        location = /health {
            default_type application/json;
            return 200 '{"status": "healthy", "gateway": "api-gateway", "version": "1.0"}';
        }

        # User service
        location /users/ {
            proxy_pass http://user-service:80/;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Service-Name "user-service";
            proxy_set_header X-Gateway "api-gateway";
            proxy_pass_request_headers on;
        }

        # Order service
        location /orders/ {
            proxy_pass http://order-service:80/;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Service-Name "order-service";
            proxy_set_header X-Gateway "api-gateway";
            proxy_pass_request_headers on;
        }

        # Payment service
        location /payments/ {
            proxy_pass http://payment-service:80/;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Service-Name "payment-service";
            proxy_set_header X-Gateway "api-gateway";
            proxy_pass_request_headers on;
        }
    }
}

Key features:

  • Routes requests based on path prefixes
  • Adds service identification headers
  • Forwards standard proxy headers
  • Passes through all client headers

.httptests/config.yml

yaml
mock:
  network_aliases:
    - user-service
    - order-service
    - payment-service

nginx:
  environment:
    GATEWAY_VERSION: v1
    ENVIRONMENT: test

Configuration breakdown:

  • Three network aliases simulate three different microservices
  • All aliases point to the same mock service (simplifies testing)
  • Environment variables for the gateway

.httptests/test.json

json
{
    "collectionHeaders": [
        ["X-Request-ID"],
        ["X-Client-Version"]
    ],
    "hosts": {
        "gateway.microservices.local": [
            {
                "paths": ["/health"],
                "method": "GET",
                "expectedStatus": 200,
                "expectedResponseHeaders": [
                    ["Content-Type", "application/json"]
                ]
            },
            {
                "paths": ["/users/profile"],
                "method": "GET",
                "expectedStatus": 200,
                "expectedRequestHeadersToUpstream": [
                    ["$collectionHeaders"],
                    ["X-Forwarded-For"],
                    ["X-Real-IP"],
                    ["X-Service-Name", "user-service"],
                    ["X-Gateway", "api-gateway"]
                ]
            },
            {
                "paths": ["/orders/12345"],
                "method": "GET",
                "expectedStatus": 200,
                "expectedRequestHeadersToUpstream": [
                    ["$collectionHeaders"],
                    ["X-Forwarded-For"],
                    ["X-Real-IP"],
                    ["X-Service-Name", "order-service"],
                    ["X-Gateway", "api-gateway"]
                ]
            },
            {
                "paths": ["/payments/status"],
                "method": "GET",
                "expectedStatus": 200,
                "expectedRequestHeadersToUpstream": [
                    ["$collectionHeaders"],
                    ["X-Forwarded-For"],
                    ["X-Real-IP"],
                    ["X-Service-Name", "payment-service"],
                    ["X-Gateway", "api-gateway"]
                ]
            }
        ]
    }
}

Test Coverage

The test suite validates:

  • ✅ Gateway routing to correct upstream services
  • ✅ Request header forwarding between services
  • ✅ Service-specific identification headers
  • ✅ Collection header propagation
  • ✅ Health check endpoints
  • ✅ Cross-service communication patterns

GitHub Actions Workflow

yaml
name: Microservices Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: serviceguards-com/httptests-action@v2
        with:
          httptests-directory: ./microservices

Expected Test Output

🔧 Adding X-Upstream-Target headers to nginx configs...
🚀 Starting environment...
🧪 Running tests for httptests-microservices

test_endpoints (__main__.IntegrationTests.test_endpoints) ... ok
  → Testing: GET gateway.microservices.local/health
    ✓ Status code: 200 (expected 200)
    ✓ Response header: content-type = application/json
  → Testing: GET gateway.microservices.local/users/profile
    ✓ Status code: 200 (expected 200)
    ✓ Request header forwarded: x-request-id
    ✓ Request header forwarded: x-client-version
    ✓ Request header forwarded: x-forwarded-for
    ✓ Request header forwarded: x-real-ip
    ✓ Request header: x-service-name = user-service
    ✓ Request header: x-gateway = api-gateway
  → Testing: GET gateway.microservices.local/orders/12345
    ✓ Status code: 200 (expected 200)
    ✓ Request header: x-service-name = order-service
    ✓ Request header: x-gateway = api-gateway
  → Testing: GET gateway.microservices.local/payments/status
    ✓ Status code: 200 (expected 200)
    ✓ Request header: x-service-name = payment-service
    ✓ Request header: x-gateway = api-gateway
============================================================
Total assertions passed: 18
============================================================

Key Testing Patterns

Service Identification

Each route adds a service identifier header:

nginx
location /users/ {
    proxy_pass http://user-service:80/;
    proxy_set_header X-Service-Name "user-service";
}

Tests verify this header reaches the upstream:

json
{
    "paths": ["/users/profile"],
    "expectedRequestHeadersToUpstream": [
        ["X-Service-Name", "user-service"]
    ]
}

Gateway Metadata

The gateway adds its own identification:

nginx
proxy_set_header X-Gateway "api-gateway";

This helps services know they're being called through the gateway.

Collection Headers

Client-side headers are defined once and reused:

json
"collectionHeaders": [
    ["X-Request-ID"],
    ["X-Client-Version"]
]

Referenced in tests:

json
"expectedRequestHeadersToUpstream": [
    ["$collectionHeaders"]  // Expands to both headers
]

Extending This Example

Add Service Discovery

Simulate dynamic service discovery with Consul or etcd:

nginx
# Use variables for service endpoints
set $user_service_url "http://user-service:80";

location /users/ {
    proxy_pass $user_service_url/;
}

Add Load Balancing

yaml
# config.yml
mock:
  additional_ports:
    - 8081
    - 8082
    - 8083
  network_aliases:
    - user-service-1
    - user-service-2
    - user-service-3
nginx
upstream user_service_cluster {
    server user-service-1:8081;
    server user-service-2:8082;
    server user-service-3:8083;
}

location /users/ {
    proxy_pass http://user_service_cluster/;
}

Add Circuit Breaking

Simulate timeout and retry behavior:

nginx
location /users/ {
    proxy_pass http://user-service:80/;
    proxy_connect_timeout 5s;
    proxy_send_timeout 10s;
    proxy_read_timeout 10s;
    proxy_next_upstream error timeout;
}

Add Service Authentication

Services authenticate with each other:

nginx
location /users/ {
    proxy_pass http://user-service:80/;
    proxy_set_header X-Service-Token "gateway-service-token-12345";
    proxy_set_header X-Calling-Service "api-gateway";
}

Test it:

json
{
    "paths": ["/users/profile"],
    "expectedRequestHeadersToUpstream": [
        ["X-Service-Token", "gateway-service-token-12345"],
        ["X-Calling-Service", "api-gateway"]
    ]
}

Add Correlation IDs

Track requests across services:

nginx
# Generate or forward correlation ID
location /users/ {
    proxy_pass http://user-service:80/;
    proxy_set_header X-Correlation-ID $http_x_correlation_id;
}

Real-World Microservices Patterns

Service Mesh Headers

json
{
    "paths": ["/users/profile"],
    "expectedRequestHeadersToUpstream": [
        ["X-Service-Name", "user-service"],
        ["X-Service-Version", "v1"],
        ["X-Request-ID"],
        ["X-Trace-ID"],
        ["X-Span-ID"]
    ]
}

Health Check Aggregation

nginx
location /health {
    # Aggregate health from all services
    default_type application/json;
    return 200 '{"status": "healthy", "services": ["user", "order", "payment"]}';
}

API Versioning

nginx
location /v1/users/ {
    proxy_pass http://user-service:80/v1/;
    proxy_set_header X-API-Version "v1";
}

location /v2/users/ {
    proxy_pass http://user-service-v2:80/v2/;
    proxy_set_header X-API-Version "v2";
}

Next Steps

Released under the MIT License.