feat: backend service
This commit is contained in:
+54
-140
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user