This PR contains the latest updates from the generic Website template, which have been added while working on #loud-amsterdam. Reviewed-on: #1 Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Persistence
The Fluent-based data layer the platform's services build on: runtime selection between a PostgreSQL backend and an ephemeral in-memory SQLite one, single-place migration registration, and a database readiness probe.
Overview
| Role | Types |
|---|---|
| Backend selection | Driver (postgres or inMemory), Configuration (the PostgreSQL connection parameters), TLS (the connection's TLS posture) |
| Service | Service, which builds the Fluent service configured for the chosen driver |
| Migrations | PrepareDB, the single registrar declaring every migration, in order |
| Readiness | Probe, which reports whether the default database answers a SELECT 1 within a deadline |
| Scaffolding | The internal ExampleRecord and CreateExampleRecord, and the public Example snapshot and ExampleRepository — the model → migration → repository pattern, to be replaced by the first real domain model |
Design rules
- The package reads no configuration. The executable maps its
database.*keys onto aDriverand hands it over; connection values arrive as plain data. The Website service'sConfigReader+Propertieshas the mapping. - One default database.
Serviceregisters the selected backend as the default database, so repositories resolve it with a plainfluent.db()and stay agnostic of which driver is in use. - Migrations are declared once, and append-only.
PrepareDBregisters every migration in the order it must run; alter the schema by adding a migration, never by editing one that has already run. Registering does not apply them: the in-memory backend migrates on startup, while a shared PostgreSQL database is migrated out of band (the executable's migrate-and-exit mode), so booting instances never race. - Models never cross a concurrency boundary. FluentKit models are mutable reference types, so they stay internal to the package and repositories return
Sendablevalue-type snapshots (e.g.Example) instead. - Readiness never throws, and never hangs.
Proberuns a schema-independentSELECT 1, maps every failure tofalse, and races the query against a deadline (2 seconds by default) — a hanging database yields a prompt "not ready" instead of a stalled endpoint. - A single connection for the in-memory store. The SQLite backend is capped at one connection per event loop, so every query reaches the same in-memory database instead of each pooled connection getting a private one.
- Method structs.
Service,PrepareDB, andProbehold their lifetime-fixed configuration ininitand take only per-call inputs incallAsFunction.
Note: the driver itself enforces both TLS postures —
preferupgrades only when the server advertises TLS and continues in plaintext otherwise,requirerefuses a server that offers none. Tests pin both against a fake plaintext-only server.
Layout
Sources are split by visibility, then by kind, one type per file:
Sources/
├── Public/
│ ├── Enumerations/ Driver, TLS
│ ├── Methods/ Service, PrepareDB, Probe
│ └── Types/ Configuration
└── Internal/
├── Migrations/ CreateExampleRecord
├── Models/ ExampleRecord
└── Repositories/ ExampleRepository (returning the Example snapshot)
Tests/
├── Cases/ the test suites, mirroring the Sources/ layout
└── Utils/ the NotSQL* fakes backing the probe's non-SQL-database case, the
plaintext-only and silent fake PostgreSQL servers, and the suite Tag constants
Exception:
ExampleandExampleRepositoryarepublicdespite sitting underInternal/, so they are API a service can call. Move them toPublic/ModelsandPublic/Repositorieswhen the first real domain model replaces them.
Testing
The suite runs against the in-memory backend by default, so swift test needs no database. The PostgreSQL integration test is skipped unless POSTGRES_TEST_HOST points at one (with optional POSTGRES_TEST_PORT, POSTGRES_TEST_NAME, POSTGRES_TEST_USERNAME, and POSTGRES_TEST_PASSWORD); it reverts its migrations afterwards, leaving a shared database as it was found:
swift test # in-memory only
POSTGRES_TEST_HOST=127.0.0.1 swift test # against the local PostgreSQL (make db-mount)
Outside the application's service group, a built Fluent service must be shut down explicitly — even on failure — or its connection pool asserts on deinit; the suites' do/catch around fluent.shutdown() is the shape to follow.
Every suite carries a tag for the kind of API it exercises — .enumeration or .method, declared in Tests/Utils/Extensions/Tag+Constants.swift — so test plans and summaries can slice a run by kind. A new suite adopts the tag matching its subject, or adds one when none fits.
Requirements
- Swift 6.3 toolchain (
swift-tools-version:6.3). - macOS 15, matching the sibling packages (the services deploy to Linux containers; the packages carry no UI platforms).
- Package dependencies:
hummingbird-fluent,fluent-postgres-driver,fluent-sqlite-driver,sql-kit,postgres-nio, andswift-nio-ssl; the test target additionally depends onswift-niofor the TLS fallback tests' fake server.