Data infrastructure
One system of record, with the constraints in the database.
PostgreSQL holds everything that must be correct, with foreign keys and constraints enforced by the database rather than trusted to application code. Everything beyond it is added against a named requirement.
Correctness first, then everything else
The platform's data model is small and deliberately stable: users, organisations, memberships, products, entitlements, plans, and subscriptions. It is the layer every product depends on, so it is the layer least able to afford being wrong.
Invariants are enforced where they cannot be bypassed. Foreign keys, NOT NULL, and CHECK constraints live in the schema, not only in the service that happens to write the row. Application-level validation is a better error message; the database is the guarantee.
The schema is normalised by default and denormalised only where a measured read pattern requires it. Indexes are added against real queries rather than added speculatively and never removed.
The organisation is the tenancy boundary
An organisation owns its memberships and its product entitlements, and is the unit that access and billing attach to. A user is a separate identity that may belong to many organisations. Getting this shape right early is what avoids a painful migration later, when data has accumulated under the wrong owner.
Migrations from the first table
Schema changes go through Alembic, versioned in the repository and applied as a reviewed step separate from application deploys. Production schema is never edited by hand.
Pooled async connections
The platform is an async service, which means connection pooling is required from the first deployment rather than as a scaling optimisation. Without it, ordinary concurrency exhausts the database's connection limit quickly.
A decision rule for every store
If it must be correct, queryable, and durable, it goes in PostgreSQL. If it is disposable and short-lived — a cache entry, a rate-limit counter — it goes in Redis, once Redis exists. If it is a large binary object, it goes in object storage with a reference held in PostgreSQL. The rule is written down so the answer does not depend on who is asking.
Backups from day one
Automated backups start with the first production database, and restores are tested by actually performing them. A backup that has never been restored is an assumption.
Where the data actually sits
The database runs in a private subnet, reachable only from the API's security group — not from the internet, and not from anything else in the network. Encryption is on in transit and at rest. Credentials come from a managed secret store at runtime and are never present in the image or the repository.
Read replicas, caching, and partitioning are all understood as responses to measured load rather than as a starting configuration. The first answer to a slow query is a profile and an index, not more infrastructure.
Constraints live in the database. Application code can be wrong; a foreign key cannot be persuaded.
Designed to be examined
The data model and its constraints are documented as deliberately as the code. If you want to review the design, we are happy to walk through it.