Pattern-Katalog
Verwendete Entwurfsmuster in diesem Projekt, jeweils mit Fundstelle und Begründung. Wird mit jeder Phase ergänzt (siehe Lernpfad).
Value Object
Was: unveränderliches Objekt ohne Identität, definiert durch seine Werte; erzwingt seine eigenen Invarianten im Konstruktor/in der Fabrikmethode.
Wo: common-domain/.../Money.java — Money.of(...) lehnt negative Beträge ab,
Money.add(...) lehnt Währungs-Mismatches ab. Als Java record implementiert (strukturelle
Gleichheit, Unveränderlichkeit "for free").
Warum: verhindert, dass ein ungültiger Geldbetrag überhaupt erst instanziiert werden kann ("Make Invalid States Unrepresentable"). Siehe TDD-Katalog für die Commit-Historie der Entstehung per TDD.
Aggregate (DDD)
Was: Konsistenzgrenze mit eigener Identität, sammelt Domain Events statt sie direkt zu publizieren.
Wo: common-domain/.../AggregateRoot.java — Basisklasse für alle fachlichen Aggregate
(Account, Customer, Card, ... ab den jeweiligen Phasen).
Warum: trennt "wann passiert etwas fachlich Bedeutsames" (Aggregate-Methode) von "wann wird es
nach außen sichtbar" (pullDomainEvents(), aufgerufen von der Anwendungsschicht nach erfolgreicher
Persistenz) — siehe Architektur-Überblick.
Repository-Port (Dependency Inversion)
Was: von der Domain-/Anwendungsschicht definiertes Interface für Persistenz, implementiert von der Infrastrukturschicht.
Wo: ab Phase 1 in jedem Domain-Service (domain.<Aggregat>Repository Interface,
infrastructure.persistence.Jpa<Aggregat>RepositoryAdapter Implementierung).
Warum: siehe ADR-0002. Ermöglicht Anwendungsfall-Tests ohne Datenbank.
Domain Event / Integration Event
Was/Wo: common-domain/.../DomainEvent.java (modul-intern) vs.
common-events/.../IntegrationEvent.java (Service-übergreifender, stabiler Vertrag), siehe
CustomerRegisteredEvent.
Warum: entkoppelt den internen Domain-Ereignistyp eines Aggregats von dem, was tatsächlich "nach draußen" auf Kafka veröffentlicht wird — eine interne Refaktorierung des Aggregats muss nicht zwangsläufig den öffentlichen Event-Vertrag brechen.
Saga mit Kompensation
Was: Koordination mehrerer lokaler Transaktionen über Service-Grenzen (Abbuchung in
accounts-service → Gutschrift in accounts-service → Ledger-Buchung in payments-service, mit
explizitem Kompensationsschritt bei Fehlschlag der Gutschrift).
Wo: payments-service, InitiateTransferUseCase.
Warum: siehe ADR-0005.
Zustandsloser Domain-Service
Was: Fachliche Logik, die zu keinem einzelnen Aggregate gehört (weil sie mehrere Werte kombiniert oder reine Berechnung ohne eigenen Zustand ist), als eigene, injizierbare Klasse statt als statische Utility-Methode oder aufgeblähte Aggregate-Methode.
Wo: RiskClassifier (customer-service), IbanGenerator/PanGenerator
(accounts-/cards-service), LedgerBooking (payments-service), CreditScorer/
RepaymentScheduleGenerator (lending-service).
Warum: haelt Aggregate schlank und fokussiert auf ihre eigenen Invarianten; ein Domain-Service ist ohne Infrastruktur (keine Spring-/JPA-Abhaengigkeit) isoliert testbar - genau das macht ihn zum idealen Ziel fuer Triangulation ueber mehrere Testfaelle (siehe TDD für Entwickler).
Transactional Outbox
Was: eine ausgehende Nachricht wird nicht direkt an den Message-Broker gesendet, sondern in derselben Datenbank-Transaktion wie die fachliche Änderung als Zeile in einer eigenen "Outbox"-Tabelle gespeichert. Ein separater Prozess (Relay/Poller) liest unveröffentlichte Zeilen und sendet sie asynchron, danach markiert er sie als veröffentlicht.
Wo: customer-service, RegisterCustomerUseCase (schreibt OutboxEvent) + OutboxRelay
(@Scheduled, liest und veröffentlicht).
Warum: löst das "Dual-Write-Problem" - ohne Outbox sind "Kunde speichern" und "Event veröffentlichen" zwei getrennte, nicht atomare Schritte; ein Absturz dazwischen verliert das Event stillschweigend (in diesem Projekt bis Phase 7 tatsächlich so vorhanden, siehe ADR-0007, Nachtrag Outbox). Die Outbox-Tabelle lebt in derselben Datenbank wie die fachlichen Tabellen, wird also von derselben Transaktion mit abgesichert - "genau einmal speichern, mindestens einmal veröffentlichen" statt "vielleicht gar nicht veröffentlichen".
Message-Driven Adapter (Kafka-Konsument)
Was: Ein duenner Adapter, der ein eingehendes Integrationsevent unveraendert an einen Anwendungsfall weiterreicht - keine fachliche Logik im Adapter selbst.
Wo: notification-service, infrastructure.messaging (CustomerEventsListener,
LoanEventsListener, PaymentEventsListener). LoanEventsListener zeigt zusaetzlich einen
@KafkaListener mit mehreren @KafkaHandler-Methoden fuer verschiedene Event-Typen auf
demselben Topic.
Warum: symmetrisch zum Repository-Port/Adapter-Muster oben, nur fuer eingehende statt ausgehende Infrastruktur - der Adapter selbst wird ohne echten Broker getestet (nur die Weiterleitung), das tatsaechliche Verhalten steckt vollstaendig im Anwendungsfall.