Wenige große Kapitel. Nur Hauptkapitel sind klappbar; alle Unterabschnitte bleiben sichtbar. Alle 100 Patternkarten sind jetzt ausführlich aus deutschsprachigen Fachquellen redaktionell vertieft.
Die Seite ist jetzt in vier Buchteile geordnet. Leser beginnen mit Auswahlregeln, üben Entscheidungen an konkreten Situationen, nutzen kompakte Karten zum Nachschlagen und vertiefen anschließend die 100 ausführlichen Fachkapitel.
Zuerst wird geklärt, welches Problem vorliegt, welche Kräfte wirken und wann ein Pattern überhaupt gerechtfertigt ist.
Pattern-Beziehungen und Übergänge
Pattern-Kombinationen, Alternativen und sinnvolle Reihenfolgen
Ein Enterprise-System entsteht selten aus einem einzelnen Muster. Diese Übersicht zeigt typische Kombinationen, ihre Reihenfolge und den Punkt, an dem zusätzliche Abstraktion keinen Nutzen mehr bringt.
Factory → Strategy
Eine Factory wählt anhand eines stabilen fachlichen Schlüssels die passende Strategy. Die Factory kennt die Varianten; die Strategy enthält die austauschbare Berechnung.
Nicht einsetzen: wenn nur eine Variante existiert oder ein einfacher Konstruktor genügt.
State → Command
State schützt erlaubte Übergänge eines Aggregats. Command macht die auslösende fachliche Absicht explizit und eignet sich für Audit, Wiederholung und Autorisierung.
Reihenfolge: zuerst Zustandsinvarianten klären, danach Aktionen modellieren.
Facade → Adapter → Anti-Corruption Layer
Die Facade bündelt einen Use Case, der Adapter übersetzt das technische Protokoll und der Anti-Corruption Layer schützt das Domänenmodell vor fremden Begriffen und Sonderfällen.
Grenze: Nicht jede REST- oder SOAP-Klasse benötigt alle drei Ebenen.
Repository → Unit of Work → Outbox
Repository kapselt fachlich benötigte Persistenz, Unit of Work begrenzt die atomare Änderung und Outbox sichert die spätere Veröffentlichung von Ereignissen.
Wichtig: Outbox ersetzt keine fachliche Idempotenz.
Decorator → Chain of Responsibility
Decorator ergänzt einen einzelnen Vertrag schichtweise. Eine Chain entscheidet dagegen, welcher Bearbeiter zuständig ist oder ob die Verarbeitung weitergereicht wird.
Abgrenzung: Dekoration erweitert; die Kette routet.
Specification → Policy → Strategy
Specification beantwortet eine Ja/Nein-Frage, Policy bündelt mehrere fachliche Regeln und Strategy führt ein alternatives Verfahren aus. Diese Begriffe dürfen nicht synonym verwendet werden.
Lernregel: erst Entscheidung, dann Verfahren.
Typische Übergänge aus Refactorings
Ausgangsproblem
Erste sichere Verbesserung
Mögliches Pattern
Stopppunkt
große Fallunterscheidung
Bedingungen benennen und Varianten testen
Strategy oder State
wenn die Fallunterscheidung lokal und stabil bleibt
versteckte Fremdsystemkopplung
expliziten Port extrahieren
Adapter / ACL
wenn nur technische Übersetzung nötig ist
mehrere Erzeugungswege
Konstruktion aus Use Case lösen
Factory / Builder
wenn ein benannter Konstruktor ausreicht
unzuverlässige Ereignisverteilung
Transaktionsgrenze und Idempotenz klären
Outbox + Observer
wenn synchrone lokale Verarbeitung genügt
Leitgedanke: Ein Pattern ist kein Qualitätsbeweis. Es ist nur dann gerechtfertigt, wenn es einen konkreten Variationspunkt, eine Systemgrenze oder eine Invariante verständlicher und testbarer macht.
Kapitel 1 Design-Pattern-Grundlagen und Auswahlkriterien10 Pattern-Themen
Acht wichtige Patterns mit Problem, Einsatzgrenze, Code und Zusammenspiel auf einer großen Seite.
Pattern-Landkarte
Design Pattern Master Workbench
Acht wichtige Patterns mit Problem, Einsatzgrenze, Code und Zusammenspiel auf einer großen Seite.
Bereiche offen: 0 von 0Öffnungszustand wird lokal gespeichert.
01. Pattern-Entscheidungslogik analysis
Ein Pattern ist eine Antwort auf wiederkehrenden Änderungsdruck. Die Workbench beginnt daher immer mit dem Problem, nicht mit dem Klassendiagramm.
// Design Pattern: Strategy
@FunctionalInterface
public interface DiscountStrategy {
Money apply(Money subtotal);
}
public final class PricingService {
private final Map<CustomerTier, DiscountStrategy> strategies;
public Money price(Order order) {
DiscountStrategy strategy = strategies.get(order.tier());
return strategy.apply(order.subtotal());
}
}
03. State – Bestellstatus analysis · code
OrderState.java
// Design Pattern: State
public sealed interface OrderState permits Draft, Confirmed, Shipped {
OrderState confirm();
OrderState ship();
}
record Draft() implements OrderState {
public OrderState confirm() { return new Confirmed(); }
public OrderState ship() { throw new IllegalStateException("Draft cannot ship"); }
}
State ist sinnvoll, wenn Statusübergänge eigenes Verhalten besitzen. Ein Enum genügt, wenn nur angezeigt oder gespeichert wird.
04. Adapter – Legacy Tax API analysis · code
LegacyTaxAdapter.java
// Design Pattern: Adapter
public final class LegacyTaxAdapter implements TaxPort {
private final LegacyTaxClient client;
public Tax quote(TaxRequest request) {
LegacyResponse raw = client.calculate(request.countryCode(), request.net().amount());
return new Tax(Money.of(raw.tax(), request.net().currency()));
}
}
// Design Pattern: Factory
public DiscountStrategy create(CustomerContract contract) {
return switch (contract.type()) {
case STANDARD -> regular();
case VIP -> vip(contract.discountRate());
case PARTNER -> partner(contract.partnerLevel());
};
}
08. Chain of Responsibility – Prüfstrecke analysis · code
ValidationChain.java
// Design Pattern: Chain of Responsibility
Validator chain = new RequiredFieldsValidator(
new CreditLimitValidator(
new ComplianceValidator(null)));
ValidationResult result = chain.validate(order);
Repository abstrahiert eine fachlich relevante Aggregat-Sammlung. Es sollte nicht pauschal für jede Tabelle erzeugt werden.
10. Zusammenspiel und Anti-Patterns analysis
Kombination
Sinn
Factory + Strategy
Erzeugung und Ausführung der Variante bleiben getrennt.
Specification + Repository
Fachliche Auswahlregeln können in Query-Modelle übersetzt werden.
Decorator + Strategy
Querschnittliche Regelbestandteile werden geschichtet.
Anti-Pattern: Pattern-Soup entsteht, wenn jede Klasse nur noch delegiert, ohne dass unabhängige Variabilität oder eine klare Grenze existiert.
Buchteil II
Entscheidungslabore
Konkrete Situationen werden nicht mit Pattern-Namen begonnen, sondern mit Problem, Risiko, Varianten und überprüfbarer Entscheidung.
Kapitel 2 Entscheidungslabore: Verhalten und Zustand8 Decision Labs
Acht direkte Pattern-Vergleiche zeigen nicht nur die Implementierung, sondern die entscheidende Frage: Welches Problem variiert wirklich?
Pattern-Labs · 8 von 40
Pattern Decision Workbench
Acht direkte Pattern-Vergleiche zeigen nicht nur die Implementierung, sondern die entscheidende Frage: Welches Problem variiert wirklich?
Bereiche offen: 0 von 8Pattern-Labs: 8/40Offen: 32
Entscheidungsablauf: Problem benennen → Variationsachse erkennen → leichteste passende Abstraktion wählen → Nachteile und Alternative dokumentieren → Testgrenze festlegen.
01. Strategy oder State? Decision Lab
Strategy
Problem: Algorithmus austauschen.
State
Problem: Verhalten folgt Lebenszyklus.
Entscheidungsregel
Strategy wählen, wenn der Kontext die Variante auswählt. State wählen, wenn das Objekt selbst gültige Übergänge schützt.
Einsatzort:PricingPolicy / OrderState
PricingPolicy.java
public interface PricingPolicy {
BigDecimal price(BigDecimal base);
}
public interface OrderState {
OrderState pay();
String name();
}
Frage
Strategy
State
Was variiert?
Algorithmus austauschen
Verhalten folgt Lebenszyklus
Risiko
unnötige Abstraktion bei nur einer Variante
Pattern-Missbrauch ohne echten Zustands-/Zugriffs-/Strukturbedarf
Testfokus
Variante unabhängig testen
Übergang, Zugriff oder Strukturgrenze testen
02. Adapter oder Facade? Decision Lab
Adapter
Problem: inkompatible Schnittstelle übersetzen.
Facade
Problem: komplexes Subsystem vereinfachen.
Entscheidungsregel
Adapter verändert die Form einer Schnittstelle. Facade reduziert die sichtbare Komplexität mehrerer bereits passender Komponenten.
Einsatzort:CustomerApiAdapter / CheckoutFacade
CustomerApiAdapter.java
public final class CustomerApiAdapter {
private final LegacyCustomerApi api;
public CustomerView load(String id) {
String[] parts = api.loadCustomerRow(id).split(";");
return new CustomerView(parts[0], parts[1]);
}
}
Frage
Adapter
Facade
Was variiert?
inkompatible Schnittstelle übersetzen
komplexes Subsystem vereinfachen
Risiko
unnötige Abstraktion bei nur einer Variante
Pattern-Missbrauch ohne echten Zustands-/Zugriffs-/Strukturbedarf
Testfokus
Variante unabhängig testen
Übergang, Zugriff oder Strukturgrenze testen
03. Builder oder Factory? Decision Lab
Builder
Problem: komplexes Objekt schrittweise konfigurieren.
Factory
Problem: konkreten Produkttyp auswählen.
Entscheidungsregel
Builder löst Parameter- und Validierungsprobleme. Factory kapselt die Entscheidung, welche Implementierung entsteht.
Problem: neue Operationen über stabile Typen ergänzen.
Entscheidungsregel
Composite strukturiert Objekte. Visitor strukturiert Operationen und lohnt sich nur bei stabiler Elementhierarchie.
Einsatzort:OrgNode / DocumentVisitor
DocumentVisitor.java
public interface DocumentVisitor<R> {
R visitPdf(PdfDocument document);
R visitXml(XmlDocument document);
}
int nodes = new OrgNode("root", children).size();
Frage
Composite
Visitor
Was variiert?
Teil-Ganzes-Struktur einheitlich behandeln
neue Operationen über stabile Typen ergänzen
Risiko
unnötige Abstraktion bei nur einer Variante
Pattern-Missbrauch ohne echten Zustands-/Zugriffs-/Strukturbedarf
Testfokus
Variante unabhängig testen
Übergang, Zugriff oder Strukturgrenze testen
07. Repository oder DAO? Decision Lab
Repository
Problem: Domänenaggregate fachlich laden.
DAO
Problem: technische Tabellenoperationen kapseln.
Entscheidungsregel
Repository spricht Domänensprache und schützt Aggregatgrenzen. DAO bleibt näher an Tabellen, SQL und Datenzeilen.
Einsatzort:CustomerRepository / CustomerDao
CustomerRepository.java
public interface CustomerRepository {
Optional<CustomerView> findById(String id);
}
public interface CustomerDao {
Map<String, Object> selectRow(String id);
}
Frage
Repository
DAO
Was variiert?
Domänenaggregate fachlich laden
technische Tabellenoperationen kapseln
Risiko
unnötige Abstraktion bei nur einer Variante
Pattern-Missbrauch ohne echten Zustands-/Zugriffs-/Strukturbedarf
Testfokus
Variante unabhängig testen
Übergang, Zugriff oder Strukturgrenze testen
08. Specification oder Predicate? Decision Lab
Specification
Problem: benannte kombinierbare Fachregel.
Predicate
Problem: lokale kleine Bedingung.
Entscheidungsregel
Specification eignet sich für wiederverwendbare Regeln mit eigener Bedeutung. Predicate genügt für lokale, technische Filter.
Lab 10 · Command vs Chain of Responsibility Freigabeprozess
Command
Wähle es, wenn: Operation als Objekt.
Chain of Responsibility
Wähle es, wenn: gestufte Bearbeitung.
Java-Beispiel: Command
Java-Beispiel: Command
package com.aydinsude.workbench.decision;
// Design Pattern: Command - Operation mit Daten und Ausführung kapseln.
public record ApprovalCommand(String caseId) { public String execute(){return "approved:"+caseId;} }
Java-Beispiel: Chain of Responsibility
Java-Beispiel: Chain of Responsibility
package com.aydinsude.workbench.decision;
// Design Pattern: Chain of Responsibility - Bearbeitung schrittweise weiterreichen.
public interface ApprovalHandler { boolean handle(int amount); }
Frage
Command
Chain of Responsibility
Primäre Absicht
Operation als Objekt
gestufte Bearbeitung
Testfokus
stabiler Vertrag
austauschbares Verhalten
Risiko
zu starres Gerüst
zu viele kleine Varianten
Lab 11 · Mediator vs Observer UI-Workflow
Mediator
Wähle es, wenn: zentrale Koordination.
Observer
Wähle es, wenn: verteilte Reaktion.
Java-Beispiel: Mediator
Java-Beispiel: Mediator
package com.aydinsude.workbench.decision;
// Design Pattern: Mediator - Komponenten koordinieren sich über eine zentrale Instanz.
public final class WorkflowMediator { public String onCustomerSelected(String id){return "load-orders:"+id;} }
Java-Beispiel: Observer
Java-Beispiel: Observer
package com.aydinsude.workbench.decision;
// Design Pattern: Observer - mehrere Empfänger reagieren unabhängig.
public interface WorkflowObserver { void onEvent(String event); }
Lab 14 · Prototype vs Builder Vertragskonfiguration
Prototype
Wähle es, wenn: Vorlage kopieren.
Builder
Wähle es, wenn: komplex neu aufbauen.
Java-Beispiel: Prototype
Java-Beispiel: Prototype
package com.aydinsude.workbench.decision;
// Design Pattern: Factory Method - Unterklasse bestimmt ein Produkt.
public abstract class ReportFactoryMethod { protected abstract String createReport(); public String run(){return "generated:"+createReport();} }
Java-Beispiel: Builder
Java-Beispiel: Builder
package com.aydinsude.workbench.decision;
// Design Pattern: Prototype - geprüfte Vorlage kopieren.
public record ContractPrototype(String plan,int months){ public ContractPrototype copyWithMonths(int m){return new ContractPrototype(plan,m);} }
Frage
Prototype
Builder
Primäre Absicht
Vorlage kopieren
komplex neu aufbauen
Testfokus
stabiler Vertrag
austauschbares Verhalten
Risiko
zu starres Gerüst
zu viele kleine Varianten
Lab 15 · Memento vs Event Sourcing Editor/Aggregat
Memento
Wähle es, wenn: Snapshot wiederherstellen.
Event Sourcing
Wähle es, wenn: Historie als Quelle.
Java-Beispiel: Memento
Java-Beispiel: Memento
package com.aydinsude.workbench.decision;
// Design Pattern: Builder - komplexes Objekt schrittweise neu aufbauen.
public final class ContractBuilder { private String plan="BASIC"; private int months=12; public ContractBuilder plan(String v){plan=v;return this;} public ContractBuilder months(int v){months=v;return this;} public ContractPrototype build(){return new ContractPrototype(plan,months);} }
Java-Beispiel: Event Sourcing
Java-Beispiel: Event Sourcing
package com.aydinsude.workbench.decision;
// Design Pattern: Memento - internen Zustand als Snapshot sichern.
public record EditorMemento(String text) {}
Frage
Memento
Event Sourcing
Primäre Absicht
Snapshot wiederherstellen
Historie als Quelle
Testfokus
stabiler Vertrag
austauschbares Verhalten
Risiko
zu starres Gerüst
zu viele kleine Varianten
Lab 16 · Null Object vs Optional Benachrichtigung/Kunde
Null Object
Wähle es, wenn: polymorphes Standardverhalten.
Optional
Wähle es, wenn: explizite Abwesenheit.
Java-Beispiel: Null Object
Java-Beispiel: Null Object
package com.aydinsude.workbench.decision;
// Pattern: Event Sourcing - Ereignisfolge bildet den Zustand.
public record DomainEventRecord(long sequence,String type,String payload) {}
Java-Beispiel: Optional
Java-Beispiel: Optional
package com.aydinsude.workbench.decision;
public interface NotificationChannel { void send(String message); }
Frage
Null Object
Optional
Primäre Absicht
polymorphes Standardverhalten
explizite Abwesenheit
Testfokus
stabiler Vertrag
austauschbares Verhalten
Risiko
zu starres Gerüst
zu viele kleine Varianten
Kapitel 4 Entscheidungslabore: Ressourcen und Integration8 Decision Labs
Enterprise-Entscheidungen zu Zustandshistorie, verteilten Transaktionen, Resilienz und Abhängigkeitsmanagement.
Pattern-Labs · Abschnitt 3
Pattern Decision Workbench
Enterprise-Entscheidungen zu Zustandshistorie, verteilten Transaktionen, Resilienz und Abhängigkeitsmanagement.
Pattern-Labs: 24/40 Pattern-Labs
16 Pattern-Labs offen.
Lab 17 · Flyweight vs Object Pool Speicher vs Ressourcennutzung
Flyweight
Wähle es, wenn: gemeinsamen unveränderlichen Zustand zwischen vielen Objekten teilen.
package com.aydinsude.workbench.decision.block3;
import java.util.concurrent.ConcurrentHashMap;
public final class ProductTypeCatalog {
private final ConcurrentHashMap<String, FlyweightProductType> cache = new ConcurrentHashMap<>();
public FlyweightProductType get(String code, String label) { return cache.computeIfAbsent(code, c -> new FlyweightProductType(c,label)); }
}
Java-Beispiel: Object Pool
Java-Beispiel: Object Pool
package com.aydinsude.workbench.decision.block3;
import java.util.concurrent.ArrayBlockingQueue;
// Design Pattern: Object Pool - begrenzte Wiederverwendung teurer Parser.
public final class ParserPool {
private final ArrayBlockingQueue<PooledParser> pool;
public ParserPool(int size) { pool=new ArrayBlockingQueue<>(size); for(int i=0;i<size;i++) pool.add(String::trim); }
public String use(String input) { PooledParser p=pool.poll(); if(p==null) throw new IllegalStateException("pool exhausted"); try{return p.parse(input);} finally{pool.offer(p);} }
}
Entscheidungsfrage
Flyweight
Object Pool
Primäre Absicht
gemeinsamen unveränderlichen Zustand zwischen vielen Objekten teilen
fachliche Regeln kombinierbar und testbar ausdrücken
Typischer Vorteil
gezielte strukturelle Wirkung
bewusst einfachere Alternative
Hauptrisiko
unnötige Infrastruktur
Grenzen werden zu spät sichtbar
Testfokus
Verhalten unter Last und Fehlern
Konsistenz des einfachen Pfads
Lab 19 · Event Sourcing vs Audit Log Quelle der Wahrheit
Event Sourcing
Wähle es, wenn: den Zustand vollständig aus Ereignissen rekonstruieren.
Audit Log
Wähle es, wenn: Änderungen zusätzlich zum aktuellen Zustand protokollieren.
Java-Beispiel: Event Sourcing
Java-Beispiel: Event Sourcing
package com.aydinsude.workbench.decision.block3;
import java.time.Instant;
// Design Pattern: Event Sourcing - Ereignis ist die fachliche Historie.
public sealed interface AccountEvent permits Credited, Debited { Instant occurredAt(); long cents(); }
Java-Beispiel: Audit Log
Java-Beispiel: Audit Log
package com.aydinsude.workbench.decision.block3;
import java.time.Instant;
// Pattern: Audit Log - ergänzendes Protokoll, nicht Zustandsquelle.
public record AuditEntry(String entityId,String action,Instant occurredAt) {}
Entscheidungsfrage
Event Sourcing
Audit Log
Primäre Absicht
den Zustand vollständig aus Ereignissen rekonstruieren
Änderungen zusätzlich zum aktuellen Zustand protokollieren
Typischer Vorteil
gezielte strukturelle Wirkung
bewusst einfachere Alternative
Hauptrisiko
unnötige Infrastruktur
Grenzen werden zu spät sichtbar
Testfokus
Verhalten unter Last und Fehlern
Konsistenz des einfachen Pfads
Lab 20 · CQRS vs CRUD Komplexität der Modelle
CQRS
Wähle es, wenn: Lese- und Schreibmodelle bewusst trennen.
CRUD
Wähle es, wenn: ein gemeinsames Modell für einfache Datenpflege nutzen.
Java-Beispiel: CQRS
Java-Beispiel: CQRS
package com.aydinsude.workbench.decision.block3;
// Pattern: CQRS - Schreibmodell schützt Invarianten.
public final class WriteOrderService { public void changeStatus(String id,String status){ if(id.isBlank()||status.isBlank()) throw new IllegalArgumentException(); } }
Java-Beispiel: CRUD
Java-Beispiel: CRUD
package com.aydinsude.workbench.decision.block3;
import java.util.*;
// Pattern: CRUD - gemeinsames Modell für einfache Pflege.
public final class CrudCustomerRepository { private final Map<String,String> data=new HashMap<>(); public void save(String id,String name){data.put(id,name);} public Optional<String> find(String id){return Optional.ofNullable(data.get(id));} }
Entscheidungsfrage
CQRS
CRUD
Primäre Absicht
Lese- und Schreibmodelle bewusst trennen
ein gemeinsames Modell für einfache Datenpflege nutzen
Typischer Vorteil
gezielte strukturelle Wirkung
bewusst einfachere Alternative
Hauptrisiko
unnötige Infrastruktur
Grenzen werden zu spät sichtbar
Testfokus
Verhalten unter Last und Fehlern
Konsistenz des einfachen Pfads
Lab 21 · Saga vs Two-Phase Commit Verfügbarkeit vs Atomarität
Saga
Wähle es, wenn: verteilte Schritte mit Kompensationen koordinieren.
Two-Phase Commit
Wähle es, wenn: atomare Transaktionen über unterstützte Ressourcen erzwingen.
Java-Beispiel: Saga
Java-Beispiel: Saga
package com.aydinsude.workbench.decision.block3;
import java.util.*;
public final class SagaCoordinator { public void run(List<SagaStep> steps){ List<SagaStep> done=new ArrayList<>(); try{for(var s:steps){s.execute();done.add(s);}}catch(RuntimeException ex){Collections.reverse(done);done.forEach(SagaStep::compensate);throw ex;} } }
verteilte Schritte mit Kompensationen koordinieren
atomare Transaktionen über unterstützte Ressourcen erzwingen
Typischer Vorteil
gezielte strukturelle Wirkung
bewusst einfachere Alternative
Hauptrisiko
unnötige Infrastruktur
Grenzen werden zu spät sichtbar
Testfokus
Verhalten unter Last und Fehlern
Konsistenz des einfachen Pfads
Lab 22 · Transactional Outbox vs Dual Write Konsistenz bei Messaging
Transactional Outbox
Wähle es, wenn: Datenänderung und Event atomar in einer DB speichern.
Dual Write
Wähle es, wenn: DB und Broker direkt nacheinander schreiben.
Java-Beispiel: Transactional Outbox
Java-Beispiel: Transactional Outbox
package com.aydinsude.workbench.decision.block3;
import java.util.*; public final class OutboxStore { private final List<OutboxMessage> pending=new ArrayList<>(); public void append(OutboxMessage m){pending.add(m);} public List<OutboxMessage> pending(){return List.copyOf(pending);} }
Java-Beispiel: Dual Write
Java-Beispiel: Dual Write
package com.aydinsude.workbench.decision.block3;
// Anti-Pattern: Dual Write - zwei unabhängige Writes können auseinanderlaufen.
public final class DualWritePublisher { public void saveAndPublish(Runnable databaseWrite,Runnable brokerWrite){databaseWrite.run();brokerWrite.run();} }
Entscheidungsfrage
Transactional Outbox
Dual Write
Primäre Absicht
Datenänderung und Event atomar in einer DB speichern
DB und Broker direkt nacheinander schreiben
Typischer Vorteil
gezielte strukturelle Wirkung
bewusst einfachere Alternative
Hauptrisiko
unnötige Infrastruktur
Grenzen werden zu spät sichtbar
Testfokus
Verhalten unter Last und Fehlern
Konsistenz des einfachen Pfads
Lab 23 · Circuit Breaker vs Retry Schutz vs Wiederholung
Circuit Breaker
Wähle es, wenn: anhaltend fehlerhafte Abhängigkeiten zeitweise nicht aufrufen.
Retry
Wähle es, wenn: vorübergehende Fehler begrenzt erneut versuchen.
Java-Beispiel: Circuit Breaker
Java-Beispiel: Circuit Breaker
package com.aydinsude.workbench.decision.block3;
import java.time.*;
// Pattern: Circuit Breaker - schützt vor wiederholten Aufrufen einer gestörten Abhängigkeit.
public final class CircuitBreaker { private int failures; private Instant openUntil=Instant.MIN; public <T>T call(java.util.function.Supplier<T> action){if(Instant.now().isBefore(openUntil))throw new IllegalStateException("open");try{T value=action.get();failures=0;return value;}catch(RuntimeException ex){if(++failures>=3)openUntil=Instant.now().plusSeconds(30);throw ex;}} }
Java-Beispiel: Retry
Java-Beispiel: Retry
package com.aydinsude.workbench.decision.block3;
// Pattern: Retry - begrenzte Wiederholung transienter Fehler.
public final class RetryPolicy { public <T>T execute(int attempts,java.util.function.Supplier<T> action){RuntimeException last=null;for(int i=0;i<attempts;i++)try{return action.get();}catch(RuntimeException ex){last=ex;}throw last;} }
Entscheidungsfrage
Circuit Breaker
Retry
Primäre Absicht
anhaltend fehlerhafte Abhängigkeiten zeitweise nicht aufrufen
vorübergehende Fehler begrenzt erneut versuchen
Typischer Vorteil
gezielte strukturelle Wirkung
bewusst einfachere Alternative
Hauptrisiko
unnötige Infrastruktur
Grenzen werden zu spät sichtbar
Testfokus
Verhalten unter Last und Fehlern
Konsistenz des einfachen Pfads
Lab 24 · Dependency Injection vs Service Locator Explizitheit der Kopplung
Dependency Injection
Wähle es, wenn: Abhängigkeiten explizit über Konstruktoren bereitstellen.
Service Locator
Wähle es, wenn: Abhängigkeiten zur Laufzeit aus einem globalen Container holen.
Java-Beispiel: Dependency Injection
Java-Beispiel: Dependency Injection
package com.aydinsude.workbench.decision.block3;
// Pattern: Dependency Injection - Abhängigkeit ist im Konstruktor sichtbar.
public final class InjectedNotifier { private final NotificationPort port; public InjectedNotifier(NotificationPort port){this.port=java.util.Objects.requireNonNull(port);} public void notify(String m){port.send(m);} }
Java-Beispiel: Service Locator
Java-Beispiel: Service Locator
package com.aydinsude.workbench.decision.block3;
import java.util.*;
// Pattern: Service Locator - globaler Zugriff; bewusst als Vergleich dokumentiert.
public final class ServiceLocator { private static final Map<Class<?>,Object> SERVICES=new HashMap<>(); private ServiceLocator(){} public static <T>void register(Class<T> type,T value){SERVICES.put(type,value);} public static <T>T get(Class<T> type){return type.cast(SERVICES.get(type));} }
Entscheidungsfrage
Dependency Injection
Service Locator
Primäre Absicht
Abhängigkeiten explizit über Konstruktoren bereitstellen
Abhängigkeiten zur Laufzeit aus einem globalen Container holen
Typischer Vorteil
gezielte strukturelle Wirkung
bewusst einfachere Alternative
Hauptrisiko
unnötige Infrastruktur
Grenzen werden zu spät sichtbar
Testfokus
Verhalten unter Last und Fehlern
Konsistenz des einfachen Pfads
Kapitel 5 Entscheidungslabore: Architektur und Verteilung8 Decision Labs
Architektur-, DDD-, Resilienz-, Caching- und Integrationsentscheidungen mit echten Java-21-Beispielen.
Pattern-Labs · Abschnitt 4
Pattern Decision Workbench
Architektur-, DDD-, Resilienz-, Caching- und Integrationsentscheidungen mit echten Java-21-Beispielen.
Pattern-Labs: 32/40 Pattern-Labs
8 Pattern-Labs offen.
Lab 25 · Hexagonal Architecture vs Layered Architecture Ports vs Schichten
Hexagonal Architecture
Wähle es, wenn: fachliche Abhängigkeiten nach außen richten.
Layered Architecture
Wähle es, wenn: eine überschaubare Anwendung in technische Schichten gliedern.
Java-Beispiel: Hexagonal Architecture
Java-Beispiel: Hexagonal Architecture
package com.aydinsude.workbench.decision.block4;
import java.util.Objects;
// Architecture Pattern: Hexagonal Architecture - Fachlogik hängt nur von Ports ab.
public final class HexagonalOrderService {
private final OrderPort orderPort;
public HexagonalOrderService(OrderPort orderPort) { this.orderPort = Objects.requireNonNull(orderPort); }
public String describe(String orderId) { return "Order: " + orderPort.load(orderId); }
}
Java-Beispiel: Layered Architecture
Java-Beispiel: Layered Architecture
package com.aydinsude.workbench.decision.block4;
// Architecture Pattern: Layered Architecture - klare Schichten für einfache Anwendungen.
public final class LayeredOrderService {
private final LayeredOrderRepository repository;
public LayeredOrderService(LayeredOrderRepository repository) { this.repository = repository; }
public String describe(String orderId) { return "Order: " + repository.find(orderId); }
}
Entscheidungsfrage
Hexagonal Architecture
Layered Architecture
Primäre Absicht
fachliche Abhängigkeiten nach außen richten
eine überschaubare Anwendung in technische Schichten gliedern
Typischer Einsatz
höhere Entkopplung oder explizite fachliche Modellierung
direkterer, häufig einfacherer Kontrollfluss
Hauptrisiko
zusätzliche Abstraktionen ohne echten Änderungsdruck
zu enge Kopplung, sobald Varianten oder Last wachsen
Testfokus
Grenzen, Austauschbarkeit und Fehlerszenarien
Transaktions- und Ablaufkonsistenz
Lab 26 · Domain Service vs Application Service Regel vs Orchestrierung
Domain Service
Wähle es, wenn: eine domänenweite Regel ohne natürliche Entity-Zuordnung ausdrücken.
Application Service
Wähle es, wenn: einen Use Case, Transaktion und Ports koordinieren.
Java-Beispiel: Domain Service
Java-Beispiel: Domain Service
package com.aydinsude.workbench.decision.block4;
import java.math.BigDecimal;
// DDD Pattern: Domain Service - fachliche Regel ohne natürliche Entity-Zuordnung.
public final class CreditPolicy {
public boolean mayApprove(BigDecimal exposure, BigDecimal limit) { return exposure.compareTo(limit) <= 0; }
}
Java-Beispiel: Application Service
Java-Beispiel: Application Service
package com.aydinsude.workbench.decision.block4;
import java.math.BigDecimal;
import java.util.Objects;
// Application Service - orchestriert Use Case, Transaktion und Ports.
public final class CreditApplicationService {
private final CreditPolicy policy;
public CreditApplicationService(CreditPolicy policy) { this.policy = Objects.requireNonNull(policy); }
public String approve(BigDecimal exposure, BigDecimal limit) { return policy.mayApprove(exposure, limit) ? "APPROVED" : "REVIEW"; }
}
Entscheidungsfrage
Domain Service
Application Service
Primäre Absicht
eine domänenweite Regel ohne natürliche Entity-Zuordnung ausdrücken
einen Use Case, Transaktion und Ports koordinieren
Typischer Einsatz
höhere Entkopplung oder explizite fachliche Modellierung
direkterer, häufig einfacherer Kontrollfluss
Hauptrisiko
zusätzliche Abstraktionen ohne echten Änderungsdruck
zu enge Kopplung, sobald Varianten oder Last wachsen
Testfokus
Grenzen, Austauschbarkeit und Fehlerszenarien
Transaktions- und Ablaufkonsistenz
Lab 27 · Value Object vs Entity Wert vs Identität
Value Object
Wähle es, wenn: fachliche Bedeutung durch unveränderliche Werte und Wertgleichheit ausdrücken.
Entity
Wähle es, wenn: ein Objekt über eine stabile Identität und Lebenszyklus verfolgen.
Java-Beispiel: Value Object
Java-Beispiel: Value Object
package com.aydinsude.workbench.decision.block4;
import java.util.Objects;
// DDD Pattern: Value Object - Identität entsteht vollständig aus dem Wert.
public record CustomerId(String value) {
public CustomerId { Objects.requireNonNull(value); if (value.isBlank()) throw new IllegalArgumentException("blank customer id"); }
}
Java-Beispiel: Entity
Java-Beispiel: Entity
package com.aydinsude.workbench.decision.block4;
import java.util.Objects;
// DDD Pattern: Entity - stabile Identität trotz veränderlicher Attribute.
public final class CustomerAccount {
private final CustomerId id;
private String displayName;
public CustomerAccount(CustomerId id, String displayName) { this.id=Objects.requireNonNull(id); rename(displayName); }
public void rename(String name) { if(name==null || name.isBlank()) throw new IllegalArgumentException("blank name"); this.displayName=name; }
public CustomerId id(){ return id; }
public String displayName(){ return displayName; }
}
Entscheidungsfrage
Value Object
Entity
Primäre Absicht
fachliche Bedeutung durch unveränderliche Werte und Wertgleichheit ausdrücken
ein Objekt über eine stabile Identität und Lebenszyklus verfolgen
Typischer Einsatz
höhere Entkopplung oder explizite fachliche Modellierung
direkterer, häufig einfacherer Kontrollfluss
Hauptrisiko
zusätzliche Abstraktionen ohne echten Änderungsdruck
zu enge Kopplung, sobald Varianten oder Last wachsen
Testfokus
Grenzen, Austauschbarkeit und Fehlerszenarien
Transaktions- und Ablaufkonsistenz
Lab 28 · API Gateway vs Facade Systemgrenze vs Subsystem
API Gateway
Wähle es, wenn: externe Requests routen und zentrale Edge-Funktionen anwenden.
Facade
Wähle es, wenn: eine komplexe interne API für einen Client vereinfachen.
Java-Beispiel: API Gateway
Java-Beispiel: API Gateway
package com.aydinsude.workbench.decision.block4;
import java.util.Map;
// Architecture Pattern: API Gateway - externer Einstieg mit Routing und Querschnittsfunktionen.
public final class ApiGateway {
private final Map<String, java.util.function.Function<String,String>> routes;
public ApiGateway(Map<String, java.util.function.Function<String,String>> routes) { this.routes=Map.copyOf(routes); }
public String route(String path, String body) { var handler=routes.get(path); if(handler==null) throw new IllegalArgumentException("unknown route"); return handler.apply(body); }
}
Java-Beispiel: Facade
Java-Beispiel: Facade
package com.aydinsude.workbench.decision.block4;
// Design Pattern: Facade - vereinfacht ein internes Subsystem für einen fachlichen Client.
public final class OrderFacade {
public String place(String customerId, String sku) { return "ORDER:" + customerId + ":" + sku; }
}
Entscheidungsfrage
API Gateway
Facade
Primäre Absicht
externe Requests routen und zentrale Edge-Funktionen anwenden
eine komplexe interne API für einen Client vereinfachen
Typischer Einsatz
höhere Entkopplung oder explizite fachliche Modellierung
direkterer, häufig einfacherer Kontrollfluss
Hauptrisiko
zusätzliche Abstraktionen ohne echten Änderungsdruck
zu enge Kopplung, sobald Varianten oder Last wachsen
Testfokus
Grenzen, Austauschbarkeit und Fehlerszenarien
Transaktions- und Ablaufkonsistenz
Lab 29 · Bulkhead vs Rate Limiter Isolation vs Durchsatz
Bulkhead
Wähle es, wenn: Fehler und Ressourcenverbrauch zwischen Abhängigkeiten isolieren.
Rate Limiter
Wähle es, wenn: die Anzahl von Aufrufen pro Zeitfenster begrenzen.
Java-Beispiel: Bulkhead
Java-Beispiel: Bulkhead
package com.aydinsude.workbench.decision.block4;
import java.util.concurrent.*;
// Resilience Pattern: Bulkhead - isoliert Ressourcen pro Abhängigkeit.
public final class BulkheadExecutor implements AutoCloseable {
private final ExecutorService pool;
public BulkheadExecutor(int maxConcurrent) { this.pool=Executors.newFixedThreadPool(maxConcurrent); }
public <T> Future<T> submit(Callable<T> task) { return pool.submit(task); }
public void close() { pool.shutdown(); }
}
Java-Beispiel: Rate Limiter
Java-Beispiel: Rate Limiter
package com.aydinsude.workbench.decision.block4;
import java.time.*;
// Resilience Pattern: Rate Limiter - begrenzt Aufrufe pro Zeitfenster.
public final class RateLimiter {
private final int limit; private final Duration window; private int used; private Instant resetAt;
public RateLimiter(int limit, Duration window) { this.limit=limit; this.window=window; this.resetAt=Instant.now().plus(window); }
public synchronized boolean tryAcquire() { var now=Instant.now(); if(!now.isBefore(resetAt)){used=0;resetAt=now.plus(window);} if(used>=limit)return false; used++; return true; }
}
Entscheidungsfrage
Bulkhead
Rate Limiter
Primäre Absicht
Fehler und Ressourcenverbrauch zwischen Abhängigkeiten isolieren
die Anzahl von Aufrufen pro Zeitfenster begrenzen
Typischer Einsatz
höhere Entkopplung oder explizite fachliche Modellierung
direkterer, häufig einfacherer Kontrollfluss
Hauptrisiko
zusätzliche Abstraktionen ohne echten Änderungsdruck
zu enge Kopplung, sobald Varianten oder Last wachsen
Testfokus
Grenzen, Austauschbarkeit und Fehlerszenarien
Transaktions- und Ablaufkonsistenz
Lab 30 · Cache-Aside vs Read-Through Cache Anwendungssteuerung vs Cache-Abstraktion
Cache-Aside
Wähle es, wenn: Laden und Invalidierung explizit in der Anwendung steuern.
Read-Through Cache
Wähle es, wenn: das Nachladen vollständig hinter dem Cache kapseln.
Java-Beispiel: Cache-Aside
Java-Beispiel: Cache-Aside
package com.aydinsude.workbench.decision.block4;
import java.util.*;
// Data Pattern: Cache-Aside - Anwendung steuert Laden und Invalidierung.
public final class CacheAsideCatalog {
private final Map<String,String> cache=new HashMap<>();
private final java.util.function.Function<String,String> source;
public CacheAsideCatalog(java.util.function.Function<String,String> source){this.source=source;}
public String get(String id){return cache.computeIfAbsent(id, source);}
public void invalidate(String id){cache.remove(id);}
}
Java-Beispiel: Read-Through Cache
Java-Beispiel: Read-Through Cache
package com.aydinsude.workbench.decision.block4;
// Data Pattern: Read-Through Cache - Cache kapselt das Nachladen vollständig.
public interface ReadThroughCache<K,V> { V get(K key); }
Entscheidungsfrage
Cache-Aside
Read-Through Cache
Primäre Absicht
Laden und Invalidierung explizit in der Anwendung steuern
das Nachladen vollständig hinter dem Cache kapseln
Typischer Einsatz
höhere Entkopplung oder explizite fachliche Modellierung
direkterer, häufig einfacherer Kontrollfluss
Hauptrisiko
zusätzliche Abstraktionen ohne echten Änderungsdruck
zu enge Kopplung, sobald Varianten oder Last wachsen
Testfokus
Grenzen, Austauschbarkeit und Fehlerszenarien
Transaktions- und Ablaufkonsistenz
Lab 31 · Optimistic Locking vs Pessimistic Locking Konflikterkennung vs Sperre
Optimistic Locking
Wähle es, wenn: seltene Konflikte über Versionsprüfung beim Schreiben erkennen.
Pessimistic Locking
Wähle es, wenn: Konflikte durch exklusiven Zugriff vor der Änderung verhindern.
Java-Beispiel: Optimistic Locking
Java-Beispiel: Optimistic Locking
package com.aydinsude.workbench.decision.block4;
// Concurrency Pattern: Optimistic Locking - Versionsprüfung beim Schreiben.
public record VersionedOrder(String id, long version, String status) {
public VersionedOrder next(String newStatus){ return new VersionedOrder(id, version+1, newStatus); }
}
Java-Beispiel: Pessimistic Locking
Java-Beispiel: Pessimistic Locking
package com.aydinsude.workbench.decision.block4;
import java.util.concurrent.locks.ReentrantLock;
// Concurrency Pattern: Pessimistic Locking - exklusiver Zugriff vor der Änderung.
public final class PessimisticOrderLock {
private final ReentrantLock lock=new ReentrantLock();
public <T>T execute(java.util.function.Supplier<T> action){ lock.lock(); try{return action.get();} finally{lock.unlock();} }
}
Entscheidungsfrage
Optimistic Locking
Pessimistic Locking
Primäre Absicht
seltene Konflikte über Versionsprüfung beim Schreiben erkennen
Konflikte durch exklusiven Zugriff vor der Änderung verhindern
Typischer Einsatz
höhere Entkopplung oder explizite fachliche Modellierung
direkterer, häufig einfacherer Kontrollfluss
Hauptrisiko
zusätzliche Abstraktionen ohne echten Änderungsdruck
zu enge Kopplung, sobald Varianten oder Last wachsen
Testfokus
Grenzen, Austauschbarkeit und Fehlerszenarien
Transaktions- und Ablaufkonsistenz
Lab 32 · Event-Driven vs Request-Response Entkopplung vs unmittelbare Antwort
Event-Driven
Wähle es, wenn: mehrere unabhängige Reaktionen asynchron oder entkoppelt auslösen.
Request-Response
Wähle es, wenn: eine direkte Antwort als Teil des Aufrufvertrags benötigen.
Java-Beispiel: Event-Driven
Java-Beispiel: Event-Driven
package com.aydinsude.workbench.decision.block4;
import java.util.*;
// Architecture Pattern: Event-Driven - Publisher kennt konkrete Reaktionen nicht.
public final class DomainEventBus {
private final List<java.util.function.Consumer<Object>> subscribers=new ArrayList<>();
public void subscribe(java.util.function.Consumer<Object> subscriber){subscribers.add(subscriber);}
public void publish(Object event){subscribers.forEach(s->s.accept(event));}
}
Java-Beispiel: Request-Response
Java-Beispiel: Request-Response
package com.aydinsude.workbench.decision.block4;
// Integration Style: Request-Response - unmittelbare Antwort ist Teil des Vertrags.
public interface RequestResponseClient { String request(String payload); }
Entscheidungsfrage
Event-Driven
Request-Response
Primäre Absicht
mehrere unabhängige Reaktionen asynchron oder entkoppelt auslösen
eine direkte Antwort als Teil des Aufrufvertrags benötigen
Typischer Einsatz
höhere Entkopplung oder explizite fachliche Modellierung
direkterer, häufig einfacherer Kontrollfluss
Hauptrisiko
zusätzliche Abstraktionen ohne echten Änderungsdruck
zu enge Kopplung, sobald Varianten oder Last wachsen
Testfokus
Grenzen, Austauschbarkeit und Fehlerszenarien
Transaktions- und Ablaufkonsistenz
Kapitel 6 Pattern-Entscheidungen im Enterprise-Kontext8 Decision Labs
Migration, Integrationsgrenzen, Persistenz, verteilte Prozesse und kontrollierte Auslieferung.
Pattern-Labs · Abschluss
Pattern Decision Workbench 05
Migration, Integrationsgrenzen, Persistenz, verteilte Prozesse und kontrollierte Auslieferung.
Pattern-Labs: 40/40 Pattern-Labs abgeschlossen
0 Pattern-Labs offen. Der Bereich ist vollständig.
Lab 33 · Factory vs Registry Erzeugung vs Laufzeitkatalog
Factory
Wähle es, wenn: Objekte anhand eines Erzeugungsfalls kapseln.
Registry
Wähle es, wenn: Implementierungen dynamisch über Schlüssel auffinden.
Entscheidung
Factory erzeugt eine passende Instanz; Registry verwaltet bereits bekannte Implementierungen.
Entscheidung
package com.aydinsude.workbench.decision.block5;
import java.util.Map; import java.util.function.Supplier;
public final class FactoryRegistryExamples {
private FactoryRegistryExamples() {}
// Creational Pattern: Factory - kapselt die Erzeugungsentscheidung.
public static final class ReportFactory { public Report create(String kind){ return switch(kind){case "pdf"->new Report("PDF"); case "csv"->new Report("CSV"); default->throw new IllegalArgumentException(kind);};}}
// Pattern: Registry - Laufzeitkatalog bekannter Implementierungen.
public static final class ReportRegistry { private final Map<String,Supplier<Report>> entries; public ReportRegistry(Map<String,Supplier<Report>> entries){this.entries=Map.copyOf(entries);} public Report resolve(String key){var s=entries.get(key); if(s==null) throw new IllegalArgumentException(key); return s.get();}}
public record Report(String format) {}
}
Entscheidungsfrage
Factory
Registry
Primäre Absicht
Objekte anhand eines Erzeugungsfalls kapseln
Implementierungen dynamisch über Schlüssel auffinden
Hauptrisiko
Factory kann mit wachsender Variantenanzahl zentral werden.
Registry kann zu einem versteckten Service Locator entarten.
Prüffrage
Ist die zusätzliche Struktur durch reale Änderungsachsen gerechtfertigt?
Bleibt die einfachere Lösung unter Wachstum noch verständlich?
Lab 34 · Unit of Work vs Repository Transaktion vs Sammlung
Unit of Work
Wähle es, wenn: mehrere Änderungen als atomare Arbeitseinheit koordinieren.
Repository
Wähle es, wenn: Aggregate wie eine fachliche Sammlung laden und speichern.
Entscheidung
Repository abstrahiert Zugriff; Unit of Work koordiniert Commit und Rollback über mehrere Änderungen.
Entscheidung
package com.aydinsude.workbench.decision.block5;
import java.util.*;
public final class PersistenceBoundaryExamples {
private PersistenceBoundaryExamples() {}
// DDD Pattern: Repository - fachliche Sammlung von Aggregates.
public interface OrderRepository { Optional<Order> find(String id); void save(Order order); }
// Enterprise Pattern: Unit of Work - koordiniert atomaren Commit mehrerer Änderungen.
public interface UnitOfWork { void register(Order order); void commit(); void rollback(); }
public record Order(String id, String state) {}
}
Entscheidungsfrage
Unit of Work
Repository
Primäre Absicht
mehrere Änderungen als atomare Arbeitseinheit koordinieren
Aggregate wie eine fachliche Sammlung laden und speichern
Hauptrisiko
Unit of Work kann Transaktionsgrenzen verschleiern.
Repository kann zu generischem CRUD ohne Domänensprache verarmen.
Prüffrage
Ist die zusätzliche Struktur durch reale Änderungsachsen gerechtfertigt?
Bleibt die einfachere Lösung unter Wachstum noch verständlich?
Lab 35 · Domain Event vs Integration Event Domäne vs Systemgrenze
Domain Event
Wähle es, wenn: eine fachlich relevante Tatsache innerhalb einer Domäne ausdrücken.
Integration Event
Wähle es, wenn: eine stabile Nachricht an andere Systeme veröffentlichen.
package com.aydinsude.workbench.decision.block5;
import java.time.Instant;
public final class EventBoundaryExamples {
private EventBoundaryExamples() {}
// DDD Pattern: Domain Event - domänennahe Tatsache im Bounded Context.
public record ClaimApproved(String claimId, Instant occurredAt) {}
// Integration Pattern: Integration Event - stabiler, versionierter externer Vertrag.
public record ClaimApprovedV1(String eventId, String claimReference, String approvedAt, int schemaVersion) {}
}
Entscheidungsfrage
Domain Event
Integration Event
Primäre Absicht
eine fachlich relevante Tatsache innerhalb einer Domäne ausdrücken
eine stabile Nachricht an andere Systeme veröffentlichen
Hauptrisiko
Domain Events sollten nicht ungeprüft extern veröffentlicht werden.
Integration Events dürfen das interne Modell nicht offenlegen.
Prüffrage
Ist die zusätzliche Struktur durch reale Änderungsachsen gerechtfertigt?
Bleibt die einfachere Lösung unter Wachstum noch verständlich?
Lab 36 · Anti-Corruption Layer vs Adapter Modellschutz vs Schnittstellenanpassung
Anti-Corruption Layer
Wähle es, wenn: das eigene Domänenmodell gegen ein fremdes Modell schützen.
Adapter
Wähle es, wenn: eine inkompatible technische Schnittstelle passend machen.
Entscheidung
Ein Adapter übersetzt eine Schnittstelle; eine ACL übersetzt zusätzlich Begriffe, Regeln und Semantik.
Entscheidung
package com.aydinsude.workbench.decision.block5;
public final class IntegrationBoundaryExamples {
private IntegrationBoundaryExamples() {}
public interface LegacyCustomerApi { String loadRaw(String id); }
// Structural Pattern: Adapter - passt eine technische Schnittstelle an.
public record CustomerAdapter(LegacyCustomerApi api){ public String name(String id){return api.loadRaw(id).split(";")[0];}}
// DDD Pattern: Anti-Corruption Layer - schützt Begriffe und Regeln des eigenen Modells.
public record CustomerAcl(LegacyCustomerApi api){ public Customer translate(String id){var p=api.loadRaw(id).split(";"); return new Customer(new CustomerId(id), p[0].trim(), "A".equals(p[1])?Status.ACTIVE:Status.BLOCKED);}}
public record CustomerId(String value){} public enum Status{ACTIVE,BLOCKED} public record Customer(CustomerId id,String name,Status status){}
}
Entscheidungsfrage
Anti-Corruption Layer
Adapter
Primäre Absicht
das eigene Domänenmodell gegen ein fremdes Modell schützen
eine inkompatible technische Schnittstelle passend machen
Hauptrisiko
ACL ist für triviale Integrationen oft zu schwer.
Ein einzelner Adapter reicht bei semantischen Konflikten nicht.
Prüffrage
Ist die zusätzliche Struktur durch reale Änderungsachsen gerechtfertigt?
Bleibt die einfachere Lösung unter Wachstum noch verständlich?
Lab 37 · Strangler Fig vs Big Bang Rewrite Migration vs Ersatz
Strangler Fig
Wähle es, wenn: ein Legacy-System schrittweise durch neue Pfade ersetzen.
Big Bang Rewrite
Wähle es, wenn: ein System vollständig neu bauen und zu einem Stichtag ersetzen.
Entscheidung
Strangler reduziert Migrationsrisiko; Big Bang kann bei sehr kleinen, klar abgegrenzten Systemen schneller sein.
Entscheidung
package com.aydinsude.workbench.decision.block5;
import java.util.Set;
public final class MigrationExamples {
private MigrationExamples() {}
// Architecture Pattern: Strangler Fig - routet migrierte Fähigkeiten schrittweise neu.
public record StranglerRouter(Set<String> migrated, Handler legacy, Handler modern){ public String route(String capability,String input){return migrated.contains(capability)?modern.handle(input):legacy.handle(input);}}
// Migration Strategy: Big Bang Rewrite - vollständiger Umschaltpunkt.
public record CutoverSwitch(boolean modernEnabled,Handler legacy,Handler modern){public String route(String input){return modernEnabled?modern.handle(input):legacy.handle(input);}}
@FunctionalInterface public interface Handler{String handle(String input);}
}
Entscheidungsfrage
Strangler Fig
Big Bang Rewrite
Primäre Absicht
ein Legacy-System schrittweise durch neue Pfade ersetzen
ein System vollständig neu bauen und zu einem Stichtag ersetzen
Hauptrisiko
Strangler erzeugt vorübergehend Doppelstrukturen.
Big Bang birgt hohes fachliches und terminliches Risiko.
Prüffrage
Ist die zusätzliche Struktur durch reale Änderungsachsen gerechtfertigt?
Bleibt die einfachere Lösung unter Wachstum noch verständlich?
Lab 38 · Process Manager vs Saga Orchestrierung vs Transaktionskonzept
Process Manager
Wähle es, wenn: einen langlebigen fachlichen Prozess mit Zustand koordinieren.
Saga
Wähle es, wenn: eine verteilte Transaktion über lokale Schritte und Kompensationen realisieren.
Entscheidung
Process Manager ist ein zustandsbehafteter Koordinator; Saga beschreibt Konsistenz über lokale Transaktionen.
Entscheidung
package com.aydinsude.workbench.decision.block5;
import java.util.*;
public final class LongRunningProcessExamples {
private LongRunningProcessExamples() {}
// Enterprise Pattern: Process Manager - hält Prozesszustand und entscheidet den nächsten Schritt.
public static final class OnboardingProcessManager { private Stage stage=Stage.STARTED; public Command on(Event e){ stage=switch(e){case IDENTITY_OK->Stage.IDENTITY_VERIFIED; case CONTRACT_OK->Stage.COMPLETED; case FAILED->Stage.COMPENSATING;}; return new Command(stage.name());}}
// Distributed Pattern: Saga - lokale Schritte mit expliziten Kompensationen.
public record SagaStep(Runnable action,Runnable compensation){}
public static final class Saga { public void execute(List<SagaStep> steps){var done=new ArrayDeque<SagaStep>(); try{for(var s:steps){s.action().run();done.push(s);}}catch(RuntimeException ex){done.forEach(s->s.compensation().run());throw ex;}}}
public enum Event{IDENTITY_OK,CONTRACT_OK,FAILED} public enum Stage{STARTED,IDENTITY_VERIFIED,COMPLETED,COMPENSATING} public record Command(String name){}
}
Entscheidungsfrage
Process Manager
Saga
Primäre Absicht
einen langlebigen fachlichen Prozess mit Zustand koordinieren
eine verteilte Transaktion über lokale Schritte und Kompensationen realisieren
Hauptrisiko
Process Manager kann zum zentralen God Object werden.
Saga braucht explizite Kompensationen und Idempotenz.
Prüffrage
Ist die zusätzliche Struktur durch reale Änderungsachsen gerechtfertigt?
Bleibt die einfachere Lösung unter Wachstum noch verständlich?
Lab 39 · Materialized View vs Cache Projektion vs Beschleunigung
Materialized View
Wähle es, wenn: eine gezielt modellierte Leseprojektion persistent aufbauen.
Materialized View ist Teil des Lesemodells; Cache ist eine Optimierung und darf fachlich entbehrlich sein.
Entscheidung
package com.aydinsude.workbench.decision.block5;
import java.time.*; import java.util.*; import java.util.function.Function;
public final class ReadModelExamples {
private ReadModelExamples() {}
// CQRS Pattern: Materialized View - persistente fachliche Leseprojektion.
public record OrderSummary(String customerId,long openOrders,long totalCents){}
public interface OrderSummaryView { Optional<OrderSummary> find(String customerId); void project(OrderSummary summary); }
// Performance Pattern: Cache - entbehrliche temporäre Beschleunigung.
public static final class TimedCache<K,V>{private final Map<K,Entry<V>> data=new HashMap<>(); public V get(K key,Duration ttl,Function<K,V> loader){var now=Instant.now();var e=data.get(key);if(e==null||e.created().plus(ttl).isBefore(now)){e=new Entry<>(loader.apply(key),now);data.put(key,e);}return e.value();} private record Entry<V>(V value,Instant created){}}
}
Entscheidungsfrage
Materialized View
Cache
Primäre Absicht
eine gezielt modellierte Leseprojektion persistent aufbauen
teure Zugriffe temporär beschleunigen
Hauptrisiko
Projektionen brauchen Wiederaufbau und Konsistenzstrategie.
Caches verursachen Invalidierungs- und Stale-Data-Probleme.
Prüffrage
Ist die zusätzliche Struktur durch reale Änderungsachsen gerechtfertigt?
Bleibt die einfachere Lösung unter Wachstum noch verständlich?
Lab 40 · Feature Toggle vs Branch by Abstraction Aktivierung vs Strukturmigration
Feature Toggle
Wähle es, wenn: neues Verhalten zur Laufzeit kontrolliert aktivieren.
Branch by Abstraction
Wähle es, wenn: eine Implementierung hinter einer stabilen Abstraktion schrittweise austauschen.
Entscheidung
Toggle steuert Aktivierung; Branch by Abstraction strukturiert den parallelen Austausch im Code.
Entscheidung
package com.aydinsude.workbench.decision.block5;
public final class ChangeDeliveryExamples {
private ChangeDeliveryExamples() {}
// Release Pattern: Feature Toggle - kontrolliert Aktivierung zur Laufzeit.
public record FeatureToggle(boolean enabled, Pricing oldPricing, Pricing newPricing){public long price(long cents){return (enabled?newPricing:oldPricing).price(cents);}}
// Refactoring Pattern: Branch by Abstraction - stabiler Port erlaubt parallelen Austausch.
public static final class PricingService {private Pricing pricing; public PricingService(Pricing pricing){this.pricing=pricing;} public void migrateTo(Pricing replacement){this.pricing=replacement;} public long price(long cents){return pricing.price(cents);}}
@FunctionalInterface public interface Pricing{long price(long cents);}
}
Entscheidungsfrage
Feature Toggle
Branch by Abstraction
Primäre Absicht
neues Verhalten zur Laufzeit kontrolliert aktivieren
eine Implementierung hinter einer stabilen Abstraktion schrittweise austauschen
Hauptrisiko
Alte Toggles werden schnell zu dauerhafter Komplexität.
Abstraktionen können ohne Migrationsziel unnötig bleiben.
Prüffrage
Ist die zusätzliche Struktur durch reale Änderungsachsen gerechtfertigt?
Bleibt die einfachere Lösung unter Wachstum noch verständlich?
Buchteil III
Kompakter Pattern-Katalog
Die Pattern-Karten dienen zum gezielten Nachschlagen. Sie sind nach fachlichen Familien geordnet und ersetzen nicht die ausführlichen Vertiefungen.
Kapitel 7 Pattern-Karten: Erzeugung und Verhalten10 Pattern-Karten
Zehn kompakte Patternkarten verbinden Fachproblem, Struktur, Refactoring-Einstieg, Java-21-Code, Einsatzgrenze und Alternative auf einer großen klappbaren Seite.
Patternkarten · Abschnitt 1
Pattern Explorer
Zehn kompakte Patternkarten verbinden Fachproblem, Struktur, Refactoring-Einstieg, Java-21-Code, Einsatzgrenze und Alternative auf einer großen klappbaren Seite.
Patternkarten: 10/100 Patternkarten abgeschlossen
90 Patternkarten offen.
Karte 01 · Strategy Problem → Struktur → Java → Grenze
Fachliches Problem
Austauschbare Preis- oder Routingregeln
Code Smell
Viele if/switch-Zweige nach Typ
Struktur
Variierende Algorithmen hinter einer stabilen Schnittstelle
Alternative
State, wenn das Verhalten vom Lebenszykluszustand abhängt
Refactoring-Einstieg
Änderungsachse und bestehendes Verhalten mit Tests sichern.
Stabilen Vertrag extrahieren.
Eine konkrete Variante hinter den Vertrag verschieben.
Aufrufer auf den Vertrag umstellen und Alternativen vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
import java.math.BigDecimal;
public final class StrategyPatternLab {
public record Order(BigDecimal total) {
}
public interface DiscountStrategy {
BigDecimal apply(Order order);
}
public record PricingService(DiscountStrategy strategy) {
public BigDecimal price(Order o) {
return strategy.apply(o);
}
}
}
Verwenden
Nicht verwenden
Testfokus
Wenn variierende algorithmen hinter einer stabilen schnittstelle.
Bei nur einer stabilen Variante ohne echte Änderungsachse.
Vertragstest plus Tests je Variante.
Karte 02 · State Problem → Struktur → Java → Grenze
Fachliches Problem
Verhalten entlang eines Objektlebenszyklus
Code Smell
Statusprüfungen in vielen Methoden
Struktur
Zustandsobjekte kontrollieren erlaubte Übergänge
Alternative
Strategy, wenn kein Übergangsmodell benötigt wird
Refactoring-Einstieg
Änderungsachse und bestehendes Verhalten mit Tests sichern.
Stabilen Vertrag extrahieren.
Eine konkrete Variante hinter den Vertrag verschieben.
Aufrufer auf den Vertrag umstellen und Alternativen vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
public final class StatePatternLab {
public sealed interface State permits Draft,Approved {
State approve();
}
public record Draft() implements State {
public State approve() {
return new Approved();
}
}
public record Approved() implements State {
public State approve() {
return this;
}
}
}
Verwenden
Nicht verwenden
Testfokus
Wenn zustandsobjekte kontrollieren erlaubte übergänge.
Bei nur einer stabilen Variante ohne echte Änderungsachse.
Vertragstest plus Tests je Variante.
Karte 03 · Adapter Problem → Struktur → Java → Grenze
Fachliches Problem
Fremde oder alte Schnittstelle anbinden
Code Smell
Providerdetails dringen in den Use Case
Struktur
Übersetzt Port und Provider-API
Alternative
Facade, wenn keine Schnittstellenübersetzung nötig ist
Refactoring-Einstieg
Änderungsachse und bestehendes Verhalten mit Tests sichern.
Stabilen Vertrag extrahieren.
Eine konkrete Variante hinter den Vertrag verschieben.
Aufrufer auf den Vertrag umstellen und Alternativen vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
public final class AdapterPatternLab {
public interface PaymentPort {
String charge(long cents);
}
public interface LegacyGateway {
String pay(String amount);
}
public record LegacyPaymentAdapter(LegacyGateway gateway) implements PaymentPort {
public String charge(long cents) {
return gateway.pay(Long.toString(cents));
}
}
}
Verwenden
Nicht verwenden
Testfokus
Wenn übersetzt port und provider-api.
Bei nur einer stabilen Variante ohne echte Änderungsachse.
Vertragstest plus Tests je Variante.
Karte 04 · Facade Problem → Struktur → Java → Grenze
Fachliches Problem
Komplexes Subsystem vereinfachen
Code Smell
Aufrufer kennen zu viele technische Schritte
Struktur
Ein fachlicher Einstiegspunkt orchestriert mehrere Dienste
Alternative
Adapter, wenn inkompatible Verträge übersetzt werden
Refactoring-Einstieg
Änderungsachse und bestehendes Verhalten mit Tests sichern.
Stabilen Vertrag extrahieren.
Eine konkrete Variante hinter den Vertrag verschieben.
Aufrufer auf den Vertrag umstellen und Alternativen vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
public final class FacadePatternLab {
public interface Stock {
void reserve(String sku);
}
public interface Billing {
void charge(String customer);
}
public record CheckoutFacade(Stock stock,Billing billing) {
public void checkout(String sku,String customer) {
stock.reserve(sku);
billing.charge(customer);
}
}
}
Verwenden
Nicht verwenden
Testfokus
Wenn ein fachlicher einstiegspunkt orchestriert mehrere dienste.
Bei nur einer stabilen Variante ohne echte Änderungsachse.
Vertragstest plus Tests je Variante.
Karte 05 · Builder Problem → Struktur → Java → Grenze
Fachliches Problem
Komplexes Objekt sicher erzeugen
Code Smell
Viele optionale Konstruktorparameter
Struktur
Schrittweise Konstruktion mit finaler Validierung
Alternative
Factory Method für wenige klar benannte Varianten
Refactoring-Einstieg
Änderungsachse und bestehendes Verhalten mit Tests sichern.
Stabilen Vertrag extrahieren.
Eine konkrete Variante hinter den Vertrag verschieben.
Aufrufer auf den Vertrag umstellen und Alternativen vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
public final class BuilderPatternLab {
public record Report(String title,boolean confidential,int retentionDays) {
}
public static final class Builder {
private String title;
private boolean confidential;
private int retentionDays=30;
public Builder title(String v) {
title=v;
return this;
}
public Builder confidential(boolean v) {
confidential=v;
return this;
}
public Builder retentionDays(int v) {
retentionDays=v;
return this;
}
public Report build() {
if(title==null||title.isBlank())throw new IllegalStateException("title");
return new Report(title,confidential,retentionDays);
}
}
}
Verwenden
Nicht verwenden
Testfokus
Wenn schrittweise konstruktion mit finaler validierung.
Bei nur einer stabilen Variante ohne echte Änderungsachse.
Vertragstest plus Tests je Variante.
Karte 06 · Factory Method Problem → Struktur → Java → Grenze
Fachliches Problem
Erzeugungsentscheidung kapseln
Code Smell
Aufrufer instanziieren Implementierungen direkt
Struktur
Unterklasse oder Registry liefert konkreten Typ
Alternative
Builder bei komplexem Aufbau eines einzelnen Objekts
Refactoring-Einstieg
Änderungsachse und bestehendes Verhalten mit Tests sichern.
Stabilen Vertrag extrahieren.
Eine konkrete Variante hinter den Vertrag verschieben.
Aufrufer auf den Vertrag umstellen und Alternativen vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
public final class FactoryMethodPatternLab {
public interface Exporter {
String export(String value);
}
public static Exporter forType(String type) {
return switch(type) {
case "json"->v->"{\"value\":\""+v+"\"}";
case "text"->v->v;
default->throw new IllegalArgumentException(type);
};
}
}
Verwenden
Nicht verwenden
Testfokus
Wenn unterklasse oder registry liefert konkreten typ.
Bei nur einer stabilen Variante ohne echte Änderungsachse.
Vertragstest plus Tests je Variante.
Karte 07 · Decorator Problem → Struktur → Java → Grenze
Fachliches Problem
Verhalten dynamisch schichten
Code Smell
Logging, Metriken oder Retry werden dupliziert
Struktur
Gleicher Vertrag, zusätzliche Verantwortung
Alternative
Proxy, wenn Zugriff oder Standort kontrolliert wird
Refactoring-Einstieg
Änderungsachse und bestehendes Verhalten mit Tests sichern.
Stabilen Vertrag extrahieren.
Eine konkrete Variante hinter den Vertrag verschieben.
Aufrufer auf den Vertrag umstellen und Alternativen vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
public final class DecoratorPatternLab {
public interface Handler {
String handle(String input);
}
public record TracingDecorator(Handler delegate) implements Handler {
public String handle(String input) {
long start=System.nanoTime();
try {
return delegate.handle(input);
}
finally {
System.out.println(System.nanoTime()-start);
}
}
}
}
Verwenden
Nicht verwenden
Testfokus
Wenn gleicher vertrag, zusätzliche verantwortung.
Bei nur einer stabilen Variante ohne echte Änderungsachse.
Vertragstest plus Tests je Variante.
Karte 08 · Observer Problem → Struktur → Java → Grenze
Fachliches Problem
Mehrere lokale Reaktionen auslösen
Code Smell
Direkte Kopplung an viele Folgeaktionen
Struktur
Subjekte benachrichtigen registrierte Beobachter
Alternative
Domain Events für fachlich persistente Ereignisse
Refactoring-Einstieg
Änderungsachse und bestehendes Verhalten mit Tests sichern.
Stabilen Vertrag extrahieren.
Eine konkrete Variante hinter den Vertrag verschieben.
Aufrufer auf den Vertrag umstellen und Alternativen vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
import java.util.*;
public final class ObserverPatternLab {
public interface Listener {
void onChanged(String value);
}
public static final class Subject {
private final List<Listener> listeners=new ArrayList<>();
public void add(Listener l) {
listeners.add(l);
}
public void change(String v) {
listeners.forEach(l->l.onChanged(v));
}
}
}
Verwenden
Nicht verwenden
Testfokus
Wenn subjekte benachrichtigen registrierte beobachter.
Bei nur einer stabilen Variante ohne echte Änderungsachse.
Vertragstest plus Tests je Variante.
Karte 09 · Specification Problem → Struktur → Java → Grenze
Fachliches Problem
Fachregeln kombinierbar ausdrücken
Code Smell
Regeln sind über Services und Conditionals verteilt
Struktur
Prädikate mit and/or/not und Fachsprache
Alternative
Policy für eine komplette Entscheidungsstrategie
Refactoring-Einstieg
Änderungsachse und bestehendes Verhalten mit Tests sichern.
Stabilen Vertrag extrahieren.
Eine konkrete Variante hinter den Vertrag verschieben.
Aufrufer auf den Vertrag umstellen und Alternativen vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
public final class SpecificationPatternLab {
public interface Specification<T> {
boolean test(T v);
default Specification<T> and(Specification<T> other) {
return v->test(v)&&other.test(v);
}
}
public record Customer(int age,boolean blocked) {
}
public static Specification<Customer> eligible() {
return ((Specification<Customer>)c->c.age()>=18).and(c->!c.blocked());
}
}
Verwenden
Nicht verwenden
Testfokus
Wenn prädikate mit and/or/not und fachsprache.
Bei nur einer stabilen Variante ohne echte Änderungsachse.
Vertragstest plus Tests je Variante.
Karte 10 · Repository Problem → Struktur → Java → Grenze
Fachliches Problem
Aggregatzugriff fachlich kapseln
Code Smell
SQL/ORM-Aufrufe im Anwendungsservice
Struktur
Collection-artiger Port für Aggregate
Alternative
DAO für technisch orientierte Tabellenzugriffe
Refactoring-Einstieg
Änderungsachse und bestehendes Verhalten mit Tests sichern.
Stabilen Vertrag extrahieren.
Eine konkrete Variante hinter den Vertrag verschieben.
Aufrufer auf den Vertrag umstellen und Alternativen vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
import java.util.*;
public final class RepositoryPatternLab {
public record Order(String id) {
}
public interface OrderRepository {
Optional<Order> find(String id);
void save(Order order);
}
public static final class InMemory implements OrderRepository {
private final Map<String,Order> data=new HashMap<>();
public Optional<Order> find(String id) {
return Optional.ofNullable(data.get(id));
}
public void save(Order o) {
data.put(o.id(),o);
}
}
}
Verwenden
Nicht verwenden
Testfokus
Wenn collection-artiger port für aggregate.
Bei nur einer stabilen Variante ohne echte Änderungsachse.
Vertragstest plus Tests je Variante.
Kapitel 8 Pattern-Karten: Koordination und Struktur10 Pattern-Karten
Zehn weitere Patternkarten erklären Zugriff, Operationen, Verarbeitungsketten, Objektstrukturen, Erzeugungsfamilien und Komponentenkoordination auf einer großen klappbaren Seite.
Patternkarten · Abschnitt 2
Pattern Explorer · Koordination und Struktur
Zehn weitere Patternkarten erklären Zugriff, Operationen, Verarbeitungsketten, Objektstrukturen, Erzeugungsfamilien und Komponentenkoordination auf einer großen klappbaren Seite.
Patternkarten: 20/100 Patternkarten abgeschlossen
80 Patternkarten offen.
Karte 11 · Proxy Problem → Struktur → Java → Grenze
Fachliches Problem
Zugriff, Remote-Aufruf oder teure Initialisierung kontrollieren
Code Smell
Aufrufer mischen Fachlogik mit Zugriffsprüfung und Remote-Details
Struktur
Ein stellvertretendes Objekt implementiert denselben Vertrag und kontrolliert den Zugriff
Alternative
Decorator, wenn zusätzliche Verantwortung statt Zugriffskontrolle im Vordergrund steht
Refactoring-Einstieg
Bestehendes Verhalten mit Characterization Tests sichern.
Die konkrete Änderungsachse und den stabilen Vertrag benennen.
Eine Verantwortung hinter den neuen Vertrag verschieben.
Aufrufer schrittweise migrieren und die Alternative bewusst vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
// Design Pattern: Proxy
// Zweck: Zugriff auf einen fachlichen Dienst kontrollieren, ohne dessen Vertrag zu verändern.
public final class ProxyPatternLab {
public interface CustomerProfile { String read(String customerId); }
public record SecuredProfileProxy(CustomerProfile target, boolean allowed) implements CustomerProfile {
@Override public String read(String customerId) {
if (!allowed) throw new SecurityException("profile access denied");
return target.read(customerId);
}
}
}
Verwenden
Nicht verwenden
Testfokus
Autorisierung, Lazy Loading, Remote- oder Caching-Grenzen
Wenn nur lokales Verhalten ergänzt wird und keine Stellvertretersemantik besteht
Vertragstest für Proxy und Zielobjekt; Negativtest für verweigerten Zugriff
Karte 12 · Command Problem → Struktur → Java → Grenze
Fachliches Problem
Geschäftsoperationen als explizite, protokollierbare Objekte behandeln
Code Smell
Controller oder Scheduler kennen jeden Ausführungsschritt direkt
Struktur
Ein Command kapselt Eingaben und Ausführung; ein Handler führt ihn aus
Alternative
Strategy für austauschbare Algorithmen ohne Operationsobjekt
Refactoring-Einstieg
Bestehendes Verhalten mit Characterization Tests sichern.
Die konkrete Änderungsachse und den stabilen Vertrag benennen.
Eine Verantwortung hinter den neuen Vertrag verschieben.
Aufrufer schrittweise migrieren und die Alternative bewusst vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
// Design Pattern: Command
// Zweck: Eine Geschäftsoperation mit ihren Eingaben explizit und testbar kapseln.
public final class CommandPatternLab {
public record ApproveInvoice(String invoiceId, String approver) { }
public interface Handler<C> { void handle(C command); }
public record ApproveInvoiceHandler(InvoicePort invoices) implements Handler<ApproveInvoice> {
@Override public void handle(ApproveInvoice command) {
invoices.approve(command.invoiceId(), command.approver());
}
}
public interface InvoicePort { void approve(String invoiceId, String approver); }
}
Verwenden
Nicht verwenden
Testfokus
Audit, Queueing, Retry, Undo oder entkoppelte Anwendungsfälle
Für triviale direkte Methodenaufrufe ohne zusätzliche Lebenszyklusanforderungen
Command-Handler isoliert testen; Idempotenz und Fehlerpfade prüfen
Karte 13 · Chain of Responsibility Problem → Struktur → Java → Grenze
Fachliches Problem
Mehrere Prüf- oder Verarbeitungsschritte flexibel verketten
Code Smell
Eine große Methode enthält eine starre Folge verschachtelter Prüfungen
Struktur
Jeder Handler bearbeitet einen Aspekt und delegiert kontrolliert an den nächsten
Alternative
Pipeline, wenn jeder Schritt immer Daten transformiert und alle Stufen durchlaufen werden
Refactoring-Einstieg
Bestehendes Verhalten mit Characterization Tests sichern.
Die konkrete Änderungsachse und den stabilen Vertrag benennen.
Eine Verantwortung hinter den neuen Vertrag verschieben.
Aufrufer schrittweise migrieren und die Alternative bewusst vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
// Design Pattern: Chain of Responsibility
// Zweck: Unabhängige Prüfungen in einer austauschbaren Reihenfolge verketten.
public final class ChainOfResponsibilityPatternLab {
public record Request(int amount, boolean blocked) { }
public interface Check {
Decision check(Request request);
default Check then(Check next) {
return request -> { var result = check(request); return result.allowed() ? next.check(request) : result; };
}
}
public record Decision(boolean allowed, String reason) {
public static Decision allow() { return new Decision(true, "ok"); }
public static Decision reject(String reason) { return new Decision(false, reason); }
}
}
Verwenden
Nicht verwenden
Testfokus
Validierung, Fraud Checks, Freigaben oder Support-Routing
Wenn Reihenfolge und Anzahl der Schritte dauerhaft trivial bleiben
Jeden Handler separat und die Reihenfolge als Integrationskette testen
Karte 14 · Template Method Problem → Struktur → Java → Grenze
Fachliches Problem
Gemeinsamen Ablauf stabil halten und einzelne Schritte variieren
Code Smell
Mehrere Importer duplizieren denselben Ablauf mit kleinen Abweichungen
Struktur
Eine Basisklasse definiert den Algorithmusrahmen; Hooks variieren Teilschritte
Alternative
Strategy, wenn Komposition und Laufzeitaustausch wichtiger als Vererbung sind
Refactoring-Einstieg
Bestehendes Verhalten mit Characterization Tests sichern.
Die konkrete Änderungsachse und den stabilen Vertrag benennen.
Eine Verantwortung hinter den neuen Vertrag verschieben.
Aufrufer schrittweise migrieren und die Alternative bewusst vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
// Design Pattern: Template Method
// Zweck: Einen stabilen Importablauf vorgeben und nur fachliche Teilschritte variieren.
public final class TemplateMethodPatternLab {
public abstract static class ImportJob {
public final int run(String raw) {
var normalized = normalize(raw);
validate(normalized);
return persist(normalized);
}
protected String normalize(String raw) { return raw.trim(); }
protected abstract void validate(String value);
protected abstract int persist(String value);
}
}
Verwenden
Nicht verwenden
Testfokus
Stabile Batch-, Import- oder Exportabläufe mit wenigen Varianten
Bei vielen unabhängigen Änderungsachsen oder tiefer Vererbungshierarchie
Ablaufreihenfolge und jeden Hook über konkrete Testimplementierungen prüfen
Karte 15 · Composite Problem → Struktur → Java → Grenze
Fachliches Problem
Einzelobjekte und Objektgruppen einheitlich behandeln
Code Smell
Aufrufer unterscheiden überall zwischen Blatt und Gruppe
Struktur
Blätter und Kompositionen implementieren denselben Vertrag
Alternative
Visitor, wenn viele neue Operationen über einer stabilen Struktur benötigt werden
Refactoring-Einstieg
Bestehendes Verhalten mit Characterization Tests sichern.
Die konkrete Änderungsachse und den stabilen Vertrag benennen.
Eine Verantwortung hinter den neuen Vertrag verschieben.
Aufrufer schrittweise migrieren und die Alternative bewusst vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
import java.math.BigDecimal;
import java.util.List;
// Design Pattern: Composite
// Zweck: Einzelpositionen und Pakete über denselben Preisvertrag behandeln.
public final class CompositePatternLab {
public interface PriceComponent { BigDecimal price(); }
public record Item(BigDecimal price) implements PriceComponent { }
public record Bundle(List<PriceComponent> children) implements PriceComponent {
public Bundle { children = List.copyOf(children); }
@Override public BigDecimal price() {
return children.stream().map(PriceComponent::price).reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
}
Verwenden
Nicht verwenden
Testfokus
Produktpakete, Berechtigungsbäume, Organisations- oder Dokumentstrukturen
Wenn Hierarchie flach ist und Gruppen keine fachliche Bedeutung besitzen
Blätter, verschachtelte Gruppen und leere Kompositionen testen
Karte 16 · Visitor Problem → Struktur → Java → Grenze
Fachliches Problem
Neue Operationen über einer stabilen Objektstruktur ergänzen
Code Smell
Viele instanceof- oder switch-Blöcke für dieselben Elementtypen
Struktur
Elemente akzeptieren einen Visitor; Operationen werden im Visitor gebündelt
Alternative
Composite, wenn primär die Baumstruktur und einheitliche Behandlung wichtig sind
Refactoring-Einstieg
Bestehendes Verhalten mit Characterization Tests sichern.
Die konkrete Änderungsachse und den stabilen Vertrag benennen.
Eine Verantwortung hinter den neuen Vertrag verschieben.
Aufrufer schrittweise migrieren und die Alternative bewusst vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
// Design Pattern: Visitor
// Zweck: Neue Dokumentoperationen hinzufügen, ohne die Dokumenttypen zu verändern.
public final class VisitorPatternLab {
public sealed interface Document permits Invoice, Contract { <R> R accept(Visitor<R> visitor); }
public record Invoice(long cents) implements Document { public <R> R accept(Visitor<R> v){ return v.visit(this); } }
public record Contract(String title) implements Document { public <R> R accept(Visitor<R> v){ return v.visit(this); } }
public interface Visitor<R> { R visit(Invoice invoice); R visit(Contract contract); }
}
Verwenden
Nicht verwenden
Testfokus
Reporting, Export, Validierung oder Berechnung über stabile Elementtypen
Wenn häufig neue Elementtypen hinzukommen und Operationen stabil bleiben
Jede Element-Visitor-Kombination und vollständige Traversierung testen
Karte 17 · Bridge Problem → Struktur → Java → Grenze
Fachliches Problem
Zwei unabhängige Änderungsachsen ohne Klassenexplosion kombinieren
Code Smell
Für jeden Dokumenttyp und Ausgabekanal entsteht eine neue Unterklasse
Struktur
Abstraktion und Implementierung werden getrennt und über Komposition verbunden
Alternative
Adapter, wenn eine bereits existierende inkompatible Schnittstelle übersetzt wird
Refactoring-Einstieg
Bestehendes Verhalten mit Characterization Tests sichern.
Die konkrete Änderungsachse und den stabilen Vertrag benennen.
Eine Verantwortung hinter den neuen Vertrag verschieben.
Aufrufer schrittweise migrieren und die Alternative bewusst vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
// Design Pattern: Bridge
// Zweck: Fachlichen Bericht und technische Ausgabe unabhängig variieren.
public final class BridgePatternLab {
public interface Renderer { String render(String title, String body); }
public abstract static class Report {
private final Renderer renderer;
protected Report(Renderer renderer) { this.renderer = renderer; }
protected String render(String title, String body) { return renderer.render(title, body); }
public abstract String create();
}
public static final class RiskReport extends Report {
public RiskReport(Renderer renderer) { super(renderer); }
@Override public String create() { return render("Risk", "low"); }
}
}
Verwenden
Nicht verwenden
Testfokus
Dokumenttyp x Renderer, Fachprozess x Provider oder Gerät x Plattform
Wenn nur eine Änderungsachse existiert oder Kombinationen nicht unabhängig sind
Abstraktion gegen mehrere Implementierungen als Vertragsmatrix testen
Karte 18 · Abstract Factory Problem → Struktur → Java → Grenze
Eine Factory liefert mehrere aufeinander abgestimmte Produkte
Alternative
Factory Method für nur einen Produkttyp; Builder für komplexe Einzelobjekte
Refactoring-Einstieg
Bestehendes Verhalten mit Characterization Tests sichern.
Die konkrete Änderungsachse und den stabilen Vertrag benennen.
Eine Verantwortung hinter den neuen Vertrag verschieben.
Aufrufer schrittweise migrieren und die Alternative bewusst vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
// Design Pattern: Abstract Factory
// Zweck: Zusammenpassende Storage- und Queue-Adapter einer Umgebung erzeugen.
public final class AbstractFactoryPatternLab {
public interface Storage { String store(String value); }
public interface Queue { void publish(String event); }
public interface InfrastructureFactory { Storage storage(); Queue queue(); }
public record Runtime(InfrastructureFactory factory) {
public String process(String value) {
var id = factory.storage().store(value);
factory.queue().publish("stored:" + id);
return id;
}
}
}
Verwenden
Nicht verwenden
Testfokus
Cloud-Provider, Datenbankfamilien, UI-Themes oder Testumgebungen
Wenn nur ein unabhängiges Produkt erzeugt wird
Jede Produktfamilie als konsistente Kombination testen
Karte 19 · Prototype Problem → Struktur → Java → Grenze
Fachliches Problem
Teure oder komplex konfigurierte Objekte aus Vorlagen ableiten
Code Smell
Ähnliche Konfigurationen werden mehrfach vollständig aufgebaut
Struktur
Ein Prototyp liefert kontrollierte Kopien mit gezielten Änderungen
Alternative
Builder, wenn jede Instanz schrittweise aus Eingaben konstruiert wird
Refactoring-Einstieg
Bestehendes Verhalten mit Characterization Tests sichern.
Die konkrete Änderungsachse und den stabilen Vertrag benennen.
Eine Verantwortung hinter den neuen Vertrag verschieben.
Aufrufer schrittweise migrieren und die Alternative bewusst vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
import java.util.List;
// Design Pattern: Prototype
// Zweck: Unveränderliche Vertragsvorlagen sicher ableiten.
public final class PrototypePatternLab {
public record ContractTemplate(String name, int retentionDays, List<String> clauses) {
public ContractTemplate { clauses = List.copyOf(clauses); }
public ContractTemplate copyAs(String newName) {
return new ContractTemplate(newName, retentionDays, clauses);
}
}
}
Verwenden
Nicht verwenden
Testfokus
Vertrags-, Produkt- oder Workflowvorlagen mit vielen gemeinsamen Werten
Bei einfachen Objekten oder unklarer Tiefenkopie veränderlicher Daten
Kopie, Unabhängigkeit und unveränderte Vorlage prüfen
Karte 20 · Mediator Problem → Struktur → Java → Grenze
Fachliches Problem
Viele Komponenten koordinieren, ohne sich gegenseitig direkt zu kennen
Code Smell
Dialog-, Workflow- oder Modulkomponenten referenzieren einander kreuzweise
Struktur
Ein Mediator zentralisiert Interaktionsregeln zwischen Kollegen
Alternative
Observer für einfache Einwegbenachrichtigung ohne zentrale Koordination
Refactoring-Einstieg
Bestehendes Verhalten mit Characterization Tests sichern.
Die konkrete Änderungsachse und den stabilen Vertrag benennen.
Eine Verantwortung hinter den neuen Vertrag verschieben.
Aufrufer schrittweise migrieren und die Alternative bewusst vergleichen.
Java-21-Beispiel
Java-21-Beispiel
package com.aydinsude.workbench.explorer;
// Design Pattern: Mediator
// Zweck: Freigabe, Audit und Benachrichtigung zentral koordinieren.
public final class MediatorPatternLab {
public interface ApprovalMediator { void approved(String caseId); }
public interface AuditPort { void record(String message); }
public interface NotificationPort { void send(String message); }
public record CaseApprovalMediator(AuditPort audit, NotificationPort notifications) implements ApprovalMediator {
@Override public void approved(String caseId) {
audit.record("approved:" + caseId);
notifications.send("case approved " + caseId);
}
}
}
Verwenden
Nicht verwenden
Testfokus
UI-Dialoge, komplexe Workflows oder Modulkoordination
Wenn der Mediator selbst zur unstrukturierten God Class wird
Interaktionsregeln am Mediator und Kollegen isoliert testen
Kapitel 9 Pattern-Karten: Zustand, Ressourcen und Integration10 Pattern-Karten
Zehn weitere Patternkarten verbinden Traversierung, Wiederherstellung, Ressourcenteilung, kleine DSLs, Transaktionskoordination und zuverlässige Ereignisübertragung.
Patternkarten · Abschnitt 3
Pattern Explorer · Zustand, Ressourcen und Integration
Zehn weitere Patternkarten verbinden Traversierung, Wiederherstellung, Ressourcenteilung, kleine DSLs, Transaktionskoordination und zuverlässige Ereignisübertragung.
Patternkarten: 30/100 Patternkarten abgeschlossen
70 Patternkarten offen.
Karte 21 Iterator Sammlungsdurchlauf kapseln, ohne interne Struktur offenzulegen
Code Smell
Aufrufer kennen Indexierung und interne Datenstruktur
Struktur
Ein Iterator liefert Elemente über einen stabilen Traversierungsvertrag
Verwenden
Eigene Traversierungslogik, Baum- oder Seitenfolgen
Nicht verwenden
Für einfache Listen ohne besondere Traversierungssemantik
Alternative
Stream API, wenn nur deklarative Standardoperationen benötigt werden
Testfokus
Reihenfolge, Ende und leere Struktur testen
IteratorPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.Iterator;
import java.util.List;
// Design Pattern: Iterator
// Zweck: Traversierung einer fachlichen Sammlung kapseln.
public final class IteratorPatternLab {
public record AuditTrail(List<String> entries) implements Iterable<String> {
public AuditTrail { entries = List.copyOf(entries); }
@Override public Iterator<String> iterator() { return entries.iterator(); }
}
}
Karte 22 Memento Zustand für kontrolliertes Rücksetzen sichern
Code Smell
Undo-Logik greift direkt auf interne Felder zu
Struktur
Ein unveränderlicher Snapshot speichert den wiederherstellbaren Zustand
Verwenden
Editoren, Konfigurationen oder mehrstufige Formulare
Nicht verwenden
Für triviale Objekte ohne Undo-Anforderung
Alternative
Event Sourcing, wenn die gesamte Historie fachlich dauerhaft relevant ist
Testfokus
Snapshot-Unveränderlichkeit und Restore prüfen
MementoPatternLab.java
package com.aydinsude.workbench.explorer;
// Design Pattern: Memento
// Zweck: Zustand wiederherstellbar sichern, ohne Interna offenzulegen.
public final class MementoPatternLab {
public static final class Draft {
private String text=""; public void change(String value){ text=value; }
public Snapshot snapshot(){ return new Snapshot(text); }
public void restore(Snapshot snapshot){ text=snapshot.text(); }
public String text(){ return text; }
}
public record Snapshot(String text){}
}
Karte 23 Flyweight Viele gleichartige Objekte speichereffizient teilen
Code Smell
Jedes Objekt dupliziert dieselben unveränderlichen Metadaten
Struktur
Geteilte intrinsische Daten werden von externem Zustand getrennt
Verwenden
Katalogdaten, Symbole, Regeldefinitionen oder Formatvorlagen
Nicht verwenden
Wenn Objekte stark individuell und klein in der Anzahl sind
Alternative
Cache, wenn Wiederverwendung primär zeit- statt speichergetrieben ist
Testfokus
Identität geteilter Instanzen und fachliche Gleichheit testen
FlyweightPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.concurrent.ConcurrentHashMap;
// Design Pattern: Flyweight
// Zweck: Unveränderliche Produktmetadaten zwischen Positionen teilen.
public final class FlyweightPatternLab {
public record ProductType(String code,String label){}
public static final class ProductTypePool {
private final ConcurrentHashMap<String,ProductType> cache=new ConcurrentHashMap<>();
public ProductType get(String code){ return cache.computeIfAbsent(code,c->new ProductType(c,"Type "+c)); }
}
}
Karte 24 Interpreter Kleine fachliche Sprache ausführbar modellieren
Code Smell
Regeln werden als unlesbare String-Konkatenation oder if-Kaskade interpretiert
Struktur
Ausdrücke bilden einen Syntaxbaum mit einheitlicher Auswertung
Verwenden
Filter-, Preis- oder Berechtigungs-DSLs mit begrenzter Grammatik
Nicht verwenden
Für große Sprachen; dort Parsergenerator oder Regelengine nutzen
Alternative
Specification für kombinierbare Regeln ohne eigene Sprache
Testfokus
Ausdrücke, Priorität und ungültige Eingaben testen
InterpreterPatternLab.java
package com.aydinsude.workbench.explorer;
// Design Pattern: Interpreter
// Zweck: Kleine fachliche Ausdrücke als Objektbaum auswerten.
public final class InterpreterPatternLab {
public record Context(int amount, boolean vip){}
public interface Expression { boolean eval(Context context); }
public record AmountAtLeast(int value) implements Expression { public boolean eval(Context c){ return c.amount()>=value; } }
public record And(Expression left,Expression right) implements Expression { public boolean eval(Context c){ return left.eval(c)&&right.eval(c); } }
}
Karte 25 Null Object Fehlende Strategie ohne verstreute Null-Prüfungen behandeln
Code Smell
Jeder Aufrufer prüft dieselbe Abhängigkeit auf null
Struktur
Ein neutrales Objekt erfüllt denselben Vertrag mit bewusst leerem Verhalten
Verwenden
Logging, Metriken, Benachrichtigung oder optionale Policy
Nicht verwenden
Wenn fehlende Abhängigkeit fachlich ein Fehler ist
Alternative
Optional, wenn Abwesenheit vom Aufrufer explizit behandelt werden soll
Testfokus
Neutrales Verhalten und Vertragsgleichheit testen
NullObjectPatternLab.java
package com.aydinsude.workbench.explorer;
// Design Pattern: Null Object
// Zweck: Optionale Benachrichtigung ohne null-Verzweigungen modellieren.
public final class NullObjectPatternLab {
public interface Notifier { void send(String message); }
public enum SilentNotifier implements Notifier { INSTANCE; public void send(String message){} }
}
Karte 26 Object Pool Teure, begrenzte Ressourcen kontrolliert wiederverwenden
Code Smell
Ressourcen werden pro Aufruf neu erzeugt oder unkontrolliert geteilt
Struktur
Ein Pool verwaltet Ausleihe, Rückgabe und Kapazität
Verwenden
Teure Parser, native Handles oder begrenzte Sessions
Nicht verwenden
Für billige Java-Objekte; dort erzeugen statt poolen
Alternative
Flyweight für unveränderliche Daten; Connection Pool für spezialisierte Infrastruktur
Testfokus
Kapazität, Rückgabe und Fehler bei Erschöpfung testen
ObjectPoolPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.ArrayDeque;
// Design Pattern: Object Pool
// Zweck: Begrenzte teure Ressourcen kontrolliert wiederverwenden.
public final class ObjectPoolPatternLab {
public static final class Pool<T>{
private final ArrayDeque<T> free=new ArrayDeque<>();
public Pool(java.util.Collection<T> resources){ free.addAll(resources); }
public synchronized T borrow(){ if(free.isEmpty()) throw new IllegalStateException("pool exhausted"); return free.removeFirst(); }
public synchronized void release(T value){ free.addLast(value); }
}
}
Karte 27 Registry Implementierungen nach fachlichem Schlüssel auffindbar machen
Code Smell
Switch-Blöcke verteilen die Auswahl über mehrere Services
Struktur
Eine Registry ordnet Schlüssel explizit registrierten Strategien zu
Verwenden
Provider, Parser, Exporter oder Policy-Auswahl
Nicht verwenden
Wenn nur eine stabile Implementierung existiert
Alternative
Factory, wenn Erzeugung statt Auswahl vorhandener Komponenten im Zentrum steht
Testfokus
Doppelte Schlüssel, unbekannte Schlüssel und Auswahl testen
RegistryPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.Map;
// Design Pattern: Registry
// Zweck: Parser über einen expliziten Format-Schlüssel auswählen.
public final class RegistryPatternLab {
public interface Parser { String parse(String input); }
public record ParserRegistry(Map<String,Parser> parsers){
public ParserRegistry { parsers=Map.copyOf(parsers); }
public Parser resolve(String format){ var p=parsers.get(format); if(p==null) throw new IllegalArgumentException(format); return p; }
}
}
Karte 28 Unit of Work Mehrere Änderungen als konsistente Transaktion koordinieren
Code Smell
Repositories speichern unabhängig und erzeugen Teilzustände
Struktur
Eine Arbeitseinheit sammelt Operationen und bestätigt sie gemeinsam
Verwenden
Mehrere Aggregate innerhalb einer lokalen Transaktionsgrenze
Nicht verwenden
Für einen einzelnen atomaren Repository-Aufruf
Alternative
Saga bei verteilten Transaktionen über mehrere Systeme
Testfokus
Commit, Rollback und Reihenfolge testen
UnitOfWorkPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.ArrayList;
import java.util.List;
// Design Pattern: Unit of Work
// Zweck: Mehrere lokale Änderungen gemeinsam bestätigen.
public final class UnitOfWorkPatternLab {
public static final class UnitOfWork {
private final List<Runnable> changes=new ArrayList<>();
public void register(Runnable change){ changes.add(change); }
public void commit(){ changes.forEach(Runnable::run); changes.clear(); }
}
}
Folgeaktionen werden direkt im Aggregate oder Service aufgerufen
Struktur
Ein unveränderliches Ereignis beschreibt eine abgeschlossene fachliche Tatsache
Verwenden
Audit, Folgeprozesse und entkoppelte fachliche Reaktionen
Nicht verwenden
Für interne Implementierungsdetails ohne fachliche Bedeutung
Alternative
Observer für rein technische In-Process-Benachrichtigung
Testfokus
Ereignisinhalt, Zeitpunkt und auslösende Invariante testen
DomainEventPatternLab.java
package com.aydinsude.workbench.explorer;
import java.time.Instant;
// Design Pattern: Domain Event
// Zweck: Eine abgeschlossene fachliche Tatsache explizit transportieren.
public final class DomainEventPatternLab {
public sealed interface DomainEvent permits OrderApproved {}
public record OrderApproved(String orderId, Instant occurredAt) implements DomainEvent {}
}
Karte 30 Transactional Outbox Datenbankänderung und Ereignis atomar vorbereiten
Code Smell
Dual Write kann Daten speichern, aber Nachricht verlieren
Struktur
Fachdaten und Outbox-Eintrag werden in derselben Transaktion gespeichert
Verwenden
Zuverlässige Integration Events aus transaktionalen Services
Nicht verwenden
Ohne lokale Transaktionsdatenbank oder bei reinem In-Memory-Prozess
Alternative
Direktes Publishing, wenn Verlust tolerierbar und keine Transaktionskopplung nötig ist
Testfokus
Atomare Speicherung, Wiederholung und Idempotenz testen
TransactionalOutboxPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.ArrayList;
import java.util.List;
// Enterprise Pattern: Transactional Outbox
// Zweck: Zustandsänderung und spätere Nachricht atomar vorbereiten.
public final class TransactionalOutboxPatternLab {
public record OutboxMessage(String id,String type,String payload){}
public static final class Transaction {
private final List<OutboxMessage> outbox=new ArrayList<>();
public void saveOrderAndEvent(String orderId){ outbox.add(new OutboxMessage(orderId,"OrderSaved",orderId)); }
public List<OutboxMessage> pending(){ return List.copyOf(outbox); }
}
}
Kapitel 10 Pattern-Karten: Resilienz, Verteilung und Lesemodelle10 Pattern-Karten
Zehn Patternkarten zeigen, wie verteilte Abläufe, Fehlerabschirmung, kontrollierte Last und spezialisierte Lesewege aus problematischem Java-Code entstehen.
Patternkarten · Abschnitt 4
Pattern Explorer · Resilienz, Verteilung und Lesemodelle
Zehn Patternkarten zeigen, wie verteilte Abläufe, Fehlerabschirmung, kontrollierte Last und spezialisierte Lesewege aus problematischem Java-Code entstehen.
Patternkarten: 40/100 Patternkarten abgeschlossen
60 Patternkarten offen.
Karte 31 Saga Verteilte Geschäftsabläufe mit Kompensationen koordinieren
Code Smell
Mehrere Services werden in einer scheinbar atomaren Methode gekoppelt
Struktur
Eine Folge lokaler Transaktionen besitzt explizite Kompensationsschritte
Verwenden
Bestellung, Zahlung, Reservierung und Versand über mehrere Systeme
Nicht verwenden
Für rein lokale Transaktionen innerhalb einer Datenbank
Alternative
Process Manager, wenn die zentrale Ablaufsteuerung dauerhaft Zustand hält
Testfokus
Erfolg, Teilfehler, Kompensation und Idempotenz testen
SagaPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.ArrayDeque;
import java.util.Deque;
// Enterprise Pattern: Saga
// Zweck: Lokale Transaktionen mit expliziten Kompensationen koordinieren.
public final class SagaPatternLab {
public interface Step { void execute(); void compensate(); }
public static void run(java.util.List<Step> steps) {
Deque<Step> completed = new ArrayDeque<>();
try { for (Step step : steps) { step.execute(); completed.push(step); } }
catch (RuntimeException failure) { completed.forEach(Step::compensate); throw failure; }
}
}
Karte 32 Process Manager Lang laufende Abläufe mit explizitem Zustand steuern
Code Smell
Ereignisreaktionen verteilen sich unkoordiniert über Listener
Struktur
Ein zustandsbehafteter Koordinator entscheidet den nächsten Prozessschritt
Verwenden
Onboarding, Fulfillment, Freigaben und mehrstufige Integrationsprozesse
Nicht verwenden
Für kurze synchrone Abläufe ohne Wartezustand
Alternative
Saga, wenn primär lokale Transaktionen und Kompensation im Zentrum stehen
Testfokus
Zustandsübergänge, Duplikate, Timeout und Wiederaufnahme testen
ProcessManagerPatternLab.java
package com.aydinsude.workbench.explorer;
// Enterprise Pattern: Process Manager
// Zweck: Einen langlebigen fachlichen Ablauf explizit steuern.
public final class ProcessManagerPatternLab {
public enum State { STARTED, PAYMENT_CONFIRMED, COMPLETED, FAILED }
public static final class OrderProcess {
private State state = State.STARTED;
public void paymentConfirmed() { require(State.STARTED); state = State.PAYMENT_CONFIRMED; }
public void shipmentConfirmed() { require(State.PAYMENT_CONFIRMED); state = State.COMPLETED; }
private void require(State expected) { if (state != expected) throw new IllegalStateException(state.name()); }
public State state() { return state; }
}
}
Jeder Aufruf wartet erneut auf denselben bereits gestörten Dienst
Struktur
Ein Zustandsautomat öffnet nach Fehlern und erlaubt kontrollierte Testaufrufe
Verwenden
Remote-APIs, Provider und instabile Infrastrukturgrenzen
Nicht verwenden
Für lokale deterministische Operationen
Alternative
Retry bei kurzlebigen Einzelstörungen ohne anhaltenden Ausfall
Testfokus
Closed, Open, Half-Open, Schwelle und Erholung testen
CircuitBreakerPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.function.Supplier;
// Resilience Pattern: Circuit Breaker
// Zweck: Wiederholte Aufrufe eines gestörten Systems begrenzen.
public final class CircuitBreakerPatternLab {
public enum State { CLOSED, OPEN }
public static final class Breaker {
private final int threshold; private int failures; private State state = State.CLOSED;
public Breaker(int threshold) { this.threshold = threshold; }
public <T> T call(Supplier<T> action) {
if (state == State.OPEN) throw new IllegalStateException("circuit open");
try { T value = action.get(); failures = 0; return value; }
catch (RuntimeException ex) { if (++failures >= threshold) state = State.OPEN; throw ex; }
}
public State state() { return state; }
}
}
Karte 34 Retry Vorübergehende Fehler kontrolliert erneut versuchen
Code Smell
Beliebige while-Schleifen wiederholen ohne Grenze, Pause oder Klassifikation
Struktur
Eine Retry-Policy begrenzt Versuche und entscheidet über wiederholbare Fehler
Verwenden
Netzwerk-Timeouts, konkurrierende Updates und temporäre Providerfehler
Nicht verwenden
Bei fachlichen Ablehnungen oder nicht-idempotenten Operationen ohne Schutz
Alternative
Circuit Breaker bei anhaltenden Störungen und Schutz des Zielsystems
Testfokus
Versuchsanzahl, Abbruch, Erfolg nach Fehler und nicht wiederholbare Fehler testen
RetryPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.function.Predicate;
import java.util.function.Supplier;
// Resilience Pattern: Retry
// Zweck: Nur temporäre Fehler mit klarer Obergrenze wiederholen.
public final class RetryPatternLab {
public static <T> T execute(int maxAttempts, Supplier<T> action, Predicate<RuntimeException> retryable) {
RuntimeException last = null;
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try { return action.get(); }
catch (RuntimeException ex) { last = ex; if (!retryable.test(ex)) throw ex; }
}
throw last;
}
}
Karte 35 Bulkhead Ressourcen zwischen Aufrufgruppen isolieren
Code Smell
Ein ausgelasteter Bereich verbraucht alle Threads oder Verbindungen
Struktur
Getrennte Kapazitäten begrenzen den Ausfall auf eine Partition
Verwenden
Mandanten, Provider, kritische und optionale Funktionen
Nicht verwenden
Wenn alle Aufrufe dieselbe kleine Ressource zwingend teilen
Alternative
Rate Limiter zur zeitlichen Begrenzung statt Kapazitätsisolation
Testfokus
Kapazität, Ablehnung und Unabhängigkeit der Partitionen testen
BulkheadPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.concurrent.Semaphore;
import java.util.function.Supplier;
// Resilience Pattern: Bulkhead
// Zweck: Parallelitätskapazität pro Funktionsbereich isolieren.
public final class BulkheadPatternLab {
public static final class Partition {
private final Semaphore permits;
public Partition(int capacity) { permits = new Semaphore(capacity); }
public <T> T execute(Supplier<T> action) {
if (!permits.tryAcquire()) throw new IllegalStateException("bulkhead full");
try { return action.get(); } finally { permits.release(); }
}
}
}
Karte 36 Rate Limiter Aufrufmenge pro Zeitfenster begrenzen
Code Smell
Lastspitzen erreichen einen Dienst ohne kontrollierte Obergrenze
Struktur
Ein Token-Budget entscheidet, welche Aufrufe zugelassen werden
Verwenden
Öffentliche APIs, teure Operationen und Providerquoten
Nicht verwenden
Für interne seltene Operationen ohne Kapazitätsrisiko
Alternative
Bulkhead zur Begrenzung gleichzeitiger statt zeitlicher Last
Testfokus
Grenze, Fensterwechsel und faire Ablehnung testen
RateLimiterPatternLab.java
package com.aydinsude.workbench.explorer;
import java.time.Clock;
import java.time.Instant;
// Resilience Pattern: Rate Limiter
// Zweck: Eine feste Zahl von Aufrufen pro Zeitfenster zulassen.
public final class RateLimiterPatternLab {
public static final class FixedWindow {
private final int limit; private final long seconds; private final Clock clock;
private Instant windowStart; private int used;
public FixedWindow(int limit, long seconds, Clock clock) { this.limit=limit; this.seconds=seconds; this.clock=clock; this.windowStart=clock.instant(); }
public synchronized boolean allow() {
Instant now=clock.instant(); if(now.isAfter(windowStart.plusSeconds(seconds))){windowStart=now;used=0;}
return used < limit && ++used > 0;
}
}
}
Karte 37 Cache Aside Lesedaten bei Bedarf laden und explizit zwischenspeichern
Code Smell
Teure Daten werden bei jedem Zugriff erneut aus dem Repository geladen
Struktur
Der Anwendungscode prüft den Cache und lädt bei Miss aus der Quelle
Verwenden
Referenzdaten, Produktansichten und selten veränderte Projektionen
Nicht verwenden
Bei stark transaktionalen Daten mit schwieriger Invalidierung
Alternative
Read-Through Cache, wenn die Cache-Infrastruktur das Laden kapseln soll
Testfokus
Hit, Miss, TTL, Invalidierung und Ausfall der Quelle testen
CacheAsidePatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
// Performance Pattern: Cache Aside
// Zweck: Daten bei Cache-Miss aus einer Quelle laden.
public final class CacheAsidePatternLab {
public static final class Cache<K,V> {
private final Map<K,V> values = new HashMap<>();
public V get(K key, Function<K,V> loader) { return values.computeIfAbsent(key, loader); }
public void invalidate(K key) { values.remove(key); }
}
}
Karte 38 CQRS Schreibmodell und Lesemodell nach Verantwortung trennen
Code Smell
Ein Modell muss gleichzeitig Invarianten und komplexe Abfragen optimieren
Komplexe Domänen mit stark unterschiedlichen Schreib- und Leseanforderungen
Nicht verwenden
Für kleine Anwendungen ohne messbare Modellkonflikte
Alternative
CRUD, wenn Domäne und Abfragen einfach bleiben
Testfokus
Command-Invarianten, Projektionsaktualisierung und Eventual Consistency testen
CqrsPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.Map;
// Architecture Pattern: CQRS
// Zweck: Änderungs- und Lesemodell bewusst trennen.
public final class CqrsPatternLab {
public record RenameCustomer(String customerId, String newName) {}
public interface CommandHandler { void handle(RenameCustomer command); }
public interface CustomerView { Map<String,String> find(String customerId); }
}
Karte 39 Event Sourcing Zustand aus einer fachlichen Ereignisfolge rekonstruieren
Code Smell
Nur der aktuelle Zustand bleibt erhalten und Entscheidungen sind später kaum erklärbar
Struktur
Ein Aggregate entsteht durch Replay unveränderlicher Ereignisse
Verwenden
Finanzbuchungen, Bestandsbewegungen und revisionskritische Prozesse
Nicht verwenden
Bei einfachen CRUD-Daten ohne fachlich wertvolle Historie
Alternative
Audit Log, wenn nur Nachvollziehbarkeit ohne Zustandsrekonstruktion nötig ist
Testfokus
Replay, Versionierung, Reihenfolge und Snapshot-Grenzen testen
EventSourcingPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.List;
// Architecture Pattern: Event Sourcing
// Zweck: Kontostand aus unveränderlichen Ereignissen rekonstruieren.
public final class EventSourcingPatternLab {
public sealed interface Event permits Deposited, Withdrawn { int amount(); }
public record Deposited(int amount) implements Event {}
public record Withdrawn(int amount) implements Event {}
public static int balance(List<Event> events) {
return events.stream().mapToInt(e -> e instanceof Deposited ? e.amount() : -e.amount()).sum();
}
}
Ereignisse oder Änderungen aktualisieren eine abfrageoptimierte Projektion
Verwenden
Dashboards, Suchindizes, Reports und CQRS-Lesemodelle
Nicht verwenden
Bei seltenen einfachen Abfragen direkt auf der Quelle
Alternative
Cache für temporäre Kopien ohne eigenes fachliches Lesemodell
Testfokus
Aufbau, inkrementelle Aktualisierung, Neuaufbau und Konsistenz testen
MaterializedViewPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.HashMap;
import java.util.Map;
// Architecture Pattern: Materialized View
// Zweck: Vorberechnete Umsatzsummen für schnelle Abfragen halten.
public final class MaterializedViewPatternLab {
public static final class RevenueView {
private final Map<String,Integer> totals = new HashMap<>();
public void onSale(String region, int amount) { totals.merge(region, amount, Integer::sum); }
public int revenue(String region) { return totals.getOrDefault(region, 0); }
}
}
Kapitel 11 Pattern-Karten: Integrationsgrenzen und Modernisierung10 Pattern-Karten
Zehn Patternkarten zeigen, wie externe Zugänge, Legacy-Grenzen, kontrollierte Releases und robuste Nachrichtenverarbeitung strukturiert werden.
Patternkarten · Abschnitt 5
Pattern Explorer · Integrationsgrenzen und Modernisierung
Zehn Patternkarten zeigen, wie externe Zugänge, Legacy-Grenzen, kontrollierte Releases und robuste Nachrichtenverarbeitung strukturiert werden.
Patternkarten: 50/100 Patternkarten abgeschlossen
50 Patternkarten offen.
Karte 41 API Gateway Einen kontrollierten Eingang für mehrere Backend-APIs bereitstellen
Code Smell
Clients kennen zahlreiche interne Services, URLs und Fehlerformate
Struktur
Ein Edge-Komponent bündelt Routing, Authentisierung und Protokollanpassung
Verwenden
Externe API-Landschaften mit mehreren Backend-Services
Nicht verwenden
Für einen einzelnen stabilen Dienst ohne Edge-Anforderungen
Alternative
Facade für eine rein interne vereinfachte Programmierschnittstelle
Testfokus
Routing, Authentisierung, Timeout und Fehlerübersetzung testen
ApiGatewayPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.Map;
// Architecture Pattern: API Gateway
// Zweck: Externe Requests kontrolliert an interne Services weiterleiten.
public final class ApiGatewayPatternLab {
public interface Route { String handle(String body); }
public static final class Gateway {
private final Map<String, Route> routes;
public Gateway(Map<String, Route> routes) { this.routes = Map.copyOf(routes); }
public String dispatch(String path, String body) {
Route route = routes.get(path);
if (route == null) throw new IllegalArgumentException("unknown route: " + path);
return route.handle(body);
}
}
}
Karte 42 Backend for Frontend Ein eigenes Backend je Benutzeroberfläche zuschneiden
Code Smell
Mobile, Web und Partner-Clients teilen eine überladene Universal-API
Struktur
Jeder Client-Typ erhält eine passende Orchestrierungs- und Projektionsebene
Verwenden
Web-, Mobile- und Partnerkanäle mit deutlich verschiedenen Datenbedürfnissen
Nicht verwenden
Wenn alle Clients dieselben Daten und Abläufe benötigen
Alternative
API Gateway für gemeinsames Routing ohne clientspezifisches Fachmodell
Testfokus
Clientvertrag, Aggregation, Berechtigungen und Versionsfähigkeit testen
BackendForFrontendPatternLab.java
package com.aydinsude.workbench.explorer;
// Architecture Pattern: Backend for Frontend (BFF)
// Zweck: Eine clientspezifische Sicht auf mehrere Backend-Dienste liefern.
public final class BackendForFrontendPatternLab {
public interface CustomerPort { String customerName(String id); }
public interface OrderPort { int openOrders(String customerId); }
public record MobileDashboard(String name, int openOrders) {}
public static final class MobileBff {
private final CustomerPort customers; private final OrderPort orders;
public MobileBff(CustomerPort customers, OrderPort orders) { this.customers=customers; this.orders=orders; }
public MobileDashboard dashboard(String id) { return new MobileDashboard(customers.customerName(id), orders.openOrders(id)); }
}
}
Karte 43 Service Mesh Querschnittliche Service-Kommunikation aus Anwendungen herauslösen
Code Smell
Jeder Service implementiert selbst mTLS, Retry, Metriken und Traffic-Steuerung
Struktur
Sidecars oder Proxies übernehmen Netzwerkregeln außerhalb des Fachcodes
Verwenden
Viele Services mit einheitlichen Sicherheits- und Observability-Regeln
Nicht verwenden
Für kleine modulare Monolithen oder wenige direkte Integrationen
Alternative
Library-basierte Resilience bei kleiner Landschaft ohne Plattformbetrieb
Testfokus
Policy, mTLS, Routing, Telemetrie und Fehlerszenarien testen
ServiceMeshPatternLab.java
package com.aydinsude.workbench.explorer;
// Architecture Pattern: Service Mesh
// Zweck: Fachcode von Transport- und Netzwerkpolitik entkoppeln.
public final class ServiceMeshPatternLab {
public interface OrderClient { String load(String orderId); }
public static final class OrderApplication {
private final OrderClient client;
public OrderApplication(OrderClient client) { this.client = client; }
public String show(String orderId) { return client.load(orderId); }
}
// mTLS, Retry und Telemetrie liegen bewusst außerhalb dieser Klasse im Mesh.
}
Karte 44 Anti-Corruption Layer Ein fremdes Modell vom eigenen Domänenmodell abschirmen
Code Smell
Legacy-Begriffe und Datenformen dringen bis in die neue Domäne vor
Struktur
Übersetzer und Ports bilden das externe Modell auf eigene Fachbegriffe ab
Verwenden
Legacy-Modernisierung, Partnerintegration und fremde Bounded Contexts
Nicht verwenden
Wenn beide Seiten dasselbe stabile Fachmodell teilen
Alternative
Adapter bei rein technischer Schnittstellenanpassung ohne Modellkonflikt
Testfokus
Mapping, unbekannte Werte, Rundungsregeln und Fehlerübersetzung testen
AntiCorruptionLayerPatternLab.java
package com.aydinsude.workbench.explorer;
// DDD Pattern: Anti-Corruption Layer
// Zweck: Ein Legacy-Modell in ein eigenes Domänenmodell übersetzen.
public final class AntiCorruptionLayerPatternLab {
public record LegacyCustomer(String no, String statusCode) {}
public enum CustomerState { ACTIVE, BLOCKED }
public record Customer(String id, CustomerState state) {}
public static Customer translate(LegacyCustomer legacy) {
CustomerState state = switch (legacy.statusCode()) { case "A" -> CustomerState.ACTIVE; case "S" -> CustomerState.BLOCKED; default -> throw new IllegalArgumentException("status"); };
return new Customer(legacy.no(), state);
}
}
Karte 45 Strangler Fig Ein Legacy-System schrittweise durch neue Funktionen ersetzen
Code Smell
Eine Big-Bang-Ablösung bündelt Fach-, Daten- und Betriebsrisiken
Struktur
Eine Fassade leitet einzelne Fähigkeiten kontrolliert alt oder neu weiter
Verwenden
Große Legacy-Systeme mit schrittweise migrierbaren Fähigkeiten
Nicht verwenden
Für kleine Komponenten, die sicher in einem Schritt ersetzt werden können
Alternative
Branch by Abstraction für Austausch innerhalb einer Codebasis
Testfokus
Routing, Rückfall, Datenkonsistenz und schrittweise Umschaltung testen
StranglerFigPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.function.Predicate;
// Modernization Pattern: Strangler Fig
// Zweck: Aufrufe schrittweise vom Legacy- zum Neusystem verlagern.
public final class StranglerFigPatternLab {
public interface CustomerService { String load(String id); }
public static final class RoutingFacade implements CustomerService {
private final CustomerService legacy, modern; private final Predicate<String> useModern;
public RoutingFacade(CustomerService legacy, CustomerService modern, Predicate<String> useModern) { this.legacy=legacy; this.modern=modern; this.useModern=useModern; }
public String load(String id) { return useModern.test(id) ? modern.load(id) : legacy.load(id); }
}
}
Karte 46 Branch by Abstraction Eine Implementierung innerhalb des Hauptzweigs schrittweise austauschen
Code Smell
Ein langlebiger Feature-Branch driftet vom Hauptzweig ab
Struktur
Eine stabile Abstraktion erlaubt alte und neue Implementierung parallel
Verwenden
Große interne Umbauten mit kontinuierlicher Integration
Nicht verwenden
Für winzige lokale Änderungen ohne Parallelbetrieb
Alternative
Feature Toggle zur Laufzeitwahl bereits integrierter Verhaltensvarianten
Testfokus
Vertragsgleichheit, Umschaltung und Entfernung der Altimplementierung testen
BranchByAbstractionPatternLab.java
package com.aydinsude.workbench.explorer;
// Modernization Pattern: Branch by Abstraction
// Zweck: Alte und neue Implementierung hinter einem stabilen Vertrag parallel halten.
public final class BranchByAbstractionPatternLab {
public interface TaxCalculator { int tax(int net); }
public static final class LegacyTax implements TaxCalculator { public int tax(int net) { return net * 20 / 100; } }
public static final class PreciseTax implements TaxCalculator { public int tax(int net) { return Math.round(net * 0.20f); } }
public static final class Checkout { private final TaxCalculator taxes; public Checkout(TaxCalculator taxes){this.taxes=taxes;} public int gross(int net){return net+taxes.tax(net);} }
}
Karte 47 Feature Toggle Neues Verhalten kontrolliert zur Laufzeit aktivieren
Code Smell
Unfertige Funktionen erzwingen lange Branches oder riskante Releases
Struktur
Eine benannte Entscheidung schaltet Verhalten nach Kontext und Rollout-Regel
Verwenden
Stufenweise Releases, Experimente und schnelle Deaktivierung
Nicht verwenden
Für dauerhafte Geschäftsregeln oder unkontrolliert wachsende Toggles
Alternative
Branch by Abstraction für strukturellen Implementierungsaustausch
Testfokus
An/Aus, Zielgruppe, Default, Ablaufdatum und Entfernung testen
FeatureTogglePatternLab.java
package com.aydinsude.workbench.explorer;
// Delivery Pattern: Feature Toggle
// Zweck: Neues Verhalten kontrolliert und reversibel aktivieren.
public final class FeatureTogglePatternLab {
public interface Toggle { boolean enabled(String feature, String subject); }
public static final class PricingService {
private final Toggle toggles;
public PricingService(Toggle toggles) { this.toggles = toggles; }
public int price(String customerId, int base) { return toggles.enabled("new-pricing", customerId) ? base - base/10 : base; }
}
}
Ein erneutes Event erzeugt doppelte Buchungen oder Benachrichtigungen
Struktur
Eine atomare Message-ID-Reservierung schützt die fachliche Verarbeitung
Verwenden
At-least-once Messaging, Webhooks und wiederholte Jobs
Nicht verwenden
Wenn der Aufruf nachweislich genau einmal und lokal erfolgt
Alternative
Inbox Pattern, wenn zusätzlich Payload und Verarbeitungsstatus persistiert werden
Testfokus
Duplikat, Parallelität, Fehler nach Reservierung und Wiederaufnahme testen
IdempotentConsumerPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
// Messaging Pattern: Idempotent Consumer
// Zweck: Duplikate anhand einer stabilen Message-ID abweisen.
public final class IdempotentConsumerPatternLab {
public static final class Consumer {
private final Set<String> processed = ConcurrentHashMap.newKeySet();
public boolean consume(String messageId, Runnable action) {
if (!processed.add(messageId)) return false;
try { action.run(); return true; } catch (RuntimeException ex) { processed.remove(messageId); throw ex; }
}
}
}
Karte 49 Competing Consumers Nachrichten parallel über mehrere gleichartige Worker verteilen
Code Smell
Ein einzelner Consumer wird zum Durchsatzengpass
Struktur
Mehrere Worker konkurrieren um Nachrichten derselben Queue
Verwenden
Unabhängige Jobs, Bildverarbeitung, Exporte und E-Mail-Versand
Nicht verwenden
Wenn globale Reihenfolge oder gemeinsame mutable Zustände erforderlich sind
Alternative
Partitioned Consumer bei erforderlicher Reihenfolge pro Schlüssel
Testfokus
Verteilung, Skalierung, Duplikate, Reihenfolge und Worker-Ausfall testen
CompetingConsumersPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.concurrent.BlockingQueue;
// Messaging Pattern: Competing Consumers
// Zweck: Unabhängige Arbeit auf mehrere gleichartige Worker verteilen.
public final class CompetingConsumersPatternLab {
public static final class Worker implements Runnable {
private final BlockingQueue<Runnable> queue;
public Worker(BlockingQueue<Runnable> queue) { this.queue = queue; }
public void run() { try { while (!Thread.currentThread().isInterrupted()) queue.take().run(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } }
}
}
Karte 50 Dead Letter Channel Dauerhaft nicht verarbeitbare Nachrichten isolieren
Code Smell
Poison Messages blockieren Wiederholungen oder verschwinden ohne Diagnose
Struktur
Nach Ausschöpfen der Versuche wird Nachricht mit Fehlerkontext separat abgelegt
Verwenden
Messaging-Systeme mit fachlich oder technisch fehlerhaften Nachrichten
Nicht verwenden
Wenn Fehler synchron an den Aufrufer zurückgegeben werden können
Alternative
Retry Queue für zeitversetzte erneute Verarbeitung temporärer Fehler
Testfokus
Weiterleitung, Metadaten, Datenschutz, Replay und Alarmierung testen
DeadLetterChannelPatternLab.java
package com.aydinsude.workbench.explorer;
// Messaging Pattern: Dead Letter Channel
// Zweck: Nicht verarbeitbare Nachrichten mit Fehlerkontext isolieren.
public final class DeadLetterChannelPatternLab {
public record Message(String id, String payload) {}
public record DeadLetter(Message message, String reason) {}
public interface DeadLetterPort { void store(DeadLetter letter); }
public static void handle(Message message, java.util.function.Consumer<Message> handler, DeadLetterPort deadLetters) {
try { handler.accept(message); } catch (RuntimeException ex) { deadLetters.store(new DeadLetter(message, ex.getMessage())); }
}
}
Kapitel 12 Pattern-Karten: Nachrichtenfluss und Integration10 Pattern-Karten
Zehn Patternkarten zeigen Routing, Transformation, Zerlegung, Zusammenführung, Reihenfolge, Payload-Auslagerung und robuste Verarbeitung.
Patternkarten · Abschnitt 6
Pattern Explorer · Nachrichtenfluss und Integration
Zehn Patternkarten zeigen Routing, Transformation, Zerlegung, Zusammenführung, Reihenfolge, Payload-Auslagerung und robuste Verarbeitung.
Mehrere Empfänger oder Prozesse mit expliziten Routingregeln
Nicht verwenden
Bei genau einem festen Ziel
Alternative
Content-Based Router für rein inhaltsbasierte Regeln
Testfokus
Regelreihenfolge, Fallback und unbekannte Nachricht testen
MessageRouterPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.Map;
// Messaging Pattern: Message Router
// Zweck: Nachrichten anhand eines stabilen Schlüssels an einen Kanal leiten.
public final class MessageRouterPatternLab {
public interface Channel { void send(String payload); }
public static final class Router {
private final Map<String, Channel> routes;
public Router(Map<String, Channel> routes){ this.routes=Map.copyOf(routes); }
public void route(String type, String payload){
Channel channel=routes.get(type);
if(channel==null) throw new IllegalArgumentException("unknown type: "+type);
channel.send(payload);
}
}
}
Transporttyp und Fachregel sind miteinander vermischt
Struktur
Prädikate bestimmen den Zielkanal anhand des Payloads
Verwenden
Fachliche Priorisierung, Betragsgrenzen und Risikoklassen
Nicht verwenden
Wenn Routing nur über einen stabilen Header erfolgt
Alternative
Message Router für feste technische Routingkeys
Testfokus
Grenzwerte, konkurrierende Regeln und Fallback testen
ContentBasedRouterPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.List;
import java.util.function.Predicate;
// Messaging Pattern: Content-Based Router
// Zweck: Das Ziel aus fachlichen Eigenschaften der Nachricht bestimmen.
public final class ContentBasedRouterPatternLab {
public record Rule(Predicate<String> matches, java.util.function.Consumer<String> target) {}
public static final class Router {
private final List<Rule> rules;
public Router(List<Rule> rules){ this.rules=List.copyOf(rules); }
public void route(String payload){
rules.stream().filter(r->r.matches().test(payload)).findFirst()
.orElseThrow(() -> new IllegalStateException("no route"))
.target().accept(payload);
}
}
}
Fremde Feldnamen und Formate dringen in die Domäne ein
Struktur
Ein Translator bildet externes auf internes Modell ab
Verwenden
Partner-APIs, Legacy-Events und Versionsmigration
Nicht verwenden
Wenn Sender und Empfänger dasselbe Modell teilen
Alternative
Anti-Corruption Layer bei umfassendem Domänenkonflikt
Testfokus
Pflichtfelder, unbekannte Werte und Rundung testen
MessageTranslatorPatternLab.java
package com.aydinsude.workbench.explorer;
// Messaging Pattern: Message Translator
// Zweck: Ein externes Nachrichtenmodell in ein internes Fachmodell übersetzen.
public final class MessageTranslatorPatternLab {
public record PartnerOrder(String orderNo, long totalCents) {}
public record OrderPlaced(String orderId, java.math.BigDecimal total) {}
public static OrderPlaced translate(PartnerOrder source){
return new OrderPlaced(source.orderNo(), java.math.BigDecimal.valueOf(source.totalCents(),2));
}
}
Karte 54 Splitter Eine große Nachricht in unabhängig verarbeitbare Teile zerlegen
Code Smell
Monolithische Verarbeitung blockiert Parallelität und Fehlerisolation
Struktur
Ein Splitter erzeugt Teilnachrichten mit Korrelation
Verwenden
Batchimporte, Sammelaufträge und Dokumentpakete
Nicht verwenden
Wenn die Nachricht atomar bleiben muss
Alternative
Iterator bei rein lokaler Traversierung
Testfokus
Korrelation, Reihenfolge, leere Eingabe und Teilfehler testen
SplitterPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.List;
// Messaging Pattern: Splitter
// Zweck: Eine Sammelnachricht in korrelierte Teilnachrichten zerlegen.
public final class SplitterPatternLab {
public record Batch(String batchId, List<String> items) {}
public record Part(String batchId, int index, int total, String value) {}
public static List<Part> split(Batch batch){
return java.util.stream.IntStream.range(0,batch.items().size())
.mapToObj(i->new Part(batch.batchId(),i,batch.items().size(),batch.items().get(i))).toList();
}
}
Karte 55 Aggregator Mehrere Teilnachrichten zu einem fachlichen Ergebnis zusammenführen
Code Smell
Teilresultate werden ungeordnet und ohne Abschlusskriterium gesammelt
Struktur
Ein Aggregator nutzt Korrelation und Completion-Regel
Verwenden
Split-Workflows, parallele Preisabfragen und Dokumentprüfung
Nicht verwenden
Wenn jedes Teil unabhängig abgeschlossen werden kann
Alternative
Process Manager bei langlaufendem Ablauf mit mehreren Schritten
Testfokus
Duplikate, Timeout, Reihenfolge und vollständige Menge testen
AggregatorPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.*;
// Messaging Pattern: Aggregator
// Zweck: Korrelierte Teilresultate kontrolliert zu einem Ergebnis zusammenführen.
public final class AggregatorPatternLab {
public static final class Aggregator {
private final Map<String,List<String>> parts=new HashMap<>();
public Optional<List<String>> add(String correlationId, int expected, String value){
List<String> values=parts.computeIfAbsent(correlationId,k->new ArrayList<>()); values.add(value);
if(values.size()<expected) return Optional.empty();
parts.remove(correlationId); return Optional.of(List.copyOf(values));
}
}
}
Parallelität zerstört die fachlich erwartete Reihenfolge
Struktur
Sequenznummern puffern, bis die nächste erwartete Nachricht verfügbar ist
Verwenden
Events mit Sequenznummern und parallele Verarbeitung
Nicht verwenden
Wenn Reihenfolge fachlich irrelevant ist
Alternative
Aggregator wenn mehrere Teile zu einem Ergebnis verschmelzen
Testfokus
Lücken, Duplikate, Timeout und Neustart testen
ResequencerPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.*;
// Messaging Pattern: Resequencer
// Zweck: Nachrichten anhand einer Sequenznummer geordnet freigeben.
public final class ResequencerPatternLab {
public static final class Buffer {
private final NavigableMap<Long,String> pending=new TreeMap<>(); private long next=1;
public List<String> accept(long sequence,String payload){
pending.putIfAbsent(sequence,payload); List<String> ready=new ArrayList<>();
while(pending.containsKey(next)) ready.add(pending.remove(next++));
return List.copyOf(ready);
}
}
}
Karte 57 Claim Check Große Payloads aus Nachrichten auslagern
Code Smell
Nachrichten transportieren große Dokumente mehrfach durch die Landschaft
Struktur
Ein Claim verweist auf extern gespeicherte Payload
Verwenden
Große Dateien, sensible Inhalte und Brokerlimits
Nicht verwenden
Bei kleinen kurzlebigen Payloads
Alternative
Message Translator bei Modellumformung ohne Auslagerung
Testfokus
Ablaufzeit, Berechtigung, Verlust und Bereinigung testen
ClaimCheckPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
// Messaging Pattern: Claim Check
// Zweck: Große Payload extern speichern und nur eine Referenz transportieren.
public final class ClaimCheckPatternLab {
public static final class Store {
private final Map<String,byte[]> data=new ConcurrentHashMap<>();
public String put(byte[] value){ String id=java.util.UUID.randomUUID().toString(); data.put(id,value.clone()); return id; }
public byte[] take(String id){ byte[] value=data.remove(id); if(value==null) throw new IllegalArgumentException("missing claim"); return value.clone(); }
}
}
Karte 58 Wire Tap Nachrichten beobachten, ohne den Hauptfluss zu verändern
Code Smell
Logging und Audit sind fest in die Fachverarbeitung eingebaut
Struktur
Eine Kopie wird an einen Beobachtungskanal gesendet
Verwenden
Audit, Debugging, Metriken und Compliance
Nicht verwenden
Wenn sensible Daten nicht kopiert werden dürfen
Alternative
Decorator bei lokalem Methodenaufruf
Testfokus
Fehler im Tap, Datenschutz und Nichtbeeinflussung testen
WireTapPatternLab.java
package com.aydinsude.workbench.explorer;
// Messaging Pattern: Wire Tap
// Zweck: Eine Nachricht zusätzlich beobachten, ohne den Hauptkanal zu verändern.
public final class WireTapPatternLab {
public interface Channel { void send(String message); }
public static final class TappedChannel implements Channel {
private final Channel primary, tap;
public TappedChannel(Channel primary, Channel tap){this.primary=primary;this.tap=tap;}
public void send(String message){ tap.send(message); primary.send(message); }
}
}
Karte 59 Inbox Pattern Eingehende Nachrichten dauerhaft und idempotent verarbeiten
Code Smell
Crash zwischen Empfang und Fachwirkung führt zu Verlust oder Doppelwirkung
Struktur
Eine Inbox persistiert Nachricht und Verarbeitungsstatus atomar
Verwenden
Kritische Events mit Wiederanlauf und Auditbedarf
Nicht verwenden
Bei rein synchronen lokalen Aufrufen
Alternative
Idempotent Consumer ohne benötigte Wiederaufnahmehistorie
Testfokus
Duplikat, Crash-Recovery und Statusübergänge testen
InboxPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.concurrent.ConcurrentHashMap;
// Messaging Pattern: Inbox
// Zweck: Empfang und Verarbeitungsstatus dauerhaft nachvollziehbar machen.
public final class InboxPatternLab {
public enum Status { RECEIVED, DONE }
public static final class Inbox {
private final ConcurrentHashMap<String,Status> entries=new ConcurrentHashMap<>();
public boolean reserve(String id){ return entries.putIfAbsent(id,Status.RECEIVED)==null; }
public void complete(String id){ entries.computeIfPresent(id,(k,v)->Status.DONE); }
}
}
Karte 60 Polling Consumer Nachrichten kontrolliert in einem eigenen Takt abrufen
Code Smell
Push-Verarbeitung überfordert langsame oder begrenzte Verbraucher
Struktur
Ein Consumer pollt, verarbeitet und steuert Backpressure
Verwenden
Legacy-Queues, Batchsysteme und begrenzte externe APIs
Nicht verwenden
Wenn geringe Latenz zwingend ist
Alternative
Event-Driven Consumer für sofortige Push-Reaktion
Testfokus
Leere Polls, Backoff, Shutdown und Wiederholung testen
PollingConsumerPatternLab.java
package com.aydinsude.workbench.explorer;
import java.time.Duration;
import java.util.Optional;
// Messaging Pattern: Polling Consumer
// Zweck: Nachrichten mit kontrolliertem Takt und explizitem Backpressure abrufen.
public final class PollingConsumerPatternLab {
public interface Source { Optional<String> poll(Duration timeout); }
public static final class Consumer {
private final Source source; private final java.util.function.Consumer<String> handler;
public Consumer(Source source, java.util.function.Consumer<String> handler){this.source=source;this.handler=handler;}
public boolean runOnce(){ return source.poll(Duration.ofSeconds(1)).map(m->{handler.accept(m); return true;}).orElse(false); }
}
}
Kapitel 13 Pattern-Karten: Architektur und Persistenz10 Pattern-Karten
Zehn Patternkarten verbinden Architekturgrenzen, Modularisierung und Datenzugriff mit echten Java-21-Beispielen.
Patternkarten · Abschnitt 7
Pattern Explorer · Architektur und Persistenz
Zehn Patternkarten verbinden Architekturgrenzen, Modularisierung und Datenzugriff mit echten Java-21-Beispielen.
Patternkarten: 70/100 Patternkarten abgeschlossen
30 Patternkarten offen.
Karte 61 Layered Architecture Verantwortlichkeiten in klaren technischen Schichten ordnen
Code Smell
UI, Fachlogik und Persistenz greifen direkt ineinander
Struktur
Presentation, Application, Domain und Infrastructure werden gerichtet gekoppelt
Verwenden
Klassische Geschäftsanwendungen mit überschaubarer Domäne
Nicht verwenden
Wenn die Domäne durch viele externe Adapter geprägt ist
Alternative
Hexagonal Architecture bei starken externen Integrationsgrenzen
Testfokus
Schichtgrenzen, verbotene Abhängigkeiten und Use-Case-Tests
LayeredArchitecturePatternLab.java
package com.aydinsude.workbench.explorer;
// Architecture Pattern: Layered Architecture
// Zweck: Abhängigkeiten von außen nach innen klar begrenzen.
public final class LayeredArchitecturePatternLab {
public record Order(String id) {}
public interface OrderRepository { void save(Order order); }
public static final class PlaceOrderService {
private final OrderRepository repository;
public PlaceOrderService(OrderRepository repository){ this.repository=repository; }
public void place(String id){ repository.save(new Order(id)); }
}
}
Karte 62 Hexagonal Architecture Fachkern von technischen Ein- und Ausgängen isolieren
Code Smell
Framework- und Infrastrukturtypen dringen in die Fachlogik ein
Struktur
Ports definieren Verträge, Adapter übersetzen Technik in Fachaufrufe
Verwenden
Mehrere UIs, Datenbanken, Broker oder Partner-Schnittstellen
Nicht verwenden
Wenn keine austauschbaren Integrationen existieren
Alternative
Layered Architecture bei einfachen internen Anwendungen
Testfokus
Port-Verträge, Adapter-Mapping und fachkernnahe Tests
HexagonalArchitecturePatternLab.java
package com.aydinsude.workbench.explorer;
// Architecture Pattern: Hexagonal Architecture (Ports and Adapters)
// Zweck: Der Fachkern kennt nur Ports, nicht die technische Umsetzung.
public final class HexagonalArchitecturePatternLab {
public interface PaymentPort { boolean charge(String account, long cents); }
public static final class CheckoutUseCase {
private final PaymentPort payment;
public CheckoutUseCase(PaymentPort payment){ this.payment=payment; }
public boolean checkout(String account,long cents){ return payment.charge(account,cents); }
}
}
Karte 63 Clean Architecture Geschäftsregeln unabhängig von UI, Framework und Datenbank halten
Code Smell
Use Cases hängen von konkreten technischen Details ab
Struktur
Entities und Use Cases liegen innen; Details implementieren äußere Schnittstellen
Verwenden
Langlebige Systeme mit mehreren technischen Oberflächen
Nicht verwenden
Bei kleinen CRUD-Anwendungen ohne komplexe Regeln
Alternative
Hexagonal Architecture für pragmatischere Port-/Adapter-Sicht
Testfokus
Dependency Rule, Interactor-Tests und Boundary-Mapping
CleanArchitecturePatternLab.java
package com.aydinsude.workbench.explorer;
// Architecture Pattern: Clean Architecture
// Zweck: Use Cases bleiben unabhängig von technischen Details.
public final class CleanArchitecturePatternLab {
public record Request(String customerId) {}
public record Response(boolean eligible) {}
public interface CustomerFacts { int scoreOf(String customerId); }
public static final class CheckEligibility {
private final CustomerFacts facts;
public CheckEligibility(CustomerFacts facts){ this.facts=facts; }
public Response execute(Request request){ return new Response(facts.scoreOf(request.customerId()) >= 700); }
}
}
Karte 64 Onion Architecture Domänenmodell als innersten stabilen Kern schützen
Code Smell
Domänenobjekte referenzieren Services und Infrastruktur
Domäneninvarianten, Richtung der Abhängigkeiten und Adaptertests
OnionArchitecturePatternLab.java
package com.aydinsude.workbench.explorer;
// Architecture Pattern: Onion Architecture
// Zweck: Domänenregeln bilden den innersten unabhängigen Kern.
public final class OnionArchitecturePatternLab {
public record Money(long cents) {
public Money { if(cents < 0) throw new IllegalArgumentException("negative"); }
}
public static final class Invoice {
private final Money total;
public Invoice(Money total){ this.total=total; }
public boolean requiresApproval(){ return total.cents() > 100_000; }
}
}
Karte 65 Modular Monolith Ein deploybares System intern durch harte Modulgrenzen strukturieren
Code Smell
Ein Monolith wird zu einem unkontrollierten Klassennetz
Struktur
Module besitzen öffentliche APIs und verstecken interne Implementierung
Verwenden
Geschäftssysteme mit hoher Konsistenz und klaren Domänenmodulen
Nicht verwenden
Wenn Teams zwingend unabhängig deployen müssen
Alternative
Microservices bei unabhängiger Skalierung und Deployment
Testfokus
Modulabhängigkeiten, ArchUnit-Regeln und öffentliche API
ModularMonolithPatternLab.java
package com.aydinsude.workbench.explorer;
// Architecture Pattern: Modular Monolith
// Zweck: Fachmodule bleiben intern gekapselt, obwohl gemeinsam deployt wird.
public final class ModularMonolithPatternLab {
public interface BillingApi { void issueInvoice(String orderId); }
public static final class OrderModule {
private final BillingApi billing;
public OrderModule(BillingApi billing){ this.billing=billing; }
public void complete(String orderId){ billing.issueInvoice(orderId); }
}
}
Karte 66 Microkernel Stabilen Kern durch austauschbare Plugins erweitern
Ein kleiner Core stellt Extension Points für Plugins bereit
Verwenden
Regelplattformen, IDEs, Produktfamilien und Importformate
Nicht verwenden
Wenn Erweiterungen nicht unabhängig entwickelt werden
Alternative
Strategy bei wenigen lokalen Varianten
Testfokus
Plugin-Vertrag, Isolation, Reihenfolge und fehlendes Plugin
MicrokernelPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.List;
// Architecture Pattern: Microkernel / Plugin Architecture
// Zweck: Erweiterungen werden über stabile Plugin-Verträge eingebunden.
public final class MicrokernelPatternLab {
public interface Plugin { boolean supports(String type); String execute(String input); }
public static final class Kernel {
private final List<Plugin> plugins;
public Kernel(List<Plugin> plugins){ this.plugins=List.copyOf(plugins); }
public String run(String type,String input){
return plugins.stream().filter(p->p.supports(type)).findFirst()
.orElseThrow(() -> new IllegalArgumentException("no plugin")).execute(input);
}
}
}
Karte 67 Service Layer Use Cases hinter einer klaren Anwendungsoberfläche bündeln
Code Smell
Controller und Batchjobs orchestrieren Fachobjekte unterschiedlich
Struktur
Eine Service-Schicht koordiniert Transaktion, Berechtigung und Domänenaufrufe
Verwenden
Mehrere technische Eingänge mit denselben Use Cases
Nicht verwenden
Wenn ein einzelner Adapter nur triviales CRUD ausführt
Alternative
Application Service in DDD als präzisere Benennung
Testfokus
Transaktionsgrenze, Orchestrierung und identisches Verhalten je Eingang
ServiceLayerPatternLab.java
package com.aydinsude.workbench.explorer;
// Enterprise Pattern: Service Layer
// Zweck: Einen Use Case als stabile Anwendungsoperation anbieten.
public final class ServiceLayerPatternLab {
public interface AccountRepository { boolean exists(String id); void close(String id); }
public static final class AccountService {
private final AccountRepository repository;
public AccountService(AccountRepository repository){ this.repository=repository; }
public void closeAccount(String id){
if(!repository.exists(id)) throw new IllegalArgumentException("unknown account");
repository.close(id);
}
}
}
Karte 68 Active Record Ein einfaches Datenobjekt direkt mit Persistenzverhalten verbinden
Code Smell
Triviales CRUD erzeugt unnötige Mapper- und Repository-Schichten
Struktur
Das Objekt kapselt Daten und grundlegende Persistenzoperationen
Verwenden
Kleine administrative Tabellen und einfache Stammdaten
Nicht verwenden
Bei reichhaltigen Aggregaten oder mehreren Speicherformen
Alternative
Data Mapper bei komplexem Domänenmodell
Testfokus
CRUD-Lebenszyklus, Transaktion und Datenbankfehler
ActiveRecordPatternLab.java
package com.aydinsude.workbench.explorer;
// Data Source Pattern: Active Record
// Zweck: Einfaches Datenobjekt und Persistenzoperation eng koppeln.
public final class ActiveRecordPatternLab {
public interface Store { void upsert(String id,String name); }
public static final class CustomerRecord {
private final String id; private String name;
public CustomerRecord(String id,String name){ this.id=id; this.name=name; }
public void rename(String name){ this.name=name; }
public void save(Store store){ store.upsert(id,name); }
}
}
Karte 69 Data Mapper Domänenmodell und Persistenzmodell unabhängig halten
Code Smell
SQL- oder ORM-Details prägen Fachobjekte
Struktur
Ein Mapper übersetzt zwischen Datenzeile und Domänenobjekt
Verwenden
Reichhaltige Domänenmodelle und wechselnde Persistenz
Nicht verwenden
Wenn Modell und Tabelle praktisch identisch bleiben
Alternative
Active Record für sehr einfache CRUD-Objekte
Testfokus
Rundreise-Mapping, Nullwerte, Versionierung und Typkonvertierung
DataMapperPatternLab.java
package com.aydinsude.workbench.explorer;
// Data Source Pattern: Data Mapper
// Zweck: Persistenzdarstellung und Domänenmodell explizit übersetzen.
public final class DataMapperPatternLab {
public record CustomerRow(String id,String displayName) {}
public record Customer(String id,String name) {}
public static final class Mapper {
public Customer toDomain(CustomerRow row){ return new Customer(row.id(),row.displayName()); }
public CustomerRow toRow(Customer customer){ return new CustomerRow(customer.id(),customer.name()); }
}
}
Karte 70 Identity Map Innerhalb einer Arbeitseinheit dieselbe Entität nur einmal laden
Code Smell
Mehrfach geladene Objektkopien widersprechen sich
Struktur
Eine Map hält pro Identität genau eine Instanz
Verwenden
Unit of Work, ORM-Sessions und komplexe Objektgraphen
Nicht verwenden
Bei unveränderlichen Value Objects oder reinem Streaming
Alternative
Second-Level Cache für arbeitsübergreifende Wiederverwendung
Testfokus
Identitätsgleichheit, Eviction und Unit-of-Work-Grenze
IdentityMapPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
// Data Source Pattern: Identity Map
// Zweck: Pro Arbeitseinheit existiert je Identität genau eine Objektinstanz.
public final class IdentityMapPatternLab {
public static final class IdentityMap<T> {
private final Map<String,T> entries=new HashMap<>();
public T get(String id, Function<String,T> loader){ return entries.computeIfAbsent(id,loader); }
public void clear(){ entries.clear(); }
}
}
Zehn Patternkarten zeigen Ladeverhalten, Query-Kapselung, Gateways und relationale Abbildung von Wertobjekten und Hierarchien.
Patternkarten · Abschnitt 8
Pattern Explorer · Persistenz-Mapping
Zehn Patternkarten zeigen Ladeverhalten, Query-Kapselung, Gateways und relationale Abbildung von Wertobjekten und Hierarchien.
Patternkarten: 80/100 Patternkarten abgeschlossen
20 Patternkarten offen.
Karte 71 Lazy Load Große oder selten benötigte Daten erst bei tatsächlichem Zugriff laden
Code Smell
Aggregate laden immer teure Anhänge und Historien
Struktur
Ein Proxy oder Supplier lädt den Wert genau einmal bei Bedarf
Verwenden
Große Objektgraphen, Dokumente und selten verwendete Detaildaten
Nicht verwenden
Wenn verzögertes Laden Transaktionsgrenzen verschleiert
Alternative
Eager Load bei kleinen, immer benötigten Daten
Testfokus
Einmaliges Laden, Fehlerweitergabe und Thread-Sicherheit
LazyLoadPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.Objects;
import java.util.function.Supplier;
// Data Source Pattern: Lazy Load
// Zweck: Teure Daten erst beim ersten tatsächlichen Zugriff laden.
public final class LazyLoadPatternLab {
public static final class LazyValue<T> {
private Supplier<T> supplier; private T value;
public LazyValue(Supplier<T> supplier){ this.supplier=Objects.requireNonNull(supplier); }
public synchronized T get(){
if(supplier != null){ value=supplier.get(); supplier=null; }
return value;
}
}
}
Karte 72 Query Object Komplexe Suchkriterien als testbares Objekt ausdrücken
Code Smell
SQL-Fragmente und Filterparameter verteilen sich über Services
Struktur
Ein Objekt kapselt Kriterien und erzeugt eine sichere Query-Repräsentation
Verwenden
Dynamische Suchmasken, Reporting und kombinierbare Filter
Nicht verwenden
Bei einer einzigen statischen Abfrage
Alternative
Specification für reine Domänenregeln
Testfokus
Parameterbindung, leere Kriterien und kombinierte Filter
QueryObjectPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.ArrayList;
import java.util.List;
// Data Source Pattern: Query Object
// Zweck: Suchkriterien typisiert und zusammensetzbar kapseln.
public final class QueryObjectPatternLab {
public record Criterion(String field,String operator,Object value) {}
public static final class CustomerQuery {
private final List<Criterion> criteria=new ArrayList<>();
public CustomerQuery active(){ criteria.add(new Criterion("status","=","ACTIVE")); return this; }
public CustomerQuery minScore(int score){ criteria.add(new Criterion("score",">=",score)); return this; }
public List<Criterion> criteria(){ return List.copyOf(criteria); }
}
}
Karte 73 Table Data Gateway Alle Zugriffe auf eine Tabelle hinter einem Gateway bündeln
Code Smell
SQL liegt verstreut in Controllern und Services
Struktur
Ein Gateway bietet tabellenbezogene Operationen und kapselt SQL
Verwenden
Einfache tabellenzentrierte Anwendungen und Integrationsadapter
Nicht verwenden
Bei reichhaltigem Domänenmodell mit Aggregaten
Alternative
Repository bei domänenorientierter Aggregat-Sicht
Testfokus
SQL-Mapping, Transaktionen und Fehlerübersetzung
TableDataGatewayPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.List;
// Data Source Pattern: Table Data Gateway
// Zweck: Tabellenzugriffe an einer technischen Grenze bündeln.
public final class TableDataGatewayPatternLab {
public record CustomerRow(String id,String name) {}
public interface CustomerTableGateway {
CustomerRow findById(String id);
List<CustomerRow> findActive();
void insert(CustomerRow row);
}
}
Karte 74 Row Data Gateway Eine Datenzeile durch ein Objekt mit Persistenzoperationen repräsentieren
Code Smell
Call-Sites hantieren direkt mit ResultSet und SQL
Struktur
Ein Gateway-Objekt kapselt Daten einer Zeile und deren Persistenz
Verwenden
Legacy-Systeme mit zeilenorientiertem Datenzugriff
Nicht verwenden
Bei komplexen Aggregaten und invariantenreicher Domäne
Alternative
Active Record bei stärkerem Domänenverhalten
Testfokus
Insert, Update, Delete und konkurrierende Änderungen
RowDataGatewayPatternLab.java
package com.aydinsude.workbench.explorer;
// Data Source Pattern: Row Data Gateway
// Zweck: Eine Tabellenzeile samt Persistenzoperationen kapseln.
public final class RowDataGatewayPatternLab {
public interface Store { void update(String id,String name); }
public static final class CustomerRowGateway {
private final String id; private String name;
public CustomerRowGateway(String id,String name){ this.id=id; this.name=name; }
public void rename(String value){ name=value; }
public void update(Store store){ store.update(id,name); }
}
}
Karte 75 Metadata Mapping Mapping-Regeln deklarativ statt pro Klasse handgeschrieben verwalten
Code Smell
Viele Mapper wiederholen dieselben Feldzuordnungen
Struktur
Metadaten beschreiben Spalten, Felder und Konvertierungen
Verwenden
Viele gleichförmige Tabellen und technische Integrationsmodelle
Nicht verwenden
Wenn Mapping fachliche Entscheidungen enthält
Alternative
Expliziter Data Mapper bei kritischer Fachsemantik
Testfokus
Unbekannte Felder, Typkonvertierung und Versionswechsel
MetadataMappingPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.Map;
// Data Source Pattern: Metadata Mapping
// Zweck: Wiederkehrende Feldzuordnungen deklarativ beschreiben.
public final class MetadataMappingPatternLab {
public record FieldMapping(String column,String property,Class<?> type) {}
public record EntityMapping(String table,Map<String,FieldMapping> fields) {
public EntityMapping { fields=Map.copyOf(fields); }
}
}
Karte 76 Embedded Value Mehrere Spalten als fachliches Value Object modellieren
Code Smell
Adresse oder Geldbetrag zerfällt in lose primitive Felder
Struktur
Ein unveränderliches Value Object wird in derselben Tabelle eingebettet
Verwenden
Adressen, Geld, Zeiträume und Maßeinheiten
Nicht verwenden
Wenn der Wert unabhängig referenziert und geändert wird
Alternative
Separate Entity bei eigener Identität und Lebenszyklus
Testfokus
Roundtrip-Mapping, Nullbehandlung und Wertgleichheit
EmbeddedValuePatternLab.java
package com.aydinsude.workbench.explorer;
// Data Source Pattern: Embedded Value
// Zweck: Zusammengehörige Spalten als unveränderlichen Fachwert modellieren.
public final class EmbeddedValuePatternLab {
public record Address(String street,String city,String postalCode) {
public Address {
if(street.isBlank() || city.isBlank() || postalCode.isBlank())
throw new IllegalArgumentException("incomplete address");
}
}
public record CustomerRow(String id,Address address) {}
}
Viele volatile Detailfelder erzwingen häufige Schemaänderungen
Struktur
Ein klar versioniertes Dokument wird als LOB serialisiert
Verwenden
Snapshots, Auditpayloads und externe Rohdokumente
Nicht verwenden
Wenn einzelne Felder gesucht, gefiltert oder referenziert werden
Alternative
Normalisierte Tabellen bei häufigen Teilabfragen
Testfokus
Schema-Version, Rückwärtskompatibilität und Größenlimit
SerializedLobPatternLab.java
package com.aydinsude.workbench.explorer;
// Data Source Pattern: Serialized LOB
// Zweck: Versionierten Snapshot als unteilbares Dokument speichern.
public final class SerializedLobPatternLab {
public record Snapshot(int schemaVersion,String json) {
public Snapshot {
if(schemaVersion < 1) throw new IllegalArgumentException("schemaVersion");
if(json == null || json.isBlank()) throw new IllegalArgumentException("json");
}
}
public interface SnapshotStore { void save(String aggregateId,Snapshot snapshot); }
}
Karte 78 Single Table Inheritance Eine Vererbungshierarchie in einer Tabelle speichern
Code Smell
Jeder Subtyp erhält redundante Tabellen und Joins
Struktur
Ein Typ-Diskriminator entscheidet, welche Spalten gültig sind
Verwenden
Kleine Hierarchien mit vielen gemeinsamen Feldern
Nicht verwenden
Bei vielen nullable Spalten oder strikten Constraints
Alternative
Class Table Inheritance bei stark unterschiedlichen Subtypen
Testfokus
Diskriminator, ungültige Spaltenkombinationen und Migration
SingleTableInheritancePatternLab.java
package com.aydinsude.workbench.explorer;
// Data Source Pattern: Single Table Inheritance
// Zweck: Eine kleine Hierarchie mit Diskriminator in einer Tabelle abbilden.
public final class SingleTableInheritancePatternLab {
public sealed interface Payment permits CardPayment,BankPayment {}
public record CardPayment(String maskedCard) implements Payment {}
public record BankPayment(String iban) implements Payment {}
public record PaymentRow(String type,String card,String iban) {}
}
Karte 79 Class Table Inheritance Gemeinsame und subtypeigene Felder relational sauber trennen
Code Smell
Eine Einzeltabelle enthält viele leere subtypeigene Spalten
Struktur
Basistabelle und je Subtyp eine zusätzliche Tabelle werden verbunden
Verwenden
Hierarchien mit klar getrennten subtypeigenen Daten
Nicht verwenden
Wenn Joins in kritischen Lesewegen zu teuer sind
Alternative
Single Table Inheritance für schnellere einfache Reads
Testfokus
Join-Vollständigkeit, Löschreihenfolge und Typkonsistenz
ClassTableInheritancePatternLab.java
package com.aydinsude.workbench.explorer;
// Data Source Pattern: Class Table Inheritance
// Zweck: Gemeinsame und subtypeigene Persistenzdaten trennen.
public final class ClassTableInheritancePatternLab {
public record PaymentBaseRow(String id,String type,long cents) {}
public record CardPaymentRow(String id,String maskedCard) {}
public record BankPaymentRow(String id,String iban) {}
public record CardPayment(String id,long cents,String maskedCard) {}
}
Karte 80 Concrete Table Inheritance Jeden konkreten Subtyp vollständig in eigener Tabelle speichern
Code Smell
Polymorphe Reads zwingen unklare Mischmodelle
Struktur
Jede konkrete Tabelle enthält geerbte und eigene Felder vollständig
Verwenden
Wenige unabhängige Subtypen mit getrennten Zugriffspfaden
Nicht verwenden
Bei häufigen polymorphen Abfragen über alle Subtypen
Alternative
Class Table Inheritance bei zentralen gemeinsamen Constraints
Testfokus
Union-Abfragen, ID-Eindeutigkeit und duplizierte Spalten
ConcreteTableInheritancePatternLab.java
package com.aydinsude.workbench.explorer;
// Data Source Pattern: Concrete Table Inheritance
// Zweck: Konkrete Subtypen unabhängig und vollständig persistieren.
public final class ConcreteTableInheritancePatternLab {
public record CardPaymentRow(String id,long cents,String maskedCard) {}
public record BankPaymentRow(String id,long cents,String iban) {}
public interface PaymentReader {
CardPaymentRow card(String id);
BankPaymentRow bank(String id);
}
}
Kapitel 15 Pattern-Karten: Domain Model und Fachgrenzen10 Pattern-Karten
Zehn Patternkarten verbinden Transaktionslogik, DDD-Bausteine, Modulgrenzen und stabile Integrationssprache.
Patternkarten · Abschnitt 9
Pattern Explorer · Domain Model & Fachgrenzen
Zehn Patternkarten verbinden Transaktionslogik, DDD-Bausteine, Modulgrenzen und stabile Integrationssprache.
Patternkarten: 90/100 Patternkarten abgeschlossen
10 Patternkarten offen.
Karte 81 Transaction Script Einen fachlichen Ablauf pro Use Case als klaren Transaktionsschritt bündeln
Code Smell
Services enthalten verstreute SQL- und Regel-Fragmente
Struktur
Eine Prozedur koordiniert Laden, Regeln, Schreiben und Commit
Verwenden
Einfache Use Cases, CRUD-nahe Fachlogik, Migration
Nicht verwenden
Bei wachsendem Domänenverhalten und vielen Invarianten
Alternative
Domain Model für reichhaltiges Verhalten
Testfokus
Transaktionsgrenzen, Rollback und Idempotenz
TransactionScriptPatternLab.java
package com.aydinsude.workbench.explorer;
// Enterprise Pattern: Transaction Script
// Zweck: Einen vollständigen Use Case in einer klaren Transaktionsgrenze koordinieren.
public final class TransactionScriptPatternLab {
public interface Orders { Order load(String id); void save(Order order); }
public record Order(String id, boolean paid) { Order pay(){ if(paid) throw new IllegalStateException("already paid"); return new Order(id,true); } }
public static final class PayOrder {
private final Orders orders; public PayOrder(Orders orders){this.orders=orders;}
public void execute(String id){ var order=orders.load(id); orders.save(order.pay()); }
}
}
Karte 82 Domain Model Fachregeln und Invarianten in verhaltensreichen Objekten bündeln
Code Smell
Anämische Datenobjekte und Regelcode in Services
Struktur
Entities und Value Objects schützen ihre Invarianten selbst
Verwenden
Komplexe Domänen mit langlebigen Regeln
Nicht verwenden
Bei sehr einfachen datengetriebenen Abläufen
Alternative
Transaction Script für überschaubare Use Cases
Testfokus
Invarianten, Zustandsübergänge und Persistenzfreiheit
DomainModelPatternLab.java
package com.aydinsude.workbench.explorer;
// Domain Model Pattern
// Zweck: Fachregeln und Invarianten im Modell statt in technischen Services halten.
public final class DomainModelPatternLab {
public static final class Account {
private long balance; public Account(long balance){ if(balance<0) throw new IllegalArgumentException(); this.balance=balance; }
public void withdraw(long amount){ if(amount<=0 || amount>balance) throw new IllegalArgumentException("invalid withdrawal"); balance-=amount; }
public long balance(){ return balance; }
}
}
Karte 83 Aggregate Konsistenzgrenzen mit einer einzigen Wurzel kontrollieren
Code Smell
Beliebige Objekte werden unabhängig verändert
Struktur
Aggregate Root autorisiert Änderungen innerhalb einer Grenze
Verwenden
DDD, konkurrierende Änderungen und klare Transaktionen
Nicht verwenden
Bei reinem Reporting oder lose gekoppelten Daten
Alternative
Kleinere Entities ohne gemeinsame Konsistenzgrenze
Testfokus
Invarianten, Versionskonflikte und externe Referenzen
AggregatePatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.ArrayList; import java.util.List;
// DDD Pattern: Aggregate
// Zweck: Änderungen und Invarianten über eine einzige Aggregate Root kontrollieren.
public final class AggregatePatternLab {
public static final class Order {
private final String id; private final List<String> items=new ArrayList<>(); private boolean submitted;
public Order(String id){this.id=id;} public void add(String item){ if(submitted) throw new IllegalStateException(); items.add(item); }
public void submit(){ if(items.isEmpty()) throw new IllegalStateException("empty"); submitted=true; }
public String id(){return id;}
}
}
Karte 84 Entity Fachobjekte über stabile Identität statt Feldgleichheit unterscheiden
Code Smell
Objekte werden nur anhand aller Werte verglichen
Struktur
Identität bleibt trotz Zustandsänderungen stabil
Verwenden
Kunden, Verträge, Tickets und Bestellungen
Nicht verwenden
Für reine Werte ohne eigenen Lebenszyklus
Alternative
Value Object bei Wertgleichheit
Testfokus
Identität, Lebenszyklus und Gleichheitsvertrag
EntityPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.Objects;
// DDD Pattern: Entity
// Zweck: Gleichheit und Lebenszyklus über stabile Identität definieren.
public final class EntityPatternLab {
public static final class Customer {
private final String id; private String name; public Customer(String id,String name){this.id=id;this.name=name;}
public void rename(String value){name=value;} public String name(){return name;}
public boolean equals(Object o){return o instanceof Customer c && id.equals(c.id);}
public int hashCode(){return Objects.hash(id);}
}
}
Karte 85 Value Object Fachwerte unveränderlich, validiert und vergleichbar modellieren
Code Smell
Primitive Obsession und verstreute Validierung
Struktur
Ein unveränderlicher Typ kapselt Wert und Regeln
Verwenden
Geld, Adresse, Zeitraum, Kennung und Maßeinheit
Nicht verwenden
Bei Objekten mit eigenem Lebenszyklus
Alternative
Entity bei stabiler Identität
Testfokus
Konstruktion, Wertgleichheit und ungültige Eingaben
ValueObjectPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.Currency;
// DDD Pattern: Value Object
// Zweck: Fachwerte unveränderlich, validiert und mit Wertgleichheit modellieren.
public final class ValueObjectPatternLab {
public record Money(long minorUnits, Currency currency) {
public Money { if(currency==null) throw new IllegalArgumentException("currency"); }
public Money add(Money other){ if(!currency.equals(other.currency)) throw new IllegalArgumentException("currency mismatch"); return new Money(minorUnits+other.minorUnits,currency); }
}
}
Karte 86 Domain Service Domänenlogik ohne natürliche Entity-Zuordnung ausdrücken
Code Smell
Regeln werden künstlich in eine Entity gedrückt
Struktur
Ein zustandsloser Dienst arbeitet ausschließlich mit Domänentypen
Verwenden
Berechnung oder Entscheidung über mehrere Aggregate
Nicht verwenden
Für technische Orchestrierung und I/O
Alternative
Application Service für Use-Case-Koordination
Testfokus
Fachregeln, Determinismus und Infrastrukturunabhängigkeit
DomainServicePatternLab.java
package com.aydinsude.workbench.explorer;
// DDD Pattern: Domain Service
// Zweck: Fachlogik ausdrücken, die keiner einzelnen Entity natürlich gehört.
public final class DomainServicePatternLab {
public record Route(int kilometers, boolean international) {}
public static final class ShippingPrice {
public long calculate(Route route){ return route.kilometers()*12L + (route.international()?2500:0); }
}
}
Karte 87 Application Service Use Cases, Transaktionen und Ports koordinieren
Code Smell
Controller enthalten Geschäftsablauf und Infrastrukturzugriffe
Struktur
Ein dünner Dienst orchestriert Domäne und Ports
Verwenden
API-, Batch- und Messaging-Einstiegspunkte
Nicht verwenden
Für reine Domänenregeln ohne Orchestrierung
Alternative
Domain Service für fachliche Berechnungen
Testfokus
Transaktionsgrenze, Portaufrufe und Fehlerübersetzung
ApplicationServicePatternLab.java
package com.aydinsude.workbench.explorer;
// DDD Pattern: Application Service
// Zweck: Use Case, Domäne und technische Ports in einer Transaktionsgrenze koordinieren.
public final class ApplicationServicePatternLab {
public interface Repository { Ticket load(String id); void save(Ticket ticket); }
public interface Events { void publish(String event); }
public record Ticket(String id, boolean closed){ Ticket close(){return new Ticket(id,true);} }
public static final class CloseTicket {
private final Repository repo; private final Events events; public CloseTicket(Repository r,Events e){repo=r;events=e;}
public void execute(String id){var t=repo.load(id).close(); repo.save(t); events.publish("TicketClosed:"+id);}
}
}
Booleans und if-Kaskaden verstecken Geschäftsregeln
Struktur
Eine Policy liefert eine fachlich benannte Entscheidung
Verwenden
Freigabe, Risiko, Preis und Berechtigung
Nicht verwenden
Bei rein technischer Algorithmusvariation
Alternative
Strategy für technische Varianten
Testfokus
Grenzwerte, Kombinationen und nachvollziehbare Gründe
PolicyPatternLab.java
package com.aydinsude.workbench.explorer;
// DDD Pattern: Policy
// Zweck: Austauschbare fachliche Entscheidung explizit und testbar machen.
public final class PolicyPatternLab {
public record Claim(long cents,int riskScore) {}
public record Decision(boolean approved,String reason) {}
public interface ApprovalPolicy { Decision decide(Claim claim); }
public static final class StandardApproval implements ApprovalPolicy {
public Decision decide(Claim c){ return c.cents()<=100_000 && c.riskScore()<70 ? new Decision(true,"auto") : new Decision(false,"manual review"); }
}
}
Karte 89 Module Zusammengehörige Domänentypen hinter einer stabilen Oberfläche bündeln
Code Smell
Pakete spiegeln Technik statt Fachlichkeit
Struktur
Ein Modul kapselt Modell, Dienste und öffentliche API
Verwenden
Modularer Monolith und klar abgegrenzte Fachbereiche
Nicht verwenden
Bei winzigen Projekten ohne sinnvolle Grenze
Alternative
Package-by-layer für sehr einfache Anwendungen
Testfokus
Sichtbarkeit, Abhängigkeitsrichtung und API-Stabilität
ModulePatternLab.java
package com.aydinsude.workbench.explorer;
// DDD Pattern: Module
// Zweck: Zusammengehörige Fachlogik hinter einer kleinen öffentlichen Oberfläche bündeln.
public final class ModulePatternLab {
public interface BillingModule { Invoice issue(String orderId,long cents); }
public record Invoice(String orderId,long cents,String status) {}
static final class DefaultBillingModule implements BillingModule {
public Invoice issue(String orderId,long cents){ if(cents<=0) throw new IllegalArgumentException(); return new Invoice(orderId,cents,"ISSUED"); }
}
}
Karte 90 Published Language Integrationsverträge als gemeinsame, versionierte Sprache definieren
Code Smell
Systeme teilen interne Modelle und implizite Bedeutungen
Struktur
Ein explizites Schema stabilisiert Begriffe und Versionen
Verwenden
Events, APIs und organisationsübergreifende Integration
Nicht verwenden
Für rein interne Aufrufe im selben Modul
Alternative
DTO ohne langfristigen Integrationsvertrag
Testfokus
Kompatibilität, Versionierung und semantische Eindeutigkeit
PublishedLanguagePatternLab.java
package com.aydinsude.workbench.explorer;
import java.time.Instant;
// Integration Pattern: Published Language
// Zweck: Einen expliziten, versionierten und systemübergreifenden Vertrag definieren.
public final class PublishedLanguagePatternLab {
public record OrderAcceptedV1(String eventId,String orderId,long totalMinor,String currency,Instant occurredAt) {}
public interface ContractValidator { void validate(OrderAcceptedV1 event); }
}
Kapitel 16 Pattern-Karten: Integration und verteilte Koordination10 Pattern-Karten
Die letzten zehn Patternkarten schließen den Explorer mit Ereignisverträgen, Messaging-Korrelation, Normalisierung und verteilter Koordination ab.
Patternkarten · Abschnitt 10
Pattern Explorer · Integration & Distributed Coordination
Die letzten zehn Patternkarten schließen den Explorer mit Ereignisverträgen, Messaging-Korrelation, Normalisierung und verteilter Koordination ab.
Karte 91 Event Notification Ein Ereignis signalisiert nur, dass sich etwas geändert hat
Code Smell
Implizite Kopplung, unklare Zuständigkeit oder fragile verteilte Abstimmung
Struktur
Ein Ereignis signalisiert nur, dass sich etwas geändert hat
Verwenden
Verteilte Systeme, wenn Empfänger bei Bedarf nachladen können
Nicht verwenden
Wenn Empfänger sofort alle Daten benötigen
Alternative
Event-Carried State Transfer
Testfokus
Vertragsstabilität, Duplikate und Nachladen
EventNotificationPatternLab.java
package com.aydinsude.workbench.explorer;
import java.time.Instant;
// Integration Pattern: Event Notification
// Zweck: Eine Zustandsänderung knapp signalisieren, ohne das gesamte Fachobjekt zu übertragen.
public final class EventNotificationPatternLab {
public record CustomerChanged(String customerId, Instant occurredAt) {}
public interface Publisher { void publish(CustomerChanged event); }
public static final class CustomerService {
private final Publisher publisher; public CustomerService(Publisher p){publisher=p;}
public void rename(String id,String name){ if(name.isBlank()) throw new IllegalArgumentException(); publisher.publish(new CustomerChanged(id,Instant.now())); }
}
}
Karte 92 Event-Carried State Transfer Ein Event transportiert den für Empfänger nötigen Zustand
Code Smell
Implizite Kopplung, unklare Zuständigkeit oder fragile verteilte Abstimmung
Struktur
Ein Event transportiert den für Empfänger nötigen Zustand
Verwenden
Entkoppelte Lesemodelle und verteilte Datenkopien
Nicht verwenden
Bei großen, häufig wechselnden Payloads
Alternative
Event Notification
Testfokus
Versionierung, Reihenfolge und veraltete Events
EventCarriedStateTransferPatternLab.java
package com.aydinsude.workbench.explorer;
import java.time.Instant;
// Integration Pattern: Event-Carried State Transfer
// Zweck: Empfänger mit ausreichendem Zustand versorgen und synchrone Nachladeaufrufe vermeiden.
public final class EventCarriedStateTransferPatternLab {
public record CustomerSnapshotV1(String id,String name,String segment,long version,Instant occurredAt) {}
public interface Consumer { void apply(CustomerSnapshotV1 event); }
}
Karte 93 Request-Reply Eine Anfrage erwartet eine eindeutig zugeordnete Antwort
Code Smell
Implizite Kopplung, unklare Zuständigkeit oder fragile verteilte Abstimmung
Struktur
Eine Anfrage erwartet eine eindeutig zugeordnete Antwort
Verwenden
Asynchrone Integration mit fachlich benötigter Antwort
Nicht verwenden
Für reine Benachrichtigungen ohne Rückkanal
Alternative
Event Notification
Testfokus
Timeout, Korrelation und Antwortfehler
RequestReplyPatternLab.java
package com.aydinsude.workbench.explorer;
import java.util.UUID;
// Messaging Pattern: Request-Reply
// Zweck: Asynchrone Anfrage und Antwort über eine stabile Korrelation verbinden.
public final class RequestReplyPatternLab {
public record Request(String correlationId,String customerId) { public static Request of(String id){return new Request(UUID.randomUUID().toString(),id);} }
public record Reply(String correlationId,boolean eligible,String reason) {}
public interface Gateway { Reply request(Request request); }
}
Karte 94 Correlation Identifier Zusammengehörige Nachrichten über eine stabile Kennung verbinden
Code Smell
Implizite Kopplung, unklare Zuständigkeit oder fragile verteilte Abstimmung
Struktur
Zusammengehörige Nachrichten über eine stabile Kennung verbinden
Verwenden
Request-Reply, Saga und verteiltes Tracing
Nicht verwenden
Wenn eine einzige synchrone Transaktion genügt
Alternative
Process Manager
Testfokus
Eindeutigkeit, Weitergabe und Log-Konsistenz
CorrelationIdentifierPatternLab.java
package com.aydinsude.workbench.explorer;
// Messaging Pattern: Correlation Identifier
// Zweck: Nachrichten eines fachlichen Ablaufs eindeutig zusammenführen.
public final class CorrelationIdentifierPatternLab {
public record Message(String messageId,String correlationId,String payload) {}
public static boolean belongsTogether(Message left,Message right){ return left.correlationId().equals(right.correlationId()); }
}
Karte 95 Return Address Die Antwortadresse explizit in der Nachricht mitführen
Code Smell
Implizite Kopplung, unklare Zuständigkeit oder fragile verteilte Abstimmung
Struktur
Die Antwortadresse explizit in der Nachricht mitführen
Verwenden
Dynamische Antwortkanäle in Messaging-Systemen
Nicht verwenden
Wenn ein fester synchroner Rückkanal existiert
Alternative
Request-Reply Gateway
Testfokus
Adressvalidierung, Berechtigung und Routing
ReturnAddressPatternLab.java
package com.aydinsude.workbench.explorer;
// Messaging Pattern: Return Address
// Zweck: Den Rückkanal unabhängig vom sendenden Transport explizit bestimmen.
public final class ReturnAddressPatternLab {
public record Envelope(String correlationId,String replyTo,String payload) {
public Envelope { if(replyTo==null || replyTo.isBlank()) throw new IllegalArgumentException("replyTo"); }
}
public interface Sender { void send(String destination,String payload); }
public static void reply(Envelope request,String body,Sender sender){ sender.send(request.replyTo(),body); }
}
Karte 96 Normalizer Verschiedene Eingangsformate in ein einheitliches internes Format überführen
Code Smell
Implizite Kopplung, unklare Zuständigkeit oder fragile verteilte Abstimmung
Struktur
Verschiedene Eingangsformate in ein einheitliches internes Format überführen
Verwenden
Mehrere Partnerformate und Legacy-Schnittstellen
Nicht verwenden
Wenn alle Quellen bereits denselben Vertrag verwenden
Alternative
Message Translator
Testfokus
Formatfehler, semantische Gleichheit und Erweiterbarkeit
NormalizerPatternLab.java
package com.aydinsude.workbench.explorer;
// Integration Pattern: Normalizer
// Zweck: Unterschiedliche externe Nachrichten in ein stabiles internes Modell übersetzen.
public final class NormalizerPatternLab {
public record CanonicalOrder(String id,long totalMinor,String currency) {}
public sealed interface Input permits CsvOrder,JsonOrder {}
public record CsvOrder(String id,String cents,String currency) implements Input {}
public record JsonOrder(String orderId,long totalMinor,String currency) implements Input {}
public static CanonicalOrder normalize(Input in){
return switch(in){ case CsvOrder c -> new CanonicalOrder(c.id(),Long.parseLong(c.cents()),c.currency()); case JsonOrder j -> new CanonicalOrder(j.orderId(),j.totalMinor(),j.currency()); };
}
}
Karte 97 Canonical Data Model Eine gemeinsame Integrationssprache zwischen mehreren Systemen etablieren
Code Smell
Implizite Kopplung, unklare Zuständigkeit oder fragile verteilte Abstimmung
Struktur
Eine gemeinsame Integrationssprache zwischen mehreren Systemen etablieren
Verwenden
Viele Systeme mit wiederkehrenden gemeinsamen Begriffen
Nicht verwenden
Wenn Domänen stark verschieden sind oder sich unabhängig entwickeln
Alternative
Published Language plus lokale Übersetzer
Testfokus
Semantik, Governance und Versionsmigration
CanonicalDataModelPatternLab.java
package com.aydinsude.workbench.explorer;
// Integration Pattern: Canonical Data Model
// Zweck: Punkt-zu-Punkt-Übersetzungen durch einen gemeinsamen Integrationsvertrag reduzieren.
public final class CanonicalDataModelPatternLab {
public record Party(String id,String displayName) {}
public record Money(long minor,String currency) {}
public record CanonicalInvoice(String id,Party buyer,Money total,String status) {}
}
Karte 98 Distributed Lock Kritische verteilte Arbeit zeitweise exklusiv reservieren
Code Smell
Implizite Kopplung, unklare Zuständigkeit oder fragile verteilte Abstimmung
Struktur
Kritische verteilte Arbeit zeitweise exklusiv reservieren
Verwenden
Seltene exklusive Jobs über mehrere Instanzen
Nicht verwenden
Wenn Idempotenz oder Partitionierung genügt
Alternative
Leader Election oder Optimistic Locking
Testfokus
Lease-Ablauf, Fencing Token und Fehler beim Freigeben
DistributedLockPatternLab.java
package com.aydinsude.workbench.explorer;
import java.time.Duration;
// Distributed Systems Pattern: Distributed Lock
// Zweck: Eine kritische Operation über Prozessgrenzen hinweg exklusiv ausführen.
public final class DistributedLockPatternLab {
public interface LockPort { boolean tryAcquire(String key,Duration lease); void release(String key); }
public static final class SettlementJob {
private final LockPort locks; public SettlementJob(LockPort locks){this.locks=locks;}
public boolean run(String day){ if(!locks.tryAcquire("settlement:"+day,Duration.ofMinutes(5))) return false; try { return true; } finally { locks.release("settlement:"+day); } }
}
}
Karte 99 Leader Election Genau eine Instanz für koordinierende Aufgaben bestimmen
Code Smell
Implizite Kopplung, unklare Zuständigkeit oder fragile verteilte Abstimmung
Struktur
Genau eine Instanz für koordinierende Aufgaben bestimmen
Verwenden
Clusterweite Scheduler, Rebalancing und Koordination
Nicht verwenden
Für unabhängig partitionierbare Arbeit
Alternative
Competing Consumers
Testfokus
Split Brain, Failover und Amtszeit
LeaderElectionPatternLab.java
package com.aydinsude.workbench.explorer;
// Distributed Systems Pattern: Leader Election
// Zweck: Eine von mehreren Instanzen kontrolliert koordinierende Hintergrundarbeit.
public final class LeaderElectionPatternLab {
public interface Election { boolean isLeader(String instanceId); }
public static final class Scheduler {
private final String instanceId; private final Election election; public Scheduler(String id,Election e){instanceId=id;election=e;}
public boolean tick(){ return election.isLeader(instanceId); }
}
}
Karte 100 Sidecar Technische Querschnittsfunktionen neben dem Anwendungsprozess betreiben
Code Smell
Implizite Kopplung, unklare Zuständigkeit oder fragile verteilte Abstimmung
Struktur
Technische Querschnittsfunktionen neben dem Anwendungsprozess betreiben
Verwenden
Telemetry, Proxying, Konfiguration und Zertifikate
Nicht verwenden
Wenn die Funktion eng zur Domänenlogik gehört
Alternative
Library oder Service Mesh
Testfokus
Lebenszyklus, Ressourcenverbrauch und lokale Ausfälle
SidecarPatternLab.java
package com.aydinsude.workbench.explorer;
// Cloud Pattern: Sidecar
// Zweck: Technische Querschnittsfunktionen unabhängig vom Fachprozess bereitstellen.
public final class SidecarPatternLab {
public interface LocalTelemetrySidecar { void emit(String metric,long value); }
public static final class OrderApplication {
private final LocalTelemetrySidecar telemetry; public OrderApplication(LocalTelemetrySidecar t){telemetry=t;}
public void accept(String orderId){ telemetry.emit("orders.accepted",1); }
}
}
Buchteil IV
100 ausführliche Pattern-Fachkapitel
Die Vertiefungsbände behandeln Zweck, Problemkräfte, Umsetzung, Grenzen, Tests, verwandte Patterns und typische Fehlanwendungen.
Vertiefungsband 1 Kernmuster und EntscheidungsschwellenPattern 1–20
Klassische Erzeugungs-, Struktur- und Verhaltensmuster mit klarer Problemformulierung, Einsatzschwelle und Gegenanzeige.
Dieses Kapitel ersetzt die bisherigen Ein-Satz-Erklärungen für die wichtigsten Muster durch ausführliche deutsche Fachtexte. Nur das Hauptkapitel ist klappbar; alle Unterabschnitte bleiben innerhalb des geöffneten Kapitels sichtbar.
Strategy ist sinnvoll, wenn derselbe fachliche Auftrag mit mehreren Verfahren erfüllt werden kann und diese Verfahren unabhängig voneinander geändert, ausgewählt und getestet werden sollen.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Context: kennt den fachlichen Ablauf, aber nicht die konkrete Berechnungsvariante.
Strategy: definiert den gemeinsamen Vertrag der Varianten.
Konkrete Strategy: enthält genau eine Variante.
Resolver oder Registry: wählt die Variante anhand fachlicher Merkmale aus.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
wachsende if-/switch-Ketten für Preis-, Risiko- oder Routingvarianten
Varianten besitzen eigene Regeln und Release-Zyklen
jede Variante soll isoliert testbar sein
Wann nicht?
bei nur einer stabilen Variante
wenn sich ausschließlich Daten unterscheiden und eine Tabelle genügt
wenn die Auswahl selbst unkontrolliert über viele Registries verteilt würde
Abgrenzung und typische Fehlanwendung
State kapselt zustandsabhängiges Verhalten und verwaltet Übergänge; Strategy kapselt eine gewählte Vorgehensweise, ohne selbst den Lebenszyklus des Context zu steuern.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
02
State
Zustandsabhängiges Verhalten und erlaubte Übergänge
Verhaltensmuster
Fachliche Einordnung
State macht Zustände zu eigenständigen Verhaltensobjekten. Dadurch stehen nicht nur Statusnamen, sondern auch erlaubte Aktionen, Übergänge und Fehlermeldungen an einer klaren Stelle.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Context: hält den aktuellen Zustand und delegiert Aktionen.
State: gemeinsamer Vertrag für zustandsabhängige Operationen.
Konkreter Zustand: implementiert erlaubtes Verhalten und Übergänge.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
große switch-Blöcke über Statuswerte
dieselbe Statusprüfung taucht in mehreren Methoden auf
Übergänge besitzen fachliche Vorbedingungen
Wann nicht?
wenn ein Enum nur angezeigt oder gespeichert wird
bei einem linearen Ablauf ohne alternatives Verhalten
wenn Übergänge besser als kleine explizite Tabelle verständlich bleiben
Abgrenzung und typische Fehlanwendung
Strategy wird von außen ausgewählt; State ändert sich während des Lebenszyklus und bestimmt, welche Aktion aktuell zulässig ist.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
03
Adapter
Unpassende Schnittstelle übersetzen
Strukturmuster
Fachliche Einordnung
Ein Adapter schützt den eigenen Code vor einer fremden oder veralteten Schnittstelle. Er übersetzt Aufruf, Datentypen, Fehler und Semantik in einen Vertrag, der zur eigenen Fachsprache passt.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Ziel-Schnittstelle: der vom eigenen System benötigte Vertrag.
Adapter: übersetzt zwischen Ziel-Schnittstelle und Fremdsystem.
Adaptee: vorhandene Bibliothek, Legacy-API oder externer Dienst.
Mapper: übersetzt Daten und Fehler, falls dies nicht trivial ist.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
Drittanbieter-API verwendet andere Typen oder Begriffe
Legacy-Client darf nicht in die Domäne durchsickern
Austauschbarkeit und Testbarkeit einer Integration sind wichtig
Wann nicht?
wenn nur ein Methodenname abweicht und keine stabile Grenze nötig ist
wenn eigentlich das eigene Subsystem vereinfacht werden soll - dafür ist Facade geeigneter
wenn unterschiedliche Fachmodelle geschützt werden müssen - dann ist ein ACL umfassender
Abgrenzung und typische Fehlanwendung
Facade vereinfacht ein eigenes komplexes Subsystem; Adapter macht eine inkompatible Schnittstelle passend. Ein Anti-Corruption Layer schützt zusätzlich die fachliche Semantik.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
04
Facade
Komplexes Subsystem über einen klaren Einstieg anbieten
Strukturmuster
Fachliche Einordnung
Eine Fassade stellt ausgewählte, häufig benötigte Abläufe eines Subsystems über eine verständliche Oberfläche bereit. Sie verbirgt technische Reihenfolgen, ohne das Subsystem selbst neu zu modellieren.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Facade: bietet wenige aufgabenorientierte Operationen.
Subsystemklassen: führen die spezialisierten Teilschritte aus.
Client: hängt nur von der vereinfachten Oberfläche ab.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
Aufrufer müssen mehrere technische Dienste in fester Reihenfolge bedienen
viele interne Klassen sind nach außen sichtbar
ein stabiler Einstiegspunkt soll Änderungen im Subsystem abschirmen
Wann nicht?
wenn die Fassade zu einer God Class mit sämtlicher Fachlogik wird
wenn nur eine fremde Schnittstelle übersetzt werden muss
wenn unterschiedliche Fachmodelle voneinander isoliert werden müssen
Abgrenzung und typische Fehlanwendung
Mediator koordiniert die Zusammenarbeit gleichrangiger Komponenten. Facade bietet dagegen vor allem eine vereinfachte Außensicht auf ein Subsystem.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
05
Builder
Komplexe, valide Objekte schrittweise aufbauen
Erzeugungsmuster
Fachliche Einordnung
Builder trennt die schrittweise Zusammenstellung eines Objekts von dessen fertiger Repräsentation. Besonders nützlich ist er bei vielen optionalen Werten, fachlichen Voreinstellungen und einer abschließenden Validierung.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Builder: sammelt Eingaben in verständlichen Schritten.
Produkt: das fertig erzeugte, möglichst unveränderliche Objekt.
build(): prüft Invarianten und erzeugt das Produkt.
Director optional: beschreibt eine wiederverwendbare Baufolge.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
Konstruktoren werden lang und unlesbar
optionale Parameter führen zu vielen Überladungen
Objekte müssen erst am Ende vollständig validiert werden
Wann nicht?
bei kleinen Records mit wenigen Pflichtwerten
wenn ein statisches Factory-Verfahren die Varianten bereits klar ausdrückt
wenn der Builder ungültige Zwischenzustände dauerhaft nach außen gibt
Abgrenzung und typische Fehlanwendung
Factory entscheidet primär, welcher konkrete Typ erzeugt wird. Builder beschreibt, wie ein komplexes Objekt Schritt für Schritt zusammengesetzt wird.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
06
Factory Method
Erzeugungsentscheidung hinter einem Vertrag kapseln
Erzeugungsmuster
Fachliche Einordnung
Die Fabrikmethode verlagert die konkrete Objekterzeugung hinter eine Methode. Im GoF-Sinn entscheiden Unterklassen über das Produkt; in Java-Projekten wird der Begriff oft auch für benannte statische Erzeugungsmethoden verwendet.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Creator: definiert den Erzeugungspunkt.
Concrete Creator: bestimmt das konkrete Produkt.
Product: gemeinsamer Vertrag der erzeugten Objekte.
Factory Method: liefert ein passendes Produkt zurück.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
der konkrete Typ soll vom Aufrufer verborgen bleiben
Erzeugung benötigt Validierung, Caching oder Auswahlregeln
direkte Konstruktoraufrufe verteilen sich im Code
Wann nicht?
wenn new bereits eindeutig und korrekt ist
wenn eine umfangreiche schrittweise Konfiguration nötig ist - dann Builder
wenn eine ganze zusammengehörige Produktfamilie erzeugt wird - dann Abstract Factory
Abgrenzung und typische Fehlanwendung
Eine einfache statische Factory ist nicht automatisch die polymorphe GoF-Fabrikmethode. Die Dokumentation sollte offenlegen, welche Variante gemeint ist.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Deutschsprachige fachliche Grundlagen
Fabrikmethode - fachliche Grundlage für Begriff, Rollen oder Einsatzgrenzen.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
07
Decorator
Verhalten eines Objekts schichtweise ergänzen
Strukturmuster
Fachliche Einordnung
Decorator legt zusätzliche Verantwortung um ein Objekt, das denselben Vertrag erfüllt. Mehrere Ergänzungen können kombiniert werden, ohne für jede Kombination eine neue Unterklasse zu erzeugen.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Component: gemeinsamer Vertrag.
Concrete Component: ursprüngliche Funktion.
Decorator: hält einen Delegate desselben Typs.
Concrete Decorator: ergänzt Verhalten vor oder nach der Delegation.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
Logging, Metriken, Validierung oder Caching sollen kombinierbar sein
Vererbung erzeugt eine Klassenexplosion
Zusatzverhalten soll pro Instanz variieren
Wann nicht?
wenn die Reihenfolge vieler Dekorierer nicht mehr nachvollziehbar ist
wenn Zugriffskontrolle oder Remote-Stellvertretung die Hauptabsicht ist - dann Proxy
wenn nur ein einzelner fester Zusatzschritt benötigt wird
Abgrenzung und typische Fehlanwendung
Proxy kontrolliert den Zugang zu einem Objekt; Decorator erweitert dessen fachliches oder technisches Verhalten bei gleichem Vertrag.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
08
Observer
Abhängige Komponenten über Änderungen informieren
Verhaltensmuster
Fachliche Einordnung
Observer ermöglicht, dass ein Subjekt mehrere unbekannte Beobachter über eine Änderung informiert. Die Kopplung bleibt auf einen kleinen Benachrichtigungsvertrag beschränkt.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Subject: verwaltet An- und Abmeldung.
Observer: definiert die Reaktion auf Änderungen.
Concrete Subject: hält den beobachteten Zustand.
Concrete Observer: aktualisiert seine eigene Sicht.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
mehrere Darstellungen oder Folgeaktionen reagieren auf dieselbe Änderung
der Sender soll Empfänger nicht konkret kennen
Beobachter sollen zur Laufzeit hinzugefügt werden
Wann nicht?
bei verteilten, dauerhaften Nachrichten - dort sind Broker und Integration Events geeigneter
wenn Reihenfolge, Wiederholung und Fehlerbehandlung streng garantiert werden müssen
wenn versteckte Ketten von Nebenwirkungen entstehen
Abgrenzung und typische Fehlanwendung
Domain Events drücken eine fachlich bedeutsame Tatsache aus. Observer beschreibt zunächst nur die technische Benachrichtigungsbeziehung innerhalb eines Prozesses.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
09
Command
Eine Aktion als eigenständiges Objekt darstellen
Verhaltensmuster
Fachliche Einordnung
Command kapselt einen auszuführenden Auftrag samt benötigten Eingaben. Dadurch lassen sich Aufträge protokollieren, verzögern, wiederholen, autorisieren oder in einer Warteschlange verarbeiten.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Command: beschreibt die ausführbare Operation.
Concrete Command: hält Parameter und ruft den Empfänger.
Invoker: plant oder startet die Ausführung.
Receiver: besitzt die eigentliche Fähigkeit.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
Aufträge sollen gespeichert, geplant oder wiederholt werden
Undo oder Audit ist erforderlich
Sender und ausführender Empfänger sollen entkoppelt sein
Wann nicht?
wenn eine direkte Funktion klarer ist
wenn nur Daten transportiert werden, aber keine Ausführungssemantik besteht
wenn Command-Objekte zu untypisierten Allzweck-Nachrichten werden
Abgrenzung und typische Fehlanwendung
Chain of Responsibility sucht einen Bearbeiter entlang einer Kette. Command beschreibt dagegen den Auftrag selbst und kann von einem Invoker ausgeführt werden.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
10
Chain of Responsibility
Anfrage durch eine geordnete Bearbeitungskette führen
Verhaltensmuster
Fachliche Einordnung
Die Zuständigkeitskette verbindet mehrere Bearbeiter. Jeder Bearbeiter entscheidet, ob er die Anfrage verarbeitet, anreichert, ablehnt oder an den nächsten Bearbeiter weitergibt.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Handler: gemeinsamer Bearbeitungsvertrag.
Concrete Handler: führt genau eine Prüfung oder Bearbeitung aus.
Next-Verbindung oder Pipeline: bestimmt die Reihenfolge.
Request/Context: trägt typisierte Eingaben und Ergebnisse.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
mehrere unabhängige Prüfungen laufen in definierter Reihenfolge
der Aufrufer soll den konkreten Bearbeiter nicht kennen
Schritte sollen leicht ergänzt oder ausgetauscht werden
Wann nicht?
wenn alle Schritte immer ausgeführt werden und eine einfache Pipeline klarer ist
wenn Reihenfolge nur zufällig durch Dependency Injection entsteht
wenn Fehler und Abbruchgründe nicht explizit modelliert sind
Abgrenzung und typische Fehlanwendung
Decorator ergänzt dasselbe Verhalten schichtweise. Eine Zuständigkeitskette kann die Bearbeitung dagegen beenden oder gezielt an einen Nachfolger weiterreichen.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Deutschsprachige fachliche Grundlagen
Zuständigkeitskette - fachliche Grundlage für Begriff, Rollen oder Einsatzgrenzen.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
11
Repository
Fachobjekte unabhängig von Persistenz zugänglich machen
DDD/Persistenzmuster
Fachliche Einordnung
Repository stellt der Domäne eine sammlungsähnliche Sicht auf Aggregate oder Fachobjekte bereit. Datenbankabfragen, Mapping und technische Persistenzdetails bleiben hinter der Grenze.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Repository-Schnittstelle: fachlich benannte Lade- und Speicheroperationen.
Domänenobjekt/Aggregat: bleibt frei von Datenbank-APIs.
Adapter: implementiert die Schnittstelle mit JPA, SQL oder Remote-Zugriff.
Mapper optional: übersetzt Persistenz- und Domänenmodell.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
Domänenlogik hängt direkt von JPA/SQL ab
mehrere Speichertechniken oder Fakes sollen möglich sein
Abfragen sollen fachlich benannt und wiederverwendet werden
Wann nicht?
für reine Tabellen-Gateways ohne reiches Fachmodell
wenn ein generisches Repository sämtliche Aggregate gleich behandelt
wenn beliebige technische Query-Details durch die Domänenschnittstelle lecken
Abgrenzung und typische Fehlanwendung
DAO ist häufig tabellen- oder technikorientiert. Repository orientiert sich an Aggregaten und an der Sprache des Domänenmodells.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
12
Specification
Fachliche Regeln ausdrücken und kombinieren
DDD/Muster
Fachliche Einordnung
Specification modelliert eine fachliche Aussage als benanntes Objekt. Regeln lassen sich prüfen, kombinieren und in unterschiedlichen Anwendungsfällen wiederverwenden, ohne ihre Bedeutung in anonyme Prädikate zu verlieren.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Specification: beantwortet, ob ein Kandidat eine Regel erfüllt.
Konkrete Specification: trägt einen fachlichen Namen.
Komposition: AND, OR und NOT verbinden Regeln.
Kandidat: das geprüfte Fachobjekt.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
Regeln werden an mehreren Stellen benötigt
Fachbegriffe sollen im Code sichtbar sein
Regeln müssen kombiniert und gezielt getestet werden
Wann nicht?
bei einmaligen trivialen Bedingungen
wenn Datenbankübersetzung erzwungen wird und die Domäne dadurch technische Ausdrücke übernimmt
wenn hunderte Mini-Spezifikationen die Lesbarkeit verschlechtern
Abgrenzung und typische Fehlanwendung
Ein Predicate ist eine technische Funktion. Eine Specification besitzt eine fachliche Bedeutung, einen Namen und oft einen bewussten Platz im Domänenmodell.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
13
Domain Event
Eine eingetretene fachliche Tatsache ausdrücken
DDD/Muster
Fachliche Einordnung
Ein Domain Event beschreibt in Vergangenheitsform, dass in der Domäne etwas fachlich Relevantes geschehen ist. Es macht Folgeaktionen sichtbar und entkoppelt Aggregate oder Anwendungsdienste.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Event: unveränderliche fachliche Tatsache.
Aggregate: erzeugt das Ereignis bei erfolgreicher Zustandsänderung.
Handler: reagiert innerhalb derselben Domäne.
Dispatcher: veröffentlicht nach einer klaren Transaktionsregel.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
eine Zustandsänderung löst mehrere fachliche Reaktionen aus
Folgeaktionen sollen nicht direkt im Aggregat aufgerufen werden
fachliche Abläufe sollen auditierbar und verständlich werden
Wann nicht?
für rein technische Log- oder Metrikmeldungen
wenn das Event vor erfolgreichem Commit veröffentlicht wird
wenn interne Domain Events ungeprüft als öffentliche Integrationsverträge dienen
Abgrenzung und typische Fehlanwendung
Integration Events überschreiten Systemgrenzen und benötigen stabile Verträge. Domain Events gehören zunächst zur Sprache und Konsistenzgrenze der eigenen Domäne.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Domain Events im DDD - fachliche Grundlage für Begriff, Rollen oder Einsatzgrenzen.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
14
Transactional Outbox
Datenänderung und ausgehende Nachricht atomar vormerken
Integrationsmuster
Fachliche Einordnung
Transactional Outbox verhindert den Dual-Write-Fehler: Fachzustand und zu veröffentlichende Nachricht werden in derselben lokalen Datenbanktransaktion gespeichert. Ein separater Publisher überträgt die Outbox-Einträge später an den Broker.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Use Case: ändert Fachzustand.
Outbox Writer: speichert Nachricht in derselben Transaktion.
Outbox Publisher: liest und veröffentlicht zuverlässig.
Idempotenter Empfänger: toleriert Wiederholungen.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
Datenbankänderung und Broker-Publish müssen zusammenpassen
globale Transaktionen sind nicht verfügbar oder unerwünscht
mindestens einmalige Zustellung ist akzeptabel
Wann nicht?
wenn keine Datenbanktransaktion existiert
wenn Duplikate beim Empfänger nicht beherrscht werden
wenn Outbox-Tabellen ohne Retention und Monitoring wachsen
Abgrenzung und typische Fehlanwendung
Event Sourcing speichert Ereignisse als primäre Zustandsquelle. Die Outbox speichert ausgehende Nachrichten zusätzlich zu einem herkömmlichen Zustandsmodell.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
15
Saga
Langen Geschäftsprozess über lokale Transaktionen koordinieren
Verteiltes Prozessmuster
Fachliche Einordnung
Eine Saga zerlegt einen verteilten Geschäftsvorgang in lokale Transaktionen. Schlägt ein später Schritt fachlich fehl, werden bereits ausgeführte Schritte durch ausdrücklich modellierte Kompensationen ausgeglichen.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Saga-Schritte: lokale Transaktionen.
Ereignisse oder Commands: lösen Folgeschritte aus.
Kompensation: fachlicher Ausgleich, kein technisches Zurückrollen.
Saga-Zustand: dokumentiert Fortschritt und Fehler.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
ein Vorgang umfasst mehrere autonome Dienste
globale Sperren oder 2PC sind nicht geeignet
Eventual Consistency ist fachlich vertretbar
Wann nicht?
wenn eine lokale ACID-Transaktion genügt
wenn fachliche Kompensationen nicht definiert werden können
wenn der Prozess ohne Korrelation, Timeout und Wiederanlauf implementiert wird
Abgrenzung und typische Fehlanwendung
Saga ist das übergeordnete Konsistenzkonzept. Ein Process Manager ist eine konkrete koordinierende Komponente, die den Ablauf und Zustand einer solchen Saga steuern kann.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
16
Process Manager
Mehrschrittigen Prozess explizit koordinieren
Integrationsmuster
Fachliche Einordnung
Ein Process Manager hält den Fortschritt eines länger laufenden Ablaufs und entscheidet anhand eingehender Ereignisse, welcher Command als Nächstes gesendet wird. Die Prozesslogik bleibt dadurch an einer sichtbaren Stelle.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
viele Ereignisse bilden einen fachlichen Gesamtprozess
Korrelation, Timeout und Wiederanlauf sind erforderlich
reine Choreografie wird schwer nachvollziehbar
Wann nicht?
bei einem kurzen synchronen Ablauf
wenn der Manager zur zentralen Fach-God-Class wird
wenn Zustandsübergänge und Idempotenz nicht persistiert werden
Abgrenzung und typische Fehlanwendung
Choreografie verteilt Entscheidungen auf Teilnehmer. Der Process Manager orchestriert sie explizit und bietet dadurch bessere Sichtbarkeit, erzeugt aber eine zentrale Prozesskomponente.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
17
CQRS
Schreib- und Lesemodelle bewusst trennen
Architekturmuster
Fachliche Einordnung
CQRS trennt Commands, die Zustand verändern, von Queries, die Daten lesen. Beide Seiten dürfen unterschiedliche Modelle und Speicherformen verwenden, wenn der fachliche und betriebliche Nutzen die zusätzliche Komplexität rechtfertigt.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Command Model: schützt Invarianten und verarbeitet Änderungen.
Synchronisationsmechanismus: definiert Konsistenz und Verzögerung.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
Schreib- und Leseanforderungen unterscheiden sich stark
komplexe Domänenlogik und viele spezialisierte Abfragen treffen aufeinander
Leseseite muss unabhängig skalieren oder denormalisieren
Wann nicht?
bei einfachem CRUD
wenn Eventual Consistency nicht fachlich beherrscht wird
wenn zwei Modelle ohne klaren Eigentümer und Monitoring entstehen
Abgrenzung und typische Fehlanwendung
CQRS benötigt nicht zwingend Event Sourcing. Event Sourcing kann CQRS unterstützen, ist aber eine eigenständige Entscheidung über die Zustandsquelle.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
18
Event Sourcing
Zustand aus einer Folge fachlicher Ereignisse rekonstruieren
Architekturmuster
Fachliche Einordnung
Beim Event Sourcing werden Zustandsänderungen als unveränderliche Ereignisse gespeichert. Der aktuelle Zustand entsteht durch das Wiederanwenden dieser Ereignisse; Projektionen liefern geeignete Lesesichten.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Aggregate: rekonstruiert Zustand und erzeugt neue Events.
Projection: baut Lesemodelle.
Snapshot optional: verkürzt lange Replays.
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
vollständige Historie und zeitliche Nachvollziehbarkeit sind Kernerfordernisse
neue Projektionen sollen rückwirkend aufgebaut werden
Domänenänderungen sind natürlich als Ereignisse formulierbar
Wann nicht?
bei einfachem CRUD ohne historischen Nutzen
wenn Ereignisschemata und Datenschutz nicht langfristig gepflegt werden können
wenn Teams Replay, Versionierung und Projektionen unterschätzen
Abgrenzung und typische Fehlanwendung
Ein Audit Log protokolliert Änderungen zusätzlich. Event Sourcing verwendet Ereignisse als maßgebliche Quelle des Zustands und hat deshalb deutlich weiterreichende Konsequenzen.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
19
Anti-Corruption Layer
Eigenes Fachmodell vor fremder Semantik schützen
DDD/Integrationsmuster
Fachliche Einordnung
Ein Anti-Corruption Layer übersetzt nicht nur Datentypen, sondern auch Begriffe, Regeln und Bedeutungen zwischen zwei Modellen. So beeinflusst ein Legacy- oder Fremdsystem nicht die Sprache und Struktur der eigenen Domäne.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
eigene Domänenschnittstelle
Übersetzungs- und Mappingkomponenten
Adapter/Fassade zum Fremdsystem
Fehler- und Semantikübersetzung
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
zwei Systeme verwenden unterschiedliche Begriffe und Regeln
Legacy-Datenmodell droht in die neue Domäne einzudringen
Integration wird langfristig bestehen
Wann nicht?
bei einer kleinen technischen Schnittstellenanpassung
wenn die Übersetzung nur verdeckt, dass beide Systeme eigentlich dasselbe Modell besitzen
wenn der Layer zu einem unkontrollierten zweiten Fachmodell anwächst
Abgrenzung und typische Fehlanwendung
Ein Adapter passt eine Schnittstelle an. Ein ACL schützt umfassend das eigene Modell und darf dafür mehrere Adapter, Mapper und Fassaden enthalten.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
20
Strangler Fig
Legacy-System schrittweise ersetzen
Modernisierungsmuster
Fachliche Einordnung
Das Strangler-Fig-Muster ersetzt ein Altsystem inkrementell. Eine vorgeschaltete Routing- oder Fassadenschicht leitet ausgewählte Funktionen zur neuen Lösung, während der Rest zunächst im Legacy-System verbleibt.
Das Muster ist keine fertige Klassenbibliothek. Es beschreibt eine wiederkehrende Aufteilung von Verantwortung. Entscheidend ist deshalb nicht, ob Klassen genauso heißen wie im Lehrbuch, sondern ob die Abhängigkeiten und Änderungsgründe sauber getrennt sind.
Beteiligte Rollen
Routing-Weiche oder Fassade
Legacy-Anwendung
neue Komponenten
Migrationsschnitt und Abschaltkriterien
Typischer Ablauf
Der Aufrufer startet einen fachlichen Vorgang.
Die stabile Grenze nimmt typisierte Eingaben entgegen.
Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
Tests prüfen sowohl den Normalfall als auch die Einsatzgrenze.
Wann sinnvoll?
Big-Bang-Neuentwicklung wäre zu riskant
Funktionen lassen sich fachlich oder über Requests schneiden
alter und neuer Pfad können zeitweise parallel betrieben werden
Wann nicht?
wenn keine klare Routinggrenze existiert
wenn Alt und Neu dauerhaft doppelte Fachlogik enthalten
wenn kein Plan zur Abschaltung und Datenmigration besteht
Abgrenzung und typische Fehlanwendung
Branch by Abstraction modernisiert innerhalb einer Codebasis über eine Abstraktion. Strangler Fig ersetzt Systemteile an einer äußeren Routing- oder Integrationsgrenze.
Eine häufige Fehlanwendung besteht darin, das Muster nur anhand seiner Klassenform nachzubauen. Dadurch entstehen zusätzliche Typen, ohne dass Änderungsdruck, Testbarkeit oder fachliche Verständlichkeit besser werden. Der vorhandene Code in dieser Workbench dient deshalb als Ausgangspunkt: Erst wird der konkrete Smell sichtbar gemacht, dann wird schrittweise refaktoriert.
Java- und Enterprise-Bezug
Im Java-Projekt sollte der zentrale Vertrag klein, typisiert und unabhängig von Framework-Annotationen bleiben. Spring oder Jakarta EE dürfen die Implementierungen verdrahten, sollen aber die fachliche Rolle nicht ersetzen. Der Testfokus liegt auf dem Vertrag, den Varianten und den Fehlerfällen; Framework-Integration wird getrennt als Adaptertest geprüft.
Die Beschreibung wurde redaktionell aus deutschsprachigen Fachquellen und dem konkreten Java-Beispiel der Workbench neu aufgebaut. Es wurden keine längeren Quellpassagen übernommen.
Vertiefungsband 2 Struktur, Verhalten und DomänenmodellierungPattern 21–40
Patterns für Komposition, Koordination und fachliche Modellierung – einschließlich typischer Fehlentscheidungen.
Der zweite Vertiefungsblock behandelt klassische Struktur- und Verhaltensmuster sowie Persistenz-, Resilienz- und Integrationsmuster. Nur dieses Hauptkapitel ist klappbar; Rollen, Abläufe, Grenzen, Fehlanwendungen, Java-Bezug und Quellen bleiben innerhalb des geöffneten Kapitels dauerhaft sichtbar.
Template Method beschreibt einen Algorithmus als feste Abfolge in einer Basisklasse. Einzelne Schritte werden als überschreibbare Operationen angeboten, während Reihenfolge, gemeinsame Vor- und Nachbedingungen sowie Fehlerbehandlung zentral bleiben. Das Muster eignet sich besonders dann, wenn mehrere Prozesse denselben Rahmen besitzen, sich aber an wenigen, klar benennbaren Stellen unterscheiden.
Der zentrale Nutzen liegt nicht darin, Vererbung einzusetzen, sondern darin, die unveränderliche Prozessstruktur von variablen Schritten zu trennen. In Java muss deshalb sichtbar bleiben, welche Schritte zwingend sind, welche Hooks optional sind und welche Invarianten eine Unterklasse nicht umgehen darf.
Beteiligte Rollen
Abstrakte Basisklasse: enthält die Template-Methode und den unveränderlichen Ablauf.
Primitive Operationen: definieren die variablen Schritte.
Hook: optionaler Erweiterungspunkt mit sinnvoller Standardimplementierung.
Konkrete Unterklasse: implementiert nur die vorgesehenen Variationen.
Typischer Ablauf
Der Aufrufer startet genau eine öffentliche Template-Methode.
Die Basisklasse prüft gemeinsame Vorbedingungen.
Feste und variable Schritte werden in dokumentierter Reihenfolge ausgeführt.
Hooks ergänzen Verhalten, ohne den Gesamtprozess neu zu ordnen.
Die Basisklasse stellt gemeinsame Nachbedingungen und Aufräumarbeiten sicher.
Wann sinnvoll?
mehrere Import-, Export- oder Abrechnungsabläufe besitzen dieselbe Reihenfolge
gemeinsame Fehler- und Transaktionsbehandlung darf nicht dupliziert werden
Variationspunkte sind klein, stabil und gut benennbar
Wann nicht?
Unterklassen müssten fast jeden Schritt überschreiben
Varianten sollen zur Laufzeit austauschbar sein - dann ist Strategy meist klarer
die Basisklasse kennt zu viele fachlich unabhängige Details
Abgrenzung zu ähnlichen Mustern
Strategy delegiert einen gesamten variablen Schritt an ein zusammengesetztes Objekt und kann zur Laufzeit wechseln. Template Method bindet die Variation über Vererbung und hält den Ablauf in der Basisklasse. In modernen Java-Systemen ist Strategy häufig flexibler; Template Method bleibt sinnvoll, wenn ein wirklich stabiler Lebenszyklus geschützt werden muss.
Typische Fehlanwendungen
geschützte Methoden werden ohne klare Semantik angeboten
Unterklassen umgehen Invarianten oder rufen Basismethoden in falscher Reihenfolge
die Basisklasse wächst zur God Class, weil immer neue Sonderfälle aufgenommen werden
Java- und Enterprise-Beispiel
Ein typisches Beispiel ist eine Dokumentimport-Pipeline: Datei öffnen, Metadaten prüfen, fachlich validieren, transformieren, speichern und Audit schreiben. CSV und XML variieren nur beim Lesen und Mapping. In Spring oder Jakarta EE sollte die Basisklasse fachlich bleiben; Transaktion, Storage und Messaging werden über Ports eingebunden.
Entwurfsmuster - Überblick - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
22
Composite
Einzelobjekte und Objektgruppen einheitlich behandeln
Strukturmuster
Fachliche Einordnung
Composite bildet Teil-Ganzes-Hierarchien so ab, dass ein einzelnes Element und eine Gruppe von Elementen denselben fachlichen Vertrag erfüllen. Ein Aufrufer kann dadurch eine Baumstruktur rekursiv bearbeiten, ohne an jeder Stelle zwischen Blatt und Container unterscheiden zu müssen.
Die gemeinsame Schnittstelle darf nicht künstlich breit sein. Gute Composite-Modelle enthalten nur Operationen, die für Blätter und Gruppen fachlich sinnvoll sind. Verwaltungsoperationen wie add oder remove gehören oft ausschließlich auf den Container.
Leaf: führt die eigentliche Operation ohne Kinder aus.
Composite: hält Kinder und aggregiert oder delegiert Operationen.
Client: arbeitet ausschließlich gegen Component.
Typischer Ablauf
Der Client startet eine Operation am Wurzelelement.
Ein Blatt berechnet sein eigenes Ergebnis.
Ein Composite delegiert an seine Kinder.
Teilergebnisse werden nach einer klaren Aggregationsregel kombiniert.
Fehler und leere Gruppen werden explizit behandelt.
Wann sinnvoll?
Produktpakete, Organisationsbäume oder Berechtigungsstrukturen sind hierarchisch
dieselbe Operation soll für Blatt und Gruppe gelten
rekursive Traversierung soll nicht in jedem Client wiederholt werden
Wann nicht?
Blätter und Gruppen haben kaum gemeinsame Operationen
der Baum ist nur eine technische Datenstruktur ohne fachliches Verhalten
Zyklen oder sehr große Graphen erfordern eine andere Traversierungs- und Speicherstrategie
Abgrenzung zu ähnlichen Mustern
Composite beschreibt die Objektstruktur. Visitor kapselt zusätzliche Operationen über einer stabilen Struktur. Iterator abstrahiert die Traversierung. Diese Muster können zusammenarbeiten, lösen aber unterschiedliche Probleme.
Typische Fehlanwendungen
ein überladener Component-Vertrag zwingt Blätter zu bedeutungslosen Methoden
Kinderlisten werden veränderbar nach außen gegeben
Zyklen und doppelte Elternbeziehungen werden nicht verhindert
Java- und Enterprise-Beispiel
Beispiel: Eine Versicherungsdeckung besteht aus Einzeldeckungen und Paketen. calculatePremium gilt für beide, aber nur Pakete besitzen Kinder. Das Modell kann Prämien aggregieren, ohne dass der Anwendungsservice die Baumform kennt. Persistenz-Mapping bleibt in einem Adapter, weil rekursive Aggregate in JPA besondere Lade- und Transaktionsgrenzen benötigen.
Deutschsprachige fachliche Grundlagen
Kompositum (Entwurfsmuster) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Entwurfsmuster (Buch) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
23
Visitor
Neue Operationen über einer stabilen Objektstruktur ergänzen
Verhaltensmuster
Fachliche Einordnung
Visitor verlagert eine Operation aus einer heterogenen Objektstruktur in ein separates Besucherobjekt. Die Elemente stellen eine accept-Methode bereit; über Double Dispatch wird die passende visit-Operation für den konkreten Elementtyp gewählt.
Das Muster ist dann stark, wenn die Menge der Elementtypen stabil ist, aber regelmäßig neue Auswertungen, Exporte oder Prüfungen hinzukommen. Ändert sich dagegen die Elementhierarchie häufig, verursacht jeder neue Typ Anpassungen an allen Besuchern.
Beteiligte Rollen
Element: bietet accept(Visitor).
Concrete Element: ruft die typgerechte visit-Methode auf.
Visitor: deklariert Operationen pro Elementtyp.
Concrete Visitor: implementiert eine zusammengehörige Auswertung oder Transformation.
Typischer Ablauf
Der Client wählt einen Visitor für eine fachliche Aufgabe.
Die Struktur traversiert ihre Elemente.
Jedes Element ruft visitor.visit(this) auf.
Der konkrete Visitor verarbeitet den exakten Typ.
Optional sammelt der Visitor ein Gesamtergebnis.
Wann sinnvoll?
eine stabile Dokument-, AST- oder Produktstruktur braucht viele neue Operationen
Typprüfungen mit instanceof verteilen sich über mehrere Auswertungen
eine Operation benötigt typabhängiges Verhalten über viele Elementklassen
Wann nicht?
neue Elementtypen entstehen häufig
eine einzelne polymorphe Methode genügt
die Operation gehört eindeutig zur Verantwortung des Elements selbst
Abgrenzung zu ähnlichen Mustern
Composite organisiert die Struktur, Visitor organisiert zusätzliche Operationen darüber. Strategy tauscht einen Algorithmus für einen einzelnen Kontext aus; Visitor deckt mehrere konkrete Elementtypen in einer Operation ab.
Typische Fehlanwendungen
Visitor wird als Ausweichlösung für ein schwaches Domänenmodell verwendet
Besucher erhalten unkontrollierten Zugriff auf interne Zustände
Traversal, Zustandsaggregation und Ausgabeformat werden in einem Visitor vermischt
Java- und Enterprise-Beispiel
Ein Dokumentenmodell aus Text, Tabelle, Bild und Signatur kann separate Besucher für PDF-Export, Barrierefreiheitsprüfung und Datenschutzklassifizierung verwenden. Der Visitor bleibt frameworkfrei; Dateisystem oder Rendering-Engine werden als Ports injiziert.
Deutschsprachige fachliche Grundlagen
Besucher (Entwurfsmuster) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Entwurfsmuster (Buch) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
24
Bridge
Abstraktion und technische Umsetzung unabhängig variieren
Strukturmuster
Fachliche Einordnung
Bridge trennt eine fachliche Abstraktion von ihrer Implementierung, sodass beide Hierarchien unabhängig erweitert werden können. Statt jede Kombination durch Vererbung abzubilden, hält die Abstraktion eine Referenz auf einen Implementor.
Der entscheidende Hinweis ist eine drohende Kombinationsmatrix: mehrere fachliche Varianten treffen auf mehrere technische Plattformen oder Ausgabewege. Bridge verhindert daraus entstehende Unterklassen wie PdfInvoice, HtmlInvoice, PdfReminder und HtmlReminder.
Beteiligte Rollen
Abstraction: fachlicher Einstiegspunkt.
Refined Abstraction: fachliche Varianten.
Implementor: technischer Vertrag.
Concrete Implementor: konkrete Plattform- oder Formatimplementierung.
Typischer Ablauf
Der Client wählt eine fachliche Abstraktion.
Bei der Konstruktion wird ein Implementor zugeordnet.
Die Abstraktion entscheidet fachlich, was passieren soll.
Der Implementor führt den technischen Teil aus.
Beide Seiten können separat getestet und erweitert werden.
Wann sinnvoll?
zwei unabhängige Änderungsachsen erzeugen eine Klassenexplosion
fachliche Typen und Ausgabekanäle entwickeln sich getrennt
Plattformdetails sollen aus der Domäne verschwinden
Wann nicht?
es gibt nur eine stabile Implementierung
ein Adapter um eine bestehende Fremdschnittstelle genügt
die Trennung erzeugt keinen eigenständigen Änderungsgrund
Abgrenzung zu ähnlichen Mustern
Adapter wird meist nachträglich eingesetzt, um eine vorhandene unpassende Schnittstelle anzupassen. Bridge wird bewusst entworfen, damit zwei Variationsachsen unabhängig bleiben.
Typische Fehlanwendungen
Abstraction und Implementor schneiden dieselbe Verantwortung doppelt
der Implementor enthält Fachentscheidungen
jede triviale Hilfsklasse wird als Bridge bezeichnet
Java- und Enterprise-Beispiel
Rechnungsarten und Renderkanäle sind ein klassisches Beispiel. Invoice und Reminder bestimmen Inhalt und Regeln; PdfRenderer, HtmlRenderer oder EdiRenderer übernehmen die technische Darstellung. Spring oder CDI verdrahten die Kombination, ohne die fachlichen Klassen zu annotieren.
Deutschsprachige fachliche Grundlagen
Brücke (Entwurfsmuster) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Entwurfsmuster (Buch) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Abstract Factory definiert einen Vertrag zur Erzeugung mehrerer zusammengehöriger Produkttypen. Eine konkrete Fabrik liefert eine konsistente Familie, ohne dass der Client konkrete Klassen kennen muss.
Der Mehrwert entsteht durch die Familienkonsistenz: Ein Cloud-Provider, Datenbanktreiber oder UI-Theme bringt mehrere aufeinander abgestimmte Komponenten mit. Die Fabrik verhindert, dass unpassende Produkte verschiedener Familien kombiniert werden.
Beteiligte Rollen
Abstract Factory: Erzeugungsvertrag für alle Produktarten.
Concrete Factory: erzeugt eine konkrete Familie.
Abstract Products: gemeinsame Verträge der Produktarten.
Der Client fordert Produkte nur über abstrakte Methoden an.
Die Fabrik erzeugt eine konsistente Familie.
Der Client verwendet ausschließlich Produktverträge.
Ein Familienwechsel erfolgt an einer zentralen Verdrahtungsstelle.
Wann sinnvoll?
mehrere Produktarten müssen immer zusammenpassen
Umgebung oder Provider soll komplett austauschbar sein
Clients dürfen konkrete Implementierungen nicht kennen
Wann nicht?
nur ein einzelnes Produkt wird erzeugt
neue Produktarten werden sehr häufig ergänzt - dann muss jede Fabrik geändert werden
ein DI-Container kann die Familie bereits klar und transparenter verdrahten
Abgrenzung zu ähnlichen Mustern
Factory Method kapselt typischerweise einen Erzeugungspunkt. Abstract Factory erzeugt eine ganze Familie. Builder setzt ein einzelnes komplexes Objekt schrittweise zusammen.
Typische Fehlanwendungen
die Fabrik wird zum Service Locator
Produkte enthalten versteckte globale Zustände
die Familienzugehörigkeit ist nicht durch Tests abgesichert
Java- und Enterprise-Beispiel
Eine Storage-Familie kann Repository, LockProvider und TransactionManager für PostgreSQL oder In-Memory bereitstellen. Ein Contract-Test wird gegen jede Fabrik ausgeführt und prüft, dass alle Produkte dieselben fachlichen Garantien erfüllen.
Deutschsprachige fachliche Grundlagen
Abstrakte Fabrik - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Entwurfsmuster (Buch) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
26
Prototype
Objekte aus validen Vorlagen kopieren und gezielt verändern
Erzeugungsmuster
Fachliche Einordnung
Prototype erzeugt neue Objekte durch Kopieren eines vorhandenen, bereits konfigurierten Exemplars. Es eignet sich, wenn die Konstruktion teuer oder komplex ist und Varianten überwiegend aus kleinen Änderungen an einer Grundkonfiguration entstehen.
In Java ist clone() selten die beste Umsetzung. Explizite copyOf-, with- oder copy-Methoden sind lesbarer, erlauben defensive Kopien und machen sichtbar, ob eine tiefe oder flache Kopie entsteht.
Beteiligte Rollen
Prototype: Vertrag für eine kontrollierte Kopie.
Concrete Prototype: definiert Kopiersemantik und Invarianten.
Registry optional: verwaltet benannte Vorlagen.
Client: kopiert eine Vorlage und verändert erlaubte Werte.
Typischer Ablauf
Eine valide Vorlage wird erstellt oder aus einer Registry geladen.
Der Client fordert eine Kopie an.
Veränderliche Unterobjekte werden gemäß definierter Semantik kopiert.
Erlaubte Abweichungen werden über typisierte with-Methoden gesetzt.
Die neue Instanz wird erneut validiert.
Wann sinnvoll?
komplexe Konfigurationen unterscheiden sich nur in wenigen Werten
Erzeugung oder Laden einer Grundstruktur ist teuer
Vorlagen sind fachlich verständlich und versionierbar
Wann nicht?
Kopiersemantik für verschachtelte mutable Objekte ist unklar
ein Builder die Konstruktion verständlicher ausdrückt
Identität und Lebenszyklus eines Entity-Objekts versehentlich kopiert würden
Abgrenzung zu ähnlichen Mustern
Builder beschreibt den Aufbau; Prototype startet von einer vorhandenen Instanz. Memento speichert Zustand zur späteren Wiederherstellung und ist nicht primär ein Erzeugungsmuster.
Vorlagen werden nachträglich verändert und beeinflussen weitere Kopien
Java- und Enterprise-Beispiel
Tarif- oder Vertragsvorlagen können als immutable Prototypes modelliert werden. Neue Angebote übernehmen Regeln und Deckungen, erhalten aber neue Identität und Gültigkeit. Eine Registry darf nur freigegebene Versionen liefern.
Deutschsprachige fachliche Grundlagen
Prototyp (Entwurfsmuster) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Entwurfsmuster (Buch) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
27
Mediator
Zusammenarbeit vieler Komponenten an einer Stelle koordinieren
Verhaltensmuster
Fachliche Einordnung
Mediator verhindert, dass viele Komponenten einander direkt kennen und dadurch ein dicht gekoppeltes Netz entsteht. Stattdessen melden sie Ereignisse oder Anfragen an einen Vermittler, der die Zusammenarbeit koordiniert.
Ein guter Mediator enthält Interaktionslogik, aber nicht beliebig viel Fachlogik. Wird er zum universellen Orchestrator für das gesamte System, entsteht lediglich eine neue God Class.
Beteiligte Rollen
Mediator: definiert die Koordinationsschnittstelle.
Concrete Mediator: kennt beteiligte Komponenten und Interaktionsregeln.
Colleagues: führen ihre lokale Verantwortung aus und kommunizieren über den Mediator.
Client oder Composition Root: verdrahtet das Ensemble.
Typischer Ablauf
Eine Komponente meldet eine fachlich benannte Aktion oder Änderung.
Der Mediator entscheidet, welche anderen Komponenten reagieren müssen.
Er ruft diese in einer expliziten Reihenfolge auf.
Ergebnisse werden zusammengeführt oder weitergereicht.
Die Komponenten bleiben voneinander unabhängig testbar.
Wann sinnvoll?
viele UI-, Workflow- oder Subsystemkomponenten rufen sich gegenseitig auf
Interaktionsregeln ändern sich häufiger als die Komponenten selbst
direkte Abhängigkeiten verhindern isolierte Tests
Wann nicht?
nur zwei Komponenten kommunizieren
die Koordination eigentlich ein fachlicher Prozessmanager oder Application Service ist
asynchrone lose Kopplung über Events geeigneter ist
Abgrenzung zu ähnlichen Mustern
Observer verteilt Benachrichtigungen ohne zentrale Ablaufentscheidung. Mediator entscheidet aktiv über Zusammenarbeit. Facade bietet einen vereinfachten Einstieg von außen, koordiniert aber nicht zwingend gleichrangige Komponenten.
Typische Fehlanwendungen
alle Fachentscheidungen wandern in eine riesige Mediator-Klasse
Nachrichten sind untypisierte Strings
Aufrufketten werden versteckt und schwer beobachtbar
Java- und Enterprise-Beispiel
In einem Genehmigungsdialog koordiniert ein Mediator Eingabevalidierung, Dokumentvorschau und Freigabeoptionen. In einem Backend entspricht die Rolle häufig einem kleinen Application Service; ein lang laufender verteilter Ablauf ist eher ein Process Manager.
Deutschsprachige fachliche Grundlagen
Vermittler (Entwurfsmuster) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Entwurfsmuster (Buch) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
28
Iterator
Eine Sammlung durchlaufen, ohne ihre interne Struktur offenzulegen
Verhaltensmuster
Fachliche Einordnung
Iterator kapselt den Traversierungszustand einer Sammlung. Der Client kann Elemente nacheinander lesen, ohne zu wissen, ob die Daten in einer Liste, einem Baum, einer paginierten Quelle oder einer zusammengesetzten Struktur liegen.
Java stellt Iterator und Iterable bereits bereit. Ein eigener Iterator ist dann gerechtfertigt, wenn die Traversierung fachliche Regeln, Filter, Seitenwechsel oder eine definierte Reihenfolge enthält.
Beteiligte Rollen
Aggregate: stellt einen Iterator bereit.
Iterator: definiert hasNext und next oder eine domänenspezifische Alternative.
Concrete Iterator: hält Position und Traversierungslogik.
Client: konsumiert Elemente über den Vertrag.
Typischer Ablauf
Der Client fordert eine Traversierung mit klarer Reihenfolge an.
Der Iterator hält Cursor oder Stack.
next liefert genau ein Element und bewegt den Zustand.
Fehler, Ende und Ressourcenfreigabe sind definiert.
Die Sammlung bleibt gekapselt.
Wann sinnvoll?
eine Baum- oder Seitenstruktur soll wie eine Folge konsumiert werden
mehrere Traversierungsreihenfolgen werden benötigt
interne Speicherung darf nicht nach außen gelangen
Wann nicht?
eine normale Collection und Stream-Operation vollständig genügen
Traversierung große externe Ressourcen offen hält, ohne Close-Semantik
der Iterator Fachentscheidungen statt reiner Traversierung übernimmt
Abgrenzung zu ähnlichen Mustern
Composite bildet die Struktur, Iterator durchläuft sie. Visitor führt typabhängige Operationen aus und kann einen Iterator zur Traversierung verwenden.
Typische Fehlanwendungen
Concurrent Modification wird ignoriert
Iteratoren teilen unbeabsichtigt Cursorzustand
Fehler beim Seitenwechsel werden als leeres Ende verschluckt
Java- und Enterprise-Beispiel
Ein LedgerIterator kann Buchungen über Datenbankseiten in stabiler Reihenfolge liefern. Der Client sieht keine Page- oder Offset-Details. Für Ressourcen sollte ein CloseableIterator oder ein Stream mit klarer Lebensdauer verwendet werden.
Deutschsprachige fachliche Grundlagen
Iterator (Entwurfsmuster) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Entwurfsmuster (Buch) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
29
Memento
Zustand kontrolliert sichern und später wiederherstellen
Verhaltensmuster
Fachliche Einordnung
Memento speichert einen Zustand, ohne die Kapselung des Ursprungsobjekts aufzugeben. Das Originator-Objekt erzeugt ein Memento und kann daraus seinen früheren Zustand wiederherstellen; ein Caretaker verwaltet die Historie, kennt aber die internen Daten nicht.
Das Muster ist für Undo, Entwürfe und lokale Wiederherstellung geeignet. Es ersetzt kein fachliches Audit Log und kein Event Sourcing, weil ein Memento normalerweise nur einen Zustand konserviert und nicht erklärt, warum er entstanden ist.
Beteiligte Rollen
Originator: besitzt den geschützten Zustand.
Memento: immutable Zustandsabbildung.
Caretaker: verwaltet Reihenfolge, Aufbewahrung und Auswahl.
Policy optional: begrenzt Anzahl oder Gültigkeit der Snapshots.
Typischer Ablauf
Vor einer riskanten Änderung erzeugt der Originator ein Memento.
Der Caretaker speichert es ohne interne Interpretation.
Die Änderung wird ausgeführt.
Bei Abbruch oder Undo wird das Memento zurückgegeben.
Der Originator validiert und restauriert den Zustand.
Wann sinnvoll?
Undo oder temporäre Entwürfe benötigt werden
interne Felder nicht öffentlich gemacht werden dürfen
der Zustand kompakt und kontrolliert kopierbar ist
Wann nicht?
jede fachliche Änderung revisionssicher nachvollziehbar sein muss
Snapshots sehr groß oder häufig sind
externe Seiteneffekte nicht durch Zustandswiederherstellung rückgängig werden
Abgrenzung zu ähnlichen Mustern
Event Sourcing speichert fachliche Ereignisse als Quelle der Wahrheit und rekonstruiert Zustand. Memento speichert einen Zustandspunkt. Command kann eine Undo-Operation besitzen und dazu ein Memento verwenden.
Typische Fehlanwendungen
Mementos enthalten mutable Referenzen
Caretaker interpretiert interne Details
Rollback suggeriert fälschlich, externe E-Mails oder Zahlungen würden zurückgenommen
Java- und Enterprise-Beispiel
Ein komplexer Vertragseditor kann vor jedem Bearbeitungsschritt einen kompakten immutable Snapshot speichern. Für persistierte Geschäftsvorfälle wird dagegen ein Audit- oder Ereignismodell benötigt.
Deutschsprachige fachliche Grundlagen
Memento (Entwurfsmuster) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Entwurfsmuster (Buch) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
30
Flyweight
Große Mengen gleichartiger Objekte durch geteilten Zustand entlasten
Strukturmuster
Fachliche Einordnung
Flyweight trennt unveränderlichen, gemeinsam nutzbaren intrinsischen Zustand von extrinsischem Zustand, der beim Aufruf bereitgestellt wird. Dadurch können sehr viele logisch eigenständige Objekte dieselbe Repräsentation teilen.
Das Muster ist eine gezielte Speicheroptimierung und sollte erst nach Messung eingesetzt werden. Es erhöht die konzeptionelle Komplexität, weil der Client den extrinsischen Kontext korrekt mitführen muss.
Beteiligte Rollen
Flyweight: gemeinsamer unveränderlicher Zustand.
Flyweight Factory: findet oder erzeugt geteilte Instanzen.
Extrinsischer Context: individueller Zustand außerhalb des Flyweights.
Client: kombiniert beides bei der Operation.
Typischer Ablauf
Ein Schlüssel beschreibt den intrinsischen Zustand.
Die Factory liefert eine vorhandene oder neue immutable Instanz.
Der Client hält nur individuellen Kontext.
Bei der Operation wird Kontext an das Flyweight übergeben.
Metriken prüfen Cachegröße, Trefferquote und tatsächlichen Nutzen.
Wann sinnvoll?
Millionen gleichartige Objekte teilen große unveränderliche Daten
Profiling zeigt relevanten Speicherdruck
Identität der geteilten Instanz ist fachlich bedeutungslos
Wann nicht?
Objektzahl klein ist
Zustand häufig mutiert
die Factory zu einem unkontrollierten, niemals geleerten Cache wird
Abgrenzung zu ähnlichen Mustern
Object Pool verwaltet teure, zeitweise ausgeliehene Ressourcen. Flyweight teilt immutable Daten dauerhaft. Cache Aside optimiert Datenzugriffe, nicht primär Objektstruktur.
Typische Fehlanwendungen
veränderlicher Zustand wird versehentlich geteilt
equals und Identität werden verwechselt
Optimierung wird ohne Messdaten eingeführt
Java- und Enterprise-Beispiel
Ein Regelkatalog kann unveränderliche Regeldefinitionen als Flyweights teilen, während Kunden-, Vertrags- und Laufzeitdaten als extrinsischer Kontext übergeben werden. Die Factory benötigt klare Größen- und Lebenszyklusgrenzen.
Entwurfsmuster (Buch) - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Null Object stellt eine konkrete Implementierung eines Vertrags bereit, die bewusst neutrales oder leeres Verhalten besitzt. Clients können dadurch polymorph arbeiten, ohne an jeder Stelle null zu prüfen.
Das Muster ist nur korrekt, wenn Abwesenheit tatsächlich ein gültiger fachlicher Fall mit eindeutigem Verhalten ist. Es darf fehlende Pflichtdaten oder Konfigurationsfehler nicht verstecken.
Optional modelliert die mögliche Abwesenheit eines Werts am Aufrufpunkt. Null Object modelliert ein Objekt mit neutralem Verhalten über denselben Vertrag.
Typische Fehlanwendungen
ein Null Object liefert erfundene fachliche Daten
Fehlkonfiguration bleibt unbemerkt
viele unterschiedliche Null Objects haben widersprüchliche Semantik
Java- und Enterprise-Beispiel
Ein AuditSink.noop() kann für lokale Tests zulässig sein, in Produktion aber durch eine Startvalidierung verboten werden. Ein fehlender Kunde darf hingegen nicht als NullCustomer mit Fantasiewerten modelliert werden.
Deutschsprachige fachliche Grundlagen
Nullobjekt - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Entwurfsmuster - Überblick - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
32
Unit of Work
Zusammengehörige Persistenzänderungen als konsistente Arbeitseinheit koordinieren
Persistenzmuster
Fachliche Einordnung
Unit of Work verfolgt Änderungen an mehreren Objekten oder Repositories und koordiniert deren gemeinsames Schreiben. Ziel ist eine fachlich abgeschlossene Arbeitseinheit, die entweder vollständig bestätigt oder vollständig verworfen wird.
In JPA übernimmt der Persistence Context Teile dieser Aufgabe durch Dirty Checking und Transaktionsintegration. Eine zusätzliche eigene Unit-of-Work-Abstraktion ist nur sinnvoll, wenn sie eine klare fachliche oder infrastrukturelle Grenze sichtbar macht und nicht bloß EntityManager umbenennt.
Beteiligte Rollen
Unit of Work: begin, commit und rollback beziehungsweise execute.
Repositories: arbeiten innerhalb derselben Einheit.
Transaction Adapter: bindet Datenbank- oder Messagingtransaktion an.
Application Service: definiert die fachliche Grenze.
Typischer Ablauf
Der Application Service startet eine Arbeitseinheit.
Mehrere Aggregate werden geladen und verändert.
Repositories registrieren oder persistieren Änderungen im gemeinsamen Kontext.
Die Unit of Work prüft Nebenläufigkeit und bestätigt atomar.
Fehler führen zu Rollback und einem typisierten Ergebnis.
Wann sinnvoll?
mehrere Repository-Änderungen müssen atomar sein
Transaktionsgrenzen sollen im Application Service sichtbar werden
Nebenläufigkeitskonflikte zentral behandelt werden müssen
Wann nicht?
jede Operation nur ein einzelnes autarkes Write ausführt
verteilte Systeme fälschlich über eine lokale Transaktion gekoppelt werden
Framework und eigene UoW doppelte Lebenszyklen verwalten
Abgrenzung zu ähnlichen Mustern
Repository kapselt Zugriff auf Aggregate. Unit of Work koordiniert Änderungen über mehrere Repositories. Transaction Script enthält den gesamten fachlichen Ablauf in einer Prozedur.
Typische Fehlanwendungen
Transaktion wird über Remote-Aufrufe offen gehalten
commit wird an tiefen Stellen aufgerufen
eine globale Unit of Work macht Tests und Nebenläufigkeit unklar
Java- und Enterprise-Beispiel
Bei einer Bestellung werden Auftrag, Reservierung und Outbox-Datensatz in einer lokalen Transaktion gespeichert. Der externe Versanddienst wird nicht Teil der Unit of Work; er wird später über das Outbox-Ereignis angesprochen.
Deutschsprachige fachliche Grundlagen
Unit of Work - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Circuit Breaker beobachtet Fehler an einer Remote-Grenze und verhindert weitere Aufrufe, wenn eine Abhängigkeit wahrscheinlich nicht verfügbar ist. Typische Zustände sind geschlossen, offen und halb offen. Dadurch werden Ressourcen geschont und eine überlastete Abhängigkeit erhält Zeit zur Erholung.
Das Muster ersetzt keine Fehlerbehebung. Schwellenwerte, Zeitfenster und erlaubte Testaufrufe müssen zur Abhängigkeit passen und über Metriken beobachtet werden. Fachlich ungültige Anfragen dürfen nicht als technische Ausfälle gewertet werden.
Beteiligte Rollen
Protected Call: Remote- oder Ressourcenoperation.
Circuit-Breaker-Policy: klassifiziert Fehler und führt Zustandswechsel aus.
Im geschlossenen Zustand werden Aufrufe zugelassen und bewertet.
Überschreitet die Fehlerquote den Grenzwert, öffnet der Breaker.
Im offenen Zustand scheitern Aufrufe sofort.
Nach einer Wartezeit werden wenige Testaufrufe halb offen zugelassen.
Erfolg schließt, erneuter Fehler öffnet den Breaker wieder.
Wann sinnvoll?
Remote-Abhängigkeiten können länger ausfallen
wiederholte Aufrufe würden Threads, Verbindungen oder CPU verschwenden
schnelles, beobachtbares Scheitern ist besser als lange Timeouts
Wann nicht?
Fehler sofort und dauerhaft fachlich sind
ein lokaler Methodenaufruf geschützt werden soll
Fallback falsche oder sicherheitskritische Daten liefern würde
Abgrenzung zu ähnlichen Mustern
Retry behandelt kurzzeitige Fehler durch erneute Versuche. Circuit Breaker stoppt Versuche bei anhaltenden Fehlern. Timeout begrenzt nur die Dauer eines einzelnen Aufrufs.
Typische Fehlanwendungen
alle Ausnahmen zählen gleich
ein globaler Breaker koppelt unabhängige Endpunkte
Fallback verschleiert einen Totalausfall
Java- und Enterprise-Beispiel
Ein PaymentProviderAdapter besitzt je Provider und Operation einen Breaker. Ablehnungen wie insufficient_funds zählen nicht als Systemfehler. Dashboards zeigen Zustand, Fehlerquote und Open-Dauer; ein Fallback darf höchstens einen Status „vorübergehend nicht verfügbar“ liefern.
Deutschsprachige fachliche Grundlagen
Circuit Breaker-Pattern - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Cloudentwurfsmuster - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
34
Retry
Vorübergehende Fehler kontrolliert erneut versuchen
Resilienzmuster
Fachliche Einordnung
Retry wiederholt eine fehlgeschlagene Operation, wenn der Fehler mit hoher Wahrscheinlichkeit vorübergehend ist. Anzahl, Abstand, Backoff und Jitter müssen begrenzt und an die Semantik der Operation angepasst sein.
Wiederholung ist nur sicher, wenn die Operation idempotent ist oder ein Idempotency Key doppelte Wirkung verhindert. Fachfehler, Validierungsfehler und dauerhafte Authentisierungsfehler dürfen nicht automatisch wiederholt werden.
Beteiligte Rollen
Operation: der geschützte Aufruf.
Error Classifier: unterscheidet transient, permanent und unbekannt.
Retry Policy: legt Versuche, Backoff und Jitter fest.
Attempt Context: dokumentiert Nummer, Deadline und Ursache.
Typischer Ablauf
Der erste Aufruf wird innerhalb einer Gesamtdauer gestartet.
Ein Fehler wird klassifiziert.
Nur transiente Fehler erhalten einen nächsten Versuch.
Backoff und Jitter verhindern synchronisierte Lastspitzen.
Nach Erfolg oder Budgetende wird ein typisiertes Ergebnis geliefert.
Wann sinnvoll?
Netzwerkunterbrechungen oder kurzzeitige Überlastung erwartbar sind
der Anbieter Fehler klar klassifiziert
Operation und Zeitbudget Wiederholung erlauben
Wann nicht?
Fehler dauerhaft oder fachlich sind
nicht-idempotente Seiteneffekte doppelt ausgelöst werden können
Retries eine überlastete Abhängigkeit weiter bedrängen
Abgrenzung zu ähnlichen Mustern
Circuit Breaker verhindert Aufrufe bei anhaltendem Fehler. Retry versucht innerhalb eines begrenzten Budgets erneut. Beide können kombiniert werden, müssen aber gemeinsame Deadlines beachten.
Typische Fehlanwendungen
unbegrenzte Schleifen
kein Jitter bei vielen Instanzen
jeder Layer wiederholt selbst und vervielfacht dadurch die Versuche
Java- und Enterprise-Beispiel
Ein Dokumentdownload darf bei 503 oder Verbindungsabbruch dreimal mit exponentiellem Backoff wiederholt werden. Ein Zahlungsauftrag benötigt zusätzlich einen Idempotency Key. Die Policy liegt am Adapter, nicht in der Domäne.
Deutschsprachige fachliche Grundlagen
Erneutversuchen-Muster - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Circuit Breaker-Pattern - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
35
Bulkhead
Ressourcen isolieren, damit ein Fehler nicht das gesamte System blockiert
Resilienzmuster
Fachliche Einordnung
Bulkhead teilt begrenzte Ressourcen in voneinander isolierte Bereiche. Wenn eine Abhängigkeit, ein Mandant oder eine Arbeitslast alle zugewiesenen Threads, Verbindungen oder Queue-Plätze verbraucht, bleiben andere Bereiche funktionsfähig.
Die Isolierung ist eine Kapazitätsentscheidung. Zu kleine Pools verursachen unnötige Ablehnungen, zu große Pools verhindern keine Kaskade. Deshalb müssen Grenzwerte, Warteschlangen und Ablehnungssemantik gemessen und getestet werden.
Beteiligte Rollen
Workload Class: fachlich oder technisch isolierte Gruppe.
Resource Pool: Thread-, Connection- oder Queue-Budget.
Admission Policy: entscheidet über Annahme oder Ablehnung.
Metrics: beobachtet Auslastung, Wartezeit und Rejections.
Typischer Ablauf
Ein Aufruf wird einer Workload-Klasse zugeordnet.
Nur der zugehörige Pool darf Ressourcen bereitstellen.
Bei Kapazität wird der Aufruf ausgeführt.
Bei Erschöpfung wird früh abgelehnt oder begrenzt gepuffert.
Andere Pools bleiben unbeeinflusst.
Wann sinnvoll?
eine langsame Abhängigkeit alle Worker blockieren könnte
Mandanten oder Prioritätsklassen isoliert werden müssen
Fan-out zu mehreren Diensten unterschiedliche Budgets braucht
Wann nicht?
Ressourcen ohnehin vollständig unabhängig sind
Grenzen ohne Lastmessung willkürlich gesetzt werden
unbegrenzte Queues nur den Ausfall verzögern
Abgrenzung zu ähnlichen Mustern
Rate Limiting begrenzt die Eintrittsrate. Bulkhead trennt Kapazität. Circuit Breaker reagiert auf Fehler einer Abhängigkeit. Zusammen bilden sie eine Resilienzschicht.
Typische Fehlanwendungen
gemeinsamer Executor trotz scheinbarer Isolation
unbegrenzte Queue vor dem Pool
kritische und unkritische Last teilen denselben Pool
Java- und Enterprise-Beispiel
Zahlungsprovider A und B erhalten separate Executor- und Connection-Pools. Ein Ausfall von A blockiert nicht B. Interaktive Aufträge werden außerdem von nächtlichen Batchläufen getrennt.
Deutschsprachige fachliche Grundlagen
Bulkhead-Muster - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Cloudentwurfsmuster - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
36
Rate Limiter
Zugriffe anhand eines nachvollziehbaren Budgets begrenzen
Resilienzmuster
Fachliche Einordnung
Rate Limiting begrenzt, wie viele Operationen in einem Zeitraum oder innerhalb eines gleitenden Budgets zugelassen werden. Es schützt Systeme vor Überlastung, unfairer Nutzung und externen Quotenüberschreitungen.
Die Policy muss festlegen, nach welchem Schlüssel begrenzt wird: Mandant, Benutzer, API-Key, Endpunkt oder Kostenklasse. Eine pauschale globale Grenze kann wichtige und unwichtige Last ungewollt gegeneinander ausspielen.
Beteiligte Rollen
Limit Key: Identität der zu begrenzenden Einheit.
Rate Policy: Kapazität, Zeitraum und Burst.
Counter oder Token Bucket: hält den Verbrauch.
Decision: erlaubt, verzögert oder lehnt ab und nennt Retry-Informationen.
Typischer Ablauf
Ein Request wird einem Limit Key zugeordnet.
Kosten oder Tokenbedarf werden berechnet.
Die Policy prüft das verfügbare Budget atomar.
Der Request wird zugelassen oder mit klarer Rückmeldung abgelehnt.
Metriken zeigen Verbrauch, Rejections und Hot Keys.
Wann sinnvoll?
externe APIs Quoten besitzen
Mandanten fair geteilt werden müssen
Überlastung bereits am Eintrittspunkt begrenzt werden soll
Wann nicht?
eigentliche Engstelle durch falsche Architektur bestehen bleibt
verteilte Zähler keine ausreichende Konsistenz bieten
Sicherheits- oder Geschäftsregeln mit Rate Limits verwechselt werden
Abgrenzung zu ähnlichen Mustern
Throttling kann Last verzögern oder reduzieren; Rate Limiting formuliert ein explizites Zugriffsbudget. Bulkhead isoliert Ressourcen, Admission Control kann zusätzlich Kosten und Systemzustand einbeziehen.
Typische Fehlanwendungen
nur IP-Adresse als Schlüssel in Proxy-Umgebungen
kein Retry-After oder nachvollziehbarer Fehler
lokale Zähler widersprechen sich in mehreren Instanzen
Java- und Enterprise-Beispiel
Eine Partner-API erhält pro Vertrag ein Token-Bucket-Budget. Teure Report-Aufrufe kosten mehrere Token, leichte Statusabfragen einen. Entscheidungen werden als typisiertes RateLimitDecision protokolliert.
Deutschsprachige fachliche Grundlagen
Ratenbegrenzungsmuster - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Drosselungsmuster - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
37
Cache Aside
Daten bei Bedarf laden und Cache-Lebenszyklus explizit steuern
Datenzugriffsmuster
Fachliche Einordnung
Beim Cache-Aside-Muster prüft die Anwendung zuerst den Cache. Bei einem Miss lädt sie aus dem maßgeblichen Datenspeicher, legt den Wert mit einer passenden Lebensdauer ab und liefert ihn zurück. Schreiboperationen aktualisieren den Store und invalidieren oder erneuern den Cache bewusst.
Der Cache ist nicht die Quelle der Wahrheit. Konsistenz, TTL, Stampede-Schutz, negative Einträge und Fehler des Caches müssen als Teil des Designs behandelt werden.
Beteiligte Rollen
Cache Port: get, put und invalidate.
Authoritative Store: verlässliche Datenquelle.
Cache Policy: Schlüssel, TTL und Invalidation.
Loader: lädt und validiert bei Miss.
Typischer Ablauf
Der Leser berechnet einen stabilen Cache-Key.
Ein Hit wird nur verwendet, wenn Version und Gültigkeit passen.
Bei Miss lädt der Store.
Der Wert wird mit TTL gespeichert.
Schreibvorgänge invalidieren oder aktualisieren nach definierter Reihenfolge.
Wann sinnvoll?
Lesezugriffe dominieren und Daten teuer zu laden sind
kurze kontrollierte Veraltung akzeptabel ist
Trefferquote und Invalidation messbar sind
Wann nicht?
starke unmittelbare Konsistenz erforderlich ist
Daten selten wiederverwendet werden
Cache-Ausfall die Anwendung komplett unbenutzbar machen würde
Abgrenzung zu ähnlichen Mustern
Read-Through verbirgt Laden im Cache-System. Cache Aside lässt die Anwendung den Ablauf steuern. Flyweight teilt Objektzustand im Speicher, nicht persistierte Datenzugriffe.
Typische Fehlanwendungen
kein Schutz gegen Cache Stampede
ein globales TTL für alle Datentypen
Write und Invalidation in falscher Reihenfolge
Java- und Enterprise-Beispiel
Produktdetails werden aus einem Repository geladen und fünf Minuten gecacht. Preisänderungen invalidieren den Schlüssel nach erfolgreichem Commit. Ein Single-Flight-Mechanismus verhindert, dass viele Misses gleichzeitig dieselben Daten laden.
Deutschsprachige fachliche Grundlagen
Cache-Aside Entwurfsmuster - deutschsprachige Grundlage für Begriff, Rollen, Einsatzgrenzen oder Betriebsaspekte.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
38
Idempotent Consumer
Doppelt zugestellte Nachrichten ohne doppelte Wirkung verarbeiten
Integrationsmuster
Fachliche Einordnung
Ein Idempotent Consumer erkennt bereits verarbeitete Nachrichten oder gestaltet die fachliche Operation so, dass eine erneute Zustellung dasselbe Ergebnis besitzt. Das Muster ist notwendig, weil Messaging-Systeme bei Fehlern und Wiederanläufen Nachrichten mehrfach liefern können.
Exactly-once ist selten eine Eigenschaft der gesamten verteilten Kette. Verlässlicher ist, mindestens einmal zuzustellen und die Wirkung am Consumer mit Message-ID, Business-Key oder Versionsprüfung zu deduplizieren.
Beteiligte Rollen
Message Identity: stabile eindeutige Kennung.
Inbox oder Dedup Store: registriert Verarbeitung atomar.
Consumer Handler: führt die fachliche Operation aus.
Retry/Dead-Letter Policy: behandelt wiederholbare und dauerhafte Fehler.
Typischer Ablauf
Der Consumer empfängt Nachricht und Kennung.
Inbox und fachliche Änderung werden in einer lokalen Transaktion koordiniert.
Ist die Kennung neu, wird verarbeitet und als erledigt markiert.
Ist sie bekannt, wird ohne erneute Wirkung bestätigt.
Fehler lassen die Nachricht erneut zustellen oder gezielt quarantänisieren.
Wiederanlauf und Netzwerkfehler Duplikate erzeugen können
Wann nicht?
Nachrichten keine stabile Identität besitzen
Dedup-Einträge zu früh gelöscht werden
fachliche Reihenfolge und Versionskonflikte ignoriert werden
Abgrenzung zu ähnlichen Mustern
Transactional Outbox sichert das Senden auf Producer-Seite. Inbox beziehungsweise Idempotent Consumer sichert die Wirkung auf Consumer-Seite. Beides zusammen reduziert Lücken, ersetzt aber kein fachliches Konfliktmodell.
Typische Fehlanwendungen
nur In-Memory-Set verwenden
Nachricht als verarbeitet markieren, bevor die Fachänderung bestätigt ist
Message-ID und Business-Idempotenz verwechseln
Java- und Enterprise-Beispiel
Ein PaymentCaptured-Consumer schreibt Message-ID und Buchung in derselben Datenbanktransaktion. Eine erneute Zustellung wird bestätigt, erzeugt aber keine zweite Buchung. Tests spielen dieselbe Nachricht mehrfach und parallel ein.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
39
Dead Letter Channel
Nicht verarbeitbare Nachrichten sichtbar isolieren und kontrolliert nachbearbeiten
Integrationsmuster
Fachliche Einordnung
Ein Dead Letter Channel nimmt Nachrichten auf, die nach definierten Versuchen nicht zugestellt oder verarbeitet werden können. Er verhindert, dass eine vergiftete Nachricht den normalen Durchsatz dauerhaft blockiert, und bewahrt Diagnoseinformationen für Analyse und Wiederverarbeitung.
Eine DLQ ist kein Endlager. Betrieb, Ownership, Alarmierung, Aufbewahrung, Datenschutz und ein sicherer Replay-Prozess müssen ausdrücklich definiert sein.
Beteiligte Rollen
Normaler Channel und Consumer.
Failure Classifier: transient, permanent oder unbekannt.
Dead Letter Channel: isolierte Speicherung.
Operator/Recovery Workflow: analysiert, korrigiert und spielt kontrolliert erneut ein.
Typischer Ablauf
Ein Verarbeitungsversuch schlägt fehl.
Die Policy zählt Versuche und klassifiziert die Ursache.
Nach Überschreiten des Budgets wird Nachricht mit Fehlerkontext verschoben.
Monitoring erzeugt Alarm und Zuständigkeit.
Nach Korrektur erfolgt Replay mit neuer Auditspur oder endgültige Archivierung.
Wann sinnvoll?
einzelne fehlerhafte Nachrichten den Stream blockieren können
Fehler manuelle oder spätere technische Korrektur benötigen
Broker und Betrieb eine separate Fehlerwarteschlange unterstützen
Wann nicht?
DLQ ohne Monitoring und Verantwortliche betrieben wird
sensible Daten unbegrenzt gespeichert werden
jede fachliche Ablehnung als technischer Fehler endet
Abgrenzung zu ähnlichen Mustern
Retry behandelt erwartete vorübergehende Fehler. Dead Letter Channel isoliert Nachrichten nach ausgeschöpftem oder ungeeignetem Retry. Quarantine kann zusätzliche Sicherheits- und Freigaberegeln besitzen.
Typische Fehlanwendungen
Fehlerursache und ursprüngliche Metadaten gehen verloren
automatisches Replay erzeugt Endlosschleifen
DLQ wächst unbemerkt
Java- und Enterprise-Beispiel
Eine fehlerhafte Vertragsnachricht wird nach fünf Versuchen mit Schema-Version, Correlation-ID, Stack-Kategorie und erstem/letztem Fehlerzeitpunkt in die DLQ verschoben. Ein Runbook beschreibt Korrektur, Freigabe und idempotentes Replay.
Die Darstellung ist eine eigenständige fachredaktionelle Zusammenfassung auf Basis der genannten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Vertiefungsband 3 Integration, Messaging und PlattformgrenzenPattern 41–60
Patterns für Integrationsflüsse, Messaging, Idempotenz und stabile Plattformgrenzen.
Dieser Block vertieft Ressourcen-, Plattform- und Nachrichtenmuster. Nur das Hauptkapitel ist klappbar; Einordnung, Rollen, Ablauf, Grenzen, Fehlanwendungen, Java-Bezug und Quellen bleiben innerhalb des geöffneten Kapitels dauerhaft sichtbar.
Fachlich vertieft: 60 zentrale Patterns21 neue Fachkapitel · deutschsprachige Quellen · projektbezogene Java- und Enterprise-Erklärung
Object Pool
Teure und begrenzte Ressourcen kontrolliert ausleihen, zurücksetzen und wiederverwenden
Erzeugungs- und Ressourcenmuster
Fachliche Einordnung
Ein Object Pool verwaltet eine begrenzte Menge wiederverwendbarer Objekte, deren Erzeugung, Initialisierung oder exklusive Nutzung teuer ist. Der Client erzeugt diese Objekte nicht selbst, sondern leiht eine Instanz aus dem Pool aus und gibt sie nach der Verwendung zurück.
Das Muster ist vor allem ein Lebenszyklus- und Kapazitätsmuster. Es muss festlegen, wie Objekte validiert, zurückgesetzt, bei Defekten verworfen und bei Erschöpfung behandelt werden. Für gewöhnliche kurzlebige Java-Objekte ist Pooling dagegen meist unnötig und erhöht nur die Komplexität.
Beteiligte Rollen
Pool: besitzt freie und ausgeliehene Instanzen und setzt Kapazitätsgrenzen durch.
Pooled Object: teure oder knappe Ressource, beispielsweise Datenbankverbindung oder Parserinstanz.
Borrower: leiht eine Ressource für einen klar begrenzten Abschnitt.
Validation/Reset Policy: prüft die Rückgabe und stellt einen sauberen Ausgangszustand her.
Typischer Ablauf
Der Client fordert eine Ressource mit Deadline an.
Der Pool liefert eine freie, validierte Instanz oder erzeugt innerhalb der Kapazitätsgrenze eine neue.
Der Client verwendet die Ressource ausschließlich im vereinbarten Scope.
Beim Schließen wird die Instanz validiert und zurückgesetzt.
Defekte Instanzen werden verworfen; Metriken zeigen Auslastung, Wartezeit und Leaks.
Wann sinnvoll?
Aufbau und Abbau einer Ressource sind messbar teuer
die Ressource ist knapp oder serverseitig begrenzt
Ausleihe und Rückgabe lassen sich sicher kapseln
Wann nicht?
normale kleine Java-Objekte sollen nur wegen vermeintlicher Performance gepoolt werden
der Pool verdeckt eine zu hohe Parallelität statt sie zu begrenzen
Abgrenzung zu ähnlichen Mustern
Ein Cache bewahrt Ergebnisse oder zustandsbehaftete Daten für spätere Lesezugriffe. Ein Object Pool verwaltet dagegen exklusive, zurückzugebende Arbeitsressourcen. Flyweight teilt unveränderlichen Zustand, ohne Ausleih- und Rückgabeprotokoll.
Typische Fehlanwendungen
fehlende Rückgabe bei Exceptions
Ressourcen werden ohne Gesundheitsprüfung erneut ausgeliehen
unbegrenztes Warten oder ein unbegrenzter Pool verschieben den Engpass
Java- und Enterprise-Beispiel
Ein Connection Pool leiht pro Transaktion eine Datenbankverbindung aus. Der Adapter verwendet try-with-resources, damit die Verbindung auch bei Fehlern zurückgegeben wird. Poolgröße, Borrow-Timeout und Validierung werden als betriebliche Konfiguration überwacht.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
API Gateway
Einen kontrollierten Eintrittspunkt vor mehreren Diensten bereitstellen
Architektur- und Integrationsmuster
Fachliche Einordnung
Ein API Gateway steht zwischen Clients und einer Menge interner Dienste. Es nimmt externe Anforderungen entgegen, routet sie an passende Backends und kann querschnittliche Aufgaben wie Authentifizierung, TLS-Terminierung, Ratenbegrenzung, Protokollierung und Antwortaggregation übernehmen.
Die zentrale Grenze darf nicht zur neuen Monolithen-Schicht werden. Fachliche Entscheidungen bleiben in den zuständigen Diensten; das Gateway behandelt vor allem Protokoll-, Sicherheits- und Routingbelange sowie bewusst ausgewählte clientnahe Komposition.
Beteiligte Rollen
Client: verwendet einen stabilen externen Vertrag.
Gateway: authentifiziert, routet, begrenzt und beobachtet Verkehr.
Backend-Dienste: besitzen Fachlogik und interne APIs.
Policy/Route Configuration: versioniert Regeln, Limits und Zielzuordnung.
Typischer Ablauf
Der Client sendet eine Anfrage an den öffentlichen Endpunkt.
Das Gateway validiert Transport- und Sicherheitsanforderungen.
Eine Route bestimmt den zuständigen Dienst oder eine kontrollierte Aggregation.
Antworten werden ohne Verlust fachlicher Fehlersemantik zurückgegeben.
Metriken, Traces und Auditdaten machen den Grenzverkehr sichtbar.
Wann sinnvoll?
mehrere Dienste sollen hinter einer stabilen Außengrenze liegen
querschnittliche Sicherheits- und Verkehrsregeln müssen konsistent gelten
Clients dürfen interne Topologie und Dienstadressen nicht kennen
Wann nicht?
nur ein kleiner stabiler Dienst existiert
Fachlogik und lange Orchestrierungen würden im Gateway landen
das Gateway wird ohne Hochverfügbarkeit zum zentralen Ausfallpunkt
Abgrenzung zu ähnlichen Mustern
Eine Facade vereinfacht ein Subsystem innerhalb einer Anwendung oder Domäne. Ein API Gateway ist eine verteilte Systemgrenze mit Netzwerk-, Sicherheits- und Betriebsverantwortung. Ein Backend for Frontend spezialisiert diese Grenze zusätzlich je Clienttyp.
Typische Fehlanwendungen
alle Backends werden über einen universellen, ständig wachsenden Vertrag vereinheitlicht
interne Fehler werden pauschal in HTTP 500 umgewandelt
Routingregeln sind nicht versioniert oder testbar
Java- und Enterprise-Beispiel
Eine Versicherungsplattform veröffentlicht Vertrags-, Schaden- und Dokumentfunktionen über ein Gateway. Das Gateway prüft OAuth-Token, setzt Mandantenkontext, begrenzt Partnerquoten und routet anschließend an fachlich getrennte Dienste.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Backend for Frontend
Für jede Benutzeroberfläche eine passende serverseitige Schnittstelle gestalten
Architekturmuster
Fachliche Einordnung
Backend for Frontend, kurz BFF, trennt client-spezifische Backend-Anforderungen voneinander. Web-, Mobil- oder Partneroberflächen erhalten jeweils eine kleine serverseitige Schicht, die Datenform, Aufrufgranularität und Interaktionsablauf auf den jeweiligen Client zuschneidet.
Das BFF soll keine zweite Domäne aufbauen. Es komponiert vorhandene Fachfunktionen, übersetzt clientnahe Darstellungen und übernimmt genau die Belange, die sich zwischen Oberflächen tatsächlich unterscheiden.
Beteiligte Rollen
Frontend: besitzt konkrete Darstellungs- und Interaktionsanforderungen.
BFF: stellt einen clientoptimierten Vertrag bereit.
Gateway/Identity Provider: übernimmt übergreifende Eintritts- und Identitätsaufgaben.
Typischer Ablauf
Ein Client ruft sein eigenes BFF auf.
Das BFF prüft clientbezogene Eingaben und baut benötigte Backend-Aufrufe.
Mehrere fachliche Antworten werden bei Bedarf clientgerecht kombiniert.
Das Ergebnis enthält nur die für diesen Client erforderlichen Daten.
Änderungen am Client können unabhängig von anderen BFFs ausgeliefert werden.
Wann sinnvoll?
mobile und Desktop-Clients benötigen deutlich unterschiedliche Payloads oder Abläufe
ein gemeinsames Backend sammelt immer mehr clientbezogene Sonderfälle
Frontend-Teams benötigen eigene Lieferzyklen
Wann nicht?
alle Clients stellen nahezu identische Anforderungen
nur eine Oberfläche existiert
Fachlogik und dauerhafte Datenhaltung wandern in das BFF
Abgrenzung zu ähnlichen Mustern
Ein API Gateway stellt eine gemeinsame technische Außengrenze bereit. Ein BFF spezialisiert die serverseitige Schnittstelle je Oberfläche. GraphQL kann einige Aggregationsprobleme lösen, ersetzt aber nicht automatisch clientbezogene Orchestrierung und Ownership.
Typische Fehlanwendungen
duplizierte Fachregeln in mehreren BFFs
jedes BFF greift direkt auf Datenbanken anderer Dienste zu
kleine Unterschiede führen vorschnell zu zu vielen Diensten
Java- und Enterprise-Beispiel
Die mobile Schaden-App benötigt kompakte Listen, Upload-Status und offlinefreundliche Antworten. Das Desktop-BFF liefert dagegen umfangreiche Prüfdaten. Beide verwenden dieselben Schaden- und Dokumentdienste, aber unterschiedliche clientnahe Verträge.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Service Mesh
Dienst-zu-Dienst-Kommunikation als eigene Infrastrukturschicht steuern
Cloud- und Infrastrukturmuster
Fachliche Einordnung
Ein Service Mesh ist eine Infrastrukturschicht für die Kommunikation zwischen Diensten. Proxies und eine zentrale Steuerungsebene übernehmen unter anderem mTLS, Dienstidentität, Verkehrssteuerung, Telemetrie und ausgewählte Resilienzfunktionen, ohne dass jede Anwendung dieselben Netzwerkmechanismen selbst implementiert.
Das Mesh löst keine fachlichen Kopplungen. Schlechte API-Verträge, chatty Kommunikation und unklare Zuständigkeiten bleiben Anwendungsprobleme. Außerdem entstehen zusätzliche Betriebs-, Latenz- und Diagnosekosten.
Beteiligte Rollen
Data Plane: Proxies verarbeiten den tatsächlichen Dienstverkehr.
Control Plane: verteilt Identitäten, Routen und Policies.
Workload: Fachservice mit klarer Anwendungsschnittstelle.
Platform Team: betreibt Standards, Telemetrie und sichere Defaults.
Typischer Ablauf
Ein Dienst adressiert einen anderen Dienst über dessen logischen Namen.
Der lokale Proxy baut eine gesicherte Verbindung auf.
Routing-, Retry- oder Timeoutregeln werden an der Infrastrukturgrenze angewendet.
Der Zielproxy authentifiziert die Workload-Identität.
Metriken und Traces werden mit Kontext erfasst.
Wann sinnvoll?
viele polyglotte Dienste benötigen konsistente Transport- und Sicherheitsregeln
mTLS und Serviceidentität sollen zentral verwaltet werden
Traffic Splitting und netzwerknahe Telemetrie werden häufig benötigt
Wann nicht?
wenige Dienste rechtfertigen die Plattformkomplexität nicht
fachliche Retries oder Transaktionen sollen in Infrastruktur verschoben werden
das Team kann Control Plane und Proxy-Flotte nicht zuverlässig betreiben
Abgrenzung zu ähnlichen Mustern
Ein API Gateway steuert überwiegend Nord-Süd-Verkehr zwischen externen Clients und Diensten. Ein Service Mesh konzentriert sich auf Ost-West-Verkehr zwischen Diensten. Sidecars sind eine mögliche technische Umsetzung, nicht das vollständige Muster.
Typische Fehlanwendungen
doppelte Retry-Schichten in Anwendung und Mesh
fehlende Ende-zu-Ende-Deadlines
das Mesh wird als Ersatz für API- und Domänendesign betrachtet
Java- und Enterprise-Beispiel
Eine Plattform mit vielen Microservices verwendet Workload-Identitäten und mTLS im Mesh. Canary-Traffic wird pro Dienstversion aufgeteilt. Fachliche Idempotenz, Fehlerklassifizierung und Transaktionslogik bleiben dennoch im Java-Code.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Competing Consumers
Nachrichtenlast über mehrere gleichartige Worker verteilen
Integrationsmuster
Fachliche Einordnung
Beim Muster Competing Consumers lesen mehrere gleichartige Consumer aus demselben Punkt-zu-Punkt-Kanal. Jede Nachricht wird einem Consumer zur Verarbeitung überlassen, wodurch Durchsatz und Ausfallsicherheit horizontal skaliert werden können.
Die Parallelisierung macht Reihenfolge, Idempotenz und Ressourcenkonflikte sichtbar. Consumer müssen denselben Vertrag erfüllen, und die Anzahl paralleler Instanzen darf nachgelagerte Systeme nicht überlasten.
Beteiligte Rollen
Queue: puffert Arbeit und vermittelt exklusive Zustellung.
Consumer-Gruppe: mehrere funktional gleichartige Worker.
Acknowledgement/Lease: steuert Besitz und erneute Zustellung.
Scaling Policy: passt Parallelität an Queue-Tiefe und Kapazität an.
Typischer Ablauf
Producer legen unabhängige Arbeitseinheiten in die Queue.
Freie Consumer konkurrieren um verfügbare Nachrichten.
Eine Nachricht wird für die Bearbeitungsdauer einem Consumer zugeordnet.
Erfolg wird bestätigt; Fehler führen zu Retry oder Dead Letter.
Skalierung orientiert sich an Rückstand, Bearbeitungszeit und Downstream-Kapazität.
Wann sinnvoll?
Arbeitseinheiten unabhängig oder partitionierbar sind
Lastspitzen durch eine Queue gepuffert werden
mehrere Instanzen denselben Handler ausführen können
Wann nicht?
strenge globale Reihenfolge erforderlich ist
Nachrichten konkurrierend dieselbe nicht gesicherte Ressource ändern
Downstream-Systeme die zusätzliche Parallelität nicht verkraften
Abgrenzung zu ähnlichen Mustern
Publisher-Subscriber liefert eine Nachricht an mehrere fachlich unterschiedliche Abonnenten. Competing Consumers verteilen mehrere Nachrichten innerhalb derselben Consumer-Gruppe, sodass jede Nachricht fachlich nur einmal verarbeitet werden soll.
Typische Fehlanwendungen
Autoscaling nur nach CPU statt Queue-Rückstand
fehlende Idempotenz bei erneuter Zustellung
unbegrenzte Parallelität erzeugt neue Engpässe
Java- und Enterprise-Beispiel
Mehrere Dokumentworker lesen aus derselben Importqueue. Die Nachricht enthält Dokument-ID und Idempotency-Key. Die Worker skalieren bis zu einer Grenze, die Datenbank- und Virenscanner-Kapazität berücksichtigt.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Message Router
Nachrichtenwege durch eine explizite, testbare Routingentscheidung steuern
Integrationsmuster
Fachliche Einordnung
Ein Message Router nimmt Nachrichten aus einem Eingangskanal entgegen und entscheidet, über welchen Ausgangskanal sie weiterlaufen. Die sendende Anwendung kennt dadurch nicht alle Empfänger, und Routingregeln werden aus fachlichen Produzenten und Konsumenten herausgelöst.
Ein Router soll Nachrichtenwege bestimmen, nicht gleichzeitig Fachlogik, Transformation, Persistenz und lange Prozesssteuerung übernehmen. Je komplexer Regeln werden, desto wichtiger sind typisierte Entscheidungen und eine nachvollziehbare Fallback- oder Fehlerroute.
Beteiligte Rollen
Input Channel: liefert Nachrichten an den Router.
Routing Policy: bewertet Metadaten, Typ oder fachliche Merkmale.
Unroutable/Invalid Channel: behandelt nicht zuordenbare Nachrichten.
Typischer Ablauf
Der Router empfängt eine unveränderte Nachricht.
Eine Policy liest nur die für die Entscheidung nötigen Merkmale.
Genau eine oder bewusst mehrere Zielrouten werden bestimmt.
Die Nachricht wird ohne versteckte Nebenwirkung weitergeleitet.
Nicht zuordenbare Fälle werden sichtbar abgelehnt oder quarantänisiert.
Wann sinnvoll?
Producer sollen Zielsysteme nicht kennen
Routingregeln ändern sich unabhängig von Fachhandlern
unterschiedliche Nachrichtentypen teilen einen Eingangskanal
Wann nicht?
ein direkter stabiler Kanal genügt
der Router wird zum zentralen Prozessmotor
Routing basiert auf unversionierten, beliebigen Payload-Feldern
Abgrenzung zu ähnlichen Mustern
Content-Based Router ist eine konkrete Variante, die den Nachrichteninhalt auswertet. Ein Process Manager hält dagegen Zustand über mehrere Nachrichten und steuert einen lang laufenden Ablauf.
Typische Fehlanwendungen
ein riesiger switch-Block ohne Policy-Struktur
stille Default-Route für unbekannte Nachrichten
Transformation und Routing werden untrennbar vermischt
Java- und Enterprise-Beispiel
Ein Eingangsrouter ordnet Vertragsereignisse anhand von Ereignistyp und Schema-Version den passenden Verarbeitungskanälen zu. Die Entscheidung ist ein reines Java-Objekt und wird separat getestet.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Ein Content-Based Router untersucht definierte Felder einer Nachricht und leitet sie anhand expliziter Regeln an passende Kanäle weiter. Er ist sinnvoll, wenn die Zielwahl aus stabilen fachlichen Merkmalen wie Dokumenttyp, Region oder Risikoklasse folgt.
Die Entscheidung muss auf einem bekannten, versionierten Vertrag beruhen. Ein Router, der beliebige JSON-Pfade und ständig wechselnde Sonderfälle auswertet, wird schnell zu einer schwer testbaren Schattenfachlogik.
Beteiligte Rollen
Message Contract: stellt typisierte Routingmerkmale bereit.
Routing Rule: beschreibt eine nachvollziehbare Bedingung.
Router: wertet Regeln deterministisch aus.
Destination Channel: repräsentiert den Zielworkflow.
Typischer Ablauf
Die Nachricht wird gegen Schema und Pflichtfelder validiert.
Routingmerkmale werden in ein kleines Decision Input übersetzt.
Regeln werden in dokumentierter Priorität geprüft.
Ein Ziel oder eine explizite Mehrfachzustellung wird ausgewählt.
Unbekannte Kombinationen landen in einer Fehlerroute.
Wann sinnvoll?
ein gemeinsamer Eingang mehrere klar getrennte Fachpfade versorgt
die Routingmerkmale stabil und erklärbar sind
Regeln ohne Änderung der Producer verwaltet werden sollen
Wann nicht?
die Entscheidung benötigt lang laufenden Prozesszustand
das Ziel ist bereits durch einen klaren Nachrichtentyp bestimmt
jede neue Fachregel landet zentral im Router
Abgrenzung zu ähnlichen Mustern
Ein Message Router ist der Oberbegriff. Ein Content-Based Router entscheidet anhand von Inhalt oder Metadaten. Ein Recipient List Router kann mehrere dynamische Empfänger liefern.
Typische Fehlanwendungen
Reihenfolge überlappender Regeln ist unklar
fehlende Default-Fehlerroute
Routing wird mit Transformation oder Autorisierung vermischt
Java- und Enterprise-Beispiel
Ein Schadenereignis wird nach Sparte und Dringlichkeit an Standardprüfung, Betrugsprüfung oder manuelle Eskalation geroutet. Die Entscheidung liefert ein RoutingDecision-Objekt mit Begründung.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Message Translator
Unterschiedliche Nachrichtenmodelle an einer expliziten Grenze übersetzen
Integrationsmuster
Fachliche Einordnung
Ein Message Translator wandelt eine Nachricht aus dem Format und Bedeutungsmodell eines Systems in den Vertrag eines anderen Systems um. Die Übersetzung umfasst nicht nur technische Formate, sondern häufig Benennungen, Wertebereiche, Einheiten und fachliche Semantik.
Der Translator schützt beide Seiten vor direkter Modellkopplung. Er darf fehlende Bedeutung nicht erfinden; nicht abbildbare Werte müssen als expliziter Fehler oder als dokumentierte Kompatibilitätsentscheidung behandelt werden.
Beteiligte Rollen
Source Contract: Modell des sendenden Systems.
Translator: führt validierte, nachvollziehbare Zuordnungen aus.
Target Contract: erwartetes Modell des Empfängers.
Mapping Error: typisierte Darstellung nicht möglicher Übersetzungen.
Typischer Ablauf
Die Quellnachricht wird gegen ihr Schema validiert.
Felder werden in semantische Zwischenwerte überführt.
Einheiten, Codes und Strukturen werden gezielt übersetzt.
Das Zielobjekt wird vollständig validiert.
Mappingfehler werden mit Feld- und Versionskontext gemeldet.
Wann sinnvoll?
Systeme unterschiedliche Datenmodelle oder Versionen besitzen
Legacy-Codes vom neuen Domänenmodell ferngehalten werden sollen
Verträge unabhängig weiterentwickelt werden
Wann nicht?
nur Transportserialisierung ohne semantische Änderung nötig ist
ein gemeinsames kanonisches Modell unnötige Zentralisierung erzeugt
unbekannte Werte stillschweigend verworfen werden
Abgrenzung zu ähnlichen Mustern
Ein Adapter passt eine Programmierschnittstelle an. Ein Message Translator übersetzt übertragene Daten und Bedeutung. Eine Anti-Corruption Layer kann mehrere Translatoren, Adapter und Policies bündeln.
Typische Fehlanwendungen
Reflection-basierte Feldkopie ohne Semantik
ein universeller Mapper kennt alle Systeme
Versionsänderungen werden nicht durch Contract-Tests abgesichert
Java- und Enterprise-Beispiel
Ein Legacy-Schadenssystem sendet numerische Statuscodes und Beträge in Cent. Der Translator erzeugt typisierte Status-Enums und Money-Werte, dokumentiert unbekannte Codes und hält das neue Modell frei von Legacy-Begriffen.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Splitter
Eine zusammengesetzte Nachricht in unabhängig verarbeitbare Teile zerlegen
Integrationsmuster
Fachliche Einordnung
Ein Splitter zerlegt eine größere zusammengesetzte Nachricht in mehrere Teilnachrichten. Jeder Teil kann anschließend unabhängig geroutet, parallel verarbeitet oder einzeln wiederholt werden.
Die Teilung benötigt Identität und Herkunftsinformationen. Ohne Correlation-ID, Sequenznummer und erwartete Teilanzahl kann ein späterer Aggregator Vollständigkeit und Reihenfolge nicht zuverlässig beurteilen.
Beteiligte Rollen
Composite Message: enthält eine fachlich teilbare Menge.
Splitter: erzeugt Teilnachrichten nach einer klaren Regel.
Sequence Metadata: Correlation-ID, Position und Vollständigkeitsangabe.
Downstream Handler: verarbeitet einzelne Teile.
Typischer Ablauf
Die Eingangsnachricht wird validiert.
Eine Split-Policy bestimmt die fachlichen Einheiten.
Für jedes Teil wird eine neue Nachricht mit Sequenzmetadaten erzeugt.
Teile werden unabhängig verarbeitet.
Fehler einzelner Teile bleiben über ihre Identität nachvollziehbar.
Wann sinnvoll?
ein Batch oder Dokument aus unabhängigen Positionen besteht
Teile parallel oder getrennt geroutet werden sollen
einzelne Fehler ohne Wiederholung der Gesamtnachricht behandelbar sein sollen
Wann nicht?
Teile gemeinsam atomar bleiben müssen
die Nachricht nur technisch, nicht fachlich teilbar ist
Reihenfolge und Aggregation nicht geklärt sind
Abgrenzung zu ähnlichen Mustern
Splitter zerlegt eine Nachricht. Aggregator führt korrelierte Ergebnisse wieder zusammen. Pipes and Filters beschreibt eine Folge von Verarbeitungsschritten, nicht die Vervielfachung einer Nachricht.
Eine Sammelrechnung wird in Rechnungspositionen aufgeteilt. Jede Position erhält Batch-ID, Position und Gesamtanzahl. Steuer- und Kontierungsprüfungen laufen parallel, bevor Ergebnisse aggregiert werden.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Aggregator
Mehrere korrelierte Teilnachrichten nach einer Vollständigkeitsregel zusammenführen
Integrationsmuster
Fachliche Einordnung
Ein Aggregator sammelt zusammengehörige Nachrichten und erzeugt daraus ein fachliches Gesamtergebnis. Er benötigt eine Korrelationsregel, einen persistenten Zwischenzustand und eine Abschlussbedingung, beispielsweise erwartete Anzahl, fachliches Endsignal oder Deadline.
Aggregation ist zustandsbehaftet. Duplikate, verspätete Teile, Teilfehler und Neustarts müssen ausdrücklich behandelt werden; ein rein flüchtiger Speicher reicht in produktiven Abläufen oft nicht aus.
Beteiligte Rollen
Correlation Strategy: ordnet Nachrichten einer Gruppe zu.
Aggregate Store: hält Zwischenstand dauerhaft.
Completion Policy: entscheidet über Vollständigkeit oder Timeout.
Result Builder: erzeugt das Gesamtergebnis.
Typischer Ablauf
Eine Teilnachricht wird anhand ihrer Correlation-ID geladen.
Duplikate werden erkannt und ignoriert oder aktualisiert.
Der Zwischenstand wird atomar ergänzt.
Die Completion Policy prüft Anzahl, Endsignal oder Deadline.
Bei Abschluss wird genau ein Ergebnis veröffentlicht und der Zustand geschlossen.
Wann sinnvoll?
Teilaufgaben parallel laufen und ein gemeinsames Ergebnis benötigen
Antworten in beliebiger Reihenfolge eintreffen
Vollständigkeit fachlich definiert werden kann
Wann nicht?
ein Stream unendlich ist und kein Fenster existiert
Teilergebnisse sofort einzeln nutzbar sind
fehlende Teile nicht sinnvoll behandelt werden können
Abgrenzung zu ähnlichen Mustern
Splitter erzeugt Teilnachrichten; Aggregator korreliert Ergebnisse. Resequencer ordnet Nachrichten, ohne sie zwingend zu einem Objekt zusammenzufassen.
Typische Fehlanwendungen
Completion nur über erwartete Anzahl ohne Timeout
nicht persistenter Zustand geht bei Neustart verloren
doppelte Abschlussereignisse bei konkurrierenden Nachrichten
Java- und Enterprise-Beispiel
Ergebnisse aus Bonitäts-, Betrugs- und Dokumentprüfung werden pro Antrag gesammelt. Der Aggregator schließt bei drei Ergebnissen oder nach einer Deadline mit einem expliziten unvollständigen Decision Report.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Ein Resequencer puffert korrelierte Nachrichten und gibt sie in einer fachlich erforderlichen Reihenfolge weiter. Er wird benötigt, wenn Transport, Parallelisierung oder Wiederholung die ursprüngliche Reihenfolge verändern können.
Die Ordnung braucht einen stabilen Sequenzschlüssel und eine Strategie für Lücken. Unbegrenztes Warten auf eine verlorene Nachricht führt zu Speicherwachstum und blockierten Gruppen.
Beteiligte Rollen
Sequence Key: bestimmt die Position innerhalb einer Gruppe.
Gap/Timeout Policy: entscheidet über fehlende Positionen.
Typischer Ablauf
Eine Nachricht wird ihrer Sequenzgruppe zugeordnet.
Ist sie die nächste erwartete Position, wird sie freigegeben.
Spätere Positionen werden gepuffert.
Nach jeder Freigabe werden direkt folgende Pufferwerte ausgespielt.
Timeout oder Endmarker behandelt dauerhaft fehlende Positionen.
Wann sinnvoll?
fachliche Zustandsänderungen müssen in Versionreihenfolge verarbeitet werden
Parallelisierung verändert Ankunftsreihenfolge
Sequenznummern sind zuverlässig vorhanden
Wann nicht?
Reihenfolge fachlich bedeutungslos ist
globale Ordnung über große verteilte Streams verlangt wird
fehlende Nachrichten keine definierte Behandlung besitzen
Abgrenzung zu ähnlichen Mustern
Ein Aggregator kombiniert mehrere Nachrichten zu einem Ergebnis. Ein Resequencer gibt einzelne Nachrichten geordnet weiter. Brokerpartitionen können lokale Reihenfolge liefern, ersetzen aber keine fachliche Versionsprüfung.
Typische Fehlanwendungen
globale statt korrelationsbezogener Reihenfolge
kein Limit für Puffer und Wartezeit
Duplikate werden als neue Sequenzposition behandelt
Java- und Enterprise-Beispiel
Vertragsänderungen tragen eine Aggregate-Version. Treffen Version 13 und 12 vertauscht ein, hält der Resequencer Version 13 zurück, verarbeitet 12 und gibt danach 13 frei.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Claim Check
Große Nutzlasten aus Nachrichten auslagern und nur einen sicheren Verweis transportieren
Integrationsmuster
Fachliche Einordnung
Claim Check lagert eine große oder sensible Nutzlast in einen separaten Speicher aus. Die Nachricht enthält statt der vollständigen Daten einen kleinen, kontrollierten Verweis, mit dem berechtigte Consumer die Nutzlast abrufen können.
Der Verweis ist kein beliebiger Dateipfad. Lebensdauer, Berechtigung, Integrität, Verschlüsselung und Aufräumen müssen Teil des Vertrags sein, sonst entstehen verwaiste Daten oder unkontrollierter Zugriff.
Beteiligte Rollen
Payload Store: speichert große Inhalte sicher.
Claim Token: referenziert Objekt, Version und Integritätswert.
Producer: schreibt Payload vor dem Versand.
Consumer: autorisiert und lädt Payload bei Bedarf.
Typischer Ablauf
Der Producer speichert die Nutzlast und erhält ein Token.
Eine kleine Nachricht mit Token und Metadaten wird veröffentlicht.
Der Consumer prüft Identität und Berechtigung.
Die Nutzlast wird geladen und gegen Prüfsumme oder Version validiert.
Retention- und Cleanup-Policy entfernen nicht mehr benötigte Daten.
Wann sinnvoll?
Brokergrenzen oder Übertragungskosten große Nachrichten problematisch machen
mehrere Consumer dieselbe große Nutzlast lesen
sensible Binärdaten nicht im Broker verbleiben sollen
Wann nicht?
Payload klein und kurzlebig ist
Speicher und Nachricht keine konsistente Lebensdauer besitzen
Token ungeschützt direkten Zugriff ermöglicht
Abgrenzung zu ähnlichen Mustern
Content Enricher ergänzt Informationen in einer Nachricht. Claim Check entfernt große Inhalte aus dem Transport und stellt nur einen Abrufverweis bereit.
Typische Fehlanwendungen
Nutzlast wird gelöscht, bevor alle Consumer fertig sind
Token enthält öffentliche Speicheradresse ohne Autorisierung
keine Prüfsumme gegen vertauschte oder veränderte Daten
Java- und Enterprise-Beispiel
Ein Dokumentservice speichert PDF-Dateien verschlüsselt im Objektspeicher. Die Ereignisnachricht enthält Dokument-ID, Version, Hash und Ablaufzeit. Consumer laden nur bei fachlichem Bedarf.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Wire Tap
Nachrichten beobachten, ohne den fachlichen Hauptfluss zu verändern
Integrationsmuster
Fachliche Einordnung
Wire Tap erstellt an einer definierten Stelle eine Kopie einer Nachricht und leitet sie an einen separaten Beobachtungs- oder Auditkanal. Der normale Verarbeitungspfad soll dadurch weder seine Zieladresse noch seine fachliche Semantik ändern.
Beobachtung darf nicht unbemerkt zur synchronen Pflichtabhängigkeit werden. Sensible Daten, Volumen und Backpressure des Nebenzweigs müssen kontrolliert werden.
Beteiligte Rollen
Primary Channel: trägt den fachlichen Hauptfluss.
Tap: kopiert Nachricht oder ausgewählte Metadaten.
Observation Channel: erhält die Kopie.
Observer: schreibt Audit, Telemetrie oder Diagnoseinformationen.
Typischer Ablauf
Eine Nachricht passiert einen markierten Kanalpunkt.
Der Tap erzeugt eine unveränderliche Kopie oder ein redigiertes Beobachtungsereignis.
Der Hauptfluss wird unabhängig fortgesetzt.
Der Beobachtungszweig verarbeitet asynchron.
Fehler im Beobachtungszweig werden separat überwacht.
Wann sinnvoll?
Audit oder Diagnose ohne Änderung des Producers benötigt wird
Nachrichtenflüsse kontrolliert beobachtet werden sollen
Beobachtung fachlich nicht blockierend sein darf
Wann nicht?
der Nebenzweig für den Geschäftserfolg zwingend ist
vollständige sensible Payloads unkontrolliert kopiert werden
zusätzliche Last den Hauptkanal indirekt überlastet
Abgrenzung zu ähnlichen Mustern
Observer beschreibt Objektbenachrichtigungen innerhalb eines Systems. Wire Tap kopiert Nachrichten an einer Integrationsstrecke. Ein Domain Event ist ein fachliches Ereignis, nicht bloß eine technische Beobachtung.
Typische Fehlanwendungen
synchrones Audit blockiert den Hauptfluss
personenbezogene Daten werden ohne Maskierung kopiert
fehlende Correlation-ID verhindert Zuordnung
Java- und Enterprise-Beispiel
Vor dem Versand an einen Partner wird ein redigiertes AuditEvent mit Message-ID, Vertrag, Ziel und Hash abgezweigt. Der eigentliche Versand hängt nicht von der Auditdatenbank ab.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Inbox Pattern
Eingehende Nachrichten dauerhaft erfassen und idempotent verarbeiten
Integrations- und Konsistenzmuster
Fachliche Einordnung
Das Inbox Pattern speichert die Identität und häufig auch den Inhalt eingehender Nachrichten in einem lokalen, transaktionalen Store. Fachliche Verarbeitung und Statuswechsel der Inbox werden so koordiniert, dass erneute Zustellungen keine doppelte Wirkung erzeugen.
Die Inbox ist mehr als ein In-Memory-Deduplizierungsset. Sie benötigt Aufbewahrung, Zustandsmodell, Fehlerklassifizierung und eine atomare Beziehung zur lokalen Fachänderung.
Beteiligte Rollen
Inbox Store: hält Message-ID, Status, Zeitpunkte und gegebenenfalls Payload.
Receiver: registriert eine Nachricht atomar.
Handler: führt fachliche Verarbeitung aus.
Recovery Worker: setzt offene oder fehlgeschlagene Einträge fort.
Typischer Ablauf
Eine Nachricht wird mit stabiler ID empfangen.
Ein Insert-if-absent registriert sie lokal.
Neue Einträge werden fachlich verarbeitet.
Fachänderung und Inbox-Status werden in einer lokalen Transaktion bestätigt.
Duplikate lesen den vorhandenen Status und erzeugen keine zweite Wirkung.
Wann sinnvoll?
Broker mindestens einmal zustellt
lokale Datenbankänderungen idempotent koordiniert werden müssen
Wiederanlauf und Diagnose erforderlich sind
Wann nicht?
Message-IDs nicht stabil oder nicht eindeutig sind
Aufbewahrung kürzer als mögliche Wiederzustellung ist
Inbox und Fachänderung getrennt ohne Konsistenzstrategie gespeichert werden
Abgrenzung zu ähnlichen Mustern
Transactional Outbox sichert lokale Änderung und ausgehende Nachricht. Inbox sichert eingehende Nachricht und lokale Wirkung. Idempotent Consumer beschreibt das Ziel; Inbox ist eine verbreitete technische Umsetzung.
Typische Fehlanwendungen
vor Fachcommit als verarbeitet markieren
keine Bereinigung oder Partitionierung großer Inbox-Tabellen
Payload enthält sensible Daten ohne Retention-Regel
Java- und Enterprise-Beispiel
Ein PaymentCaptured-Consumer registriert die Event-ID und erzeugt die Buchung in derselben Datenbanktransaktion. Nach einem Crash kann ein Recovery Worker offene Inbox-Einträge erneut sicher ausführen.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Polling Consumer
Nachrichten dann abrufen, wenn der Consumer Kapazität zur Verarbeitung besitzt
Integrationsmuster
Fachliche Einordnung
Ein Polling Consumer fragt einen Kanal aktiv nach verfügbaren Nachrichten ab. Der Consumer kontrolliert Abrufrhythmus, Batchgröße und Verarbeitungskapazität und eignet sich damit für Quellen, die keine ereignisgetriebene Zustellung anbieten oder bewusst pull-basiert integriert werden.
Polling muss Last und Latenz ausbalancieren. Zu häufige leere Abfragen verschwenden Ressourcen; zu lange Intervalle erhöhen Verzögerung. Besitz, Sichtbarkeit und Bestätigung einer Nachricht müssen klar geregelt sein.
Beteiligte Rollen
Polling Loop: plant Abrufe mit Backoff und Shutdown-Semantik.
Message Source: liefert eine oder mehrere Nachrichten.
Handler: verarbeitet innerhalb eines begrenzten Budgets.
Er ruft einen begrenzten Batch mit Lease oder Cursor ab.
Nachrichten werden verarbeitet und bestätigt.
Bei leerer Quelle wird das Intervall kontrolliert erhöht.
Fehler beeinflussen nur betroffene Nachrichten und nicht die gesamte Schleife.
Wann sinnvoll?
Quelle nur Pull-Zugriff unterstützt
Consumer Durchsatz selbst steuern muss
Batchabruf effizienter als Einzelzustellung ist
Wann nicht?
niedrigste Latenz zwingend ist und Push verfügbar ist
mehrere Poller ohne Lease dieselben Daten lesen
unbegrenzte Batches Speicher und Downstream überlasten
Abgrenzung zu ähnlichen Mustern
Event-Driven Consumer reagiert auf zugestellte Nachrichten. Polling Consumer zieht Arbeit aktiv. Competing Consumers können mehrere Polling-Instanzen verwenden, benötigen dann aber exklusive Besitzregeln.
Typische Fehlanwendungen
fester enger Polling-Takt bei leerer Quelle
kein Graceful Shutdown zwischen Abruf und Commit
Cursor wird vor erfolgreicher Verarbeitung fortgeschrieben
Java- und Enterprise-Beispiel
Ein Importdienst liest alle fünf Sekunden höchstens 100 offene Datensätze mit Skip-Locked-Lease. Nach leerem Abruf erhöht er den Backoff, bei hoher Last begrenzt er Parallelität anhand der Datenbankkapazität.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Pipes and Filters
Komplexe Verarbeitung in unabhängige, kombinierbare Schritte zerlegen
Architektur- und Integrationsmuster
Fachliche Einordnung
Pipes and Filters zerlegt eine Verarbeitungskette in einzelne Filter, die jeweils eine klar umrissene Transformation oder Prüfung ausführen. Pipes verbinden die Schritte und transportieren Daten, ohne die fachliche Logik der Filter zu kennen.
Die Vorteile entstehen nur bei sauberen Verträgen. Versteckter gemeinsamer Zustand, unklare Fehlersemantik oder ein universelles Map-Objekt zwischen allen Stufen führen wieder zu enger Kopplung.
Beteiligte Rollen
Filter: führt eine einzelne, testbare Operation aus.
Pipe: transportiert Daten und gegebenenfalls Backpressure.
Pipeline Composition: legt Reihenfolge und Varianten fest.
Ein Input wird in einen typisierten Pipelinevertrag überführt.
Jeder Filter validiert seine Vorbedingungen.
Das Ergebnis wird unverändert oder transformiert an den nächsten Schritt gegeben.
Fehler stoppen, verzweigen oder kompensieren nach definierter Policy.
Metriken zeigen Dauer und Fehler je Filter.
Wann sinnvoll?
Verarbeitung aus klar trennbaren Schritten besteht
Schritte wiederverwendet oder unterschiedlich kombiniert werden
einzelne Stufen unabhängig getestet und skaliert werden sollen
Wann nicht?
alle Schritte eine gemeinsame Transaktion und stark geteilten Zustand benötigen
der Pipelinevertrag zu einem untypisierten Datenbeutel wird
Netzwerkgrenzen ohne Nutzen zwischen jeden Schritt gelegt werden
Abgrenzung zu ähnlichen Mustern
Chain of Responsibility sucht typischerweise einen zuständigen Handler oder lässt eine Anfrage durch Prüfungen laufen. Pipes and Filters transformiert Daten schrittweise und kann jedes Ergebnis an die nächste Stufe weitergeben.
Typische Fehlanwendungen
Filter kennen konkrete Vorgänger und Nachfolger
Fehlerbehandlung ist pro Stufe unterschiedlich und undokumentiert
zu feine verteilte Filter erzeugen hohe Latenz
Java- und Enterprise-Beispiel
Eine Dokumentpipeline besteht aus Virenprüfung, Formatvalidierung, OCR, Klassifikation und Archivierung. Jeder Filter besitzt einen typisierten Vertrag; große Dateien werden per Claim Check referenziert.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Publisher-Subscriber
Ereignisse an mehrere unabhängige Abonnenten verteilen
Integrationsmuster
Fachliche Einordnung
Publisher-Subscriber entkoppelt einen Sender von der Anzahl und Identität der Empfänger. Der Publisher veröffentlicht ein Ereignis auf einem Topic oder Kanal; mehrere Abonnenten erhalten jeweils eine eigene Zustellung und können unabhängig reagieren.
Das Muster erhöht zeitliche und organisatorische Entkopplung, erschwert aber die Gesamtübersicht. Verträge, Zustellungsgarantien, Idempotenz und Beobachtbarkeit müssen deshalb deutlich stärker sein als bei einem direkten Methodenaufruf.
Beteiligte Rollen
Publisher: veröffentlicht ein fachlich benanntes Ereignis.
Topic/Channel: verteilt an Abonnements.
Subscription: besitzt Filter, Retention und Zustellstatus.
Subscriber: verarbeitet unabhängig und idempotent.
Typischer Ablauf
Ein Publisher erzeugt ein versioniertes Ereignis.
Der Broker verteilt es an passende Abonnements.
Jeder Subscriber verarbeitet seine eigene Zustellung.
Fehler und Retries eines Subscribers beeinflussen andere nicht.
Neue Subscriber können ohne Änderung des Publishers hinzukommen.
Wann sinnvoll?
mehrere unabhängige Reaktionen auf dasselbe Ereignis nötig sind
Sender und Empfänger unterschiedliche Lebenszyklen besitzen
neue Abonnenten ohne Produceränderung ergänzt werden sollen
Wann nicht?
eine synchrone Antwort für den Geschäftsabschluss erforderlich ist
das Ereignis nur ein versteckter Remote-Command ist
Vertragseigentümer und Schemaentwicklung unklar sind
Abgrenzung zu ähnlichen Mustern
Observer ist meist ein In-Process-Muster mit direkter Registrierung. Publisher-Subscriber arbeitet häufig über verteilte Kanäle mit Persistenz und Zustellgarantien. Competing Consumers verteilen innerhalb eines Abonnements, nicht an mehrere fachliche Abonnenten.
Typische Fehlanwendungen
Ereignisse tragen komplette interne Aggregate
unbekannte Anzahl von Seiteneffekten ohne Monitoring
Breaking Schema Changes ohne Versionierungsstrategie
Java- und Enterprise-Beispiel
Nach Vertragsabschluss veröffentlicht der Vertragsdienst ContractActivated. Rechnung, Dokument, CRM und Analytics besitzen unabhängige Abonnements und eigene Retry- sowie Inbox-Policies.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Request-Reply
Eine Anfrage und ihre spätere Antwort über Nachrichten korrelieren
Nachrichtenmuster
Fachliche Einordnung
Request-Reply bildet eine Frage-Antwort-Interaktion über Nachrichtenkanäle ab. Der Request enthält eine Identität und häufig eine Rücksendeadresse; die Antwort verweist über eine Correlation-ID auf die ursprüngliche Anfrage.
Das Muster kann synchron wirken, bleibt aber technisch asynchron. Timeouts, verspätete Antworten, Duplikate und der Lebenszyklus wartender Requests müssen explizit behandelt werden.
Beteiligte Rollen
Requester: sendet Anfrage und verwaltet Erwartung oder Future.
Request Channel: transportiert Befehle oder Abfragen.
Replier: verarbeitet und erzeugt typisierte Antwort.
Reply Channel/Correlation Store: ordnet Antwort der Anfrage zu.
Typischer Ablauf
Der Requester erzeugt Request-ID, Deadline und Return Address.
Die Anfrage wird veröffentlicht.
Der Replier verarbeitet innerhalb der Deadline.
Die Antwort trägt die Correlation-ID des Requests.
Der Requester ordnet sie zu oder behandelt Timeout und verspätete Antwort.
Wann sinnvoll?
eine Antwort benötigt wird, aber direkte synchrone Kopplung vermieden werden soll
Bearbeitung länger dauern oder gepuffert werden kann
Brokerbasierte Kommunikation ohnehin vorhanden ist
Wann nicht?
Fire-and-forget oder Ereignisbenachrichtigung genügt
der Aufrufer blockiert unbegrenzt auf eine Antwort
verteilte Transaktion durch Messaging nur versteckt werden soll
Abgrenzung zu ähnlichen Mustern
Ein Command verlangt eine Wirkung, aber nicht zwingend eine unmittelbare Antwort. Request-Reply ergänzt einen expliziten Rückkanal. Asynchronous Request-Reply kann zusätzlich Statusressourcen statt einer Antwortqueue verwenden.
Typische Fehlanwendungen
Correlation-ID wird neu erzeugt statt vom Request übernommen
Timeouts entfernen Zustand, aber verspätete Antworten werden nicht behandelt
Reply-Kanäle sind nicht abgesichert oder räumbar
Java- und Enterprise-Beispiel
Ein Exportauftrag wird über eine Queue gestartet. Der Worker sendet ExportAccepted oder ExportRejected an die Return Address. Die Antwort enthält Request-ID und Deadline-Kontext; spätere Fertigstellung erfolgt als separates Ereignis.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Correlation Identifier
Zusammengehörige Nachrichten über Prozess- und Systemgrenzen eindeutig verbinden
Nachrichtenmuster
Fachliche Einordnung
Ein Correlation Identifier ist eine stabile Kennung, mit der Antworten, Teilnachrichten oder Folgeschritte einem ursprünglichen Geschäftsvorgang zugeordnet werden. Er wird über Nachrichten- und Systemgrenzen weitergegeben, ohne dabei die technische Message-ID zu ersetzen.
Eine gute Korrelationskennung ist unveränderlich und hat einen klaren Scope. Sie darf nicht gleichzeitig Benutzeridentität, Autorisierung oder globale Primärschlüsselbedeutung übernehmen.
Beteiligte Rollen
Initiator: erzeugt oder übernimmt die Korrelation.
Envelope/Header: transportiert die Kennung.
Intermediaries: geben sie unverändert weiter.
Logs/Traces/Stores: verwenden sie für Zuordnung und Diagnose.
Typischer Ablauf
Am Beginn eines Vorgangs wird eine Correlation-ID bestimmt.
Jede abgeleitete Nachricht übernimmt sie.
Neue Message-IDs bleiben pro Nachricht eindeutig.
Antworten und Teilresultate können dem Vorgang zugeordnet werden.
Logs und Traces sind über die Kennung durchsuchbar.
Wann sinnvoll?
asynchrone Antworten oder Teilnachrichten zusammengeführt werden
ein Vorgang mehrere Dienste durchläuft
Betriebsdiagnose eine Ende-zu-Ende-Sicht benötigt
Wann nicht?
eine einzige synchrone Methode ohne Systemgrenze betroffen ist
Correlation-ID als Sicherheitsnachweis missverstanden wird
Kennungen bei jedem Hop überschrieben werden
Abgrenzung zu ähnlichen Mustern
Message-ID identifiziert genau eine Nachricht. Correlation-ID verbindet mehrere Nachrichten eines Vorgangs. Trace-ID dient Observability und kann denselben Vorgang abbilden, besitzt aber einen anderen technischen Lebenszyklus.
Typische Fehlanwendungen
Geschäftsschlüssel mit personenbezogenen Daten als Korrelation verwenden
neue Korrelation bei jeder Weiterleitung
keine Trennung von Parent- und Child-Operationen
Java- und Enterprise-Beispiel
Ein Schadenfall startet Dokument-, Risiko- und Zahlungsprüfung. Alle Nachrichten besitzen eigene Message-IDs, teilen aber die ClaimProcess-Correlation-ID. Aggregator und Support können damit alle Teilschritte finden.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Return Address
Den Antwortkanal als expliziten Teil des Nachrichtenvertrags angeben
Nachrichtenmuster
Fachliche Einordnung
Return Address bezeichnet die Adresse oder logische Route, an die ein Empfänger seine Antwort senden soll. Der Requester gibt damit den Rückweg an, ohne dass der Replier konkrete Clienttopologie oder fest codierte Antwortkanäle kennen muss.
Die Rücksendeadresse muss validiert und auf zulässige Ziele begrenzt werden. Andernfalls kann sie zu Fehlrouting, Datenabfluss oder einer offenen Relay-Funktion führen.
Beteiligte Rollen
Requester: setzt eine erlaubte Reply-To-Adresse.
Request Envelope: transportiert Return Address und Correlation-ID.
Replier: sendet Antwort an die validierte Adresse.
Address Policy: prüft Namespace, Mandant und Lebensdauer.
Typischer Ablauf
Der Requester wählt einen registrierten Antwortkanal.
Die Adresse wird mit Request und Correlation-ID gesendet.
Der Replier validiert die Adresse gegen seine Policy.
Die Antwort wird an den angegebenen Kanal veröffentlicht.
Temporäre Kanäle werden nach Deadline bereinigt.
Wann sinnvoll?
mehrere Requester unterschiedliche Antwortkanäle benötigen
Replier nicht an einzelne Clients gekoppelt werden soll
Broker Reply-To nativ unterstützt
Wann nicht?
eine gemeinsame feste Antwortadresse ausreicht
beliebige externe Ziele ohne Whitelist erlaubt würden
Antworten personenbezogene Daten an kurzlebige ungeschützte Kanäle senden
Abgrenzung zu ähnlichen Mustern
Return Address bestimmt wohin eine Antwort geht. Correlation Identifier bestimmt zu welcher Anfrage sie gehört. Beide werden im Request-Reply-Muster meist gemeinsam verwendet.
Typische Fehlanwendungen
ungeprüfte dynamische Queue-Namen
temporäre Kanäle werden nicht gelöscht
Antwortadresse wird bei Weiterleitung verloren
Java- und Enterprise-Beispiel
Ein Partnerportal sendet Validierungsaufträge mit einer registrierten Mandanten-Reply-Queue. Der Validierungsdienst akzeptiert nur Adressen aus dem konfigurierten Namespace und übernimmt die Request-Correlation-ID.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Normalizer
Unterschiedliche Eingangsformate auf einen gemeinsamen internen Vertrag bringen
Integrationsmuster
Fachliche Einordnung
Ein Normalizer nimmt Nachrichten in mehreren zulässigen Quellformaten entgegen und überführt sie in ein einheitliches internes Modell. Nachgelagerte Verarbeitung muss dadurch nicht für jede Quelle eigene Varianten und Sonderfälle enthalten.
Der gemeinsame Vertrag sollte die eigene fachliche Sprache ausdrücken und nicht den kleinsten gemeinsamen Nenner aller Fremdsysteme. Quellspezifische Informationen werden nur übernommen, wenn sie fachlich benötigt oder als Erweiterungsmetadaten bewusst erhalten werden.
Beteiligte Rollen
Format Detector/Router: erkennt Quelle oder Version.
Source Translator: übersetzt ein konkretes Format.
Validation Policy: prüft Semantik und Pflichtinformationen.
Typischer Ablauf
Eingangstyp und Schema-Version werden erkannt.
Der passende Translator wird ausgewählt.
Codes, Einheiten und Strukturen werden normalisiert.
Der gemeinsame Vertrag wird vollständig validiert.
Nachgelagerte Schritte arbeiten ausschließlich mit dem Normalform-Modell.
Wann sinnvoll?
mehrere Partner oder Versionen denselben Fachprozess speisen
nachgelagerte Logik von Formatvarianten befreit werden soll
ein stabiler interner Vertrag existiert
Wann nicht?
ein zentrales kanonisches Modell alle Domänen zwangsweise vereinheitlicht
Quellbedeutung nicht verlustfrei verstanden wird
jede kleine Formatänderung den gesamten Normalizer betrifft
Abgrenzung zu ähnlichen Mustern
Message Translator übersetzt zwischen zwei konkreten Verträgen. Normalizer koordiniert mehrere Translatoren auf ein gemeinsames Zielformat. Eine Anti-Corruption Layer schützt zusätzlich das Domänenmodell und kann Normalisierung enthalten.
Typische Fehlanwendungen
kanonisches Modell wächst zu einem unternehmensweiten Universalobjekt
Quellinformationen werden still verworfen
Formatdetektion basiert auf unsicheren Heuristiken
Java- und Enterprise-Beispiel
Schadenmeldungen treffen als Partner-JSON, altes XML und CSV-Batch ein. Drei Translatoren erzeugen denselben ClaimIntake-Vertrag mit typisierten Geld-, Datums- und Codewerten. Der Fachprozess kennt die Quellformate nicht.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Vertiefungsband 4 Architektur, Persistenz und KonsistenzPattern 61–80
Patterns für Persistenz, Konsistenz, Lesemodelle und architektonische Schnittstellen.
Dieser Block vertieft Architektur-, Datenzugriffs- und ORM-Muster. Nur das Hauptkapitel ist klappbar; Einordnung, Rollen, Ablauf, Grenzen, Fehlanwendungen, Java-Bezug und Quellen bleiben dauerhaft sichtbar.
Fachlich vertieft: 80 zentrale Patterns20 neue Fachkapitel · deutschsprachige Quellen · projektbezogene Java- und Enterprise-Erklärung
Layered Architecture
Ein System durch gerichtete technische Schichten verständlich strukturieren
Architekturmuster
Fachliche Einordnung
Die Schichtenarchitektur ordnet Verantwortlichkeiten in Ebenen wie Präsentation, Anwendung, Domäne und Infrastruktur. Entscheidend ist nicht die Anzahl der Ordner, sondern eine nachvollziehbare Abhängigkeitsregel: Eine höhere Schicht nutzt definierte Leistungen tieferer Schichten, während technische Details nicht unkontrolliert nach oben durchsickern.
Das Muster bietet einen einfachen Einstieg und unterstützt klare Zuständigkeiten. Es wird problematisch, wenn jede fachliche Änderung alle Schichten durchqueren muss, Domänenlogik in Controller oder Persistenzklassen verteilt wird oder eine vermeintliche Serviceschicht nur Methoden weiterleitet.
Beteiligte Rollen
Präsentationsschicht: nimmt externe Eingaben an und übersetzt sie in Anwendungsaufrufe.
Anwendungsschicht: koordiniert Use Cases, Transaktionen und Ports.
Domänenschicht: enthält Fachregeln, Invarianten und Entscheidungen.
Infrastrukturschicht: implementiert Datenbank-, Messaging- und Fremdsystemzugriffe.
Typischer Ablauf
Ein Adapter übersetzt die externe Anfrage in einen typisierten Use-Case-Aufruf.
Die Anwendungsschicht koordiniert Domänenobjekte und notwendige Ports.
Die Domäne entscheidet ohne Kenntnis von HTTP, SQL oder Frameworks.
Infrastrukturadapter erfüllen die angeforderten technischen Leistungen.
Das Ergebnis wird an der Außengrenze in das benötigte Format übersetzt.
Wann sinnvoll?
ein verständliches Grundmodell für Verantwortlichkeiten benötigt wird
Team und System von klaren Abhängigkeitsregeln profitieren
technische und fachliche Belange sauber getrennt werden können
Wann nicht?
Schichten nur als Ordner bestehen, aber beliebig aufeinander zugreifen
jede Anfrage viele nutzlose Weiterleitungsklassen durchläuft
fachliche Module wichtiger wären als rein technische Gruppierung
Abgrenzung zu ähnlichen Mustern
Hexagonale, Onion- und Clean Architecture verschärfen die Richtung der Abhängigkeiten und stellen den Fachkern ins Zentrum. Eine klassische Schichtenarchitektur kann dagegen weiterhin zulassen, dass die Domäne von einer darunterliegenden Persistenzabstraktion abhängt.
Typische Fehlanwendungen
Controller enthalten Fachentscheidungen
Repository-Entities werden bis in die Benutzeroberfläche gereicht
Services bilden nur eine zusätzliche Weiterleitungsebene ohne eigene Verantwortung
Java- und Enterprise-Beispiel
Eine Auftragsplattform trennt REST-Adapter, Anwendungsfälle, Fachmodell und JPA-Adapter. Ein Architekturtest verhindert, dass Domänenpakete Spring- oder Jakarta-Persistence-Typen importieren.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Hexagonal Architecture
Den Fachkern über Ports von technischen Ein- und Ausgängen entkoppeln
Architekturmuster
Fachliche Einordnung
Die hexagonale Architektur, auch Ports-and-Adapters genannt, stellt die Anwendung in das Zentrum und beschreibt ihre Kommunikation mit der Außenwelt über Ports. Ein eingehender Port formuliert, was die Anwendung anbietet; ein ausgehender Port beschreibt, welche Leistung sie von Datenbank, Nachrichtensystem oder Fremddienst benötigt. Adapter übersetzen konkrete Technologien auf diese Verträge.
Das Sechseck ist nur eine Darstellungsform. Der eigentliche Wert liegt in der Umkehr technischer Abhängigkeiten: Der Kern bestimmt die Schnittstelle, nicht das Framework. Dadurch kann derselbe Use Case über REST, Batch oder Tests aufgerufen und dieselbe Persistenzanforderung durch JPA, JDBC oder einen In-Memory-Fake erfüllt werden.
Beteiligte Rollen
Application Core: enthält Use Cases und Fachmodell.
Adapter: übersetzt Protokolle und Datenmodelle zwischen Port und Technik.
Typischer Ablauf
Ein eingehender Adapter nimmt eine Anfrage entgegen.
Er übersetzt sie in den Vertrag eines Driving Ports.
Der Kern führt den Use Case aus und verwendet bei Bedarf Driven Ports.
Ausgehende Adapter sprechen konkrete Infrastruktur an.
Antworten werden an den Grenzen zurückübersetzt.
Wann sinnvoll?
Framework- und Infrastrukturwechsel realistisch sind
Use Cases ohne Container oder Datenbank testbar sein sollen
mehrere Ein- oder Ausgangsadapter dieselbe Anwendung nutzen
Wann nicht?
Ports bloß technische CRUD-Methoden spiegeln
für jede private Hilfsmethode ein Interface erzeugt wird
der Fachkern trotz Ports Frameworkannotation und Persistenzmodell kennt
Abgrenzung zu ähnlichen Mustern
Clean und Onion Architecture verwenden eine ähnliche Abhängigkeitsrichtung, unterscheiden aber zusätzliche innere Ringe. Layered Architecture ordnet primär Ebenen; Hexagonal Architecture betont symmetrische Ein- und Ausgangsgrenzen.
Typische Fehlanwendungen
ein universeller Service-Port für das gesamte System
Adapter enthalten Fachlogik
Domänentypen werden durch Transport-DTOs ersetzt
Java- und Enterprise-Beispiel
Ein Zahlungs-Use-Case besitzt einen PaymentUseCase-Port und ausgehende FraudCheck-, PaymentProvider- und PaymentRepository-Ports. REST, Kafka und Tests nutzen denselben Kern über unterschiedliche Adapter.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Clean Architecture
Abhängigkeiten konsequent auf stabile Geschäftsregeln ausrichten
Architekturmuster
Fachliche Einordnung
Clean Architecture ordnet das System in konzentrische Verantwortungsbereiche. Entitäten und Geschäftsregeln liegen innen, Anwendungsfälle darum, Schnittstellenadapter weiter außen und Frameworks sowie Infrastruktur am Rand. Die zentrale Dependency Rule verlangt, dass Quellcodeabhängigkeiten nur nach innen zeigen.
Der Ansatz schützt langfristig wertvolle Regeln vor kurzlebigen technischen Entscheidungen. Er ist jedoch kein Aufruf, jede Anwendung mit maximal vielen Ringen und DTOs zu versehen. Die Zahl der Grenzen muss zur fachlichen und organisatorischen Komplexität passen.
Beteiligte Rollen
Entities: unternehmensweite oder domänenspezifische Kernregeln.
Use Cases: anwendungsspezifische Abläufe und Entscheidungen.
Interface Adapters: Presenter, Controller, Gateways und Mapper.
Frameworks/Drivers: Web, Datenbank, Messaging und Laufzeitumgebung.
Typischer Ablauf
Ein äußerer Adapter erstellt ein Input Model.
Ein Use Case verarbeitet es und arbeitet mit Entitäten.
Benötigte Infrastruktur wird über innere Schnittstellen angefordert.
Adapter implementieren diese Schnittstellen außen.
Ein Output Boundary oder Presenter erzeugt die Antwortdarstellung.
Wann sinnvoll?
Geschäftsregeln deutlich langlebiger als Frameworks sind
mehrere Technologien dieselben Use Cases bedienen
Architekturgrenzen automatisiert geprüft werden können
Wann nicht?
eine kleine CRUD-Anwendung durch viele Modelle und Mapper unnötig aufgebläht wird
innere Schichten nur Frameworktypen umbenennen
jede Änderung mechanisch durch identische DTOs kopiert werden muss
Abgrenzung zu ähnlichen Mustern
Clean Architecture fasst Ideen aus Hexagonal und Onion Architecture zusammen und benennt zusätzliche Rollen für Use Cases und Interface Adapter. Der konkrete Paketaufbau ist weniger wichtig als die nach innen gerichtete Abhängigkeit.
Typische Fehlanwendungen
Use Cases werden zu bloßen Repository-Weiterleitungen
Domain Entities und Datenbankentities sind unbemerkt identisch
Dependency Injection wird mit Dependency Inversion verwechselt
Java- und Enterprise-Beispiel
Die Kreditentscheidung liegt in frameworkfreien Use Cases und Entities. Spring Boot oder Jakarta EE konfigurieren nur Adapter und Composition Root; ArchUnit prüft die Importregeln.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Onion Architecture
Domäne und Anwendungsregeln als inneren Kern durch konzentrische Ringe schützen
Architekturmuster
Fachliche Einordnung
Onion Architecture strukturiert eine Anwendung in konzentrischen Ringen. Im Zentrum liegt das Domänenmodell, darum gruppieren sich Domain Services und Application Services; Infrastruktur und Benutzeroberfläche bleiben außen. Abhängigkeiten zeigen zum inneren Kern, während konkrete Technik über Interfaces angeschlossen wird.
Der Ansatz eignet sich für fachlich reichhaltige Systeme, in denen das Domänenmodell nicht durch Persistenz oder Transport bestimmt werden soll. Er verliert seinen Nutzen, wenn die inneren Ringe nur Datencontainer enthalten und alle Regeln weiterhin außen liegen.
Beteiligte Rollen
Domain Model: Entities, Value Objects und Invarianten.
Domain Services: fachliche Operationen ohne natürliche Objektzuordnung.
Application Services: Use-Case-Koordination und Transaktionsrahmen.
Infrastructure/UI: technische Adapter und Frameworkintegration.
Typischer Ablauf
Ein äußerer Adapter ruft einen Application Service auf.
Dieser koordiniert Domänenobjekte und Domain Services.
Benötigte externe Leistungen werden über innere Interfaces beschrieben.
Äußere Adapter implementieren diese Verträge.
Die Domäne bleibt unabhängig von Datenbank und UI.
Wann sinnvoll?
ein verhaltensreiches Domänenmodell vorhanden ist
technische Abhängigkeiten aus dem Fachkern entfernt werden sollen
langfristige Wartbarkeit wichtiger als minimale Klassenanzahl ist
Wann nicht?
das System fast ausschließlich einfache Datentransformation ausführt
Domänenobjekte nur Getter und Setter besitzen
Ringe ohne klare Verantwortungsunterschiede dupliziert werden
Abgrenzung zu ähnlichen Mustern
Onion Architecture betont Domänenringe, Clean Architecture benennt stärker Use Cases und Interface Adapter. Hexagonal Architecture beschreibt dieselbe Grundrichtung über Ports und Adapter statt über konzentrische Schichten.
Typische Fehlanwendungen
Infrastrukturinterfaces werden außen statt innen definiert
Application Services enthalten die gesamte Fachlogik
Mapping wird zum Selbstzweck ohne echte Grenztrennung
Java- und Enterprise-Beispiel
Im Schadenmanagement besitzt der Kern Claim, Coverage und Money. Application Services koordinieren Prüfung und Auszahlung; JPA, REST und Messaging bleiben in äußeren Adaptern.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Modular Monolith
Ein deploybares System durch fachlich harte interne Module strukturieren
Architekturmuster
Fachliche Einordnung
Ein modularer Monolith wird als eine Einheit gebaut und ausgeliefert, ist intern aber in fachlich eigenständige Module mit klaren öffentlichen Schnittstellen gegliedert. Module besitzen ihre Modelle und Regeln selbst; direkte Zugriffe auf interne Pakete oder Tabellen anderer Module sind verboten oder technisch kontrolliert.
Das Muster verbindet geringe Betriebs- und Transaktionskomplexität mit bewusster Modularität. Es ist nicht einfach ein großer Monolith mit Paketnamen: Grenzen müssen durch Buildmodule, Sichtbarkeit, Architekturtests, Ereignisse und Datenbesitz tatsächlich durchgesetzt werden.
Beteiligte Rollen
Fachmodule: besitzen Use Cases, Domänenmodell und Datenverantwortung.
Public API: schmale synchrone oder ereignisbasierte Moduloberfläche.
Composition Root: verbindet Module zur gemeinsamen Laufzeit.
fachliche Grenzen vorhanden sind, aber unabhängiges Deployment keinen ausreichenden Nutzen bringt
starke Konsistenz und einfache lokale Entwicklung wichtig sind
eine spätere Extraktion einzelner Module möglich bleiben soll
Wann nicht?
Module nur technische Layer statt Fachfähigkeiten darstellen
jede Datenbanktabelle global verwendet wird
Teams zwingend unabhängige Skalierung und Releases benötigen
Abgrenzung zu ähnlichen Mustern
Microservices trennen zusätzlich Laufzeit, Deployment und häufig Datenbanken. Ein modularer Monolith behält Prozess- und Deploymentgrenze gemeinsam und reduziert damit Netzwerk- und Betriebsaufwand.
Typische Fehlanwendungen
Shared-Kernel wird zur Ablage beliebiger Fachtypen
öffentliche APIs geben interne Entities zurück
Modulgrenzen werden nur dokumentiert, aber nicht geprüft
Java- und Enterprise-Beispiel
Order, Payment und Shipping sind Maven-Module mit eigener API und eigenen Tabellenpräfixen. Spring Modulith oder ArchUnit prüfen Abhängigkeiten; ein einzelnes Artefakt wird ausgeliefert.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Microkernel
Einen stabilen Kern durch austauschbare Plugins erweitern
Architekturmuster
Fachliche Einordnung
Die Microkernel- oder Plugin-Architektur reduziert das Kernsystem auf stabile Grundfunktionen und Erweiterungspunkte. Zusätzliche Fähigkeiten werden als Plugins implementiert, die definierte Service Provider Interfaces erfüllen und vom Kern entdeckt, validiert und ausgeführt werden.
Der Kern muss klein und langfristig stabil bleiben. Wenn jedes Plugin intern auf Kernimplementierungen zugreift oder sich Plugins gegenseitig kennen, entsteht statt Erweiterbarkeit ein schwer kontrollierbares Geflecht. Versionierung und Isolation sind daher zentrale Teile des Musters.
Beteiligte Rollen
Core System: minimale stabile Funktionen und Lebenszyklus.
Plugin: implementiert optionale oder kundenspezifische Fähigkeit.
Registry/Loader: entdeckt, prüft und aktiviert Erweiterungen.
Typischer Ablauf
Der Kern startet und lädt seine Basiskonfiguration.
Ein Loader entdeckt verfügbare Plugins.
Kompatibilität und Berechtigungen werden geprüft.
Plugins registrieren ihre Fähigkeiten über das SPI.
Der Kern delegiert passende Aufträge an ausgewählte Plugins.
Wann sinnvoll?
Produktvarianten und kundenspezifische Erweiterungen häufig sind
ein stabiler Kern unabhängig von Erweiterungen bleiben soll
Plugins separat entwickelt oder ausgeliefert werden
Wann nicht?
alle Funktionen eng denselben veränderlichen Zustand teilen
Pluginverträge nicht versionierbar sind
für wenige feste Varianten eine einfache Strategy genügt
Abgrenzung zu ähnlichen Mustern
Strategy wählt zwischen bekannten Verhaltensvarianten innerhalb der Anwendung. Microkernel definiert eine ganze Erweiterungsplattform mit Discovery, Versionierung und Lebenszyklus.
Typische Fehlanwendungen
zu viel Fachlogik im Kernel
Plugins umgehen das SPI über interne Klassen
keine Kompatibilitäts- oder Sicherheitsprüfung beim Laden
Java- und Enterprise-Beispiel
Eine Dokumentplattform besitzt einen Kern für Auftrags- und Auditverwaltung. PDF-, Signatur- und Archivprovider werden als versionierte Java-ServiceLoader-Plugins eingebunden.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Service Layer
Anwendungsfälle hinter einer klaren fachlichen Oberfläche bündeln
Enterprise Application Pattern
Fachliche Einordnung
Eine Service Layer definiert die Grenze einer Anwendung durch Operationen, die für Benutzeroberflächen, Integrationen oder Batchprozesse verfügbar sind. Sie koordiniert Transaktionen, Autorisierung, Domänenobjekte und technische Ports, ohne selbst zum Ersatz für das Domänenmodell zu werden.
Die Schicht ist besonders wertvoll, wenn mehrere Adapter dieselben Use Cases nutzen. Sie wird zum Problem, wenn sie nur CRUD-Methoden erzeugt, Entities unverändert nach außen reicht oder sämtliche Fachlogik in große Serviceklassen zieht.
Beteiligte Rollen
Application Service: repräsentiert einen Use Case.
Input/Output Models: stabile Verträge an der Anwendungsgrenze.
Domain Model: führt Fachentscheidungen aus.
Ports: kapseln benötigte Infrastruktur.
Typischer Ablauf
Ein Adapter ruft eine benannte Anwendungsoperation auf.
Der Service prüft anwendungsweite Vorbedingungen und startet den Transaktionsrahmen.
Domänenobjekte treffen fachliche Entscheidungen.
Ports speichern Ergebnisse oder veröffentlichen Ereignisse.
Ein typisiertes Ergebnis verlässt die Service Layer.
Wann sinnvoll?
mehrere Eingangsadapter dieselben Anwendungsfälle benötigen
Transaktions- und Autorisierungsgrenzen explizit sein sollen
Use Cases als stabile API dokumentiert werden
Wann nicht?
jeder Service nur ein Repository durchreicht
Domänenregeln aus Entities in Services verschoben werden
eine globale Serviceklasse alle Fachbereiche vereint
Abgrenzung zu ähnlichen Mustern
Domain Services enthalten fachliche Logik ohne natürliche Entity-Zuordnung. Application/Service Layer koordiniert einen Use Case. Eine Facade vereinfacht primär die Nutzung eines Subsystems.
Typische Fehlanwendungen
anämisches Domänenmodell
ungeklärte Transaktionsgrenzen
technische DTOs bestimmen die Fachsprache
Java- und Enterprise-Beispiel
CreateClaimService lädt Vertrag und Deckung, lässt Claim und Coverage entscheiden, speichert über Ports und gibt eine ClaimDecision zurück. REST und Kafka verwenden denselben Service.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Active Record
Datenzeile und Persistenzoperationen in einem einfachen Objekt verbinden
Persistenzmuster
Fachliche Einordnung
Active Record repräsentiert einen Datensatz als Objekt, das neben Feldern auch Operationen wie speichern, aktualisieren oder löschen anbietet. Das Muster passt gut zu einfachen datenorientierten Modellen, weil Objekt und Tabellenzeile eng zusammengehören und wenig zusätzliche Abbildungsschicht benötigt wird.
Bei komplexen Domänen führt die Verbindung von Fachlogik und Persistenz schnell zu schwer testbaren Objekten. Transaktionen, Beziehungen und Queries können technische Details in das gesamte Modell tragen. Dann sind Data Mapper oder Repository häufig geeigneter.
Beteiligte Rollen
Active Record: enthält Daten und Persistenzmethoden.
Table Mapping: ordnet Felder den Spalten zu.
Database Session: führt Operationen aus.
Client: verwendet Objekt und dessen Persistenzverhalten.
Typischer Ablauf
Ein Datensatz wird als Active-Record-Objekt geladen.
Der Client ändert Felder oder ruft einfache Fachmethoden auf.
save oder delete übersetzt den Zustand in SQL.
Nach erfolgreichem Commit repräsentiert das Objekt den aktuellen Datensatz.
Wann sinnvoll?
das Datenmodell einfach und tabellennah ist
CRUD den Großteil der Anwendung ausmacht
schnelle Entwicklung wichtiger als vollständige Persistenzunabhängigkeit ist
Wann nicht?
reichhaltige Aggregate und komplexe Invarianten vorhanden sind
Domänenobjekte ohne Datenbank getestet werden müssen
mehrere Datenquellen oder Mappingmodelle nötig sind
Abgrenzung zu ähnlichen Mustern
Row Data Gateway enthält Datenzugriff für eine Zeile, aber typischerweise keine Fachlogik. Data Mapper hält Persistenz vollständig außerhalb der Domänenobjekte.
Typische Fehlanwendungen
Active Records werden zu global verfügbaren Transaktionsskripten
Relationen lösen unkontrollierte Lazy Loads aus
Geschäftsregeln hängen von einer aktiven Session ab
Java- und Enterprise-Beispiel
Eine kleine Administrationsanwendung verwaltet Referenztabellen über Active Records. Für Aufträge und Zahlungen mit komplexen Regeln verwendet dieselbe Plattform dagegen ein getrenntes Domänenmodell.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Data Mapper
Domänenobjekte und Persistenzmodell unabhängig voneinander abbilden
Persistenzmuster
Fachliche Einordnung
Ein Data Mapper überträgt Daten zwischen Domänenobjekten und einer Datenquelle, ohne dass die Domänenobjekte Persistenzoperationen kennen. Er rekonstruiert Aggregate aus Zeilen oder Dokumenten und übersetzt Änderungen wieder in das Speichermodell.
Die Entkopplung erlaubt ein fachlich ausdrucksstarkes Modell, kostet aber Mappinglogik und klare Identitäts- sowie Transaktionsregeln. Ein universeller Reflexionsmapper kann technische Arbeit reduzieren, darf jedoch Invarianten und Aggregatgrenzen nicht umgehen.
Beteiligte Rollen
Domain Object: enthält Fachzustand und Verhalten.
Data Mapper: übersetzt zwischen Domänen- und Speichermodell.
Persistence Model/Record: repräsentiert Tabellen oder Dokumente.
Unit of Work/Repository: koordiniert Laden und Speichern.
Typischer Ablauf
Ein Repository fordert ein Aggregate an.
Der Mapper liest Datensätze und rekonstruiert Value Objects und Entities.
Die Domäne arbeitet ohne Persistenzkenntnis.
Beim Speichern extrahiert der Mapper den relevanten Zustand.
Unit of Work koordiniert die Änderungen transaktional.
Wann sinnvoll?
Domänenmodell und Datenbankschema unterschiedliche Strukturen besitzen
Fachobjekte unabhängig von ORM und SQL bleiben sollen
komplexe Aggregate und Value Objects abgebildet werden
Wann nicht?
eine einfache CRUD-Tabelle keinen eigenen Domänenwert besitzt
Mappingcode mehr Komplexität als Nutzen erzeugt
Mapper Invarianten durch reflektives Setzen umgeht
Abgrenzung zu ähnlichen Mustern
Active Record verbindet Objekt und Persistenz. Data Mapper trennt beides. Repository bietet eine fachnahe Sammlungsoberfläche und verwendet intern häufig Mapper.
Typische Fehlanwendungen
bidirektionales Mapping verliert Informationen
Mapper lädt unbegrenzt komplette Objektgraphen
Domänenkonstruktoren werden umgangen und ungültige Objekte erzeugt
Java- und Enterprise-Beispiel
ClaimMapper baut aus ClaimRow, DocumentRow und Money-Spalten ein gültiges Claim-Aggregate. Speichern erfolgt über explizite Mappermethoden innerhalb einer Unit of Work.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Identity Map
Innerhalb eines Kontexts pro Datenidentität genau eine Objektinstanz verwenden
Persistenzmuster
Fachliche Einordnung
Eine Identity Map merkt sich bereits geladene Objekte anhand ihrer stabilen Identität. Wird dieselbe Datenzeile erneut angefordert, liefert sie dieselbe Objektinstanz statt eines zweiten, möglicherweise widersprüchlichen Exemplars.
Der Scope ist entscheidend: Typischerweise lebt die Map pro Transaktion, Request oder Session. Eine globale Map würde zu veralteten Daten, Speicherwachstum und schwer erklärbaren Nebenwirkungen führen.
Beteiligte Rollen
Identity Key: Typ und Primärschlüssel eines Objekts.
Identity Map: speichert Instanzen im aktuellen Kontext.
Mapper/Repository: prüft die Map vor einem Datenbankzugriff.
Unit of Work: nutzt dieselben Instanzen für Änderungsverfolgung.
Typischer Ablauf
Ein Repository erhält eine ID.
Es prüft die Identity Map des aktuellen Kontexts.
Bei Treffer wird dieselbe Instanz zurückgegeben.
Bei Fehlen wird geladen und registriert.
Am Ende des Scopes wird die Map verworfen.
Wann sinnvoll?
dieselbe Entität innerhalb eines Use Cases mehrfach erreichbar ist
Objektidentität und konsistente Änderungen wichtig sind
eine Unit of Work Änderungen verfolgt
Wann nicht?
langfristige globale Caches gesucht werden
Objekte unveränderliche Value Objects ohne Identität sind
Scope und Invalidierung nicht klar definiert sind
Abgrenzung zu ähnlichen Mustern
Ein Cache optimiert Wiederverwendung über längere Zeit und mehrere Anfragen. Identity Map garantiert Objektidentität innerhalb eines kurzen Arbeitskontexts.
Typische Fehlanwendungen
globale statische Map
Mischung unterschiedlicher Mandanten im selben Schlüsselraum
Detached Objects werden unkontrolliert erneut registriert
Java- und Enterprise-Beispiel
Eine JPA-Persistence-Context-Session stellt sicher, dass Contract 4711 innerhalb der Transaktion als dieselbe Entity-Instanz erscheint. Außerhalb wird der Kontext beendet.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Lazy Load
Teure oder selten benötigte Daten erst bei tatsächlichem Bedarf laden
Persistenzmuster
Fachliche Einordnung
Lazy Load verschiebt das Laden eines Wertes oder einer Beziehung bis zum ersten Zugriff. Dadurch werden Objektgraphen nicht pauschal vollständig aufgebaut, wenn ein Use Case nur einen kleinen Ausschnitt benötigt.
Das Muster spart nur dann Arbeit, wenn Zugriffe bewusst geplant werden. Verdeckte Datenbankabfragen in Schleifen, Zugriffe nach geschlossener Session oder unvorhersehbare Serialisierung führen zu N+1-Problemen und Laufzeitfehlern.
Beteiligte Rollen
Lazy Holder/Proxy: erkennt den ersten Zugriff.
Loader: beschafft den fehlenden Zustand.
Session/Context: stellt Zugriff und Identität sicher.
Fetch Plan: entscheidet use-case-bezogen, was vorab geladen wird.
Typischer Ablauf
Ein Objekt wird mit ungeladenem Teilzustand erzeugt.
Der erste Zugriff prüft den Ladezustand.
Der Loader beschafft die Daten genau einmal.
Der Wert wird im Objekt oder Kontext gespeichert.
Weitere Zugriffe verwenden den geladenen Zustand.
Wann sinnvoll?
Daten groß oder selten benötigt sind
Use Cases deutlich unterschiedliche Sichten benötigen
Ladezeitpunkt und Sessiongrenze kontrolliert werden
Wann nicht?
jeder Aufruf dieselben Daten ohnehin benötigt
Serialisierung unbemerkt den gesamten Graphen traversiert
Ladevorgänge in fachlichen Methoden verborgen bleiben
Abgrenzung zu ähnlichen Mustern
Eager Loading lädt vorab. Query-spezifische Projektionen vermeiden Objektgraphen vollständig. Lazy Load ist eine Zustandsstrategie innerhalb eines Objekts oder ORM-Kontexts.
Typische Fehlanwendungen
N+1-Abfragen
LazyInitializationException außerhalb der Session
unbegrenzte Graphnavigation durch JSON-Serializer
Java- und Enterprise-Beispiel
Eine Vertragsliste lädt nur ID, Status und Kunde. Dokumenthistorie wird über einen expliziten Detail-Use-Case geladen statt beim Rendern jeder Zeile unbemerkt nachzuladen.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Query Object
Komplexe Suchkriterien als benanntes, kombinierbares Objekt ausdrücken
Persistenz- und Abfragemuster
Fachliche Einordnung
Ein Query Object kapselt Suchkriterien, Sortierung, Projektion und gegebenenfalls Pagination in einem typisierten Objekt. Dadurch werden dynamisch zusammengesetzte Stringabfragen oder immer längere Repositorymethoden durch eine explizite Abfrageabsicht ersetzt.
Das Objekt sollte Fachsprache ausdrücken und nicht lediglich SQL-Fragmente transportieren. Übersetzung in SQL, Criteria API oder einen externen Suchdienst bleibt Aufgabe eines Adapters.
Query Handler/Translator: übersetzt in konkrete Datenquellenabfrage.
Result Projection: definiert benötigte Ergebnisform.
Repository/Gateway: führt die übersetzte Abfrage aus.
Typischer Ablauf
Der Client erstellt ein Query Object aus validierten Kriterien.
Ein Handler wählt Datenquelle und Projektion.
Ein Translator erzeugt Parameter und technische Query.
Die Datenquelle führt paginiert aus.
Ergebnisse werden in fachliche oder lesespezifische Typen abgebildet.
Wann sinnvoll?
viele optionale Kriterien kombinierbar sind
Suchabsichten wiederverwendet und getestet werden sollen
mehrere Datenquellen denselben fachlichen Filter unterstützen
Wann nicht?
eine einzelne feste Repositorymethode völlig ausreicht
das Query Object beliebige SQL-Strings akzeptiert
Schreiboperationen versteckt integriert werden
Abgrenzung zu ähnlichen Mustern
Specification beschreibt häufig eine fachliche Ja/Nein-Regel und kann teilweise in Queries übersetzt werden. Query Object modelliert gezielt eine Abfrage einschließlich Sortierung, Projektion und Pagination.
Typische Fehlanwendungen
technische Spaltennamen in der öffentlichen API
unbegrenzte Resultsets
ein Mega-Query-Objekt für alle Domänen
Java- und Enterprise-Beispiel
ClaimSearch enthält Statusmenge, Zeitraum, Risikoobergrenze, Cursor und Sortierung. Ein JPA-Adapter übersetzt es in Criteria API; ein Elasticsearch-Adapter in eine Suchanfrage.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Table Data Gateway
Alle Zugriffe auf eine Tabelle oder Sicht hinter einem Gateway bündeln
Persistenzmuster
Fachliche Einordnung
Ein Table Data Gateway stellt Operationen für alle Zeilen einer Tabelle oder Datenbanksicht bereit. Eine Gateway-Instanz kapselt SQL, Parameterbindung und Ergebnisabbildung für diese tabellennahe Datenquelle.
Das Muster ist nützlich für einfache Datenzugriffsschichten, Reporting oder Transaktionsskripte. Bei komplexen Domänen darf das Gateway nicht als fachliches Repository ausgegeben werden, wenn seine Methoden weiterhin Tabellen- und Spaltenlogik widerspiegeln.
Beteiligte Rollen
Gateway: enthält Select-, Insert-, Update- und Delete-Operationen.
Row/DTO: repräsentiert tabellennahe Daten.
Connection/Transaction: stellt technischen Ausführungskontext bereit.
Client: verwendet das Gateway ohne SQL-Details.
Typischer Ablauf
Der Client ruft eine benannte Gatewayoperation auf.
Das Gateway erzeugt parametriertes SQL.
Die Datenbank liefert Zeilen.
Das Gateway bildet sie in Records oder DTOs ab.
Transaktionsgrenzen bleiben beim aufrufenden Service oder Unit of Work.
Wann sinnvoll?
Datenzugriff tabellennah und überschaubar ist
SQL bewusst und zentral kontrolliert werden soll
Reporting- oder Integrationssichten verwendet werden
Wann nicht?
Aggregate mehrere Tabellen und Invarianten kapseln
Gatewaymethoden ungeordnet alle möglichen Joins aufnehmen
Fachcode direkt Tabellenstrukturen kennen muss
Abgrenzung zu ähnlichen Mustern
Row Data Gateway repräsentiert eine einzelne Zeile pro Objekt. Table Data Gateway verwaltet alle Zeilen einer Tabelle. Repository spricht dagegen in Begriffen von Aggregaten und Fachsprache.
Typische Fehlanwendungen
ein Gateway pro gesamter Datenbank
Transaktionslogik im Gateway
SQL-Resultsets werden untypisiert als Maps weitergereicht
Java- und Enterprise-Beispiel
InvoiceTableGateway bietet findOpenBefore, insert und markPaid. Ein ReconciliationService verwendet diese tabellennahen Operationen innerhalb einer klaren Transaktion.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Row Data Gateway
Eine einzelne Datenzeile als Objekt mit Datenzugriffsoperationen darstellen
Persistenzmuster
Fachliche Einordnung
Beim Row Data Gateway entspricht jede Gatewayinstanz einer Zeile. Das Objekt trägt die Spaltenwerte und kann seine Zeile aktualisieren oder löschen; Finder erzeugen passende Instanzen.
Anders als Active Record sollte ein Row Data Gateway keine umfangreiche Fachlogik enthalten. Es bleibt eine datenquellennahe Repräsentation und eignet sich vor allem für einfache Skript- oder Gatewayarchitekturen.
Beteiligte Rollen
Row Gateway: repräsentiert eine Zeile und deren Persistenzoperationen.
Finder: findet Zeilen und erzeugt Gateways.
Connection/Session: führt SQL aus.
Domain/Service Client: nutzt das Gateway als Datenzugriffsobjekt.
Typischer Ablauf
Ein Finder lädt eine Zeile.
Er erzeugt ein Gateway mit Spaltenwerten und Identität.
Der Client ändert Werte oder fordert delete an.
Das Gateway führt ein parametriertes Update aus.
Nach Commit entspricht es dem gespeicherten Datensatz.
Wann sinnvoll?
Zeilenstruktur stabil und Fachlogik gering ist
ein objektorientierter Zugriff auf tabellennahe Daten gewünscht wird
Transaktionsskripte mit einzelnen Datensätzen arbeiten
Wann nicht?
Objekt als reichhaltige Entity missverstanden wird
komplexe Aggregate mehrere Zeilen koordinieren
Gatewayinstanzen außerhalb ihres Transaktionskontexts leben
Abgrenzung zu ähnlichen Mustern
Active Record kombiniert Persistenz und häufig einfache Fachlogik. Row Data Gateway bleibt auf Datenzugriff fokussiert. Table Data Gateway besitzt eine Instanz für die gesamte Tabelle.
Typische Fehlanwendungen
Finderlogik über viele Klassen verstreut
optimistisches Locking fehlt
Zeilenobjekte werden als öffentliche API verwendet
Java- und Enterprise-Beispiel
Ein Importprozess verwendet CustomerRowGateway für Staging-Zeilen. Nach Validierung übersetzt ein Mapper die Daten in ein echtes Customer-Aggregate.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Metadata Mapping
Abbildungsregeln deklarativ statt in jedem Mapper fest verdrahten
Persistenzmuster
Fachliche Einordnung
Metadata Mapping beschreibt die Zuordnung zwischen Klassen, Feldern, Beziehungen und Datenbankstrukturen durch Metadaten wie Annotationen, XML oder ein Mappingmodell. Eine generische Laufzeit liest diese Angaben und führt Laden sowie Speichern aus.
Das Muster reduziert wiederholten Mappingcode, verschiebt Fehler aber in Konfiguration und Laufzeit. Metadaten müssen versioniert, validiert und in Tests gegen Schema und Domänenmodell geprüft werden.
Beteiligte Rollen
Mapping Metadata: beschreibt Klassen, Spalten, Schlüssel und Beziehungen.
Mapping Engine: interpretiert die Metadaten.
Domain/Persistence Types: werden verbunden.
Schema Validator: prüft Konsistenz zur Datenbank.
Typischer Ablauf
Beim Start werden Mappinginformationen geladen.
Die Engine baut ein Metamodell auf.
Queries und Schreiboperationen verwenden dieses Modell.
Objektzustand wird reflektiv oder generiert übertragen.
Validierung erkennt Abweichungen zwischen Mapping und Schema.
Wann sinnvoll?
viele ähnliche Klassen abgebildet werden
Mappingregeln zentral oder deklarativ pflegbar sein sollen
ein ORM-Framework bewusst eingesetzt wird
Wann nicht?
komplexe Transformationen nicht deklarativ ausdrückbar sind
Metadaten ungetestet direkt in Produktion interpretiert werden
fachliche Invarianten durch generisches Setzen umgangen werden
Abgrenzung zu ähnlichen Mustern
Data Mapper kann handgeschrieben oder metadatengetrieben sein. Metadata Mapping beschreibt den Mechanismus, mit dem eine generische Mapping Engine viele Typen verarbeitet.
Typische Fehlanwendungen
Annotationen vermischen Fachmodell und technische Details unkontrolliert
Mappingfehler werden erst spät erkannt
Reflexion verdeckt teure Ladevorgänge
Java- und Enterprise-Beispiel
Hibernate liest JPA-Annotationen für Entity, Embedded Value und Vererbung. Ein Starttest validiert das Schema; Domänenkonstruktoren schützen weiterhin Invarianten.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Embedded Value
Ein fachliches Value Object in mehrere Spalten derselben Tabelle abbilden
Persistenzmuster
Fachliche Einordnung
Embedded Value bildet ein Value Object ohne eigene Identität in die Tabelle seines Besitzers ein. Mehrere Spalten gehören logisch zu einem Fachwert wie Adresse, Geldbetrag oder Zeitraum, obwohl sie relational nebeneinander gespeichert werden.
Die Einbettung sollte die fachliche Kapselung erhalten. Werden dieselben Spaltengruppen überall einzeln als Strings oder Zahlen behandelt, geht der Nutzen des Value Objects verloren. Sehr große oder unabhängig lebende Daten gehören dagegen eher in eine eigene Entity oder Tabelle.
Beteiligte Rollen
Owner Entity: besitzt den eingebetteten Wert.
Value Object: validiert und kapselt zusammengehörige Attribute.
Mapping Metadata: ordnet Attribute den Spalten zu.
Mapper/ORM: rekonstruiert den Wert atomar.
Typischer Ablauf
Die Entity wird aus einer Tabellenzeile gelesen.
Mehrere Spalten werden an den Value-Object-Konstruktor übergeben.
Der Konstruktor prüft Invarianten.
Änderungen ersetzen das unveränderliche Value Object vollständig.
Beim Speichern werden dessen Komponenten in die Spalten geschrieben.
Wann sinnvoll?
Werte keine eigene Identität oder Lebensdauer besitzen
mehrere Spalten eine fachliche Einheit bilden
Value Objects unveränderlich modelliert werden
Wann nicht?
das Objekt unabhängig referenziert oder geteilt werden muss
sehr große Daten nur selten geladen werden
partielle Nullzustände fachlich unklar sind
Abgrenzung zu ähnlichen Mustern
Entity besitzt Identität und eigenen Lebenszyklus. Embedded Value gehört vollständig seinem Owner. Serialized LOB speichert den gesamten Wert als einzelnes serialisiertes Feld statt strukturiert in Spalten.
Value Object enthält Persistenzannotation und Fachlogik untrennbar
Java- und Enterprise-Beispiel
Money wird als amount und currency in der Payment-Tabelle gespeichert. Beim Laden entsteht nur über Money.of ein gültiger Wert; Änderungen ersetzen das komplette Money-Objekt.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Serialized LOB
Komplexen, selten abgefragten Zustand als zusammenhängenden großen Wert speichern
Persistenzmuster
Fachliche Einordnung
Serialized LOB speichert einen komplexen Objektzustand als einzelnes Large Object, beispielsweise JSON, XML oder Binärdaten. Die Datenbank behandelt den Inhalt weitgehend undurchsichtig; die Anwendung serialisiert und rekonstruiert ihn als Ganzes.
Das Muster ist nur sinnvoll, wenn der Inhalt nicht relational gesucht, verbunden oder partiell aktualisiert werden muss. Versionierung, Größenbegrenzung und sichere Deserialisierung sind zwingend, da das Format langfristig persistiert bleibt.
Beteiligte Rollen
Serialized Value: versioniertes Format des Objektzustands.
Serializer/Deserializer: kontrolliert Umwandlung und Sicherheit.
LOB Column/Store: speichert den Gesamtwert.
Migration Policy: überführt alte Versionen.
Typischer Ablauf
Die Anwendung erzeugt einen versionierten Snapshot.
Ein Serializer schreibt JSON, XML oder Binärdaten.
Der LOB wird atomar gespeichert.
Beim Laden wird Format und Version geprüft.
Ein Upcaster oder Migrator überführt ältere Repräsentationen.
Wann sinnvoll?
Inhalt selten strukturell abgefragt wird
Objektzustand als Ganzes gelesen und geschrieben wird
Schemaflexibilität wichtiger als relationale Auswertbarkeit ist
Wann nicht?
einzelne Felder indiziert oder gefiltert werden müssen
unkontrollierte native Java-Serialisierung verwendet wird
sehr große Werte häufig aktualisiert werden
Abgrenzung zu ähnlichen Mustern
Embedded Value verteilt strukturierte Attribute auf Spalten. Serialized LOB behandelt den Zustand als opaken Gesamtwert. Event Sourcing speichert dagegen fachliche Änderungen als Ereignisfolge.
Typische Fehlanwendungen
keine Formatversion
unsichere Deserialisierung beliebiger Klassen
LOB wird bei jeder kleinen Änderung vollständig neu geschrieben
Java- und Enterprise-Beispiel
Eine selten gelesene externe Partnerkonfiguration wird als versioniertes JSON gespeichert. Suchrelevante Felder wie Partner-ID und Status bleiben separat indexiert.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Single Table Inheritance
Eine gesamte Vererbungshierarchie in einer gemeinsamen Tabelle speichern
ORM-Vererbungsmuster
Fachliche Einordnung
Single Table Inheritance speichert alle konkreten Subtypen einer Hierarchie in einer Tabelle. Eine Diskriminatorspalte kennzeichnet den Typ; gemeinsame und subtypeigene Felder liegen nebeneinander. Polymorphe Abfragen sind dadurch einfach und benötigen keine Joins.
Der Preis sind viele nullable Spalten und schwächere relationale Constraints für subtypeigene Pflichtfelder. Die Strategie passt nur, wenn Hierarchie und Feldmenge überschaubar bleiben.
Beteiligte Rollen
Basistyp: definiert gemeinsame Identität und Felder.
Subtypen: liefern zusätzliche Attribute und Verhalten.
Discriminator: bestimmt den konkreten Typ.
ORM Mapper: erzeugt passende Objektinstanz.
Typischer Ablauf
Eine Query liest die gemeinsame Tabelle.
Der Diskriminator bestimmt den Subtyp.
Gemeinsame und relevante subtypeigene Spalten werden abgebildet.
Änderungen werden in derselben Zeile gespeichert.
Wann sinnvoll?
polymorphe Abfragen häufig sind
wenige Subtypen mit begrenzten Zusatzfeldern existieren
einfache und schnelle Reads wichtiger sind
Wann nicht?
viele Subtypen sehr unterschiedliche Spalten besitzen
Class Table Inheritance verwendet Joins über Basistabelle und Subtypentabelle. Concrete Table Inheritance dupliziert gemeinsame Spalten in jeder konkreten Tabelle.
Typische Fehlanwendungen
breite Tabelle mit zahlreichen Nullspalten
Diskriminatorwerte werden unkontrolliert geändert
Domänenvererbung wird nur wegen des Datenbankschemas eingeführt
Java- und Enterprise-Beispiel
PaymentMethod speichert CardPayment und BankTransfer in PAYMENT_METHOD mit TYPE. Für wenige stabile Varianten ermöglicht dies einfache polymorphe Abfragen.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Class Table Inheritance
Gemeinsame und subtypeigene Felder in verbundenen Tabellen trennen
ORM-Vererbungsmuster
Fachliche Einordnung
Class Table Inheritance, in JPA häufig JOINED, legt gemeinsame Attribute in einer Basistabelle und zusätzliche Attribute in je einer Subtypentabelle ab. Ein Objekt eines Subtyps wird durch Join über dieselbe Identität rekonstruiert.
Die relationale Struktur bleibt normalisiert und subtypeigene Constraints sind gut ausdrückbar. Polymorphe Abfragen und tiefe Hierarchien können jedoch viele Joins erzeugen und die Performance sowie Migrationen erschweren.
Beteiligte Rollen
Base Table: gemeinsame Identität und Felder.
Subclass Table: subtypeigene Attribute.
Shared Primary Key: verbindet die Tabellen.
ORM Mapper: führt Joins und Instanziierung aus.
Typischer Ablauf
Eine polymorphe Query liest Basistabelle und relevante Subtabellen.
Gemeinsame ID verbindet die Zeilen.
Der Mapper erkennt den vorhandenen Subtyp.
Änderungen können mehrere Tabellen betreffen.
Wann sinnvoll?
Subtypen deutlich verschiedene Felder besitzen
relationale Normalisierung und Constraints wichtig sind
Joinkosten beherrschbar bleiben
Wann nicht?
sehr tiefe Hierarchien oder extrem häufige polymorphe Reads auftreten
Schreibpfade keine Mehrtabellenoperationen vertragen
Vererbung fachlich instabil ist
Abgrenzung zu ähnlichen Mustern
Single Table vermeidet Joins, akzeptiert aber Nullspalten. Concrete Table dupliziert gemeinsame Felder und verwendet für polymorphe Queries häufig UNION.
Document als Basistabelle enthält ID und Status; PDF_DOCUMENT und XML_DOCUMENT enthalten formatbezogene Attribute. Ein Repository nutzt gezielte Fetchpläne statt pauschaler Polymorphie.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Concrete Table Inheritance
Jeden konkreten Subtyp vollständig in einer eigenen Tabelle speichern
ORM-Vererbungsmuster
Fachliche Einordnung
Concrete Table Inheritance, in JPA TABLE_PER_CLASS, legt für jeden konkreten Subtyp eine eigene Tabelle mit allen geerbten und subtypeigenen Spalten an. Einzelne Subtypen lassen sich ohne Join lesen; gemeinsame Felder werden jedoch in mehreren Tabellen wiederholt.
Polymorphe Abfragen benötigen häufig UNION-Operationen. Eindeutige IDs, gemeinsame Constraints und Änderungen an Basiseigenschaften müssen über alle Tabellen hinweg koordiniert werden.
Abstract Base Type: liefert gemeinsames Verhalten ohne eigene Tabelle.
ID Strategy: garantiert Eindeutigkeit über Tabellen.
ORM Mapper: führt subtypebezogene oder polymorphe Queries aus.
Typischer Ablauf
Eine subtypebezogene Query greift auf genau eine Tabelle zu.
Der Mapper erzeugt den konkreten Typ.
Polymorphe Queries vereinigen mehrere Tabellen.
Gemeinsame Änderungen müssen alle Tabellenmappings berücksichtigen.
Wann sinnvoll?
Subtypen überwiegend getrennt verarbeitet werden
Joins vermieden werden sollen
Anzahl konkreter Typen klein und stabil ist
Wann nicht?
häufige polymorphe Abfragen nötig sind
globale Schlüssel und Constraints schwer koordinierbar sind
gemeinsame Felder häufig geändert werden
Abgrenzung zu ähnlichen Mustern
Single Table bündelt alles in einer Tabelle. Class Table normalisiert gemeinsame Felder. Concrete Table optimiert subtypebezogene Zugriffe durch vollständige Tabellen, bezahlt aber mit Duplikation und UNIONs.
Typische Fehlanwendungen
IDs kollidieren zwischen Tabellen
Basisspalten driften auseinander
neue Subtypen werden in polymorphen Queries vergessen
Java- und Enterprise-Beispiel
Unterschiedliche historische Importdatensätze besitzen getrennte Tabellen und werden fast ausschließlich typbezogen verarbeitet. Ein gemeinsamer Reporting-View übernimmt seltene polymorphe Auswertungen.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Vertiefungsband 5 Domäne, Verteilung und ModernisierungPattern 81–100
Patterns für verteilte Zusammenarbeit, Domänenabgrenzung und kontrollierte Modernisierung.
Der Abschlussblock vertieft Domain-Model-, Vertrags-, Nachrichten- und Koordinationsmuster. Nur das Hauptkapitel ist klappbar; alle fachlichen Unterabschnitte bleiben dauerhaft sichtbar.
Fachlich vertieft: 100 von 100 Patternkarten20 neue Fachkapitel · deutschsprachige Quellen · Einsatzgrenzen, Abgrenzungen und Enterprise-Beispiele
Transaction Script
Einen fachlichen Vorgang als klaren, transaktionalen Ablauf organisieren
Enterprise-Anwendungsmuster
Fachliche Einordnung
Transaction Script bündelt die Verarbeitung eines Use Cases in einer prozeduralen Operation. Die Methode liest Eingaben, prüft Regeln, verändert Daten und schreibt das Ergebnis innerhalb einer klaren Transaktionsgrenze zurück. Das Muster ist bewusst einfach und eignet sich für Vorgänge mit überschaubarer Fachlogik.
Problematisch wird es, wenn viele Skripte dieselben Regeln kopieren, Zustandsänderungen nicht mehr überblickbar sind oder ein Skript zum Sammelpunkt für Validierung, Persistenz, Benachrichtigung und Integration wird. Dann ist der Übergang zu Domain Model, Policy-Objekten oder Application Services meist sinnvoll.
Beteiligte Rollen
Use-Case-Funktion: bildet genau einen fachlichen Vorgang ab.
Gateway oder Repository: kapselt Datenzugriff.
Transaktionsgrenze: hält Änderungen zusammen.
Resultat: beschreibt Erfolg oder fachliche Ablehnung.
Typischer Ablauf
Eingabe wird validiert und in Fachwerte übersetzt.
Benötigte Daten werden geladen.
Regeln werden in definierter Reihenfolge ausgewertet.
Änderungen werden innerhalb einer Transaktion gespeichert.
Ein typisiertes Ergebnis wird zurückgegeben.
Wann sinnvoll?
wenige, lineare Fachregeln
kleine Anwendung oder klar begrenzter Use Case
schnelle Nachvollziehbarkeit wichtiger als reiches Objektmodell
Wann nicht?
viele wiederverwendete Regeln
komplexe Zustandsübergänge und Invarianten
Skripte wachsen zu God Services
Abgrenzung zu ähnlichen Mustern
Domain Model verteilt Regeln auf fachliche Objekte. Application Service koordiniert, ohne selbst die Fachlogik zu tragen.
Typische Fehlanwendungen
Transaktionsskript enthält HTTP- und SQL-Details
gleiche Regel in mehreren Skripten kopiert
Fehlercodes statt fachlicher Resultate
Java- und Enterprise-Beispiel
Ein RefundUseCase lädt Zahlung und Rechnung, prüft Rückerstattbarkeit, speichert die Rückerstattung und publiziert erst nach Commit ein Ereignis.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Domain Model
Fachregeln und Invarianten in verhaltensreichen Domänenobjekten bündeln
Domain-Driven Design
Fachliche Einordnung
Ein Domain Model bildet zentrale Fachbegriffe als Objekte mit Verhalten ab. Entitäten, Wertobjekte, Aggregate, Policies und Domain Services tragen die Regeln dort, wo sie fachlich hingehören. Dadurch beschreibt der Code nicht nur Datenstrukturen, sondern die Sprache und Entscheidungen der Domäne.
Der Ansatz lohnt sich bei komplexer, sich entwickelnder Fachlogik. Für einfache Datenerfassung kann er überdimensioniert sein. Ein gutes Domain Model schützt Invarianten und verbirgt unzulässige Zustandsänderungen; es ist kein Satz von JPA-Entities mit öffentlichen Settern.
Beteiligte Rollen
Entitäten und Aggregate: besitzen Identität und kontrollieren Änderungen.
Value Objects: modellieren validierte Fachwerte.
Policies und Domain Services: tragen Regeln ohne natürliche Objektzuordnung.
Repositories: stellen Aggregate bereit, ohne Persistenzdetails offenzulegen.
Typischer Ablauf
Ein Use Case lädt ein Aggregat.
Fachwerte werden als Value Objects erzeugt.
Das Aggregat führt eine benannte fachliche Operation aus.
Invarianten werden innerhalb der Konsistenzgrenze geprüft.
Repository und Domain Events sichern Ergebnis und Folgekommunikation.
Wann sinnvoll?
komplexe Regeln und Fachsprache
lange Lebensdauer des Systems
mehrere Use Cases arbeiten mit denselben Invarianten
Wann nicht?
reine CRUD-Verwaltung ohne nennenswerte Regeln
Objektmodell spiegelt nur Tabellen
Team besitzt keinen Zugang zu Fachwissen
Abgrenzung zu ähnlichen Mustern
Transaction Script organisiert Abläufe prozedural. Domain Model verteilt Verhalten auf fachliche Bausteine. Anemic Domain Model sieht objektorientiert aus, lässt Regeln aber außerhalb.
Typische Fehlanwendungen
öffentliche Setter umgehen Invarianten
Framework-Annotationen bestimmen das Fachmodell
jede Operation wird in DomainService verschoben
Java- und Enterprise-Beispiel
Ein Claim-Aggregat entscheidet selbst über Freigabe, Ablehnung oder manuelle Prüfung; der Application Service koordiniert nur Laden, Speichern und Ereignisse.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Aggregate
Eine fachliche Konsistenzgrenze mit genau einer Wurzel definieren
Domain-Driven Design
Fachliche Einordnung
Ein Aggregate fasst Entitäten und Wertobjekte zu einer fachlichen Konsistenzgrenze zusammen. Von außen wird ausschließlich über die Aggregate Root zugegriffen. Sie entscheidet, welche Änderungen zulässig sind, und stellt sicher, dass Invarianten nach jeder Operation erfüllt bleiben.
Aggregate sollten klein genug für zuverlässige Transaktionen sein. Große Objektgraphen, die aus Bequemlichkeit gemeinsam geladen werden, erhöhen Sperrkonflikte und Kopplung. Beziehungen zu anderen Aggregaten werden meist über stabile Identitäten statt direkte Objektverweise modelliert.
Beteiligte Rollen
Aggregate Root: einziger externer Einstiegspunkt.
Interne Entitäten: besitzen lokale Identität innerhalb der Grenze.
Value Objects: tragen unveränderliche Fachwerte.
Repository: lädt und speichert vollständige Aggregate.
Typischer Ablauf
Repository lädt die Root mit benötigtem Zustand.
Aufrufer ruft eine fachlich benannte Operation auf.
Root delegiert intern und prüft Invarianten.
Änderungen werden atomar gespeichert.
Folgewirkungen außerhalb der Grenze werden als Events beschrieben.
Wann sinnvoll?
mehrere Objekte müssen gemeinsam konsistent sein
klare transaktionale Grenze vorhanden
Regeln lassen sich über eine Root schützen
Wann nicht?
Objekte ändern sich unabhängig
Grenze wird nur nach Datenbank-Joins gewählt
Aggregate werden über mehrere Services verteilt
Abgrenzung zu ähnlichen Mustern
Entity beschreibt Identität; Aggregate beschreibt Konsistenz und Zugriff. Saga koordiniert Änderungen über mehrere Aggregate.
fremde Aggregate werden als tiefe Objektgraphen gehalten
Root dient nur als Container ohne Verhalten
Java- und Enterprise-Beispiel
Order ist Root, OrderLine intern. Eine Zeile kann nur über order.changeQuantity(...) geändert werden; Inventory bleibt ein anderes Aggregate und wird per Ereignis koordiniert.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Entity
Ein Fachobjekt über stabile Identität und Lebenszyklus modellieren
Domain-Driven Design
Fachliche Einordnung
Eine Entity bleibt fachlich dasselbe Objekt, auch wenn sich Attribute ändern. Entscheidend ist ihre Identität, nicht die momentane Feldgleichheit. Identitäten müssen stabil, eindeutig und über relevante Systemgrenzen verständlich sein.
Entities sollten Verhalten besitzen, das ihren Lebenszyklus schützt. Werden sie als frei veränderbare Datencontainer modelliert, wandern Regeln in Services und die Identität wird mit Datenbanktechnik verwechselt.
Beteiligte Rollen
Identität: stabiler fachlicher oder technischer Schlüssel.
Zustand: veränderliche, aber kontrollierte Eigenschaften.
Verhalten: benannte Operationen und Zustandsübergänge.
Lebenszyklus: Erzeugung, Änderung und gegebenenfalls Archivierung.
Typischer Ablauf
Entity wird über Factory oder Aggregat erzeugt.
Identität wird festgelegt.
Änderungen erfolgen über fachliche Methoden.
Methoden prüfen erlaubte Übergänge.
Persistenz erhält Identität und Version.
Wann sinnvoll?
Objekt besitzt eigenen Lebenszyklus
zwei gleiche Attributsätze können verschiedene Dinge darstellen
Historie und Referenzen benötigen stabile Identität
Wann nicht?
reiner Messwert oder Adresse ohne eigene Identität
Objekt wird nur als temporäres Ergebnis genutzt
Identität wird allein wegen ORM eingeführt
Abgrenzung zu ähnlichen Mustern
Value Object wird durch seine Werte definiert und typischerweise ersetzt statt verändert. Entity behält Identität über Zustandsänderungen.
Typische Fehlanwendungen
equals/hashCode verwenden alle veränderlichen Felder
ID wird nachträglich austauschbar
öffentliche Setter erlauben ungültige Zustände
Java- und Enterprise-Beispiel
Customer bleibt derselbe Kunde nach Adressänderung. Die Adresse ist dagegen ein Value Object und wird vollständig ersetzt.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Value Object
Fachwerte unveränderlich, validiert und über Wertgleichheit ausdrücken
Domain-Driven Design
Fachliche Einordnung
Ein Value Object beschreibt einen fachlichen Wert ohne eigene Identität. Es wird durch seine Bestandteile definiert, validiert sich bei der Erzeugung und bleibt danach unveränderlich. Dadurch verschwinden primitive Parameterbündel und Regeln erhalten eine natürliche Heimat.
Gute Value Objects sind klein, aussagekräftig und sicher teilbar. Sie dürfen nicht zu generischen Wrappern ohne fachliche Bedeutung verkommen. Operationen liefern neue Werte statt versteckter Mutation.
Beteiligte Rollen
Validierter Konstruktor oder Factory.
Unveränderliche Komponenten.
Wertgleichheit über alle relevanten Bestandteile.
Fachliche Operationen, die neue Werte liefern.
Typischer Ablauf
Rohdaten werden an der Systemgrenze eingelesen.
Factory prüft Format und Invarianten.
Use Cases verwenden den typisierten Wert.
Berechnungen erzeugen neue Instanzen.
Persistenz oder Mapping zerlegt den Wert kontrolliert.
Wann sinnvoll?
primitive Obsession und wiederholte Validierung
Wert hat keine eigene Identität
fachliche Operationen gehören direkt zum Wert
Wann nicht?
Objekt besitzt Lebenszyklus oder Referenzen
Wert ist riesig und häufig partiell mutiert
Wrapper fügt keinerlei Semantik hinzu
Abgrenzung zu ähnlichen Mustern
Entity besitzt Identität. Value Object besitzt Wertgleichheit. Embedded Value beschreibt eine Persistenzabbildung, nicht das fachliche Konzept selbst.
Typische Fehlanwendungen
öffentliche Setter
ungültige Zwischenzustände
Currency oder Einheit wird bei Berechnungen ignoriert
Java- und Enterprise-Beispiel
Money(amount,currency) verhindert Addition verschiedener Währungen und übernimmt Rundung sowie Nichtnegativitätsregeln.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Domain Service
Domänenlogik ohne natürliche Zugehörigkeit zu einer einzelnen Entity ausdrücken
Domain-Driven Design
Fachliche Einordnung
Ein Domain Service enthält eine wichtige fachliche Operation, die konzeptionell mehrere Domänenobjekte betrifft oder keiner Entity beziehungsweise keinem Value Object natürlich gehört. Er spricht die Sprache der Domäne und bleibt frei von technischen Orchestrierungsdetails.
Domain Services sind eine Ausnahme, kein Standardablageort. Wenn eine Regel eindeutig die Invariante eines Aggregats schützt, gehört sie in das Aggregat. Datenbankzugriffe, Transaktionen und Benachrichtigungen gehören dagegen in Application Services oder Ports.
Beteiligte Rollen
Fachlicher Servicevertrag.
Entitäten und Value Objects als Eingaben.
Policy oder Algorithmus als Kern.
Fachliches Ergebnis ohne Infrastrukturtyp.
Typischer Ablauf
Application Service lädt benötigte Aggregate.
Er übergibt Fachobjekte an den Domain Service.
Der Service trifft eine fachliche Entscheidung.
Aggregate übernehmen daraus zulässige Änderungen.
Application Service speichert und publiziert.
Wann sinnvoll?
Regel betrifft mehrere Aggregate oder Werte
Operation ist ein benannter Fachbegriff
Service bleibt zustandslos und technisch unabhängig
Wann nicht?
Regel gehört klar zu einer Entity
Service koordiniert Transaktion oder HTTP
Klasse wird zum Sammelplatz beliebiger Helfer
Abgrenzung zu ähnlichen Mustern
Application Service orchestriert Use Cases und Infrastruktur. Domain Service entscheidet innerhalb der Fachlichkeit.
Typische Fehlanwendungen
Repository direkt im Domain Service
Name endet auf Helper oder Manager ohne Fachbegriff
Domain Service verändert interne Aggregate ohne Root
Java- und Enterprise-Beispiel
CurrencyConversionService berechnet fachlich zulässige Umrechnung aus Money und ExchangeRate; Laden der Rate übernimmt ein Port außerhalb.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Application Service
Use Cases, Transaktionen und Ports koordinieren, ohne Fachregeln zu übernehmen
Anwendungsarchitektur
Fachliche Einordnung
Ein Application Service bildet einen Anwendungsfall ab. Er nimmt typisierte Eingaben entgegen, lädt Aggregate, ruft deren Verhalten oder Domain Services auf, steuert Transaktionsgrenzen und übergibt technische Folgeaufgaben an Ports.
Sein Code sollte wie ein lesbarer Ablauf wirken. Wachsen dort komplexe Entscheidungen, Berechnungen und Zustandsregeln, ist das Domänenmodell zu schwach. Umgekehrt sollte er nicht durch unnötige Weiterleitungsschichten aufgebläht werden.
Beteiligte Rollen
Input Command oder Query.
Use-Case-Koordinator.
Repositories und technische Ports.
Typisiertes Ergebnis oder Output Model.
Typischer Ablauf
Eingabe wird autorisiert und validiert.
Benötigte Aggregate werden geladen.
Fachliche Operationen werden aufgerufen.
Änderungen werden in einer Transaktion gespeichert.
Nach Commit werden Events oder Benachrichtigungen ausgelöst.
Wann sinnvoll?
klare Use Cases und Transaktionsgrenzen
mehrere Ports müssen koordiniert werden
Domänenmodell soll technisch unabhängig bleiben
Wann nicht?
Methode enthält zentrale Fachregeln
Service ist nur Alias für Repository
ein universeller Service bedient alle Domänen
Abgrenzung zu ähnlichen Mustern
Domain Service trägt fachliche Logik ohne Objektzuordnung. Application Service trägt Ablauf, Transaktion und technische Koordination.
Typische Fehlanwendungen
Transaktion beginnt im Controller
DTOs werden ungeprüft bis in Aggregate gereicht
E-Mail wird vor Commit versendet
Java- und Enterprise-Beispiel
ApproveClaimService lädt Claim, ruft claim.approve(policy), speichert und publiziert ClaimApproved erst nach erfolgreichem Commit.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Policy
Eine austauschbare fachliche Entscheidungsregel explizit benennen
Domain-Driven Design
Fachliche Einordnung
Eine Policy macht eine fachliche Entscheidung als eigenes, benanntes Konzept sichtbar. Sie kapselt Regeln, die variieren können, aber fachlich zusammengehören, etwa Freigabe-, Preis- oder Risikoregeln.
Technisch ähnelt eine Policy häufig Strategy oder Specification. Der Name Policy betont jedoch den fachlichen Charakter und die Verantwortlichkeit für eine Entscheidung. Sie sollte ein klares Decision Object oder einen Fachwert liefern statt viele Seiteneffekte auszuführen.
Beteiligte Rollen
Policy-Vertrag mit fachlichem Namen.
Konkrete Regelvariante.
Fachlicher Kontext als typisierte Eingabe.
Decision Object mit Begründung.
Typischer Ablauf
Use Case baut den Entscheidungskontext auf.
Policy bewertet relevante Fakten.
Sie liefert Ergebnis und Begründung.
Aggregate wenden die zulässige Entscheidung an.
Tests prüfen Grenzfälle und Regelvarianten.
Wann sinnvoll?
Regelvarianten wechseln nach Produkt, Land oder Mandant
Entscheidung soll isoliert testbar sein
Begründung und Version der Regel sind relevant
Wann nicht?
ein einfacher unveränderlicher Vergleich reicht
Policy führt Datenzugriffe und Benachrichtigungen aus
Varianten werden nur technisch, nicht fachlich unterschieden
Abgrenzung zu ähnlichen Mustern
Strategy betont austauschbaren Algorithmus. Specification beantwortet meist eine boolesche Erfüllungsfrage. Policy liefert häufig eine reichere fachliche Entscheidung.
Typische Fehlanwendungen
Policy registriert sich global
Resultat ist nur boolean ohne Begründung
Regelversion ist nicht nachvollziehbar
Java- und Enterprise-Beispiel
AutoApprovalPolicy liefert Approved, ManualReview oder Rejected inklusive Regel-ID und Begründung.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Module
Zusammengehörige Domänenbegriffe hinter einer kohäsiven Grenze bündeln
Domain-Driven Design
Fachliche Einordnung
Ein DDD-Modul gruppiert fachlich eng zusammengehörige Typen und bildet einen Teil der ubiquitären Sprache ab. Es ist mehr als ein technisches package: Namen, öffentliche Oberfläche und Abhängigkeiten sollen die Domänenstruktur ausdrücken.
Ein gutes Modul besitzt hohe innere Kohäsion und wenige, bewusste Beziehungen zu anderen Modulen. Technische Querschnittspakete wie service, util oder impl verschleiern dagegen häufig die fachliche Struktur.
Beteiligte Rollen
Öffentliche Modul-API.
Interne Aggregate, Policies und Services.
Explizite Abhängigkeiten zu anderen Modulen.
Architekturregeln oder Modulbeschreibung.
Typischer Ablauf
Fachliche Begriffe werden nach Verantwortungsgebiet gruppiert.
Nur benötigte Typen werden öffentlich gemacht.
Andere Module verwenden die stabile Oberfläche.
Interne Struktur kann unabhängig refaktoriert werden.
Architekturtests überwachen unerlaubte Zugriffe.
Wann sinnvoll?
große Domäne benötigt verständliche Teilgebiete
Teams besitzen klare Verantwortlichkeiten
modularer Monolith oder Modulith geplant
Wann nicht?
Pakete werden nur nach Technik geschnitten
jedes Modul kennt alle anderen
öffentliche API entspricht dem gesamten internen Modell
Abgrenzung zu ähnlichen Mustern
Bounded Context ist eine Modell- und Sprachgrenze. Module strukturieren ein Modell innerhalb eines Kontexts oder bilden dessen technische Umsetzung.
Typische Fehlanwendungen
shared-Modul wird Sammelbecken
zyklische Modulabhängigkeiten
interne JPA-Entity wird öffentliches Vertragsmodell
Java- und Enterprise-Beispiel
Billing exportiert InvoiceUseCase und InvoiceId; interne Buchungsregeln und Persistenztypen bleiben paketprivat.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Published Language
Eine gemeinsame, versionierte Sprache für Integrationsgrenzen definieren
Strategisches Domain-Driven Design
Fachliche Einordnung
Published Language ist ein bewusst veröffentlichter Vertrag, über den mehrere Kontexte oder Systeme kommunizieren. Die Sprache besitzt definierte Begriffe, Formate und Semantik und wird unabhängig von internen Domänenmodellen versioniert.
Das Muster verhindert, dass interne Klassen oder Datenbankschemata unkontrolliert zum Integrationsvertrag werden. Es verlangt Governance: Eigentümer, Kompatibilitätsregeln, Dokumentation und Migrationspfade.
Beteiligte Rollen
Vertragsmodell und Schema.
Semantische Definition der Begriffe.
Versionierungs- und Kompatibilitätsregeln.
Publisher und Consumer mit eigenen Mappern.
Typischer Ablauf
Ein Kontext definiert den öffentlichen Vertrag.
Interne Modelle werden auf die veröffentlichte Sprache abgebildet.
Consumer validieren und übersetzen den Vertrag.
Änderungen erfolgen kompatibel oder über neue Version.
Veraltete Versionen werden kontrolliert abgekündigt.
Wann sinnvoll?
mehrere unabhängige Consumer
langfristige Integrationsverträge
gemeinsame Semantik wichtiger als technische Direktkopplung
Wann nicht?
ein einzelner interner Aufruf ohne Vertragslebensdauer
internes Domänenmodell wird 1:1 exportiert
keine Verantwortlichkeit für Schemaentwicklung
Abgrenzung zu ähnlichen Mustern
Canonical Data Model ist eine systemübergreifende gemeinsame Repräsentation. Published Language kann gezielter zwischen bestimmten Kontexten vereinbart sein.
Typische Fehlanwendungen
Datenbankspalten werden API
Bedeutung eines Feldes ändert sich still
Consumer müssen interne Enums kennen
Java- und Enterprise-Beispiel
Order Events verwenden ein versioniertes JSON-Schema mit OrderNumber, FulfillmentStatus und fachlich dokumentierter Bedeutung; interne Aggregate bleiben verborgen.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Event Notification
Nur das fachliche Geschehen melden und Details beim Sender nachladen lassen
Integrationsmuster
Fachliche Einordnung
Event Notification übermittelt eine knappe Mitteilung, dass ein relevantes Ereignis eingetreten ist. Sie enthält Identität, Ereignistyp und minimale Metadaten; Empfänger laden benötigten Zustand über eine separate Schnittstelle.
Die geringe Payload reduziert Kopplung an das Datenmodell des Senders, erhöht aber zusätzliche Netzaufrufe und das Risiko, einen späteren statt den Ereigniszustand zu lesen. Die Entscheidung muss bewusst gegen Event-Carried State Transfer abgewogen werden.
Beteiligte Rollen
Publisher des Ereignisses.
Kleine Event Notification.
Consumer.
Query-Schnittstelle zum aktuellen Zustand.
Typischer Ablauf
Sender ändert seinen Zustand.
Nach Commit wird eine Benachrichtigung publiziert.
Consumer korreliert und entscheidet, ob Daten benötigt werden.
Consumer fragt den aktuellen Zustand ab.
Verarbeitung wird idempotent abgeschlossen.
Wann sinnvoll?
Consumer benötigen selten Details
Payload und Datenschutz sollen klein bleiben
aktuellster Zustand ist ausreichend
Wann nicht?
historischer Zustand zum Ereigniszeitpunkt erforderlich
viele Consumer erzeugen Query-Sturm
Sender muss hoch verfügbar für Nachladen sein
Abgrenzung zu ähnlichen Mustern
Event-Carried State Transfer transportiert benötigte Daten im Ereignis und vermeidet Nachladen, koppelt Consumer aber stärker an Schema und Datenmenge.
Typische Fehlanwendungen
Event enthält doch komplettes internes Modell
Query ist nicht versioniert
Benachrichtigung wird vor Commit gesendet
Java- und Enterprise-Beispiel
CustomerChanged enthält CustomerId und ChangeType; nur interessierte Consumer laden über CustomerQuery die freigegebenen Daten.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Event-Carried State Transfer
Den für Empfänger nötigen Zustand direkt im Ereignis transportieren
Integrationsmuster
Fachliche Einordnung
Bei Event-Carried State Transfer enthält das Ereignis genügend Daten, damit Consumer ihre lokale Sicht ohne synchronen Rückruf aktualisieren können. Das stärkt zeitliche Entkopplung und ermöglicht autonome Lesemodelle.
Die Payload wird größer und der veröffentlichte Vertrag gewinnt an Bedeutung. Datenschutz, Schemaevolution und die Frage, welcher Zustand wirklich benötigt wird, müssen sorgfältig geregelt werden.
Beteiligte Rollen
Versioniertes Integration Event.
Publisher mit Outbox.
Consumer mit Inbox und Mapper.
Lokales Read Model oder Cache.
Typischer Ablauf
Sender speichert Domänenänderung und Outbox atomar.
Publisher veröffentlicht ein versioniertes Ereignis.
Consumer dedupliziert die Nachricht.
Payload wird in lokales Modell übersetzt.
Consumer speichert Offset oder Verarbeitungsstatus.
Wann sinnvoll?
Consumer sollen ohne Rückruf arbeiten
lokale Projektionen werden aufgebaut
Ereigniszustand muss historisch reproduzierbar sein
Wann nicht?
hoch sensible oder sehr große Daten
Consumer benötigen fast immer nur aktuelle Werte
Schema-Governance fehlt
Abgrenzung zu ähnlichen Mustern
Event Notification enthält minimale Information und verlangt Nachladen. Event Sourcing speichert Events als Quelle der Wahrheit; Event-Carried State Transfer ist primär ein Integrationsstil.
Typische Fehlanwendungen
komplette Entity wird unkontrolliert serialisiert
PII wird an alle Consumer verteilt
Breaking Changes ohne Upcaster oder neue Version
Java- und Enterprise-Beispiel
ProductChanged trägt freigegebene Produktfelder und Version; Suchindex und Shop-Frontend aktualisieren ihre lokalen Projektionen autonom.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Request-Reply Contract
Anfrage und Antwort über eindeutige Korrelation als belastbaren Vertrag modellieren
Nachrichtenmuster
Fachliche Einordnung
Request-Reply bildet eine synchrone Erwartung auf asynchroner oder nachrichtenbasierter Infrastruktur ab. Eine Anfrage trägt Korrelationskennung, Antwortadresse, Zeitbudget und Vertragsversion; genau eine fachlich passende Antwort wird zugeordnet.
Das Muster darf nicht die Nachteile synchroner Kopplung verstecken. Timeout, Duplikate, späte Antworten und Fehlerantworten müssen Teil des Vertrags sein. Für lang laufende Geschäftsprozesse ist ein Process Manager häufig geeigneter.
Beteiligte Rollen
Request Message.
Reply Message.
Correlation Identifier.
Reply Channel oder Return Address.
Typischer Ablauf
Sender erzeugt Anfrage und Korrelation.
Anfrage wird an den Bearbeiter übertragen.
Bearbeiter erzeugt typisierte Antwort.
Antwort wird zur angegebenen Adresse gesendet.
Sender korreliert oder behandelt Timeout und späte Antwort.
Wann sinnvoll?
Antwort wird zeitnah benötigt
Anfrage hat einen klaren Bearbeiter
Messaging-Infrastruktur statt direktem HTTP genutzt wird
Wann nicht?
Prozess dauert lange oder besitzt mehrere Schritte
Sender blockiert unbeschränkt
Fehler und Timeout sind nicht modelliert
Abgrenzung zu ähnlichen Mustern
Request-Response über HTTP bindet Transport und Antwortkanal enger. Process Manager verwaltet lang laufende Zustände und mehrere Nachrichten.
Typische Fehlanwendungen
Thread wartet auf unendliche Antwort
Korrelation basiert auf Payloadvergleich
späte Antworten verändern abgeschlossene Vorgänge
Java- und Enterprise-Beispiel
PricingRequest enthält requestId, replyTo und deadline; PricingReply liefert PriceDecision oder definierte Fehlerursache.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Correlation Identifier Contract
Zusammengehörige Nachrichten über eine stabile Kennung nachvollziehbar verbinden
Nachrichtenmuster
Fachliche Einordnung
Ein Correlation Identifier verbindet Anfrage, Antwort, Folgeereignisse und Logs eines fachlichen Vorgangs. Die Kennung wird an Systemgrenzen bewusst übernommen oder neu zugeordnet und darf nicht mit zufälligen technischen Trace-IDs verwechselt werden.
Korrelation verbessert nicht nur Routing, sondern auch Diagnose und Idempotenz. Die Lebensdauer und Eindeutigkeit müssen zum Geschäftsprozess passen; personenbezogene Daten gehören nicht in die Kennung.
Beteiligte Rollen
Fachliche Correlation ID.
Mapper an Integrationsgrenzen.
Message Envelope.
Observability Context.
Typischer Ablauf
Ein Vorgang erhält eine stabile Kennung.
Jede ausgehende Nachricht trägt sie im Envelope.
Adapter übernehmen oder mappen die Kennung.
Logs und Metriken referenzieren denselben Kontext.
Antworten und Duplikate werden eindeutig zugeordnet.
Wann sinnvoll?
mehrere Nachrichten gehören zu einem Prozess
asynchrone Diagnose nötig
Request-Reply oder Saga verwendet wird
Wann nicht?
Kennung wird aus veränderlichen Fachdaten berechnet
globale Trace-ID soll fachliche Prozess-ID ersetzen
Lebensdauer ist unklar
Abgrenzung zu ähnlichen Mustern
Trace ID beschreibt eine technische Ausführungskette. Correlation ID kann einen längeren fachlichen Vorgang über mehrere Traces verbinden.
Typische Fehlanwendungen
E-Mail-Adresse als Correlation ID
jede Schicht erzeugt neue ID
Header geht beim Adapter verloren
Java- und Enterprise-Beispiel
ClaimProcessingId bleibt über Intake, FraudCheck, Approval und Payment erhalten; einzelne HTTP-Aufrufe besitzen zusätzliche Trace-IDs.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Return Address Contract
Antwortziel als expliziten Bestandteil einer Nachricht übermitteln
Nachrichtenmuster
Fachliche Einordnung
Return Address beschreibt, wohin eine Antwort gesendet werden soll. Der Absender übermittelt den Rückkanal explizit, sodass der Bearbeiter nicht an einen fest codierten Zielkanal gekoppelt ist.
In modernen Broker-Systemen kann die Adresse Queue, Topic, Routing Key oder logischer Endpoint sein. Sie muss validiert werden, darf keine unkontrollierte Zielwahl ermöglichen und benötigt klare Sicherheitsregeln.
Beteiligte Rollen
Request Envelope.
Reply-To oder logische Rückadresse.
Responder.
Routing- und Sicherheitskomponente.
Typischer Ablauf
Sender wählt einen erlaubten Rückkanal.
Adresse wird im Envelope transportiert.
Responder verarbeitet die Anfrage.
Router validiert und verwendet die Rückadresse.
Antwort enthält dieselbe Korrelation.
Wann sinnvoll?
mehrere Absender unterschiedliche Antwortkanäle nutzen
temporäre Reply Queues erforderlich sind
Responder transportneutral bleiben soll
Wann nicht?
Sicherheitsgrenzen erlauben beliebige Adressen
ein fester Antwortkanal genügt
lang laufender Prozess benötigt Ereignisse statt Antwort
Abgrenzung zu ähnlichen Mustern
Correlation Identifier sagt, zu welcher Anfrage die Antwort gehört. Return Address sagt, wohin sie gesendet wird.
Typische Fehlanwendungen
beliebige Queue-Namen aus untrusted Payload
Rückadresse wird nicht autorisiert
Adresse und Korrelation werden vermischt
Java- und Enterprise-Beispiel
DocumentConversionRequest trägt replyChannelId; ein Router löst nur registrierte Kanäle auf und sendet ConversionResult mit derselben requestId.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Normalizer Contract
Heterogene Eingangsformate an der Grenze in ein stabiles internes Format überführen
Integrationsmuster
Fachliche Einordnung
Ein Normalizer akzeptiert verschiedene externe Nachrichtenformen und übersetzt sie in ein einheitliches internes Format. Nachfolgende Komponenten arbeiten dadurch unabhängig von Partnerformat, Protokoll oder Version.
Der Normalizer ist eine Integrationsgrenze, kein universeller Datenverlustfilter. Semantik, Pflichtfelder und Fehler müssen transparent bleiben; unklare oder nicht abbildbare Nachrichten gehören in Quarantäne statt stiller Defaultwerte.
Beteiligte Rollen
Format-spezifische Adapter.
Normalizer oder Translator Registry.
Kanonisches internes Nachrichtenmodell.
Quarantäne und Fehlerbericht.
Typischer Ablauf
Adapter erkennt Format und Version.
Parser validiert technische Struktur.
Normalizer übersetzt fachliche Begriffe.
Kanonisches Modell wird erneut validiert.
Nachfolgende Pipeline verarbeitet nur dieses Modell.
Wann sinnvoll?
viele Partnerformate
interne Verarbeitung soll stabil bleiben
Migration zwischen Schema-Versionen
Wann nicht?
nur ein unveränderliches Format
Übersetzung verliert wichtige Semantik
kanonisches Modell wird riesiges Allzweckschema
Abgrenzung zu ähnlichen Mustern
Message Translator übersetzt ein konkretes Formatpaar. Normalizer koordiniert mehrere Translatoren zu einem einheitlichen Zielmodell.
Typische Fehlanwendungen
unbekannte Felder werden still verworfen
Defaults verstecken Datenfehler
Partnerlogik verteilt sich in der Kernpipeline
Java- und Enterprise-Beispiel
CSV-, XML- und JSON-Bestellungen werden in NormalizedOrderCommand übersetzt; Mappingfehler landen mit Originalnachricht in einer Quarantäne.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Canonical Data Model
Eine kontrollierte gemeinsame Integrationsrepräsentation für mehrere Systeme etablieren
Integrationsarchitektur
Fachliche Einordnung
Ein Canonical Data Model reduziert die Zahl direkter Formatübersetzungen: Systeme übersetzen ihre lokalen Modelle in eine gemeinsame Repräsentation und wieder zurück. Es kann Integration vereinfachen, schafft aber selbst eine zentrale Sprache mit hoher Governance-Verantwortung.
Das Modell darf nicht zum vollständigen Unternehmensdatenmodell anwachsen. Ein sinnvoller Kanon ist auf Integrationsfälle, Bounded Contexts oder Ereignisfamilien begrenzt und wird versioniert.
Beteiligte Rollen
Kanonisches Vertragsmodell.
System-spezifische Mapper.
Schema Registry und Governance.
Kompatibilitäts- und Deprecation Policy.
Typischer Ablauf
Quellsystem mappt sein lokales Modell auf den Kanon.
Schema wird validiert und versioniert übertragen.
Zielsystem mappt in sein lokales Modell.
Inkompatible Änderungen werden früh erkannt.
Veraltete Versionen werden kontrolliert abgelöst.
Wann sinnvoll?
viele Systeme viele Formate austauschen
gemeinsame Begriffe wirklich abgestimmt sind
Schema-Governance und Eigentümer existieren
Wann nicht?
nur zwei Systeme integriert werden
ein zentraler Ausschuss jede Fachänderung blockiert
Modell versucht sämtliche lokalen Details abzubilden
Abgrenzung zu ähnlichen Mustern
Published Language ist ein veröffentlichter Vertrag zwischen Kontexten. Canonical Data Model zielt stärker auf eine gemeinsame Integrationsrepräsentation für viele Teilnehmer.
Typische Fehlanwendungen
kanonisches Monster-Schema
lokale Fachmodelle richten sich nach dem Kanon
Versionierung wird vermieden
Java- und Enterprise-Beispiel
Ein begrenztes CustomerIdentity-Canonical-Model verbindet CRM, IAM und Billing; jedes System behält sein eigenes Domänenmodell.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Distributed Lock
Eine kritische verteilte Operation zeitlich begrenzt exklusiv reservieren
Verteiltes Systemmuster
Fachliche Einordnung
Ein Distributed Lock koordiniert Instanzen, die auf eine gemeinsame Ressource zugreifen. Im Unterschied zu einem lokalen synchronized-Block muss der Lock Ausfälle, Netzpartitionen und abgelaufene Besitzer berücksichtigen. Deshalb gehören Lease-Dauer, Fencing Token und sichere Freigabe zum Modell.
Ein Lock ist kein Ersatz für idempotente Verarbeitung oder gute Datenbankconstraints. Wo atomare Operationen, Compare-and-Set oder Partitionierung ausreichen, sind sie meist robuster.
Beteiligte Rollen
Lock Store mit atomarer Vergabe.
Lease mit Ablaufzeit.
Owner Token und Fencing Token.
kritische Operation mit Idempotenzschutz.
Typischer Ablauf
Instanz fordert eine Lease an.
Store vergibt monotonen Fencing Token.
Operation akzeptiert nur aktuellen Token.
Lease wird erneuert oder läuft aus.
Freigabe erfolgt nur durch den Besitzer.
Wann sinnvoll?
seltene exklusive Koordination wirklich nötig
gemeinsamer zuverlässiger Lock Store vorhanden
Operation kann mit Fencing geschützt werden
Wann nicht?
hoher Durchsatz auf einem globalen Lock
Lock ohne Ablaufzeit
Idempotenz oder Datenbankconstraint wären ausreichend
Abgrenzung zu ähnlichen Mustern
Leader Election bestimmt eine koordinierende Instanz. Distributed Lock schützt eine konkrete Ressource oder Operation. Optimistic Locking erkennt Konflikte nach Versionsvergleich.
Typische Fehlanwendungen
Lock wird nach Prozessabsturz nie frei
alter Besitzer schreibt nach Lease-Ablauf
globale Sperre für unabhängige Schlüssel
Java- und Enterprise-Beispiel
Ein ReconciliationJob erwirbt pro tenantId eine Lease mit Fencing Token; Datenbankupdates lehnen ältere Token ab.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Leader Election
Eine aktive Instanz für koordinierende Aufgaben wählen und kontrolliert ersetzen
Verteiltes Systemmuster
Fachliche Einordnung
Leader Election bestimmt aus mehreren gleichartigen Instanzen genau eine, die koordinierende Arbeit übernimmt. Die Wahl muss bei Ausfall erneuert werden, ohne dass zwei Instanzen dauerhaft gleichzeitig als Leader handeln.
Eine robuste Lösung nutzt Lease, Epoche oder Fencing Token. Anwendungen sollten Leader-Arbeit klein halten und so entwerfen, dass ein Wechsel wiederholbar ist. Für unabhängige Partitionen ist Partition Leadership statt eines globalen Leaders oft besser.
Beteiligte Rollen
Kandidaten.
Election Store oder Consensus-System.
Leader Lease und Epoch.
Follower mit Überwachung.
Typischer Ablauf
Kandidaten versuchen atomar eine Lease zu erwerben.
Gewinner erhält neue Epoche.
Leader führt koordinierende Aufgaben aus.
Follower beobachten Ablauf oder Heartbeat.
Nach Ausfall wird mit höherer Epoche neu gewählt.
Wann sinnvoll?
genau eine Koordinationsinstanz notwendig
Aufgabe ist wiederholbar und idempotent
Election-Infrastruktur ist zuverlässig
Wann nicht?
Arbeit kann einfach partitioniert werden
Leader wird Single Point of Bottleneck
Split-Brain ist nicht durch Epoch/Fencing begrenzt
Abgrenzung zu ähnlichen Mustern
Distributed Lock schützt einzelne Ressourcen. Leader Election bestimmt eine Rolle über längere Zeit. Competing Consumers verteilen Arbeit statt sie zu zentralisieren.
Typische Fehlanwendungen
statische Leader-Konfiguration
keine Epoch in nachgelagerten Writes
Leader hält kritischen Zustand nur im Speicher
Java- und Enterprise-Beispiel
Ein Scheduler-Cluster wählt einen Leader für die Erzeugung von Batch-Aufträgen; Worker verarbeiten die Aufträge weiterhin verteilt.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.
Sidecar
Technische Querschnittsfunktionen in einem getrennten Begleitprozess betreiben
Cloud-Architekturmuster
Fachliche Einordnung
Ein Sidecar läuft neben dem Anwendungsprozess und stellt technische Fähigkeiten wie Proxying, Telemetrie, Zertifikatsrotation oder Konfigurationszugriff bereit. Anwendung und Sidecar teilen ihren Lebenszyklus oder ihre Deployment-Einheit, bleiben aber prozessual getrennt.
Das Muster ermöglicht sprachunabhängige Plattformfunktionen, erhöht jedoch Ressourcenverbrauch und Betriebs-Komplexität. Fachlogik gehört nicht in den Sidecar; die lokale Kommunikation muss klare Fehler- und Sicherheitsgrenzen besitzen.
Beteiligte Rollen
Anwendungsprozess.
Sidecar-Prozess.
lokale Schnittstelle oder Proxy.
gemeinsame Deployment- und Health-Konfiguration.
Typischer Ablauf
Anwendung startet mit Sidecar in derselben Einheit.
Aufrufe oder Telemetrie gehen über lokale Schnittstelle.
Sidecar übernimmt technische Integration.
Health Checks betrachten beide Prozesse.
Versionen und Ausfälle werden gemeinsam betrieben.
Wann sinnvoll?
mehrere Sprachen dieselbe Plattformfähigkeit benötigen
Querschnittsfunktion unabhängig aktualisiert wird
lokale Kommunikation günstiger als Bibliothekskopplung ist
Wann nicht?
einfache Bibliothek genügt
Sidecar enthält Fachentscheidungen
Ressourcenbudget ist sehr knapp
Abgrenzung zu ähnlichen Mustern
Ambassador ist auf ausgehende Kommunikation spezialisiert. Service Mesh verteilt Sidecar-Proxies systemweit. Sidecar ist das allgemeinere Begleitprozessmuster.
Typische Fehlanwendungen
Sidecar und Anwendung haben inkompatible Versionen
Health Check prüft nur einen Prozess
lokaler Port ist ungeschützt
Java- und Enterprise-Beispiel
Ein Java-Service sendet OTLP lokal an einen Telemetry-Sidecar, der Pufferung, Export und Zertifikate übernimmt; Fachcode kennt nur einen TelemetryPort.
Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.