What the gateway owns
- Routing & versioning: OCPI 2.1.1 and 2.2.1 partners coexist; the gateway routes by negotiated version so services stay single-version and simple.
- Authentication: partner tokens, OEM API keys, and internal JWT validation terminate at the edge — services trust the gateway’s asserted identity.
- Rate limiting per partner: one misbehaving integration must never degrade the platform for everyone else; limits are contractual, enforced, and monitored.
- Request/response logging: every partner interaction audited with correlation IDs — the evidence base for every interoperability dispute.
The aggregator pattern
Alongside the gateway, an API aggregator composes coarse-grained responses from multiple services: a driver app’s “nearby chargers” call fans out to locations, tariffs, and availability services and returns one payload. This keeps mobile clients on one round-trip while services stay narrow. The discipline: aggregators compose, they never own business logic — the moment an if-statement about tariffs appears in an aggregator, it belongs in the tariff service.
Hard-won rules
- The gateway is infrastructure, not a dumping ground — no business rules, no data transformation beyond protocol shaping.
- Fail fast and honestly: a 429 with Retry-After beats a queued request that times out mysteriously.
- Timeout budgets cascade: if the client allows 10s, the gateway allows 8, services get 6 — never let the edge outlive its caller.
- Canary at the edge: routing 5% of partner traffic to a new service version catches what staging never will.
Worked example: per-partner policy at the edge
Azure API Management policy on the OCPI surface — partner identified by token, rate-limited by contract, version-routed before any service sees the request:
<inbound>
<base />
<check-header name="Authorization" failed-check-httpcode="401" />
<rate-limit-by-key calls="1000" renewal-period="60"
counter-key="@(context.Request.Headers.GetValueOrDefault("Authorization"))" />
<choose>
<when condition="@(context.Request.Url.Path.Contains("/ocpi/2.2.1/"))">
<set-backend-service base-url="https://ocpi-v221.internal" />
</when>
<otherwise>
<set-backend-service base-url="https://ocpi-v211.internal" />
</otherwise>
</choose>
<set-header name="X-Correlation-Id" exists-action="skip">
<value>@(Guid.NewGuid().ToString())</value>
</set-header>
</inbound>
Services stay single-version and simple; the partner’s negotiated OCPI version is purely an edge concern.
FAQ
Managed first — Azure API Management or AWS API Gateway handle auth, limits, and versioning without ops burden. Custom (Ocelot/YARP in .NET) only when protocol quirks like OCPI routing headers demand it.
Different layers: the gateway governs north-south (external) traffic; a mesh governs east-west (service-to-service). Most platforms need a gateway long before they need a mesh — many never need the mesh.
Mirror the protocol’s own discovery where one exists (OCPI versions endpoint); otherwise URL versioning with published sunset windows. Additive changes only within a version — removing a field is a new version, no exceptions.