0010 — Identifier strategy
bigserial for event ordering, UUIDv7 for entities, FID for principals.
Decided. Three jobs, three types.
| Job | Type | Generated by |
|---|---|---|
Ordering (events.id) | bigserial | Postgres sequence |
| Entity identity | uuid (v7) | Go, uuid.NewV7() |
| Principals | text (FID) | identity — minted or adopted |
Why UUIDv7 in Go, not Postgres
v7 over v4 because it embeds a timestamp prefix, so inserts append in the b-tree instead of scattering.
Generated in the application because native uuidv7() requires Postgres 18, and the
pg_uuidv7 extension is not reliably available on managed providers. Generating in
Go removes the dependency entirely — the column is a plain uuid not null. This is
the same rule as "no extensions" generally: anything an extension would do, do in Go.
Why not auto-incrementing integers as public ids
Enumerable, and they leak volume — create two records and subtract to learn how many
exist. bigserial is correct for events.id, which is a cursor and never exposed as
an identity.
Why FIDs stay text
identity mints FIDs or adopts ones assigned upstream — Google Workspace staff
arrive with theirs. An adopted id cannot be a UUIDv7, so the type has to be an opaque
string.
FIDs identify principals only — persons, organizations, systems, groups. They are not a universal identifier scheme. A work order is not a principal and gets a UUID.
Consequences
- Entity ids are safe to expose in URLs.
- FIDs appear in other services as columns, and become real foreign keys where the local principals copy exists.
- Postgres never generates an id, so tests and fixtures control ids fully.