Skip to main content

Hybrid Gateway Adapter — Bring Your Own Gateway

Overview

STOA's Gateway Adapter Pattern allows you to orchestrate any API gateway through a unified, gateway-agnostic interface. Your APIs, policies, OIDC configuration, and applications are declared in Git; the adapter translates them into gateway-specific REST calls during the GitOps reconciliation cycle.

Git (desired state) → ArgoCD → Control Plane API → Gateway Adapter → Your Gateway

Architecture

┌─────────────────────────────────────────────────────┐
│ STOA Control Plane │
│ GitOps Reconciliation Engine │
├──────────┬──────────┬──────────┬────────────────────┤
│ webMethods│ Kong │ Apigee │ AWS API Gateway │
│ Adapter │ Adapter │ Adapter │ Adapter │
│ ✅ │ 🔜 │ 🔜 │ 🔜 │
├──────────┴──────────┴──────────┴────────────────────┤
│ GatewayAdapterInterface (ABC) │
│ health_check · sync_api · upsert_policy · ... │
└─────────────────────────────────────────────────────┘

Interface Contract

Every adapter implements GatewayAdapterInterface. Key properties:

  • Idempotent: Calling the same operation twice produces the same result
  • Declarative: You specify desired state, the adapter computes the diff
  • Auth-agnostic: Supports both OIDC proxy (JWT forwarding) and Basic Auth

Reconciliation Lifecycle

PHASE 0:   health_check()         → Verify gateway is reachable
PHASE 1: (load from Git) → Read tenant/API definitions
PHASE 1.5: upsert_policy() → Sync policies (CORS, rate-limit, etc.)
PHASE 1.7: upsert_auth_server() → Sync OIDC (Keycloak, Okta, etc.)
PHASE 2: list_apis() → Fetch current gateway state
PHASE 3: (compute diff) → Compare Git vs gateway
PHASE 4: sync_api() → Create/update/delete APIs
PHASE 4.5: provision_application() → Sync OAuth applications
PHASE 5: (portal visibility) → Publish/unpublish from portal
PHASE 6: apply_config() → Global gateway configuration
PHASE 7: export_archive() → Backup gateway state

Implementing a New Adapter

1. Create the adapter module

control-plane-api/src/adapters/
├── gateway_adapter_interface.py # Don't modify
├── __init__.py
├── webmethods/ # Reference implementation
│ ├── adapter.py
│ └── mappers.py
└── kong/ # Your new adapter
├── __init__.py
├── adapter.py
└── mappers.py

2. Implement the interface

from ..gateway_adapter_interface import GatewayAdapterInterface, AdapterResult

class KongGatewayAdapter(GatewayAdapterInterface):
async def health_check(self) -> AdapterResult:
# GET /status on Kong Admin API
...

async def sync_api(self, api_spec: dict, tenant_id: str,
auth_token=None) -> AdapterResult:
# POST /services + POST /routes on Kong Admin API
...

# ... implement all abstract methods

3. Register the adapter

In adapters/registry.py, register the new adapter class:

from ..adapters.kong import KongGatewayAdapter

ADAPTER_REGISTRY = {
"kong": KongGatewayAdapter,
# ... other adapters
}

The Control Plane API creates adapters via AdapterRegistry.create(gateway_type) — no external orchestration needed.

Supported Gateways

GatewayStatusAdapterNotes
STOA Gateway (Rust)✅ Productionadapters/stoa/Native, in-memory, MCP-first
Kong (DB-less)✅ Productionadapters/kong/Declarative reload via POST /config
Gravitee (APIM v4)✅ Productionadapters/gravitee/Full CRUD + lifecycle management
webMethods 10.x/11.x✅ Productionadapters/webmethods/Full OIDC, aliases, backup
Apigee🔜 Planned
AWS API Gateway🔜 Planned

Configuration

Environment Variables

VariableDefaultDescription
WM_GATEWAY_URLhttp://apim-gateway:5555webMethods admin URL
WM_ADMIN_USERAdministratorAdmin username (Basic Auth)
WM_ADMIN_PASSWORDAdmin password (Basic Auth)
GATEWAY_USE_OIDC_PROXYfalseUse OIDC proxy mode
GATEWAY_ADMIN_PROXY_URLOIDC proxy URL

Git Structure

webmethods/
├── aliases/
│ ├── dev.yaml
│ ├── staging.yaml
│ └── prod.yaml
├── policies/
│ ├── cors-platform.yaml
│ ├── rate-limit-default.yaml
│ └── logging-standard.yaml
├── oidc/
│ └── keycloak-auth-server.yaml
├── config/
│ └── gateway-config.yaml
└── applications/
└── .gitkeep

Local Development

# Start the STOA platform locally
docker compose up -d

# Run adapter health check
curl ${STOA_API_URL}/v1/gateways

# Trigger a sync via the Control Plane API
curl -X POST ${STOA_API_URL}/v1/deployments/{id}/sync \
-H "Authorization: Bearer $TOKEN"