Tweaks and fixes throughout the project (#27)

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>
This commit is contained in:
2026-07-30 06:33:57 +00:00
committed by javier
parent 0887135328
commit ea00b94841
71 changed files with 633 additions and 512 deletions
+60
View File
@@ -0,0 +1,60 @@
# Persistence
The [Fluent](https://github.com/hummingbird-project/hummingbird-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 a `Driver` and hands it over; connection values arrive as plain data. See the Website service's `ConfigReader+Properties` for the mapping.
- **One default database.** `Service` registers the selected backend as the *default* database, so repositories resolve it with a plain `fluent.db()` and stay agnostic of which driver is in use.
- **Migrations are declared once, and append-only.** `PrepareDB` is 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 `Sendable` value-type snapshots (e.g. `Example`) before returning, and the models themselves stay internal to the package.
- **Readiness never throws.** `Probe` runs a `SELECT 1` — the cheapest statement both backends understand, independent of any schema — and maps every failure to `false`, 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`, and `Probe` hold their lifetime-fixed configuration in `init` and take only per-call inputs in `callAsFunction`.
> **Note:** the `prefer` TLS 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). `require` currently maps to the same configuration and therefore behaves like `prefer` — 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:
```sh
# 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 `Infrastructure` and `Localization` packages (the services deploy to Linux containers; the packages carry no UI platforms).
- Package dependencies: `hummingbird-fluent`, `fluent-mysql-driver`, `fluent-sqlite-driver`, and `sql-kit`; the test target additionally depends on `mysql-nio` and `swift-nio` for the TLS fallback test.