This PR contains the work done to do a little bit of housekeeping pass across all packages and the Website service. To provide further details about the work: * Refreshed the READMEs and source documentation to match the current code; * Tagged every test case consistently across the Infrastructure, Localization, Persistence, and Website test targets; * Removed Website middleware tests now covered by Infrastructure's own suite; * Conformed the `PrepareDB` method to Sendable; * Relaxes the production Compose DATABASE_TLS default from require to prefer; * Added Persistence test verifying the prefer posture falls back to plaintext connections. Reviewed-on: rock-n-code/loud-amsterdam#27 Co-authored-by: Javier Cicchelli <javier@rock-n-code.com> Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
Persistence
The Fluent-based data layer the Loud services build on: runtime selection between a MySQL/MariaDB backend and an ephemeral in-memory SQLite one, single-place migration registration, and a database readiness probe.
Overview
The package provides, grouped by role:
| Role | Types |
|---|---|
| Backend selection | Driver (mysql or inMemory), Configuration (the MySQL/MariaDB 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 |
| Scaffolding (internal) | ExampleRecord, CreateExampleRecord, 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. See the Website service'sConfigReader+Propertiesfor 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.
PrepareDBis the single place migrations are registered, in the order they must run; alter the schema by adding a new migration, never by editing one that has already run. Registering does not apply them — the in-memory backend is migrated on startup, while a shared MySQL/MariaDB database is migrated out of band (the executable's migrate-and-exit mode), so multiple booting instances never race. - Models never cross a concurrency boundary. FluentKit models are mutable reference types; repositories map them to
Sendablevalue-type snapshots (e.g.Example) before returning, and the models themselves stay internal to the package. - Readiness never throws.
Proberuns aSELECT 1— the cheapest statement both backends understand, independent of any schema — and maps every failure tofalse, so callers translate it straight into a readiness response. - 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, rather than each pooled connection getting its own private one.
- Method structs.
Service,PrepareDB, andProbehold their lifetime-fixed configuration ininitand take only per-call inputs incallAsFunction.
Note: the
preferTLS posture is enforced by the driver itself: a supplied TLS configuration upgrades the connection only when the server advertises TLS, and continues in plaintext otherwise (pinned by a test against a fake server that offers no TLS).requirecurrently maps to the same configuration and therefore behaves likeprefer— the refusal when the server offers no TLS is not yet enforced.
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 fake MySQL server, and the suite Tag constants
Testing
The suite runs against the in-memory backend by default, so swift test needs no database. The MySQL/MariaDB integration test is skipped unless a database is pointed at via MYSQL_TEST_HOST (with optional MYSQL_TEST_PORT, MYSQL_TEST_NAME, MYSQL_TEST_USERNAME, and MYSQL_TEST_PASSWORD); it reverts its migrations afterwards, so the shared database is left as it was found:
# in-memory only
swift test
# or
# with the local MariaDB up (make db-mount):
MYSQL_TEST_HOST=127.0.0.1 swift test
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 pattern around fluent.shutdown() is the shape to follow.
Every suite carries a tag naming the kind of API it exercises — .enumeration or .method, declared in Tests/Utils/Extensions/Tag+Constants.swift — so test plans and result summaries can slice the run by kind. A new suite must adopt the tag matching its subject (or add a tag there if none fits).
Requirements
- Swift 6.3 toolchain (
swift-tools-version:6.3). - macOS 15, matching the sibling
InfrastructureandLocalizationpackages (the services deploy to Linux containers; the packages carry no UI platforms). - Package dependencies:
hummingbird-fluent,fluent-mysql-driver,fluent-sqlite-driver, andsql-kit; the test target additionally depends onmysql-nioandswift-niofor the TLS fallback test.