# Good Phorum Architecture Good Phorum is a modular Mojolicious application. It renders pages on the server, keeps canonical data in PostgreSQL and delivers durable events through an outbox. ## Architecture Audit The current boundaries are enforced by `script/architecture-check` and the integration suite: - `GPForum.pm` composes bootstrap modules. Controllers adapt HTTP requests; services own workflows, readers and writes. - `GPForum::Infrastructure::SqlFunction` calls versioned PostgreSQL functions ([ADR 0126](docs/adr/0126-sql-lives-in-postgresql-functions.md)). SQL lives in migrations; controllers and templates cannot reach resultsets. - Reader services supply presenter view models. Shared SSR components handle translation, CSRF fields and semantic landmarks. - Event, audit and outbox records commit with the write. Workers consume the durable event envelope. - Internal module dependency cycles are rejected. The [quality record](docs/QUALITY_PROGRAM.md) preserves dated audits and follow-up evidence. The sections below describe the current implementation. ## Layer Map Five layers, listed from the top. A module may depend on its own layer and the layers below it, never on one above ([ADR 0107](docs/adr/0107-layers-are-the-namespaces-that-exist.md)). `GPForum::Application::LayerMap` declares them and `t/192-layering.t` checks every module in `lib/` against the declaration; `script/architecture-check` runs the same test. | Layer | Namespaces | Holds | | --- | --- | --- | | composition | the `GPForum` class, `Bootstrap`, `CLI`, `Application` | wiring the others together | | adapter | `Controller`, `Command`, `Worker`, `Benchmark` | the ways in: HTTP, the command line, jobs | | presentation | `Web`, `ViewModel`, `View`, `Theme`, `Security`, `I18N` | turning service results into pages and headers | | service | `Service` | the application: rules, workflows, readers, stores | | foundation | `Base`, `X`, `Config`, `Log`, `Runtime`, `OS`, `Domain`, `Jobs`, `Schema`, `Migration`, `Infrastructure` | configuration, persistence, cross-cutting contracts | A new top-level namespace must be assigned a layer before the check passes. Services call PostgreSQL functions directly, as specified in ADR 0107. ## Data Access **Local processes reach PostgreSQL over Unix-domain sockets.** This includes web requests, workers, command-line tools and `LISTEN` connections. The default DSN is `dbi:Pg:dbname=gpforum`. Debian/Ubuntu commonly use `host=/var/run/postgresql`; Homebrew and FreeBSD normally use `/tmp`. A hostname or IP explicitly selects TCP for a remote database. See [ADR 0137](docs/adr/0137-the-forum-reaches-postgresql-and-its-proxy-over-unix-sockets.md) and [socket configuration](docs/DEPLOYMENT.md#unix-socket-mode). Application reads and writes call `api._v` functions. Migrations install the SQL; the [current baseline](migrations/README.md) defines the API in `2012_api_core.sql` through `2025_api_entry_points.sql`. A changed contract gets a new version. `GPForum::Infrastructure::SqlFunctionCatalogue` declares names and types; integration tests compare it with `pg_proc`. A [restricted runtime login](docs/ops/sql-api-role.md) can enforce API-only access. `GPForum::Schema` owns native Mojo::Pg connection and transaction storage ([ADR 0174](docs/adr/0174-mojo-pg-is-the-runtime-stored-function-transport.md)). It exposes no ORM resultsets. DBIx::Class, historical models and comparison profilers remain in maintainer fixtures under `t/lib` and develop dependencies. Connection settings, notifications and schema administration belong to infrastructure. ## Flow ```mermaid flowchart TD Browser["Browser / SSR Client"] --> Routes["Mojolicious Routes"] Routes --> Controllers["Thin Controllers"] Controllers --> Commands["Command / Workflow Services"] Controllers --> Queries["Reader / Query Services"] Commands --> Stores["Infrastructure Stores"] Stores --> EventLog["event_log"] Stores --> Audit["audit_log"] Stores --> Outbox["outbox_messages"] Queries --> Calls["SqlFunction: api.*_vN calls"] Stores --> Calls Calls -->|Unix-domain socket| Postgres["Local PostgreSQL functions and tables"] Outbox --> Minion["Minion / Domain Event Transport"] Outbox --> Notify["PostgreSQL NOTIFY"] Notify --> Realtime["Websocket Fanout"] Minion --> Workers["Idempotent Workers"] Workers --> Projections["Search / Inbox / Feed / Cache Projections"] Controllers --> Presenters["ViewModel Presenters"] Presenters --> Templates["Mojolicious SSR Templates"] Templates --> Browser ``` ## Current Decisions - Durable events use `GPForum::Domain::EventEnvelope`. - Forum, identity, moderation, privacy, export, attachment, admin role, mention fanout and rate-limit writes use `GPForum::Infrastructure::EventRecorder` for event/outbox/audit coordination. Canonical audit hashing lives in `GPForum::Infrastructure::AuditRecord`. - Browser headers use `GPForum::Security::BrowserHeaders`. - Repeated web error hashes use `GPForum::Web::ErrorPayload`. - Health and realtime transport messages use dedicated `GPForum::Web::*Payload` modules so technical JSON contracts stay out of controllers. - Realtime subscriptions use strict policy-backed authorization and PostgreSQL LISTEN/NOTIFY for multi-process enhancement fanout. - Metrics, robots, sitemap, and Atom feed payload composition also lives in dedicated `GPForum::Web::*Payload` modules; service builders still own crawler/feed document encoding. - SSR raw HTML pass-through uses `GPForum::Web::RenderPolicy`. - Theme metadata and supported token contracts use `GPForum::Theme::Registry`; SSR theme selection is validated before it reaches HTML attributes. ## Roadmap 1. Keep new service writes behind `Infrastructure::EventRecorder`; direct `EventLog`, `OutboxMessage`, and `AuditLog` writes should remain confined to infrastructure. 2. Extract common controller response/error/auth helpers into `Web::*`. 3. The query-plan gate explains the statements inside the hot functions (ADR 0126, section 9); a new hot read joins `GPForum::Benchmark::QueryPlanEndpoints`. 4. Add command handler interfaces around moderation, privacy, attachment, and identity writes. 5. Normalize Minion job payloads through `Jobs::*`. 6. Add i18n namespace validation and catalog extraction reporting. 7. Maintain stored-function-only runtime access through Mojo::Pg (ADR 0174).