Skip to content

API Gateway (Ocelot)

All client traffic enters through a single ASP.NET Core application hosting Ocelot 23.4 (src/ApiGateways/Ocelot.ApiGateway). It translates short, client-friendly paths into the services’ versioned routes, and it hides service host names and ports from the browser.

src/ApiGateways/Ocelot.ApiGateway/Program.cs adds one extra JSON file based on the hosting environment:

builder.Host.ConfigureAppConfiguration((env, config) =>
{
config.AddJsonFile($"ocelot.{env.HostingEnvironment.EnvironmentName}.json", true, true);
});
builder.Services.AddOcelot();
...
await app.UseOcelot();
ASPNETCORE_ENVIRONMENT File Downstream hosts Used by
Development src/ApiGateways/Ocelot.ApiGateway/ocelot.Development.json host.docker.internal:8000 to 8003 (the published Compose ports) Docker Compose
k8s src/ApiGateways/Ocelot.ApiGateway/ocelot.k8s.json Helm release service names: eshopping-catalog:80, eshopping-basket:80, eshopping-ordering:80, eshopping-discount-discount-grpc:8080 Helm chart (deploy/helm/ocelotapigw/values.yaml sets ASPNETCORE_ENVIRONMENT=k8s)
k8s via ConfigMap deploy/k8s/gateway/ocelot-configmap.yaml catalog-api-service:9000 and similar Raw Kubernetes manifests, which mount their own ocelot.k8s.json

The file is optional (optional: true) and reloads on change (reloadOnChange: true). An unknown environment name starts a gateway with no routes.

In Development, containers reach sibling services through host.docker.internal and the host’s published ports, rather than through the Compose network names. This works on Docker Desktop. On plain Linux Docker it needs an extra_hosts: host-gateway entry.

Upstream paths are what clients call. Downstream paths are what the services expose. The table below comes from the Development file; the k8s file has the same routes with different hosts.

Upstream Methods Downstream Service Extras
/Catalog GET, POST, PUT /api/v1/Catalog Catalog cache 30 s
/Catalog/GetAllProducts GET, DELETE /api/v1/Catalog/GetAllProducts Catalog
/Catalog/GetProductById/{id} GET, DELETE /api/v1/Catalog/GetProductById/{id} Catalog
/Catalog/GetProductByProductName/{productName} GET /api/v1/Catalog/GetProductByProductName/{productName} Catalog
/Catalog/GetProductsByBrandName/{brand} GET /api/v1/Catalog/GetProductsByBrandName/{brand} Catalog
/Catalog/GetAllBrands GET, DELETE /api/v1/Catalog/GetAllBrands Catalog
/Catalog/GetAllTypes GET, DELETE /api/v1/Catalog/GetAllTypes Catalog
/Catalog/CreateProduct POST /api/v1/Catalog/CreateProduct Catalog
/Catalog/UpdateProduct PUT /api/v1/Catalog/UpdateProduct Catalog
/Catalog/{id} DELETE /api/v1/Catalog/{id} Catalog
/Catalog/CreateBrand POST /api/v1/Catalog/CreateBrand Catalog
/Catalog/CreateType POST /api/v1/Catalog/CreateType Catalog
/Catalog/UploadProductImage POST /api/v1/Catalog/UploadProductImage Catalog
/Admin/MigrateImagesToS3 POST /api/v1/Admin/MigrateImagesToS3 Catalog
/Basket/GetBasket/{userName} GET /api/v1/Basket/GetBasket/{userName} Basket
/Basket/CreateBasket POST /api/v1/Basket/CreateBasket Basket
/Basket/DeleteBasket/{userName} DELETE /api/v1/Basket/DeleteBasket/{userName} Basket
/Basket/Checkout POST /api/v1/Basket/Checkout Basket rate limit: 1 per 3 s
/Basket/CheckoutV2 POST /api/v2/Basket/Checkout Basket rate limit: 1 per 3 s
/Discount/{productName} GET, DELETE /api/v1/Discount/{productName} Discount unreachable, see below
/Discount PUT, POST /api/v1/Discount Discount unreachable
/Order/{userName} GET /api/v1/Order/{userName} Ordering
/Order POST, PUT /api/v1/Order Ordering
/Order/{id} DELETE /api/v1/Order/{id} Ordering
/Activity (and /Activity/ in Development) GET /api/v1/Activity Ordering cache 10 s

GlobalConfiguration.BaseUrl is http://localhost:8010 in Development and http://ocelotapigw in k8s.

flowchart LR
  c(["Client"]) --> gw["Ocelot :8010"]
  gw -->|"/Catalog/*, /Admin/*"| cat["Catalog :8000"]
  gw -->|"/Basket/* (rate-limited checkout)"| bas["Basket :8001"]
  gw -->|"/Order/*, /Activity"| ord["Ordering :8003"]
  gw -.->|"/Discount/* : no REST API behind it"| dis["Discount :8002 (gRPC)"]
  • Path templating with placeholders ({id}, {userName}, {brand}).
  • Response caching (FileCacheOptions.TtlSeconds) on /Catalog (30 s) and /Activity (10 s).
  • Rate limiting on both checkout routes: EnableRateLimiting: true, Period: 3s, Limit: 1, PeriodTimespan: 1. This throttles double-submits of the checkout button. Ocelot identifies clients by its ClientId header, and the frontend does not send one, so every client shares the same bucket. The limit is effectively global.
  • CORS: an AllowAnyOrigin/AnyHeader/AnyMethod policy.