Doku: Anforderungen + ADRs (Symfony, htmx, SQLite, HA-Consumer)

This commit is contained in:
2026-09-13 10:25:53 +02:00
parent 4049becb4b
commit f1515d301c
8 changed files with 270 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
# 001 – Backend auf Symfony 7 (PHP)
**Status:** accepted
**Kontext.** Das Werkzeug braucht eine kleine CRUD-API, die später auch von
Home-Assistant und (ggf.) von mobilen Clients konsumiert wird. Der
Projektverantwortliche kennt Symfony und möchte die Logik im Backend tragen
(serverseitig), nicht im Frontend.
**Entscheidung.** Symfony 7 (PHP 8.3) als Backend-Framework. Eigene schlanke
Controller mit JSON-Antworten, **kein ApiPlatform** (zu viel Magie für dieses
Anwendungsfeld). Repository-Schicht via Doctrine.
**Konsequenzen.**
- + Konvention/Ökosystem des Betreibers, kein zweites Ökosystem.
- + Doctrine + Symfony-Forms decken CRUD, Validierung und Security-Grundbaukästen ab.
- + Einfach zu hosten: FrankenPHP (oder php-fpm + nginx) in einem Container, keine zweite Laufzeit.
- − Keine SPA-Features, kein React-Native-Ökosystem – passt aber zur Anordnung.
- − Symfony hat eine Learning-Curve für neue Team-Mitglieder; im aktuellen Kontext kein Problem.
+22
View File
@@ -0,0 +1,22 @@
# 002 – Frontend: serverseitig + htmx (kein React)
**Status:** accepted
**Kontext.** Die App ist eine CRUD-Liste mit Formularen, Drag-&-Drop-Reihenfolge
und einem kurzen „Inspiration"-Panel. Kein Multiplayer, kein Offline-First, keine
komplexe Client-State. Der Betrieb soll so wenig bewegliche Teile wie möglich
haben (kein `npm install` im Runtime, keine Build-Pipeline).
**Entscheidung.** Serverseitiges Rendern via Symfony (Twig) + **htmx** (~15 kB)
für fragmentbasierte Updates + **SortableJS** (~20 kB) für Drag-&-Drop.
Kein Node-Toolchain im Deployment, keine Client-State-Verwaltung.
**Konsequenzen.**
- + Sehr kleine Client-Oberfläche, kein Build-Step, keine Versionsschlachten.
- + Business-Logik bleibt in Symfony/PHP – eine einzige Quelle der Wahrheit.
- + Deploy = ein PHP-Container + statische Dateien (htmx, SortableJS).
- + Fragmentbasierte Updates sind für Listen/CHECKBOXen fast unsichtbar.
- − Keine echte Offline-First / kein Optimistic-Update über mehrere Entities –
reicht hier (nur Drag-&-Drop-Reihenfolge + Checkbox; beide sind schnell genug).
- − Bei starkem Wachstum an interaktiver UI kann später ein SPA-Client nötig werden
(API ist JSON, bleibt kompatibel).
+20
View File
@@ -0,0 +1,20 @@
# 003 – Single-User, keine Authentifizierung
**Status:** accepted
**Kontext.** Das Werkzeug wird im privaten Homelab von **einer** Person (bzw.
Familie via LAN) genutzt. Ein Login-Flow wird zur Barriere, nicht zum Feature.
Später soll Home-Assistant die API konsumieren, und dort ist es einfacher,
einen Shared-Token zu haben statt ein OIDC-Realm zu betreiben.
**Entscheidung.** Im MVP **kein Login**. Die API ist hinter dem bestehenden
Traefik-Routing `essen.ljh31.de` (ACME/TLS) erreichbar; wer die Domain kennt,
kann aufrufen. Optional (Phase 2): **Shared-API-Token** per Header
(`X-Api-Token`) für externe Clients wie HA.
**Konsequenzen.**
- + Einfachster möglicher Pfad, kein Auth-State, kein JWT.
- + HA-Integration ist trivial (einfacher Token statt OIDC).
- − Kein Benutzer-Abgrenzen, keine Rechte. Im aktuellen Kontext kein Risiko
(LAN + eigene Domain).
- − Wenn es irgendwann öffentliches Hosting wird, ADR-003 ersetzen.
+23
View File
@@ -0,0 +1,23 @@
# 004 – Home-Assistant als Consumer, nicht als Host
**Status:** accepted
**Kontext.** Frage der Diskussion: „Wäre Home-Assistant möglich?". HA ist eine
**View-/Automatisierungsplattform**, kein Logik-Backend und keine
PHP/MySQL-Sprach-Umgebung. Es kann unsere Anwendung nicht „ausführen", aber den
API-Status **anzeigen** und **benachrichtigen**.
**Entscheidung.** Symfony-API bleibt die Source of Truth. Home-Assistant wird
**nur als Consumer** angebunden:
1. (Phase 2, optional) HA holt `GET /api/today` per `rest`-Integration →
Sensor „Heute auf dem Plan" (Zeichenkette).
2. (Phase 2, optional) HA-Notify-Benachrichtigung „Heute: Bolognese" per
Automation auf den Sensor-Change.
3. **Keine** Primary-UI in HA/Lovelace.
**Konsequenzen.**
- + HA-Integration bleibt ein dünnes Add-on, kein Muss.
- + Kein zweites Ökosystem, das die Logik dupliziert.
- + HA-Sensor ist trivial zu erzeugen (ein GET + ein Template).
- − Keine „Interaktions-Card" in HA (z. B. kein Abhaken aus HA) – bewusst,
UI bleibt in der htmx-App.
+21
View File
@@ -0,0 +1,21 @@
# 005 – SQLite als Datenbank (vorerst)
**Status:** accepted
**Kontext.** Einzelne Instanz, ein Nutzer, kleine Datengröße im KB/MB-Bereich
(ein Gericht-Eintrag ~ 10 Zeilen + Historik). Backup-Pipeline (borgbackup)
deckt bereits das gesamte Homelab ab. Es gibt keinen Bedarf an HA (High
Availability), Multi-Node oder komplexen Transaktionen.
**Entscheidung.** **SQLite** via Doctrine (Symfony unterstützt SQLite natively
seit 4.x mit guter Migrations-Schicht). Ein Dateivolumen (`app.db`) im Docker-Stack.
**Konsequenzen.**
- + Extrem einfach: keine DB-Instanz, kein User/Passwort, kein Netzwerk.
- + Snapshot-Backup via `cp app.db` oder Borg direkt.
- + Kein `docker-compose`-DB-Service (einer weniger Container).
- + SQLite hat eine saubere WAL-Modus-API, die für die Zugriffsrate ausreicht.
- − Keine Remote-Replikation, kein Replikations-Setup (hier erwünscht).
- − Falls wir später mehrere Instanzen brauchen, wäre Postgres die Alternative –
aber das ist nicht aktuell (viele Homelab-Instanzen nutzen ja Postgres,
wenn sie es brauchen).
+31
View File
@@ -0,0 +1,31 @@
# 006 – Inspiration-Algorithmus: gewichteter Mix (offene Gewichtung)
**Status:** proposed (Gewichtung offen, Algorithmus fix)
**Kontext.** „Inspiration" schlägt **jeweils 5** Gerichte aus dem Archiv vor,
neue 5 auf Button. Der Betreiber möchte einen **Mix aus drei Kriterien**:
lange nicht gekocht, seltene (wenige Auftritte) und beliebte (häufig gekocht).
Genauere Gewichtung und die Definition von „seltene" / „beliebte" werden bei
der Implementierung festgelegt.
**Entscheidung (Prinzip).** Score-Funktion `score(g)` mit drei Termen:
1. **Staleness**: `now - max(gegessen_am)` (normiert an die älteste
`gegessen_am`) → gewichtet mit `w_slow` (>0, primär, z. B. 0.5).
2. **Rarity**: `1 / (auftritt_count + 1)` (auftritt = Anzahl aller Einträge
mit gleichem `name`) → gewichtet mit `w_rare` (sekundär, z. B. 0.3).
3. **Popularity**: `auftritt_count / max(auftritt_count)` → gewichtet mit
`w_hot` (tertiär, z. B. 0.2).
`score = w_slow*stale + w_rare*rare + w_hot*hot`, Sortierung absteigend.
`w_slow + w_rare + w_hot = 1.0`; konkrete Werte in `config/inspirations.yaml`
und per ADR-006-Bis (Patch) fixieren, wenn die App läuft.
**Konsequenzen.**
- + Alle drei Nutzerintentionen (Alte, Seltene, Beliebte) sind vertreten.
- + Gewichte prozentual änderbar, ohne Code.
- − „Beliebte" ist kontraintuitiv, wenn der Nutzer genau das **nicht**
möchte (z. B. „mööchte etwas Neues"). Dann `w_hot=0`.
- Offene Fragen:
- [ ] Gewichte `w_slow/w_rare/w_hot` (Default: 0.5/0.3/0.2?)
- [ ] „Auftritt" zählt **Alle Zeit** oder **letzte N Tage**? (Vorschlag: alle Zeit)
- [ ] Deduping gegen die letzte 5er-Ladung? (Vorschlag: ja, Session-basiert)
+23
View File
@@ -0,0 +1,23 @@
# Architecture Decision Records (ADRs)
Kurze, nicht-änderbare Aufzeichnungen der wichtigsten Entscheidungen.
Format: ADR-Mikrometer-Muster (Kontext, Entscheidung, Konsequenzen).
Nummerierung: `NNN-Name-slug.md`, fortlaufend, Lücken zulässig.
Wenn sich etwas ändert: **neue** ADR, alte als `superseded` markieren.
## Status
- `accepted` – gilt aktuell
- `proposed` – noch nicht endgültig
- `superseded` – durch neuere ADR ersetzt (→ verlinken)
- `rejected` – bewusst verworfen
## Index
| Nr | Titel | Status |
|----|-------|--------|
| [001](001-backend-symfony.md) | Backend auf Symfony 7 (PHP) | accepted |
| [002](002-frontend-htmx.md) | Frontend: serverseitig + htmx (kein React) | accepted |
| [003](003-single-user-mit-token.md) | Single-User, keine Authentifizierung (+ optionales API-Token) | accepted |
| [004](004-ha-consumer.md) | Home-Assistant als Consumer, nicht als Host | accepted |
| [005](005-sqlite.md) | SQLite als Datenbank (vorerst) | accepted |
| [006](006-inspiration-algorithmus.md) | Inspiration-Algorithmus: gewichteter Mix | proposed |