Skip to content

ksevindik/external-service-virtualization-example

Repository files navigation

External Service Virtualization Example

A proof-of-concept demonstrating header-based traffic routing for external service virtualization using Envoy, WireMock, and Mountebank. This pattern enables running load tests in production-like environments without affecting real external services.

Overview

This project showcases a technique for isolating load test traffic from production traffic at the infrastructure level. When your application makes calls to external services, the traffic is routed based on the X-Traffic-Type header:

  • LOAD_TEST → Routed to Mountebank (mock server for load testing)
  • PRODUCTION (or no header) → Routed to WireMock (simulating the real external service)

Architecture

                                    ┌─────────────────┐
                                    │   Mountebank    │
                                    │   (Load Test)   │
                                    │    :4545        │
                                    └────────▲────────┘
                                             │
                                             │ X-Traffic-Type: LOAD_TEST
                                             │
┌──────────┐      ┌──────────┐      ┌────────┴────────┐
│  Client  │─────▶│   App    │─────▶│     Envoy       │
│          │      │  :8080   │      │    (Proxy)      │
└──────────┘      └──────────┘      │    :15001       │
                                    └────────┬────────┘
                                             │
                                             │ X-Traffic-Type: PRODUCTION
                                             │
                                    ┌────────▼────────┐
                                    │    WireMock     │
                                    │ (Real Service)  │
                                    │    :8080        │
                                    └─────────────────┘

Components

Component Purpose Port
App Spring Boot application with PaymentClient 8080
Envoy Service proxy for header-based routing 15001
WireMock Simulates the real external payment service 8081 (host) / 8080 (container)
Mountebank Mock server for load test traffic 4545

Service Endpoints

Service Endpoint Description
Swagger UI http://localhost:8080/swagger-ui.html API documentation with built-in traffic header support
OpenAPI Spec http://localhost:8080/v3/api-docs OpenAPI 3.0 specification (JSON)
Mountebank Admin http://localhost:2525 Mountebank admin interface for managing imposters
Mountebank Imposters http://localhost:2525/imposters View/manage all configured imposters
WireMock Admin http://localhost:8081/__admin WireMock admin interface
WireMock Mappings http://localhost:8081/__admin/mappings View/manage WireMock stub mappings

💡 Tip: The Swagger UI includes X-Traffic-Type and X-Test-Run-Id header fields on all endpoints, making it easy to test traffic routing directly from the browser.

How It Works

  1. Traffic Context Filter: Incoming requests to the app are intercepted by TrafficTypeFilter which extracts the X-Traffic-Type header and stores it in a thread-local context.

  2. Header Propagation: When PaymentClient makes outbound HTTP calls, it reads the traffic context and sets the X-Traffic-Type header accordingly:

    • If the incoming request had X-Traffic-Type: LOAD_TEST → outbound request gets LOAD_TEST
    • Otherwise → outbound request gets PRODUCTION
  3. Envoy Routing: Envoy intercepts all outbound traffic (via DNS alias payment.external) and routes based on the header:

    • LOAD_TEST → Mountebank cluster
    • Everything else → WireMock cluster

Prerequisites

  • Docker & Docker Compose
  • Java 21 (for local development)

Quick Start

1. Start All Services

docker-compose up --build

2. Test Production Traffic (Routes to WireMock)

curl -X GET http://localhost:8080/payments

Expected Response:

{
  "status": "SUCCESS",
  "transactionId": "real-txn-12345",
  "message": "Payment processed via WireMock (Simulating REAL Server)"
}

3. Test Load Test Traffic (Routes to Mountebank)

curl -X GET -H "X-Traffic-Type: LOAD_TEST" http://localhost:8080/payments

Expected Response:

{
  "transactionId": "mountebank-txn-67890",
  "status": "SUCCESS",
  "message": "Payment processed via Mountebank (LOAD_TEST)"
}

4. Test via Swagger UI

Open http://localhost:8080/swagger-ui.html in your browser. Each endpoint includes X-Traffic-Type and X-Test-Run-Id header fields, allowing you to easily switch between routing targets without using curl.

Project Structure

├── docker-compose.yml          # Container orchestration
├── Dockerfile                  # App container build
├── src/
│   └── main/
│       └── kotlin/
│           └── com/example/virtualization/
│               ├── client/
│               │   └── PaymentClient.kt       # HTTP client with header propagation
│               ├── config/
│               │   └── OpenApiConfig.kt       # Swagger UI with traffic header support
│               ├── controller/
│               │   └── PaymentController.kt   # REST endpoint
│               └── traffic/
│                   ├── TrafficContext.kt      # Traffic type constants
│                   ├── TrafficContextManager.kt
│                   └── TrafficTypeFilter.kt   # Request interceptor
└── services/
    ├── envoy/
    │   ├── envoy.yaml          # Envoy routing configuration
    │   └── logs/               # Access logs
    ├── mountebank/
    │   └── imposters/
    │       └── payment-imposter.json   # Load test mock responses
    └── wiremock/
        └── mappings/
            └── payment-stub.json       # Production mock responses

Configuration

Traffic Headers

Header Values Description
X-Traffic-Type LOAD_TEST, PRODUCTION Determines traffic routing
X-Test-Run-Id Any string Optional identifier for test runs (for logging/tracing)

Envoy Access Logs

Access logs are written to services/envoy/logs/access.log with the following format:

[timestamp] "METHOD PATH PROTOCOL" STATUS FLAGS BYTES_IN BYTES_OUT DURATION "TRAFFIC_TYPE" "UPSTREAM_CLUSTER"

Example:

[2026-02-03T10:30:45.123Z] "GET /external/payment HTTP/1.1" 200 - 0 156 12 "LOAD_TEST" "mountebank"
[2026-02-03T10:30:50.456Z] "GET /external/payment HTTP/1.1" 200 - 0 148 8 "PRODUCTION" "wiremock"

Use Cases

Load Testing in Production-Like Environments

Run load tests against your application while ensuring test traffic doesn't hit real external services:

# Using k6, JMeter, or any load testing tool
# Just include the header in your requests:
curl -H "X-Traffic-Type: LOAD_TEST" http://your-app/payments

A/B Testing of External Service Responses

Configure different responses in Mountebank to test how your application handles various scenarios (errors, timeouts, edge cases) without affecting production users.

Development & Debugging

  • Use WireMock to simulate real service responses during development
  • Use Mountebank to test error handling and edge cases

Extending This Pattern

Adding More External Services

  1. Add new cluster definitions in envoy.yaml
  2. Create corresponding stubs in WireMock and Mountebank
  3. Update routing rules as needed

Adding More Traffic Types

Modify envoy.yaml to add additional header-based routing rules:

routes:
  - match:
      prefix: "/"
      headers:
        - name: X-Traffic-Type
          exact_match: "CANARY"
    route:
      cluster: canary_service

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published