feat: use postgres
This commit is contained in:
+66
-22
@@ -63,38 +63,67 @@ Intentional omissions:
|
||||
`cmd/userservice` loads config, constructs logging and telemetry, and then
|
||||
creates the runtime through `internal/app.NewRuntime`.
|
||||
|
||||
The runtime wires:
|
||||
The runtime wires, in order:
|
||||
|
||||
- Redis-backed stores for accounts, entitlement snapshots, sanctions, limits,
|
||||
and listing indexes
|
||||
- one shared `*redis.Client` opened through `pkg/redisconn` plus a Ping
|
||||
- one PostgreSQL pool opened through `pkg/postgres`, instrumented with
|
||||
`db.sql.connection.*` metrics, pinged, and migrated forward via the
|
||||
embedded `internal/adapters/postgres/migrations` filesystem
|
||||
- the PostgreSQL-backed user store from
|
||||
`internal/adapters/postgres/userstore` (accounts, blocked-emails,
|
||||
entitlement snapshot/history/lifecycle, sanction history/lifecycle,
|
||||
limit history/lifecycle, listing index)
|
||||
- two Redis Stream publishers
|
||||
(`internal/adapters/redis/domainevents` for auxiliary domain events,
|
||||
`internal/adapters/redis/lifecycleevents` for trusted user-lifecycle
|
||||
events) sharing the same `*redis.Client`
|
||||
- the trusted internal HTTP router
|
||||
- the optional admin metrics listener
|
||||
- the optional Redis-backed domain-event publishers
|
||||
- service-local helpers for clock, IDs, and validation/policy adapters
|
||||
|
||||
Startup fails fast when Redis connectivity is unavailable or configuration is
|
||||
invalid.
|
||||
Startup fails fast when Redis or PostgreSQL connectivity is unavailable, the
|
||||
mandatory connection-topology environment variables are missing, the
|
||||
embedded migration sequence cannot be applied, or configuration is otherwise
|
||||
invalid. The HTTP listeners do not open until every dependency check passes.
|
||||
|
||||
## Redis Namespaces
|
||||
## Storage Backends
|
||||
|
||||
The service uses one Redis keyspace prefix plus one auxiliary domain-events
|
||||
stream.
|
||||
The service is split between two backends per
|
||||
[`../../ARCHITECTURE.md §Persistence Backends`](../../ARCHITECTURE.md):
|
||||
|
||||
Configuration:
|
||||
PostgreSQL holds source-of-truth durable state in the `user` schema:
|
||||
|
||||
- `USERSERVICE_REDIS_KEYSPACE_PREFIX`
|
||||
- `USERSERVICE_REDIS_DOMAIN_EVENTS_STREAM`
|
||||
- `USERSERVICE_REDIS_DOMAIN_EVENTS_STREAM_MAX_LEN`
|
||||
- `accounts` (with `email` and `user_name` UNIQUE; `deleted_at` records the
|
||||
Stage 22 soft-delete state)
|
||||
- `blocked_emails` (one row per blocked address)
|
||||
- `entitlement_records` plus the denormalised `entitlement_snapshots`
|
||||
one-row-per-user current view
|
||||
- `sanction_records` plus `sanction_active(user_id, sanction_code)`
|
||||
- `limit_records` plus `limit_active(user_id, limit_code)`
|
||||
|
||||
The keyspace stores source-of-truth business state. The stream carries
|
||||
post-commit auxiliary domain events and must not be treated as the source of
|
||||
truth.
|
||||
Indexes carry the listing surface (`accounts(created_at DESC, user_id
|
||||
DESC)`), reverse-lookup filters (`accounts(declared_country)`,
|
||||
`entitlement_snapshots(plan_code, is_paid)`,
|
||||
`entitlement_snapshots(ends_at) WHERE is_paid AND ends_at IS NOT NULL`,
|
||||
`sanction_active(sanction_code)`, `limit_active(limit_code)`), and the
|
||||
per-user history scans.
|
||||
|
||||
Redis hosts only the two Stream publishers
|
||||
(`USERSERVICE_REDIS_DOMAIN_EVENTS_STREAM`,
|
||||
`USERSERVICE_REDIS_LIFECYCLE_EVENTS_STREAM`). It does not store any
|
||||
durable user state after Stage 3 of `PG_PLAN.md`.
|
||||
|
||||
Decision records:
|
||||
[`postgres-migration.md`](postgres-migration.md) for the schema and
|
||||
storage decisions.
|
||||
|
||||
## Configuration Groups
|
||||
|
||||
Required for all process starts:
|
||||
|
||||
- `USERSERVICE_REDIS_ADDR`
|
||||
- `USERSERVICE_REDIS_MASTER_ADDR`
|
||||
- `USERSERVICE_REDIS_PASSWORD`
|
||||
- `USERSERVICE_POSTGRES_PRIMARY_DSN`
|
||||
|
||||
Core process config:
|
||||
|
||||
@@ -116,16 +145,31 @@ Admin HTTP config:
|
||||
- `USERSERVICE_ADMIN_HTTP_READ_TIMEOUT`
|
||||
- `USERSERVICE_ADMIN_HTTP_IDLE_TIMEOUT`
|
||||
|
||||
Redis connectivity and namespace config:
|
||||
Redis connectivity (consumed by `pkg/redisconn`):
|
||||
|
||||
- `USERSERVICE_REDIS_USERNAME`
|
||||
- `USERSERVICE_REDIS_PASSWORD`
|
||||
- `USERSERVICE_REDIS_REPLICA_ADDRS` (optional, comma-separated)
|
||||
- `USERSERVICE_REDIS_DB`
|
||||
- `USERSERVICE_REDIS_TLS_ENABLED`
|
||||
- `USERSERVICE_REDIS_OPERATION_TIMEOUT`
|
||||
- `USERSERVICE_REDIS_KEYSPACE_PREFIX`
|
||||
|
||||
Stream-shape (kept service-local):
|
||||
|
||||
- `USERSERVICE_REDIS_DOMAIN_EVENTS_STREAM`
|
||||
- `USERSERVICE_REDIS_DOMAIN_EVENTS_STREAM_MAX_LEN`
|
||||
- `USERSERVICE_REDIS_LIFECYCLE_EVENTS_STREAM`
|
||||
- `USERSERVICE_REDIS_LIFECYCLE_EVENTS_STREAM_MAX_LEN`
|
||||
|
||||
PostgreSQL connectivity (consumed by `pkg/postgres`):
|
||||
|
||||
- `USERSERVICE_POSTGRES_REPLICA_DSNS` (optional, comma-separated)
|
||||
- `USERSERVICE_POSTGRES_OPERATION_TIMEOUT`
|
||||
- `USERSERVICE_POSTGRES_MAX_OPEN_CONNS`
|
||||
- `USERSERVICE_POSTGRES_MAX_IDLE_CONNS`
|
||||
- `USERSERVICE_POSTGRES_CONN_MAX_LIFETIME`
|
||||
|
||||
The retired Redis variables `USERSERVICE_REDIS_ADDR`,
|
||||
`USERSERVICE_REDIS_USERNAME`, `USERSERVICE_REDIS_TLS_ENABLED`,
|
||||
`USERSERVICE_REDIS_KEYSPACE_PREFIX` produce a startup error from
|
||||
`pkg/redisconn` if set; unset them before starting the service.
|
||||
|
||||
Telemetry:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user