Feature-Prinzipien
Alles ist ein Feature
Auth, Tenant, User, Audit, Search, Jobs, Config, Custom Fields — alles sind Features. Es gibt keine “magische” Core-Logik neben dem Feature-System. Der Core liefert nur die Infrastruktur (Engine, Pipeline, Dispatcher, DB, Search-Adapter, SSE), nicht Geschaefts-Semantik.
Folgen:
- Neue Funktionalitaet wird immer als Feature gebaut, nie als Sonderfall im Core.
- Wenn etwas im Core zu landen scheint, ist das ein Signal: fehlt ein Extension-Point?
- Core Features (
packages/framework/src/features/) folgen derselbendefineFeature()-API wie App Features. Der einzige Unterschied ist ihr Speicherort und dass andere Features sie viarequires()einbinden koennen.
Features sind maximal entkoppelt
Ein Feature kennt idealerweise nur sich selbst. Abhaengigkeiten werden explizit und sparsam deklariert.
Werkzeuge zur Entkopplung:
| Mittel | Wofuer |
|---|---|
r.requires("feature") | Harte Abhaengigkeit — Boot schlaegt fehl wenn fehlend |
r.optionalRequires("feature") | Weiche Abhaengigkeit — Integration nur wenn geladen |
r.extendsRegistrar() | Extension-Point fuer andere Features |
Events (r.defineEvent(), Listener) | Cross-Feature-Kommunikation ohne Import |
Registrar-Extensions (r.customFields(), r.tags(), …) | Opt-in fuer Querschnittsfunktionen |
Nicht erlaubt:
- Direkte Imports aus anderen Features (jenseits definierter Extension-APIs)
- “Weiss-Features” im Core, die Feature-Namen kennen
- Hardcoded Spezialfaelle fuer bestimmte Entities
Feature an/aus darf nichts kaputt machen
Jedes Feature muss so gebaut sein, dass das Entfernen es aus der Feature-Liste die uebrigen Features nicht bricht. Das heisst konkret:
- Keine
ALTER TABLEauf fremden Entities (eigene Tabellen sind ok) - Keine globalen Mutationen an Core-Verhalten beim Laden
optionalRequires+ defensiveif (registry.has(...))-Checks- Saubere Cleanup-Hooks fuer eigene Daten
Wann Framework, wann Core-Feature, wann App-Feature
Entscheidungsregel bei neuem Plan:
Framework-Infrastruktur
Code der nicht via defineFeature() ausgedrueckt werden kann und immer laeuft — unabhaengig davon welche Features geladen sind.
Kriterien (eines reicht):
- Wirkt auf jeden Request, nicht nur auf Features die opt-in sagen
- Aendert Kern-Pipeline (Engine, Dispatcher, Lifecycle, DB-Layer, Event-Transport)
- Andere Features koennen nicht korrekt arbeiten wenn es fehlt — aber es ist kein Feature zum “required”, sondern Baustein
Beispiele: Engine, Dispatcher, Registry, Tenant-DB-Context, Event-Transport (Outbox), Transactions, Lifecycle-Pipeline.
Wohnt in packages/framework/src/engine/, pipeline/, db/, api/, etc. — nie in features/.
Core-Feature
Normales Feature via defineFeature(), das jede Kumiko-Deployment typischerweise braucht.
Kriterien:
- Per
defineFeature()ausdrueckbar (Entities, Handler, Hooks, Events) - Von vielen App-Features als Abhaengigkeit genutzt (Auth, Config, Tenant, Secrets, Jobs, Files)
- Bleibt entfernbar — Apps die es nicht brauchen schalten es ab
- Compliance- oder Sicherheits-Basis, die man nicht jeder App einzeln aufdraengen will
Beispiele: core-auth, core-config, core-tenant, core-files, core-jobs, core-secrets, core-rate-limiting, core-tenant-export.
Wohnt in packages/framework/src/features/. Nutzt die gleiche defineFeature()-API wie App-Features.
App-Feature
Feature der konkreten App — Geschaeftslogik des Kunden.
Beispiele: orders, invoicing, fleet, mietnomade.
Wohnt ausserhalb vom Framework-Package (features/, packages/bundled-features/, oder pro App-Repo).
Faustregeln
- Wenn etwas aussieht wie Framework-Infra, aber per
defineFeature()ausdrueckbar waere → wahrscheinlich Core-Feature. Framework-Infra ist der letzte Ausweg, nicht der erste. - Wenn ein Core-Feature nur von einer bestimmten App-Domain gebraucht wird → wahrscheinlich App-Feature, nicht Core.
- Wenn Framework-Infra sich nach Geschaeftsregeln anfuehlt (Rollen, Workflows, Limits) → ist’s zu hoch im Stack, runter ins Feature.
Warum
Dieses Framework soll fuer viele unterschiedliche Apps mit unterschiedlichen Feature-Kombinationen tragen. Kopplung macht Features unauswahlbar. Extension-Points statt Core-Aenderungen halten das System offen.