feat: use postgres
This commit is contained in:
@@ -13,6 +13,149 @@ Execution priorities:
|
||||
- Defer threshold tuning until after the basic data model is working.
|
||||
- Avoid unnecessary infrastructure on the first iteration.
|
||||
|
||||
## Stage 00 — Persistence Stack and Backend Assignment
|
||||
|
||||
Goal:
|
||||
|
||||
- Pin the platform-wide persistence stack and the per-service backend
|
||||
ownership before any feature stage begins, so that subsequent stages
|
||||
design schemas, queries, and worker loops consistently with the
|
||||
project-wide rules in
|
||||
[`../ARCHITECTURE.md §Persistence Backends`](../ARCHITECTURE.md#persistence-backends)
|
||||
and the staged migration plan in
|
||||
[`../PG_PLAN.md`](../PG_PLAN.md).
|
||||
|
||||
This stage is documentation-only: no code exists in this service yet, and
|
||||
this stage adds none. It is a prerequisite to every later stage and ships
|
||||
as part of `PG_PLAN.md` Stage 8.
|
||||
|
||||
Tasks:
|
||||
|
||||
- Adopt the shared Postgres helper [`pkg/postgres`](../pkg/postgres) for
|
||||
every durable storage path:
|
||||
|
||||
- driver `github.com/jackc/pgx/v5`, exposed as `*sql.DB` via
|
||||
`github.com/jackc/pgx/v5/stdlib`;
|
||||
- query layer `github.com/go-jet/jet/v2` (PostgreSQL dialect) with
|
||||
generated code under `internal/adapters/postgres/jet/`, regenerated
|
||||
by a per-service `make jet` target and committed to the repo;
|
||||
- migrations via `github.com/pressly/goose/v3` library API embedded
|
||||
with `//go:embed`, applied at service startup before any HTTP
|
||||
listener becomes ready, with non-zero exit on failure;
|
||||
- `github.com/testcontainers/testcontainers-go` (`modules/postgres`)
|
||||
for unit tests and for hosting the transient instance used by
|
||||
`make jet`.
|
||||
- Adopt the shared Redis helper [`pkg/redisconn`](../pkg/redisconn) for
|
||||
every Redis client:
|
||||
|
||||
- master/replica/password connection shape;
|
||||
- mandatory password;
|
||||
- no `TLS_ENABLED`, no `USERNAME` (rejected at startup with a clear
|
||||
error from `pkg/redisconn.LoadFromEnv`).
|
||||
- Own the `geoprofile` schema in the shared `galaxy` PostgreSQL database.
|
||||
Connect with a dedicated `geoprofile` PG role whose grants are
|
||||
restricted to its own schema (defense-in-depth, expressed in the
|
||||
initial migration).
|
||||
- Lay out the postgres-backed adapter directory consistently with the
|
||||
PG-migrated services:
|
||||
|
||||
```text
|
||||
geoprofile/
|
||||
internal/
|
||||
adapters/
|
||||
postgres/
|
||||
migrations/ # *.sql files + migrations.go (//go:embed)
|
||||
jet/ # generated code, commit-checked
|
||||
<storeName>/ # adapter implementations matching
|
||||
# internal/ports
|
||||
config/
|
||||
config.go # Postgres + Redis schemas
|
||||
Makefile # `jet` target: testcontainers + goose + jet
|
||||
```
|
||||
- Backend assignment for the entities listed in
|
||||
[`README.md §Data Entities`](README.md#data-entities):
|
||||
|
||||
- PostgreSQL (`geoprofile` schema, source of truth):
|
||||
|
||||
- `country_observation` — durable observed-country fact rows.
|
||||
- `device_session_country_score` — per-`device_session_id` weighted
|
||||
country aggregates.
|
||||
- `device_session_geo_state` — current `usual_connection_country`
|
||||
per `device_session_id`.
|
||||
- `user_review_state` — `country_review_recommended` flag and last
|
||||
evaluation timestamp.
|
||||
- `declared_country_version` — immutable history of approved
|
||||
`declared_country` changes (with version status `recorded` /
|
||||
`applied` / `sync_failed`).
|
||||
- `session_block_action` — local audit of block-request outcomes.
|
||||
- Ingest-queue lifecycle from §Stage 05 (`accepted` / `processing` /
|
||||
`processed` / `failed`) is materialised as `status` /
|
||||
`next_attempt_at` columns on the durable observation row, not as a
|
||||
Redis ZSET. Workers select pending work via
|
||||
`SELECT ... FOR UPDATE SKIP LOCKED`, mirroring the pattern already
|
||||
in use by Mail and Notification.
|
||||
- Redis (`pkg/redisconn`):
|
||||
|
||||
- only ephemeral runtime-coordination signals if any appear during
|
||||
implementation — for example, transition-deduplication windows for
|
||||
review-flag notifications, short worker leases on processing
|
||||
claims. No durable business state.
|
||||
- the `notification:intents` Redis Stream is used by this service
|
||||
only as a producer to publish `geo.review_recommended` intents
|
||||
(see §Stage 11 and `README.md §Integration with Notification
|
||||
Service`); that connection is built via `pkg/redisconn`.
|
||||
- **Idempotency**, if added for ingest deduplication, is a `UNIQUE`
|
||||
constraint on the durable observation row, never a separate Redis kv.
|
||||
**Retry scheduling**, if added for worker reprocessing or
|
||||
`User Service` sync retries, is a column on the durable record, worked
|
||||
off via `FOR UPDATE SKIP LOCKED`. Both rules align this service with
|
||||
the platform-wide pattern.
|
||||
- Time-valued columns are `timestamptz`. Adapters normalise every
|
||||
`time.Time` value crossing the SQL boundary to `time.UTC` on bind and
|
||||
scan, per
|
||||
`../ARCHITECTURE.md §Persistence Backends — Timestamp handling`.
|
||||
- Configuration (target):
|
||||
|
||||
- PostgreSQL knobs (loaded via
|
||||
`pkg/postgres.LoadFromEnv("GEOPROFILE")`):
|
||||
|
||||
- `GEOPROFILE_POSTGRES_PRIMARY_DSN` (required;
|
||||
`postgres://geoprofile:<pwd>@<host>:5432/galaxy?search_path=geoprofile&sslmode=disable`);
|
||||
- `GEOPROFILE_POSTGRES_REPLICA_DSNS` (optional, comma-separated;
|
||||
reserved for future read-routing, not consumed yet);
|
||||
- `GEOPROFILE_POSTGRES_OPERATION_TIMEOUT`,
|
||||
`GEOPROFILE_POSTGRES_MAX_OPEN_CONNS`,
|
||||
`GEOPROFILE_POSTGRES_MAX_IDLE_CONNS`,
|
||||
`GEOPROFILE_POSTGRES_CONN_MAX_LIFETIME`.
|
||||
- Redis knobs (loaded via
|
||||
`pkg/redisconn.LoadFromEnv("GEOPROFILE")`):
|
||||
|
||||
- `GEOPROFILE_REDIS_MASTER_ADDR` (required),
|
||||
`GEOPROFILE_REDIS_REPLICA_ADDRS` (optional, comma-separated);
|
||||
- `GEOPROFILE_REDIS_PASSWORD` (required);
|
||||
- `GEOPROFILE_REDIS_DB`,
|
||||
`GEOPROFILE_REDIS_OPERATION_TIMEOUT`.
|
||||
- Per-service decision record `geoprofile/docs/postgres-migration.md`
|
||||
is created by the stage that actually implements the service. It must
|
||||
capture: schema and role grants, queue materialisation choice, retry
|
||||
pattern, and any non-trivial deviation from the platform-wide rules
|
||||
(analogous to
|
||||
[`../user/docs/postgres-migration.md`](../user/docs/postgres-migration.md),
|
||||
[`../mail/docs/postgres-migration.md`](../mail/docs/postgres-migration.md),
|
||||
[`../notification/docs/postgres-migration.md`](../notification/docs/postgres-migration.md),
|
||||
and [`../lobby/docs/postgres-migration.md`](../lobby/docs/postgres-migration.md)).
|
||||
|
||||
Exit criteria:
|
||||
|
||||
- The persistence stack and schema ownership are fixed and visible to
|
||||
implementers.
|
||||
- Every later stage (Stage 01+) designs schemas and queries on top of
|
||||
the `geoprofile` Postgres schema, or — for any ephemeral signal — on
|
||||
top of `pkg/redisconn`.
|
||||
- `../ARCHITECTURE.md §Persistence Backends` and `../PG_PLAN.md` remain
|
||||
the canonical references; this PLAN points at them rather than
|
||||
duplicating their content.
|
||||
|
||||
## Stage 01 — Freeze Service Vocabulary and Contracts
|
||||
|
||||
Goal:
|
||||
@@ -643,6 +786,7 @@ Exit criteria:
|
||||
|
||||
Recommended delivery order:
|
||||
|
||||
- Persistence stack and backend assignment
|
||||
- Domain vocabulary and ownership
|
||||
- Domain model
|
||||
- FlatBuffers schema
|
||||
|
||||
Reference in New Issue
Block a user