Skip to content

ADR-001: PostgreSQL as the relational database

Status: Accepted · Date: 2026-08

Context

PropFlow stores property records and a financial ledger: income and expenses, tax categorisation, refunds, warranties. The access patterns are report-shaped — filter a user's transactions by date range, category, amount, and status, then aggregate.

Two properties dominate the choice. The data is highly relational (a transaction belongs to a property belongs to a user), and it is money, which must be exact and must not be lost.

Options considered

MongoDB. Attractive early: the Transaction document with nested tax details, warranties, and metadata maps naturally to a document. Rejected because the invariants that matter here are relational. A document store cannot enforce "every transaction references a real property" — that check moves into application code, where it is advisory. This project has already demonstrated what happens when an invariant lives only in application code: transactions referenced users through an unvalidated string, and the model drifted.

MySQL. A reasonable choice. PostgreSQL wins on the features this workload will actually reach for: partial and functional indexes (used in V3 for case-insensitive uniqueness), NUMERIC arithmetic, richer constraint support, and EXCLUDE USING gist for booking-overlap prevention when Booking is implemented — an invariant MySQL cannot express declaratively.

SQLite. Fine for a single-writer local tool; not for a concurrently-accessed service.

Decision

PostgreSQL 15.

Consequences

Good. Foreign keys, CHECK constraints, and unique indexes enforce the domain's invariants at the only layer every writer must pass through. NUMERIC gives exact decimal money. Functional indexes made case-insensitive uniqueness a schema change rather than an application convention. A clear upgrade path exists for the two known performance limits: tsvector + GIN for free-text search, and exclusion constraints for booking overlap.

Bad. Requires a running server — Docker is a prerequisite for local development and for the test suite, where an embedded database would need nothing. Schema changes require migrations, which is discipline the document-store alternative would not have imposed. A single instance is the scaling ceiling; read replicas and connection pooling become necessary before the application itself does.

Neutral. Committing to PostgreSQL specifically — rather than "some SQL database" — is what makes ADR-004 follow: if the dialect and constraint semantics are being relied on, the tests must run against the real thing.