- Added `meldestelle-desktop` module using JVM/Compose Desktop, registered in `settings.gradle.kts`. - Integrated new screens and desktop navigation into core: `Veranstaltungen`, `TurnierDetail`, etc. - Expanded backend with `ExposedFunktionaerRepository` in `officials-infrastructure`. - Completed ADRs for bounded context mapping (`ADR-0014`) and context map (`ADR-0015`). - Updated and extended project documentation with session logs and architecture decisions. Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
17 KiB
| type | id | status | owner | last_update |
|---|---|---|---|---|
| ADR | ADR-0015 | ACTIVE | Lead Architect | 2026-03-24 |
ADR-0015: Context Map & Integration Patterns
Status
Akzeptiert
Kontext
Nach der Definition der 6 Bounded Contexts (→ ADR-0014) müssen die Beziehungen zwischen den Contexts explizit dokumentiert werden. Eine Context Map beschreibt:
- Welche Contexts miteinander kommunizieren
- In welche Richtung Abhängigkeiten fließen
- Welches Integration Pattern verwendet wird
- Wo Anti-Corruption Layers (ACL) notwendig sind
Ohne eine explizite Context Map entstehen implizite Abhängigkeiten, die die Unabhängigkeit der Contexts untergraben und die Offline-First-Strategie gefährden.
Entscheidung
Context Map (ASCII-Diagramm)
┌─────────────────────────────────────────────────────────────────────────────┐
│ EXTERNE SYSTEME │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ ZNS (Zentrales Nennungs-System / OEPS) [Upstream / Big Ball] │ │
│ └──────────────────────────┬──────────────────────────────────────────┘ │
│ │ ACL (A-Satz / B-Satz Import) │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ Keycloak (Identity Provider) [Upstream / Conformist] │ │
│ └──────────────────────────┬───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
│ JWT / OIDC
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ INTERNE CONTEXTS │
│ │
│ ┌──────────────────────┐ ┌──────────────────────────────────────┐ │
│ │ identity-context │◄───────│ (alle Contexts) │ │
│ │ [Generic Domain] │ OHS │ prüfen Berechtigungen via Token │ │
│ └──────────────────────┘ └──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────┐ ACL ┌──────────────────────────────────────┐ │
│ │ actor-context │◄───────│ ZNS (extern) │ │
│ │ [Supporting Domain] │ └──────────────────────────────────────┘ │
│ │ │ │
│ │ DomReiter │ CL/SK ┌──────────────────────────────────────┐ │
│ │ DomPferd │───────►│ registration-context │ │
│ │ DomFunktionär │ │ [Core Domain] │ │
│ │ DomVerein │ │ │ │
│ └──────────────────────┘ │ DomNennung │ │
│ │ DomNennungsTransfer │ │
│ ┌──────────────────────┐ CL/SK │ DomAbteilung │ │
│ │ event-management- │───────►│ │ │
│ │ context │ └──────────────┬───────────────────────┘ │
│ │ [Supporting Domain] │ │ │
│ │ │ │ Domain Events │
│ │ DomVeranstaltung │ │ (NennungErstellt, │
│ │ DomTurnier │ │ NennungStorniert, │
│ │ DomAusschreibung │ │ NennungTransferiert) │
│ └──────────────────────┘ │ │
│ ▼ │
│ ┌──────────────────────┐ ┌──────────────────────────────────────┐ │
│ │ billing-context │◄───────│ competition-context │ │
│ │ [Generic Domain] │ ACL │ [Supporting Domain] │ │
│ │ │ │ │ │
│ │ DomKonto │ │ DomBewerb │ │
│ │ DomGebühr │ │ DomAbteilung │ │
│ │ DomAbrechnung │ │ DomStartliste │ │
│ └──────────────────────┘ │ DomErgebnis │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ series-context [Phase 2+ — Architektur vorbereitet, nicht aktiv] │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
Legende:
ACL= Anti-Corruption LayerCL/SK= Customer/Supplier mit Shared Kernel (gemeinsame IDs)OHS= Open Host Service (standardisiertes Interface)►= Abhängigkeitsrichtung (Downstream → Upstream)
Beziehungen im Detail
1. ZNS (extern) → actor-context
Pattern: Upstream/Downstream mit Anti-Corruption Layer (ACL)
| Eigenschaft | Beschreibung |
|---|---|
| Richtung | ZNS ist Upstream (Datenquelle), actor-context ist Downstream |
| Protokoll | Datei-Import (A-Satz / B-Satz, proprietäres Format) |
| ACL-Aufgabe | Übersetzung von ZNS-Datenformaten in interne Domänenmodelle |
| Offline-Verhalten | ZNS-Daten werden lokal gecacht; Import läuft asynchron |
| Kritische Regel | Satznummer ist primärer Schlüssel; Lebensnummer und Kopfnummer sind nicht als DB-Schlüssel geeignet (ZNS-Inkonsistenzen bekannt) |
ACL-Verantwortlichkeiten:
- Normalisierung inkonsistenter Felder (z.B. Farbe
"Braun"vs."Brauner") - Generierung interner IDs für ausländische Pferde ohne UELN
- Validierung und Ablehnung korrupter ZNS-Datensätze mit Protokollierung
2. actor-context → registration-context
Pattern: Customer/Supplier mit Shared Kernel (gemeinsame IDs)
| Eigenschaft | Beschreibung |
|---|---|
| Richtung | actor-context ist Upstream (Stammdaten-Lieferant) |
| Shared Kernel | ReiterId (Satznummer), PferdId (Satznummer) als gemeinsame Referenz-IDs |
| Kommunikation | Synchron: Lookup bei Nennungs-Erstellung; Asynchron: Sperrlisten-Updates |
| Offline-Verhalten | registration-context hält lokale Kopie der benötigten Akteur-Daten |
| Kritische Regel | registration-context darf Akteur-Daten nicht direkt mutieren (nur lesen) |
3. event-management-context → registration-context
Pattern: Customer/Supplier mit Shared Kernel (gemeinsame IDs)
| Eigenschaft | Beschreibung |
|---|---|
| Richtung | event-management-context ist Upstream (Turnier-/Bewerbs-Struktur) |
| Shared Kernel | TurnierId, BewerbId als gemeinsame Referenz-IDs |
| Kommunikation | Synchron: Bewerbs-Lookup bei Nennungs-Erstellung |
| Kritische Regel | Nennungen können nur für existierende Bewerbe erstellt werden |
4. registration-context → competition-context
Pattern: Upstream/Downstream via Domain Events
| Eigenschaft | Beschreibung |
|---|---|
| Richtung | registration-context ist Upstream (Ereignis-Quelle) |
| Events | NennungErstelltEvent, NennungStorniertEvent, NennungTransferiertEvent |
| Kommunikation | Asynchron (Event Bus / lokale Event Queue) |
| Aufgabe Downstream | competition-context baut Startlisten aus Nennungs-Events auf |
| Offline-Verhalten | Events werden lokal persistiert und bei Verbindung synchronisiert |
5. registration-context → billing-context
Pattern: Upstream/Downstream via Domain Events mit ACL
| Eigenschaft | Beschreibung |
|---|---|
| Richtung | registration-context ist Upstream (Gebühren-Auslöser) |
| Events | NennungErstelltEvent (löst Nenngeld aus), NennungStorniertEvent (Gutschrift), GebührenVerzichtEvent |
| ACL-Aufgabe | Übersetzung von Nennungs-Events in Gebühren-Buchungen |
| Kritische Regel | Sportförderbeitrag und Tierwohl-Euro fallen pro Start an (nicht pro Nennung) |
6. competition-context → billing-context
Pattern: Upstream/Downstream via Domain Events mit ACL
| Eigenschaft | Beschreibung |
|---|---|
| Richtung | competition-context ist Upstream |
| Events | StartErfolgreich (löst Sportförderbeitrag + Tierwohl-Euro aus) |
| ACL-Aufgabe | Übersetzung von Start-Events in Gebühren-Buchungen |
7. Keycloak → alle Contexts
Pattern: Upstream/Downstream, Conformist (alle Contexts passen sich Keycloak an)
| Eigenschaft | Beschreibung |
|---|---|
| Richtung | Keycloak ist Upstream (Identity Provider) |
| Protokoll | OIDC / JWT-Token |
| Kommunikation | Synchron: Token-Validierung bei jedem Request |
| Offline-Verhalten | Token-Caching mit konfigurierbarer TTL; Offline-Modus mit eingeschränkten Rechten |
Anti-Corruption Layer (ACL) — Implementierungsrichtlinien
Jeder ACL wird als eigenständiges Modul innerhalb des Downstream-Contexts implementiert:
actor-context/
└── infrastructure/
└── zns/
├── ZnsImportService.kt # Orchestrierung
├── ZnsAkteurMapper.kt # Übersetzung ZNS → Dom*
├── ZnsValidationFilter.kt # Ablehnung korrupter Daten
└── ZnsImportProtokoll.kt # Audit-Log aller Imports
Prinzipien:
- Der ACL übersetzt immer in die interne Ubiquitous Language — niemals umgekehrt
- Fehlerhafte externe Daten werden protokolliert und übersprungen (kein Systemabsturz)
- Der ACL ist der einzige Ort, der das externe Datenformat kennt
Offline-First Integration
Da die Anwendung als Desktop-App (Offline-First) betrieben wird, gelten folgende Regeln:
| Szenario | Verhalten |
|---|---|
| ZNS nicht erreichbar | Lokaler Cache wird verwendet; Import-Status wird angezeigt |
| Nennungs-Erstellung offline | Lokal gespeichert; Events werden bei Sync übertragen |
| Keycloak nicht erreichbar | Gecachter Token wird verwendet (TTL-basiert) |
| Konflikt bei Sync | Optimistic Locking (409) + manuelle Auflösung (→ ADR-0013) |
Konsequenzen
Positive
- Explizite Abhängigkeiten: Alle Context-Beziehungen sind dokumentiert und nachvollziehbar
- Schutz der Kern-Domäne:
registration-contextist durch ACLs von externen Systemen isoliert - Offline-Fähigkeit: Jede Integration ist auf Offline-Betrieb ausgelegt
- Erweiterbarkeit:
series-contextkann in Phase 2+ als reiner Downstream-Consumer hinzugefügt werden
Negative
- Komplexität: ACLs und Event-Übersetzungen erhöhen den initialen Implementierungsaufwand
- Eventual Consistency: Zwischen
registration-contextundcompetition-contextgibt es keine sofortige Konsistenz
Neutral
- Die Context Map ist ein lebendes Dokument und wird mit jeder neuen Integration aktualisiert
Betrachtete Alternativen
Direkte Context-zu-Context-Aufrufe (abgelehnt)
Direkte synchrone Aufrufe zwischen Contexts würden enge Kopplung erzeugen und die Offline-Fähigkeit untergraben.
Shared Database (abgelehnt)
Eine gemeinsame Datenbank für alle Contexts würde die Context-Grenzen aufweichen und die unabhängige Entwicklung verhindern.
Referenzen
- ADR-0014: Bounded Context Mapping
- ADR-0004: Event-Driven Communication
- ADR-0006: Authentication & Authorization (Keycloak)
- ADR-0013: Tech Stack Stabilization
- Ubiquitous Language
- MASTER_ROADMAP
- Vaughn Vernon: „Implementing Domain-Driven Design", Kapitel 3 (Context Maps)
- ÖTO 2026, ZNS-Schnittstellen-Spezifikation (A-Satz / B-Satz)