Service Port Map & Header Forwarding
tektree beginner 3 min read
ELI5
Each booth in the food court has its own back-door number (8081 to 8085). The host (gateway) is the only door customers use — they never knock on a booth’s back door. When the host walks an order back, they staple a sticky note: “user 42, pro tier.”
Technical Deep Dive
Port Allocation
flowchart LR Client((Client)) -->|443/80| GW[api-gateway :8080] GW -->|HTTP| US[user-service :8081] GW -->|HTTP| KS[knowledge-service :8082] GW -->|HTTP| GS[gamification-service :8083] GW -->|HTTP| PS[payment-service :8084] GW -->|WS upgrade| RT[realtime-service :8085] WP[worker-pool : no HTTP] -.->|XREADGROUP jobs| Redis[(Redis Streams)] Legacy[services/api :8000] -.->|standalone| Mongo[(MongoDB)]| Service | Port | Direct HTTP exposure outside cluster? |
|---|---|---|
| api-gateway | 8080 | Yes (the only one) |
| user-service | 8081 | No |
| knowledge-service | 8082 | No |
| gamification-service | 8083 | No |
| payment-service | 8084 | No (Polar webhooks reach it via the gateway / ingress rule) |
| realtime-service | 8085 | No (gateway handles WS upgrade then proxies) |
| worker-pool | — | No HTTP |
| services/api (legacy) | 8000 | Pre-gateway monolith; not behind the new gateway |
Header Contract
The gateway’s ProxyTo (services/api-gateway/internal/handlers/handlers.go:58-62) injects:
| Header | Source | Used by |
|---|---|---|
X-User-ID | JWT sub claim | Every protected downstream as the caller identity |
X-User-Tier | JWT tier claim | Downstreams that gate features by plan |
X-Forwarded-Host | original Host | Logging / audit |
X-Forwarded-Proto | scheme | URL builders |
X-Request-ID | RequestID middleware | Trace correlation |
Downstream Go services read these via c.GetHeader("X-User-ID") in their handlers — they do not parse the JWT themselves. A request that arrives at a downstream port directly (bypassing the gateway) therefore has no authentication: do not expose those ports outside the cluster.
Local Development
infra/infra/docker/ ships docker-compose files that bring up Mongo + Redis and the services on these ports. To call a service directly during local debugging, hit http://localhost:8081/api/v1/users/<id> and add X-User-ID / X-User-Tier headers manually — the gateway is bypassed and so is auth.
Key Terms
- Public port →
:8080only (the gateway). - Internal-only port → 8081–8085 + 8000 (legacy); accessible inside the cluster network.
X-User-ID/X-User-Tier→ gateway-injected identity headers; the downstream’s source of truth.- No-HTTP service →
worker-poolconsumes Redis Streams and exposes no listener.
Q&A
Q: A new service needs to call user-service synchronously. Should it use port 8081 or go through the gateway? A: Service-to-service calls go to the internal port (8081) directly. The gateway is the public ingress; using it for internal hops doubles the auth/proxy latency and breaks the trust assumption (the new service would need its own JWT).
Q: Why does worker-pool have no port?
A: It is a pull-based consumer (XREADGROUP on Redis Stream jobs). Health is observed via Prometheus scrape on its metrics endpoint and via stream lag, not an HTTP /health endpoint exposed publicly.
Q: A request to legacy services/api on :8000 still works. Does it use the new auth?
A: No. The legacy monolith parses the access_token cookie itself (services/api/middleware/auth.middleware.go). It is not behind the new gateway and does not consume the X-User-ID headers.
Examples
Hitting knowledge-service directly during a unit test: curl -H 'X-User-ID: 64a...' -H 'X-User-Tier: pro' http://localhost:8082/api/areas — same response shape the gateway would have proxied.
neighbors on the map
- Two-Service Architecture onboarding to the chronicle-hq monorepo
- Choco Factory Architecture Map onboarding to a new choco service repo