feat: backend service

This commit is contained in:
Ilia Denisov
2026-05-06 10:14:55 +03:00
committed by GitHub
parent 3e2622757e
commit f446c6a2ac
1486 changed files with 49720 additions and 266401 deletions
+54 -140
View File
@@ -4,10 +4,11 @@
`cmd/gateway` starts with built-in listener defaults, but it still requires:
- one reachable Redis deployment for session lookup, replay reservations, and
both internal event streams;
- one configured session event stream via `GATEWAY_SESSION_EVENTS_REDIS_STREAM`;
- one configured client event stream via `GATEWAY_CLIENT_EVENTS_REDIS_STREAM`;
- one reachable Redis deployment used exclusively for anti-replay
reservations (no session projection, no event streams);
- one reachable `backend` instance hosting the consolidated REST surface
(`/api/v1/{public,user,internal}/*`) and the `Push.SubscribePush` gRPC
listener;
- one PKCS#8 PEM-encoded Ed25519 response-signer key referenced by
`GATEWAY_RESPONSE_SIGNER_PRIVATE_KEY_PEM_PATH`.
@@ -15,25 +16,25 @@ Required startup environment variables:
- `GATEWAY_REDIS_MASTER_ADDR`
- `GATEWAY_REDIS_PASSWORD`
- `GATEWAY_SESSION_EVENTS_REDIS_STREAM`
- `GATEWAY_CLIENT_EVENTS_REDIS_STREAM`
- `GATEWAY_BACKEND_HTTP_URL`
- `GATEWAY_BACKEND_GRPC_PUSH_URL`
- `GATEWAY_BACKEND_GATEWAY_CLIENT_ID`
- `GATEWAY_RESPONSE_SIGNER_PRIVATE_KEY_PEM_PATH`
Optional integrations:
- `GATEWAY_ADMIN_HTTP_ADDR` enables the private `/metrics` listener;
- `GATEWAY_AUTH_SERVICE_BASE_URL` enables real public auth handling through
Auth / Session Service public HTTP;
- `GATEWAY_USER_SERVICE_BASE_URL` enables direct authenticated self-service
routing to User Service internal HTTP;
- injected downstream routes are required for successful `ExecuteCommand`.
- `GATEWAY_BACKEND_HTTP_TIMEOUT`, `GATEWAY_BACKEND_PUSH_RECONNECT_BASE_BACKOFF`,
`GATEWAY_BACKEND_PUSH_RECONNECT_MAX_BACKOFF` tune the backend client.
Operational caveats:
- public auth routes stay mounted and return `503 service_unavailable` until an
auth service base URL is configured;
- authenticated gRPC starts without downstream routes, but `ExecuteCommand`
returns gRPC `UNIMPLEMENTED` until routing is configured.
- gateway issues one synchronous `/api/v1/internal/sessions/{id}` lookup per
authenticated request — there is no process-local cache; backend keeps the
source-of-truth record;
- the gRPC `SubscribePush` consumer reconnects with exponential backoff and
jitter on every backend restart and resumes from the last cursor it
observed.
Additional module docs:
@@ -639,134 +640,44 @@ Optional Redis connection variables:
> rejects the deprecated `GATEWAY_REDIS_TLS_ENABLED` and
> `GATEWAY_REDIS_USERNAME` variables at startup.
Per-subsystem Redis behavior variables (namespace, stream, timeouts):
Per-subsystem Redis behavior variables (namespace, timeouts):
- `GATEWAY_SESSION_CACHE_REDIS_KEY_PREFIX` with default `gateway:session:`
- `GATEWAY_SESSION_CACHE_REDIS_LOOKUP_TIMEOUT` with default `250ms`
- `GATEWAY_REPLAY_REDIS_KEY_PREFIX` with default `gateway:replay:`
- `GATEWAY_REPLAY_REDIS_RESERVE_TIMEOUT` with default `250ms`
- `GATEWAY_SESSION_EVENTS_REDIS_STREAM`
- `GATEWAY_SESSION_EVENTS_REDIS_READ_BLOCK_TIMEOUT` with default `1s`
- `GATEWAY_CLIENT_EVENTS_REDIS_STREAM`
- `GATEWAY_CLIENT_EVENTS_REDIS_READ_BLOCK_TIMEOUT` with default `1s`
The Redis key format is:
Gateway no longer keeps a session cache projection or the two Redis
Streams (`session_events`, `client_events`). Session lookup is a
synchronous REST call to backend, and inbound client / session events
arrive through the gRPC `Push.SubscribePush` consumer (see the
**Backend Client** section below). Redis is therefore used only by
the Replay Store.
- `<key_prefix><device_session_id>`
### Backend Client
The Redis value is one strict JSON object:
`backendclient` is the single gateway → backend adapter:
- `device_session_id`
- `user_id`
- `client_public_key`
- `status`
- optional `revoked_at_ms`
- `RESTClient` calls `/api/v1/internal/sessions/{id}` synchronously per
authenticated request, forwards public auth (`/api/v1/public/auth/*`)
and authenticated user / lobby commands (`/api/v1/user/*`) with the
verified `X-User-Id` header.
- `PushClient` consumes `Push.SubscribePush` and reconnects with
exponential backoff plus jitter, replaying the last cursor on every
reconnect.
`client_public_key` stores the standard base64-encoded raw 32-byte Ed25519
public key registered for the device session.
Required startup variables:
Malformed JSON, missing required fields, unsupported `status`, or a
`device_session_id` mismatch between the Redis value and the lookup key are
treated as SessionCache backend failures rather than as valid session states.
- `GATEWAY_BACKEND_HTTP_URL` — absolute base URL for the backend HTTP
listener;
- `GATEWAY_BACKEND_GRPC_PUSH_URL``host:port` of the backend
`Push.SubscribePush` listener;
- `GATEWAY_BACKEND_GATEWAY_CLIENT_ID` — durable identity presented to
backend so reconnects replace the previous subscription.
### Session Event Stream
Optional tuning:
The gateway keeps the process-local session snapshot cache synchronized from one
Redis Stream consumed through `go-redis/v9`.
`cmd/gateway` requires the session event stream configuration during startup,
issues a bounded `PING` against the same Redis deployment used for
`SessionCache`, and refuses to start when that Redis backend is unavailable.
Required environment variable:
- `GATEWAY_SESSION_EVENTS_REDIS_STREAM`
Optional environment variable:
- `GATEWAY_SESSION_EVENTS_REDIS_READ_BLOCK_TIMEOUT` with default `1s`
The subscriber reuses the same Redis address, ACL credentials, logical
database, timeout, and TLS settings configured for `SessionCache`.
Each gateway replica keeps its own in-memory last-seen stream ID and consumes
the stream with plain `XREAD`, not a shared consumer group.
On startup the replica resolves the current stream tail and begins from that
point, which preserves the same fresh-process semantics as Redis `$` while
avoiding a race before the first blocking read.
The session event payload is one strict full snapshot with these
fields:
- `device_session_id`
- `user_id`
- `client_public_key`
- `status`
- optional `revoked_at_ms`
Valid active and revoked snapshots upsert or replace the local session state.
Later stream entries win.
Malformed events are skipped without stopping the subscriber; when
`device_session_id` can still be extracted, the gateway evicts the local
snapshot for that session so it cannot continue using stale state.
Session event publishers must keep the stream bounded by using
`XADD ... MAXLEN ~ <limit>` or an equivalent retention policy.
The gateway intentionally does not trim the stream from the consumer side,
because consumer-side trimming could drop updates that another gateway replica
has not read yet.
### Client Event Stream
The gateway delivers client-facing push events from one dedicated Redis Stream
consumed through `go-redis/v9`.
`cmd/gateway` requires the client event stream configuration during startup,
issues a bounded `PING` against the same Redis deployment used for
`SessionCache`, and refuses to start when that Redis backend is unavailable.
Required environment variable:
- `GATEWAY_CLIENT_EVENTS_REDIS_STREAM`
Optional environment variable:
- `GATEWAY_CLIENT_EVENTS_REDIS_READ_BLOCK_TIMEOUT` with default `1s`
The subscriber reuses the same Redis address, ACL credentials, logical
database, timeout, and TLS settings configured for `SessionCache`.
Each gateway replica keeps its own in-memory last-seen stream ID and consumes
the stream with plain `XREAD`, not a shared consumer group.
On startup the replica resolves the current stream tail and begins from that
point, which preserves the same fresh-process semantics as Redis `$` while
avoiding a race before the first blocking read.
The client event payload is one strict target-plus-payload entry with
these fields:
- `user_id`
- optional `device_session_id`
- `event_type`
- `event_id`
- `payload_bytes`
- optional `request_id`
- optional `trace_id`
`payload_bytes` carries the raw binary-safe business payload bytes for the
outbound client event.
When `device_session_id` is absent or blank, the gateway fans the event out to
every active stream for `user_id`.
When `device_session_id` is present, the gateway fans the event out only to
active streams whose `user_id` and `device_session_id` both match.
Malformed client event entries are skipped without stopping the subscriber or
delivering partial data to clients.
Client event publishers must keep the stream bounded by using
`XADD ... MAXLEN ~ <limit>` or an equivalent retention policy.
The gateway intentionally does not trim the stream from the consumer side,
because consumer-side trimming could drop updates that another gateway replica
has not read yet.
- `GATEWAY_BACKEND_HTTP_TIMEOUT` with default `5s`;
- `GATEWAY_BACKEND_PUSH_RECONNECT_BASE_BACKOFF` with default `250ms`;
- `GATEWAY_BACKEND_PUSH_RECONNECT_MAX_BACKOFF` with default `30s`.
### Replay Store
@@ -965,7 +876,7 @@ The package layout keeps transport, policy, and downstream adapters separate:
- `internal/config`
- `internal/restapi`
- `internal/grpcapi`
- `internal/authn`
- `authn` *(public — canonical request/response/event signing input shared with external clients and the integration test suite)*
- `internal/session`
- `internal/replay`
- `internal/ratelimit`
@@ -1036,10 +947,12 @@ failing process startup.
Resolves the target downstream service or adapter by the full exact-match
`message_type` literal.
The default `cmd/gateway` wiring keeps the reserved `user.*` self-service
message types mounted even when `GATEWAY_USER_SERVICE_BASE_URL` is unset. In
that configuration they fail closed as dependency-unavailable instead of
falling through to a generic route miss.
The default `cmd/gateway` wiring resolves the reserved `user.*` and
`lobby.*` self-service message types through `backendclient.UserRoutes`
and `backendclient.LobbyRoutes`. When `GATEWAY_BACKEND_HTTP_URL` is
unset these routes stay mounted and fail closed as
dependency-unavailable instead of falling through to a generic route
miss.
### DownstreamClient
@@ -1049,9 +962,10 @@ An empty or whitespace-only result code is treated as an internal downstream
contract violation.
Downstream clients may be pure pass-through adapters or gateway-owned
transcoding adapters. The current User Service adapter decodes authenticated
FlatBuffers payloads, calls the trusted internal REST API, and re-encodes the
result into FlatBuffers before the signed gateway response is emitted.
transcoding adapters. The `backendclient` adapter decodes
authenticated FlatBuffers payloads, calls backend's `/api/v1/user/*`
REST surface with `X-User-Id`, and re-encodes the JSON result into
FlatBuffers before the signed gateway response is emitted.
### EventSubscriber