0003 — HTTP/JSON between services
Synchronous service calls use HTTP/JSON, not the message broker.
Decided. Services call each other with HTTP/JSON over Kubernetes Services.
Typed Go clients live in each service's api package; callers import them.
Alternatives
- NATS request/reply via the
microframework (the legacy system). Subject-based addressing, queue groups for load balancing, per-service NKey permissions. - gRPC. Typed, efficient, streaming.
- A service mesh. mTLS, retries, and traffic policy out of the application.
Why
NATS request/reply bundles four things: addressing, load balancing, transport, and
wire-level authorization. Kubernetes provides the first two — DNS for addressing, a
Service across ready pods for balancing, with the readiness probe as the drain
signal. That leaves transport and authorization, and HTTP plus NetworkPolicy plus
the receiving service's own Cedar check covers both.
Dropping the broker from the request path removes a system to operate, a set of
credentials to manage, and a class of debugging that needs specialised tooling.
curl works.
gRPC was rejected because it introduces protobuf alongside the JSON Schema that OpenAPI already gives us — two IDLs describing the same contracts. A mesh was rejected as replacing one operational burden with a larger one.
Consequences
- Load balancing is per-connection at L4. Fine for HTTP/1.1; would need client-side balancing if we ever adopt gRPC.
- Wire-level authorization is per-service (NetworkPolicy) rather than per-endpoint (NATS subject permissions). Per-endpoint control moves into the receiving service's Cedar check, which is where the architecture already puts it.
- Synchronous call depth needs watching: one hop fine, two a smell, three needs a defence.