Architektur¶
Funktionsweise im Detail (fuer Entwickler)¶
Das Problem, das wir loesen¶
Die Verwaltungs-App ist kein 1-Mann-1-Feature-Tool. Sie muss:
- Mehrere Apps bedienen — FEGH-Dokumentation (mobile) und FEGH-Verwaltung (Desktop) teilen ~80 % der Datenmodelle.
- Mehrere Cloud-Provider unterstuetzen — HiDrive, Nextcloud, ownCloud, generisches WebDAV.
- Offline-faehig sein — die Doku-App muss ohne Netz arbeiten (Schicht im Keller ohne WLAN), Sync nachholen wenn verfuegbar.
- Kryptographisch stark getrennte Teams — Team A darf Team B's Daten nicht einmal entschluesseln koennen.
- Buchhalterisch korrekt sein — GoBD verbietet nachtraegliche Aenderungen, Rechnungen sind unveraenderlich.
Diese Anforderungen treiben die Architektur zu einer modularen Shared-Package-Struktur mit scharfer Trennung zwischen App- spezifischer Logik und wiederverwendbarem Kern.
Konkretes Szenario: Ein Klient-Record wird erstellt¶
So laeuft es durch die Schichten:
- UI (
lib/features/clients/client_form_dialog.dart) sammelt das Formular-Input. - Provider (
lib/providers/client_provider.dart) ruftClientService.create(client)auf. - Service (in der Doku-App:
lib/services/client_service.dart) validiert, erzeugt UUID, legt die Instanz alsClientausfegh_corean. - Local Storage: JSON-serialisierter Record wird in
SharedPreferences(Schluesselclients_v1) gespeichert. - Crypto Layer (
fegh_crypto): Falls Cloud-Sync aktiv, wird der Record mit dem Team-Key verschluesselt (AES-256-GCM mit pro-Record-Nonce). - Cloud-Adapter (
fegh_cloud,GenericWebdavAdapteroderHidriveAdapter):PUTzum Pfad ausFeghPaths.teamClientRecord(...). - Audit-Log (
fegh_compliance): Eventclient.createmit ClientId + EmployeeId.
Jede Schicht hat eine Verantwortung und ist isoliert testbar. Beim Wechsel des Cloud-Providers wird nur der Adapter getauscht — der Service und die UI bleiben unberuehrt.
Die Schichten als Diagramm¶
flowchart TB
UI[UI-Widgets<br/>lib/features/*/]
Prov[Riverpod-Provider<br/>lib/providers/]
Svc[Services<br/>lib/services/]
Core[fegh_core<br/>Client Employee Team Shift]
Cloud[fegh_cloud<br/>WebDAV-Adapter]
Crypto[fegh_crypto<br/>AES-256-GCM DEK MEK]
Comp[fegh_compliance<br/>Audit + SIEM]
PDF[fegh_pdf_kit]
Bill[fegh_billing<br/>XRechnung UBL]
Chat[fegh_chat<br/>Matrix]
Backup[fegh_backup]
Auth[fegh_auth_oidc<br/>OIDC Loopback]
UI --> Prov
Prov --> Svc
Svc --> Core
Svc --> Cloud
Svc --> Crypto
Svc --> Comp
Svc --> PDF
Svc --> Bill
Svc --> Chat
Svc --> Backup
Svc --> Auth
Bill --> Core
Backup --> Crypto
Die Schichten im Ueberblick¶
| Schicht | Ort | Aufgabe |
|---|---|---|
| UI-Komponenten | lib/features/<modul>/ |
Flutter-Widgets, Dialoge, Listen |
| Provider | lib/providers/ |
Riverpod-State fuer UI |
| Services | lib/services/ |
Business-Logik pro App |
| Models (gemeinsam) | fegh_core/ |
Entitaeten Client, Employee, Team, Shift |
| Cloud-Adapter | fegh_cloud/ |
WebDAV-Abstraktion mit 4 Adaptern |
| Kryptographie | fegh_crypto/ |
DEK/MEK/Team-Keys, AES-256-GCM |
| Compliance | fegh_compliance/ |
AuditLogger + SIEM-Exporter |
| PDF-Erzeugung | fegh_pdf_kit/ |
Design-Tokens, Layout-Bausteine |
| Rechnung | fegh_billing/ |
XRechnung UBL 2.1 |
| Chat | fegh_chat/ |
Matrix/Olm-Adapter |
| Backup | fegh_backup/ |
RecoveryService, BackupCodec |
| OIDC SSO | fegh_auth_oidc/ |
OAuth 2.0 + PKCE + RFC 8252 Loopback |
Warum Shared-Packages statt Monorepo?¶
Doku-App und Verwaltung haben unterschiedliche Nutzer-Workflows (mobile Schicht vs. stationaer Buero) und unterschiedliche Deployment-Targets (Mobile Stores vs. Desktop). Sie teilen aber die Datenmodelle, Crypto-Stack, Cloud-Layer.
Eine naive Monorepo-Loesung haette beide Apps in eine riesige Flutter-Code-Basis gezwungen, mit Dead Code fuer jeweils die andere Plattform. Shared-Packages trennen sauber:
fegh_corekann standalone gebaut und unit-getestet werden- Jede App pullt nur die Packages, die sie braucht
- Updates am Core brechen nie zwingend beide Apps gleichzeitig
- Testabdeckung ist pro Package messbar
Die kryptographische Schichtung (Tiefe fuer Security-Auditoren)¶
Login-Passwort (in-memory only)
|
v PBKDF2-HMAC-SHA256 (100.000 Iterationen, Salt aus flutter_secure_storage)
v
DEK (Data Encryption Key, 32 Byte, geraete-lokal)
|
v AES-256-GCM-Unwrap
v
MEK (Master Encryption Key, 32 Byte, org-weit)
|
v AES-256-GCM-Unwrap pro Team
v
Team-Keys (32 Byte pro Team)
|
v AES-256-GCM mit per-Record Nonce
v
Record (Client, Shift, Medication, etc.)
Keine Schicht kann ihre uebergeordnete entschluesseln — das Geraet kennt nur den DEK, ableiten kann es nur bei Login (PBKDF2 verlangt das Original-Passwort). Der MEK liegt nur vom DEK-wrapped auf dem Geraet.
Offline-Strategie¶
Die Doku-App laeuft in Wohngruppen ohne WLAN. Strategie:
- Lokale Schreiboperationen laufen sofort — kein Netz-Wait.
- Outgoing Queue (in
SharedPreferences) sammelt ausstehende Cloud-Uploads. - Sync-Scheduler versucht alle 5 Minuten oder beim App-Start die Queue zu leeren.
- Konflikte (zwei Geraete haben denselben Record geaendert):
der neuere
updatedAt-Timestamp gewinnt; der Verlierer wandert ins Audit-Log alssync.conflict.resolved.
Das ist Last-Write-Wins — simpel, aber fuer die meisten EGH-Szenarien ausreichend (zwei Mitarbeiter aendern selten denselben Record zeitgleich). Fuer BtM-Buchungen waere LWW zu wenig (Gabe wird nie ueberschrieben, sondern hinzugefuegt — append-only fuer medication_administrations_v1).
Stack¶
- Flutter 3.9.2 / Dart 3 — Cross-platform Desktop-App (Windows/macOS/Linux)
- State-Management: Riverpod 2.x — Provider und StateNotifier, einige FutureProvider-Families
- Persistenz:
SharedPreferencesfuer Nutzdaten,flutter_secure_storagefuer Cloud-Credentials und MEK - Cloud: WebDAV via
webdav_client-Paket, abstrahiert durch den Shared-Adapterfegh_cloud - Kryptographie: AES-256-GCM mit DEK-Wrapping (PBKDF2-MEK aus Sync-Passphrase), via
fegh_crypto
Navigation¶
Die Haupt-Shell ist AppShell (lib/widgets/layouts/app_shell.dart). Sie kombiniert:
- NavigationRail links (ab 1024 px extended, sonst compact)
- AppBar oben mit Suche-Placeholder, NotificationBell und Rollen-Chip
- Content-Area rechts — haelt den Builder des aktiven
NavEntry
Alle Screens sind in einer zentralen NavEntry-Registry deklariert (lib/navigation/nav_registry.dart), jeder mit einem visibleFor(UserRole)-Praedikat. Das macht neue Features zum One-Liner und die Rollen-Sichtbarkeit deklarativ pruefbar.
Sektionen (Gruppen der Rail):
- Arbeit — Meine Arbeit, Medikation, Kassenbuch (fuer Staff)
- Klienten — Liste, ICF/TIB, Medikationsplaene (admin)
- Personal — Dashboard, Mitarbeiter, Teams, Urlaub, Kapazitaet, Wohnraum
- Planung — Dienstplan, Zeitnachweise
- Finanzen — Rechnungen, Berichte
- System — Einstellungen, Backup, Admin-Konsole
Shared-Packages¶
Beide Apps (Doku und Verwaltung) teilen sich sechs lokale Pakete aus C:/fegh-shared/, eingebunden per path:-Dependency (kein Pub-Publish):
| Paket | Zweck |
|---|---|
fegh_crypto |
Wire-Format, AES-256-GCM + DEK-Wrapping, Provisioning-Token |
fegh_cloud |
WebDAV-Adapter fuer HiDrive, Nextcloud, ownCloud, Generic |
fegh_billing |
Rechnungs-Modelle, XRechnung UBL 2.1 |
fegh_compliance |
Audit-Logger (JSON-Lines, gemeinsam fuer beide Apps) |
fegh_pdf_kit |
Design-Tokens, Header/Footer, KPI-Row, Standard-Tabellen, Preview |
fegh_backup |
Recovery-Phrase, BackupCodec (AES-256-GCM+PBKDF2), Envelope |
Details: Shared-Packages.
Datenfluss (vereinfacht)¶
UI-Widget
│
▼ ref.watch / ref.read
Riverpod-Provider (FutureProvider, StateNotifier)
│
▼
Service (MedicationService, KassenbuchService, ...)
│
├──► SharedPreferences (lokal)
│
├──► AuditLogger (fegh_compliance, JSON-Lines)
│
└──► CloudSyncService ──► CloudAdapter (fegh_cloud) ──► WebDAV
│
▼
FeghCrypto.encryptRecord
│
▼
verschluesselte Cloud-Datei
Jede Service-Aktion, die schreibt, ruft zusaetzlich AuditLogger.log(action, context: …). Das macht das Audit-Verhalten zentral und nicht am UI festgezurrt.
Rollen¶
PolicyService (in lib/services/policy_service.dart) liest die Rolle aus der roles.json (via RolesPolicyService) und bietet feingranulare Methoden: canCreateClient(), canEditClient(), canManageTeams(), canRebuildIndexes(), canViewDocumentation(). Die Navigation und die Screens fragen diese Methoden ab.
Cloud-Sync¶
Drei Abstraktionsebenen:
CloudAdapter(abstract, infegh_cloud) — provider-agnostisches Interface (list, get, put, mkcol, delete).- Konkreter Adapter (HidriveAdapter, NextcloudAdapter, ...) — Provider-spezifische Quirks (z. B. STRATO HiDrive erwartet bei MKCOL keinen Content-Type).
CloudSyncService(in der Verwaltung) — domaenen-spezifische Logik fuer "saveRecord", "loadRecord", "syncFromRemote".
Der Sync-Service arbeitet mit EncryptedRecord aus fegh_crypto: Plaintext JSON wird mit einem DEK (Data Encryption Key, pro Record frisch) verschluesselt; der DEK wird mit dem MEK (Master Encryption Key) gewrappt. So kann die Organisation ihren MEK rotieren, ohne alle Records neu zu verschluesseln — nur die Wraps werden ausgetauscht.