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-focused sites with server rendering. Use Next.js (TypeScript) for interactive marketplace 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              ← PRIMARY FOR ALL APPS
├── Main site           ← bened.works
├── Rental platform     ← trailers.bened.works
├── Support system      ← support.bened.works
├── Praxis              ← This site
└── Future apps

TimescaleDB             ← TIME-SERIES SPECIALIST
├── TradeCraft          ← Stock/crypto candles (100M+ rows)
└── Research Archive    ← Document corpus metadata

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. All active apps now on PostgreSQL/TimescaleDB ✅
  3. Legacy MySQL instances phased out

ADR-003: Centralized Secret Management

StatusAccepted
Date2025
AffectsAll applications

Context

Multiple applications need database credentials, API keys, and secrets. Each could manage its own secrets independently, or they could follow a centralized pattern.

Decision

Centralize secrets in a single secure location on the server. All apps load from this one source of truth. Never committed to version control.

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 shares the same SQL dialect as PostgreSQL — same queries, same tools, just optimized for time-series workloads.

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
AffectsResearch Archive, Support, Trailers — any app with file uploads

Context

User uploads, document archives, 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