9 Architekturentscheidungen
Architecture Decision Records (ADRs) dokumentieren die wichtigen Entscheidungen, die die Architektur des e-gov-framework prägen. Jeder ADR hält Kontext, getroffene Entscheidung, betrachtete Alternativen und Konsequenzen fest.
9.1 ADR-001: Framework als eingebettete Java-Bibliothek, nicht als eigenständiger Service
Status: Akzeptiert
Datum: 2023 (Projektgründung)
9.1.1 Kontext
E-Government-Fachapplikationen verschiedener Schweizer Behörden teilen denselben Bedarf an Querschnittsfunktionen: row-based Security, Audit-Trail, Stammdaten, Persistence-Abstraktion. Die Frage war, wie diese Funktionen bereitgestellt werden sollen.
Alternativen:
| Kriterium | Bibliothek (JAR) | Sidecar / Microservice | SaaS-Plattform |
|---|---|---|---|
| Deploymentkomplexität | Gering (eine JVM) | Hoch (zusätzlicher Prozess) | Abhängig von Anbieter |
| Latenz Permission-Check | Keine (in-process) | Netzwerk-Roundtrip (~ms) | Netzwerk-Roundtrip |
| Hoheit über Infrastruktur | Vollständig bei Behörde | Teilweise | Keine |
| Compliance / Data Residency | Einfach (Daten verlassen nie die JVM) | Möglich | Kritisch |
| Versionierung je Applikation | Unabhängig via BOM | Gemeinsamer Release-Zyklus | Vorgeschrieben |
9.1.2 Entscheidung
Das Framework wird als Maven-Bibliothek distribuiert. Jede consuming App zieht das Framework über das BOM als Abhängigkeit und betreibt es in derselben JVM.
9.1.3 Konsequenzen
Positiv: - Kein Netzwerk-Overhead bei Permission-Checks (kritisch für Row-based Security) - Jede Behörde kann unabhängig upgraden oder pinnen - Keine zusätzliche Laufzeitinfrastruktur notwendig - Vollständige Datenisolation pro Behörde garantiert
Negativ: - Framework-Logik läuft in derselben JVM wie Applikationslogik — Fehler im Framework können die gesamte Applikation betreffen - Consuming Apps tragen die Last des Upgrades (pom.xml-Änderung, Migrationstests) - Kein zentrales Monitoring über alle Deployments hinweg möglich
9.2 ADR-002: Native JDBC für die Query-Schicht statt JPA Criteria API
Status: Akzeptiert
Datum: 2023
9.2.1 Kontext
Das Framework muss jede SELECT-Abfrage automatisch um rollenbasierte WHERE-Klauseln erweitern (Row-based Security) und eine eigene Filter-DSL aus REST-Parametern umsetzen. JPA/Hibernate übernimmt das Entity-Mapping. Für die Query-Schicht standen zwei Ansätze zur Auswahl.
Alternativen:
| Kriterium | JPA Criteria API | Native JDBC / SQL |
|---|---|---|
| Kontrolle über WHERE-Klauseln | Eingeschränkt (Hibernate-Abstraktion) | Vollständig |
| Lesbarkeit komplexer Queries | Schlecht (Boilerplate) | Gut (klares SQL) |
| Multi-DB-Unterstützung | Via Hibernate-Dialekte | Manuell je Dialekt |
| Performance | Overhead durch Metadaten-Lookups | Minimal |
| Permission-Filter injizieren | Schwierig (kein direkter SQL-Zugriff) | Trivial (String-Konkatenation) |
9.2.2 Entscheidung
Die BaseRepositoryImpl und der QueryBuilder
verwenden natives JDBC für alle Leseabfragen. JPA/Hibernate bleibt für
Entity-Mapping und Schreiboperationen (saveWithTx)
zuständig.
9.2.3 Konsequenzen
Positiv: - PermissionBuilder kann
WHERE-Klauseln direkt als SQL-Fragment injizieren - Filter-DSL lässt
sich sauber auf SQL abbilden ohne Criteria-API-Umweg - Queries sind
lesbar und debuggbar - Performance-Ziel (<500 ms für 10 000
Entitäten) erreichbar
Negativ: - Zwei Persistenzmechanismen (JPA + JDBC)
müssen koordiniert werden - SQL-Dialekte (PostgreSQL vs. SQL Server)
müssen in DatabaseConfigurationService manuell behandelt
werden - BaseRepositoryImpl ist deswegen komplex
9.3 ADR-003: Command-Pattern für alle Schreiboperationen
Status: Akzeptiert
Datum: 2023
9.3.1 Kontext
Behörden-Applikationen unterliegen strikten Audit-Anforderungen: Jede fachliche Aktion muss nachvollziehbar sein (wer hat was wann getan). Ausserdem müssen Schreiboperationen validierbar und rollback-fähig sein. Es wurden zwei Modelle verglichen.
Alternativen:
| Kriterium | Command-Pattern (persistiert) | Direkte Service-Calls |
|---|---|---|
| Audit-Trail | Vollständig (jede Aktion als Datenbankzeile) | Nur via separates Log |
| Validierung vor/nach Ausführung | Steuerbar via ExecutionPlan | Inline, nicht standardisiert |
| Nachvollziehbarkeit für Behörde | Hoch (egov_cmd_command-Tabelle abfragbar) | Gering |
| Komplexität für consuming Apps | Mittel (BaseExecutor, DomainCommandFactory) | Gering |
| Distributed Execution | Möglich (RemoteCommandExecutor) | Nicht vorgesehen |
9.3.2 Entscheidung
Jede fachliche Schreiboperation wird als
Command-JPA-Entity angelegt und mit einem Status-Lifecycle
(INITIALIZED → VALIDATED → EXECUTED oder
HARD_VALIDATION_FAILED) in der Tabelle
egov_cmd_command persistiert.
BaseExecutor-Implementierungen orchestrieren den
Ablauf.
9.3.3 Konsequenzen
Positiv: - Lückenloser Audit-Trail ohne
Zusatzkonfiguration für consuming Apps - Validerungsregeln (RuleSet)
werden deklarativ via Annotationen definiert -
ExecutionPlan ermöglicht flexible Validierungsstrategien
pro Use-Case - Erweiterbar auf verteilte Ausführung über mehrere
Instanzen (ZooKeeper)
Negativ: - Jede Schreiboperation erzeugt einen
zusätzlichen Datenbank-Insert (Command-Tabelle) - Consuming Apps müssen
DomainCommandFactory und BaseExecutor ableiten
und kennen - Es besteht so das Risiko, dass das Framework falsch
verwendet wird.
9.4 ADR-004: Row-based Security als opt-out statt opt-in
Status: Akzeptiert
Datum: 2023
9.4.1 Kontext
Datenpannen in Behörden entstehen oft durch vergessene oder inkorrekte Berechtigungsprüfungen in Applikationscode. Das Framework sollte sicherstellen, dass kein Entwickler einer consuming App versehentlich ungeschützte Abfragen absetzt.
Alternativen:
| Ansatz | Beschreibung | Risiko |
|---|---|---|
| Opt-in (explizit) | Entwickler ruft PermissionBuilder manuell auf |
Vergessen möglich → Datenpanne |
| Opt-out (Default) | QueryBuilder fügt immer Permission-Filter ein;
explizites Abschalten nötig |
Kein versehentliches Bypass |
| Applikationsschicht | Permission-Prüfung liegt komplett in der consuming App | Fehleranfällig, kein Framework-Beitrag |
9.4.2 Entscheidung
Der QueryBuilder bindet den
PermissionBuilder immer automatisch ein. Jede
BaseRepositoryImpl.find()-Abfrage ist per Default
row-level-gefiltert. Ein expliziter Bypass ist nur für
interne/Admin-Operationen über den Admin-Modus
(egov.admin.is.activated) vorgesehen.
9.4.3 Konsequenzen
Positiv: - Ein Sachbearbeiter sieht niemals
Datensätze ohne Berechtigung — auch nicht durch Programmierfehler in der
consuming App - Sicherheitsverantwortliche können die Filterlogik
zentral in egov_iam_permission_definition administrieren
ohne App-Deployments
Negativ: - Höherer Aufwand beim initialen Setup: Permission-Definitionen müssen vollständig in der IAM-Tabelle gepflegt sein, bevor Daten sichtbar sind - Interne Batch-Jobs oder Admin-Operationen müssen den Admin-Modus explizit aktivieren
9.5 ADR-005: Vorgelagertes IAM — kein eigener Authentifizierungsstack
Status: Akzeptiert
Datum: 2023
9.5.1 Kontext
Schweizer Behörden betreiben eigene Identity-Management-Systeme (Azure AD, LDAP, eigene IdPs). Ein Framework, das selbst Authentifizierung implementiert, würde entweder diese Systeme duplizieren oder Integrationsaufwand in jeder Applikation erzwingen.
Alternativen:
| Ansatz | Beschreibung |
|---|---|
| Eigener JWT-Stack | Framework validiert Tokens selbst, kennt Claims-Schema |
| Spring Security Integration | Framework konfiguriert Spring Security mit konfigurierbarem IdP |
| Vorgelagertes IAM (gewählt) | F5-Firewall setzt EGOV_TOKEN; Framework vertraut dem
Cookie |
9.5.2 Entscheidung
Das Framework führt keine Authentifizierung durch. Es vertraut
darauf, dass der vorgelagerte Stack (Azure AD → F5 →
EGOV_TOKEN-Cookie) eine authentifizierte
userId liefert. Das UserIdProvider-Interface
ist der einzige Integrationspunkt: Consuming Apps implementieren es
einmalig für ihren spezifischen Cookie/Header.
9.5.3 Konsequenzen
Positiv: - Framework ist unabhängig vom konkreten
Identity-Provider der Behörde - Keine doppelte Token-Validierungslogik
in Framework und vorgelagertem Stack - Consuming Apps können
UserIdProvider auf beliebige Auth-Quellen (JWT-Claim,
Header, Session) mappen
Negativ: - Framework kann keine Claims-basierten
Attribute (Abteilung, Berechtigungsgruppe) direkt aus dem Token lesen —
consuming App muss das ggf. in UserIdProvider auflösen -
Framework vertraut blind dem Cookie-Inhalt; korrekte TLS-Terminierung
und Cookie-Sicherheit liegen vollständig beim vorgelagerten Stack
9.6 ADR-006: Unterstützung von PostgreSQL und SQL Server parallel
Status: Akzeptiert
Datum: 2023
9.6.1 Kontext
Verschiedene Behörden haben unterschiedliche Infrastrukturvorgaben: Einige betreiben PostgreSQL-Cluster (bevorzugt, Open Source, günstiger), andere sind durch Rahmenverträge oder IT-Governance an Microsoft SQL Server gebunden. Ein Framework, das nur eine Datenbank unterstützt, würde Behörden mit SQL-Server-Vorgaben ausschliessen.
Alternativen:
| Kriterium | Nur PostgreSQL | Nur SQL Server | Dual-DB (gewählt) | Polyglot (+ H2 / MySQL) |
|---|---|---|---|---|
| Abdeckung Behördenanforderungen | Unvollständig (MSSQL-Kunden ausgeschlossen) | Unvollständig (PG-Kunden ausgeschlossen) | Vollständig (alle aktuellen Kunden) | Überabdeckung, unnötige Komplexität |
| Implementierungsaufwand | Gering | Gering | Mittel (2 Dialekte) | Hoch (n Dialekte) |
| CI-Aufwand (Testcontainers) | 1 DB-Typ | 1 DB-Typ | 2 DB-Typen | n DB-Typen |
| Wartbarkeit Dialekt-Code | Hoch | Hoch | Mittel | Gering |
9.6.2 Entscheidung
Das Framework unterstützt beide Datenbanktypen vollständig.
DatabaseConfigurationService liest
egov.persistence.database.type und konfiguriert: -
Hibernate-Dialekt - Spalten-Namensschema (CAMEL_CASE /
SNAKE_CASE) - Datenbank-spezifische SQL-Fragmente im
QueryBuilder - Flyway-Skripte pro Datenbank-Typ
9.6.3 Konsequenzen
Positiv: - Framework ist für alle Behörden einsetzbar, unabhängig von ihrer DB-Infrastruktur - Wechsel von PostgreSQL zu SQL Server (oder umgekehrt) erfordert nur Property-Änderung
Negativ: - Jede Änderung am Query-Layer muss gegen
beide Datenbanken getestet werden - QueryBuilder und
Flyway-Skripte müssen Dialekt-Unterschiede kennen und behandeln -
Erhöhter CI-Aufwand: Integrationstests laufen gegen beide Datenbanken
via Testcontainers
9.7 ADR-007: Spring AI + Azure OpenAI für LLM-gestützte Regelgenerierung
Status: Akzeptiert (experimentell)
Datum: 2024
9.7.1 Kontext
Die Pflege von Validierungsregeln in egov_iam_rule_set
ist aufwändig und erfordert SQL-/Framework-Kenntnisse. Die Idee: Admins
beschreiben Regeln in natürlicher Sprache, das System generiert die
technische Regeldarstellung automatisch.
Für die LLM-Integration standen zwei Optionen:
| Kriterium | Spring AI (Abstraktionsschicht) | Direktes OpenAI SDK |
|---|---|---|
| LLM-Austauschbarkeit | Hoch (Providerwechsel via Config) | Gering (Vendor-Lock-in) |
| Reife | Milestone-Release (1.0.0-M6) | Stabil (GA) |
| Integration in Spring Boot | Native | Manuell |
| Zukünftige On-Premise-Option | Möglich (lokales LLM via Spring AI) | Schwierig |
9.7.2 Entscheidung
Das Modul e-gov-ai-rule-generation verwendet Spring AI
als Abstraktionsschicht und Azure OpenAI (gpt-4o) als Backend. Der
Prompt enthält die Grammatik der erlaubten Validierungsfunktionen;
RuleTranslator konvertiert die LLM-Antwort in das
Framework-Regelformat.
Das Modul wird bis zum GA-Release von Spring AI als experimentell gekennzeichnet.
9.7.3 Konsequenzen
Positiv: - Admins können Regeln aus Freitext generieren ohne SQL-Kenntnisse - Spring AI ermöglicht späteren Wechsel auf On-Premise-LLM ohne Code-Änderung
Negativ: - Breaking Changes zwischen Milestones
möglich (→ Risiko R-4) - Azure OpenAI-Abhängigkeit erfordert
Netzwerkzugang und API-Key-Verwaltung - Modul ist bisher ohne
Testabdeckung (→ technische Schuld TD-1) - Azure OpenAI erfordert
Cloud-Egress — dies steht in Konflikt mit der Randbedingung «Kubernetes
On-Premise» (→ Sektion 2); der Konflikt ist bewusst akzeptiert:
e-gov-ai-rule-generation ist optional, Behörden ohne
Cloud-Freigabe lassen das Modul weg
9.8 ADR-008: LUID als cluster-sichere ID-Generierungsstrategie
Status: Akzeptiert
Datum: 2024
9.8.1 Kontext
Fachapplikationen, die auf Kubernetes mit mehreren Replikas laufen, brauchen eine ID-Generierungsstrategie, die ohne zentrale Koordination skaliert. Standard-Alternativen wurden bewertet:
| Strategie | Sortierbarkeit | DB-Abhängigkeit | Cluster-sicher | Lücken |
|---|---|---|---|---|
| UUID v4 | Nicht sortierbar | Keine | Ja | Keine |
| DB-Sequenz | Sortierbar | Ja (Single Point) | Nur mit DB-Failover | Möglich |
| ULID | Sortierbar (ms-Präzision) | Keine | Bedingt (Kollision möglich) | Keine |
| LUID (gewählt) | Sortierbar (sek-Präzision) | Keine | Ja (via segmentId) | Toleriert |
9.8.2 Entscheidung
Das Modul e-gov-luid stellt LUIDGenerator
bereit — einen Hibernate IdentifierGenerator, der
zeitbasierte, sortierbare IDs ohne Datenbankzugriff generiert. Jeder Pod
erhält eine eindeutige segmentId (0–99), die Kollisionen
zwischen Instanzen ausschliesst.
ID = ((Sekunden seit 2025-01-01) * maxIDsPerTick * maxSegments)
+ (maxIDsPerTick * segmentId)
+ laufende Nummer [0 … maxIDsPerTick − 1]
9.8.3 Konsequenzen
Positiv: - Keine Datenbankabfrage bei ID-Generierung → Durchsatz und Latenz verbessert - IDs sind sortierbar → natürliche Paginierung und Index-Effizienz - Skaliert bis 100 parallele Pods bei Defaultkonfiguration
Negativ: - segmentId muss beim
Deployment korrekt konfiguriert sein — falscher Wert kann Kollisionen
verursachen - IDs sind nicht zufällig (Information Disclosure:
Erstellungszeitpunkt ableitbar) - Fehlkonfiguration (zwei Pods mit
gleicher segmentId) führt zu Kollisionen ohne sofortige
Fehlermeldung