Architectural Decisions

Why things are the way they are. Every decision has context, tradeoffs, and consequences.

What is an ADR?

An Architecture Decision Record captures the why behind technical choices. When future-you (or someone else) asks "why did we do it this way?" — the answer is here.

ADR-001: PHP for Content Sites, Next.js for SaaS

StatusAccepted
Date2025
AffectsAll new projects

Context

Multiple projects need to be built. Some are content-focused (documentation, case management). Others require complex user interactions (rental marketplace with search, booking, payments).

Decision

Use PHP for content-heavy sites with forms and server rendering. Use Next.js (TypeScript) for interactive SaaS applications.

Consequences

Alternatives Considered


ADR-002: PostgreSQL as Primary Database

StatusAccepted
DateFebruary 2026
AffectsAll new applications

Context

The ecosystem was using MySQL by default for PHP applications. However, MySQL has significant limitations that cause real problems:

Meanwhile, PostgreSQL was already being used for the trailer rental platform and TimescaleDB (which is PostgreSQL) was used for TradeCraft.

Decision

Standardize on PostgreSQL for all new applications. MySQL is legacy-only (Moodle).

Database Architecture

PostgreSQL (port 5432)     ← PRIMARY FOR ALL NEW APPS
├── praxis                 ← This site
├── trailer_rental         ← Rental platform  
├── case_portal            ← Migrate when ready
└── future apps

TimescaleDB (port 5433)    ← PRODUCTION SERVER (time-series)
└── tradecraft             ← Stock/crypto candles

MySQL (port 3306)          ← LEGACY ONLY
└── moodle                 ← Can't change (Moodle core)

Why PostgreSQL Wins

FeatureMySQLPostgreSQL
JSON queriesJSON_EXTRACT() hackNative ->, ->>, @>
ArraysINTEGER[], TEXT[]
Full-text searchBasicBuilt-in tsvector
Partial indexesWHERE status = 'active'
UPSERTON DUPLICATE KEYON CONFLICT
ExtensionsLimitedPostGIS, TimescaleDB, pgvector
Data integrityLooseStrict by default

Consequences

Migration Path

  1. New apps use PostgreSQL from day one ✅
  2. Case Portal migrates when there's bandwidth
  3. Moodle stays on MySQL forever (can't change)

ADR-003: Centralized .env at /var/www/private/

StatusAccepted
Date2025
AffectsAll PHP applications

Context

Multiple applications need database credentials, API keys, and secrets. Each could have its own .env file, or they could share one.

Decision

Single .env file at /var/www/private/.env. All PHP apps load from this location.

Consequences


ADR-004: TimescaleDB for Trading Data

StatusAccepted
Date2025
AffectsTradeCraft

Context

TradeCraft stores stock/crypto candles: 100+ symbols × 2 years × 5-minute intervals = 100M+ rows. Queries need to filter by symbol and time range, then aggregate.

Decision

Use TimescaleDB (PostgreSQL extension) instead of MySQL for candle storage.

Why TimescaleDB?

Note: TimescaleDB runs on the production TradeCraft server (port 5433), not this sandbox.

Consequences


ADR-005: Keycloak for Centralized SSO

StatusAccepted
Date2025
AffectsAll BENED applications

Context

Multiple apps need user authentication. Options: build auth into each app, or use a central identity provider.

Decision

Deploy Keycloak at auth.bened.works. All apps authenticate via OIDC.

Consequences


ADR-006: Backblaze B2 + Cloudflare for File Storage

StatusAccepted
Date2025
AffectsCase Portal, any file uploads

Context

Evidence files, user uploads, and static assets need to be stored somewhere. Options: local disk, AWS S3, or alternatives.

Decision

Use Backblaze B2 for storage with Cloudflare CDN in front for caching and fast delivery.

Why Not S3?

Consequences


ADR-007: Docker for Next.js Only

StatusAccepted
Date2025
AffectsDeployment strategy

Context

Docker provides isolation and reproducibility but adds complexity. Some apps benefit more than others.

Decision

Run PHP apps directly on the VPS. Run Next.js apps in Docker containers.

Rationale