100 vertiefte Patterns

Design Patterns & Pattern-Entscheidungen

Wenige große Kapitel. Nur Hauptkapitel sind klappbar; alle Unterabschnitte bleiben sichtbar. Alle 100 Patternkarten sind jetzt ausführlich aus deutschsprachigen Fachquellen redaktionell vertieft.

0 von 166 offen

20 Hauptkapitel · 100 ausführlich vertiefte Pattern-Fachkapitel · alle Unterabschnitte dauerhaft sichtbar.

Klare Leserführung

Vom Problem zur begründeten Pattern-Entscheidung

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.

Buchteil I

Grundlagen, Beziehungen und Auswahlregeln

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.

Übersicht typischer Pattern-Kombinationen von Strategy bis Outbox und Observer

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

AusgangsproblemErste sichere VerbesserungMögliches PatternStopppunkt
große FallunterscheidungBedingungen benennen und Varianten testenStrategy oder Statewenn die Fallunterscheidung lokal und stabil bleibt
versteckte Fremdsystemkopplungexpliziten Port extrahierenAdapter / ACLwenn nur technische Übersetzung nötig ist
mehrere ErzeugungswegeKonstruktion aus Use Case lösenFactory / Builderwenn ein benannter Konstruktor ausreicht
unzuverlässige EreignisverteilungTransaktionsgrenze und Idempotenz klärenOutbox + Observerwenn 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.

ÄnderungsdruckPattern-KandidatWarnsignal
Algorithmen variierenStrategyNur ein triviales if vorhanden
Verhalten hängt vom Zustand abStateZustände sind nur Datenlabels
Fremde API passt nichtAdapterNur Namensunterschiede
Prüfungen kombinierenSpecificationRegeln werden nie wiederverwendet

02. Strategy – Rabattvarianten analysis · code

Verhaltensmuster Kapselt austauschbare Berechnungen. Einsatzort: Preisregeln.

DiscountStrategy.java
// 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()));
    }
}

05. Specification – kombinierbare Regeln analysis · code

Specification.java
// Design Pattern: Specification
@FunctionalInterface
public interface Specification<T> {
    boolean isSatisfiedBy(T candidate);
    default Specification<T> and(Specification<T> other) {
        return value -> isSatisfiedBy(value) && other.isSatisfiedBy(value);
    }
}

06. Decorator – Preisregeln schichten analysis · code

CappedDiscount.java
// Design Pattern: Decorator
public final class CappedDiscount implements DiscountStrategy {
    private final DiscountStrategy delegate;
    private final Money maximum;
    public Money apply(Money subtotal) {
        Money discounted = delegate.apply(subtotal);
        return cap(subtotal, discounted, maximum);
    }
}

07. Factory – passende Strategy erzeugen analysis · code

DiscountStrategyFactory.java
// 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);

09. Repository – Persistenzgrenze analysis · code

OrderRepository.java
// Enterprise Pattern: Repository
public interface OrderRepository {
    Optional<Order> findById(OrderId id);
    void save(Order order);
}

Repository abstrahiert eine fachlich relevante Aggregat-Sammlung. Es sollte nicht pauschal für jede Tabelle erzeugt werden.

10. Zusammenspiel und Anti-Patterns analysis

KombinationSinn
Factory + StrategyErzeugung und Ausführung der Variante bleiben getrennt.
Specification + RepositoryFachliche Auswahlregeln können in Query-Modelle übersetzt werden.
Decorator + StrategyQuerschnittliche 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

Entscheidungsdiagramm Strategy oder State?

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();
}
FrageStrategyState
Was variiert?Algorithmus austauschenVerhalten folgt Lebenszyklus
Risikounnötige Abstraktion bei nur einer VariantePattern-Missbrauch ohne echten Zustands-/Zugriffs-/Strukturbedarf
TestfokusVariante unabhängig testenÜbergang, Zugriff oder Strukturgrenze testen

02. Adapter oder Facade? Decision Lab

Entscheidungsdiagramm Adapter oder Facade?

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]);
    }
}
FrageAdapterFacade
Was variiert?inkompatible Schnittstelle übersetzenkomplexes Subsystem vereinfachen
Risikounnötige Abstraktion bei nur einer VariantePattern-Missbrauch ohne echten Zustands-/Zugriffs-/Strukturbedarf
TestfokusVariante unabhängig testenÜbergang, Zugriff oder Strukturgrenze testen

03. Builder oder Factory? Decision Lab

Entscheidungsdiagramm Builder oder Factory?

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.

Einsatzort: ExportRequest.Builder / ExporterFactory

ExportRequest.java
ExportRequest request = ExportRequest.builder("CSV")
    .compressed(true)
    .locale("de-AT")
    .build();

Exporter exporter = new ExporterFactory().create("CSV");
FrageBuilderFactory
Was variiert?komplexes Objekt schrittweise konfigurierenkonkreten Produkttyp auswählen
Risikounnötige Abstraktion bei nur einer VariantePattern-Missbrauch ohne echten Zustands-/Zugriffs-/Strukturbedarf
TestfokusVariante unabhängig testenÜbergang, Zugriff oder Strukturgrenze testen

04. Observer oder Domain Event? Decision Lab

Entscheidungsdiagramm Observer oder Domain Event?

Observer

Problem: direkte In-Process-Reaktion.

Domain Event

Problem: fachliche Tatsache über Grenzen transportieren.

Entscheidungsregel

Observer ist leichtgewichtig und synchron. Domain Events sind explizite Fakten, die gespeichert, publiziert oder später verarbeitet werden können.

Einsatzort: OrderObserver / OrderCompleted

OrderCompleted.java
public interface OrderObserver {
    void completed(String orderId);
}

public record OrderCompleted(
    String orderId,
    long occurredAtEpochMillis
) {}
FrageObserverDomain Event
Was variiert?direkte In-Process-Reaktionfachliche Tatsache über Grenzen transportieren
Risikounnötige Abstraktion bei nur einer VariantePattern-Missbrauch ohne echten Zustands-/Zugriffs-/Strukturbedarf
TestfokusVariante unabhängig testenÜbergang, Zugriff oder Strukturgrenze testen

05. Decorator oder Proxy? Decision Lab

Entscheidungsdiagramm Decorator oder Proxy?

Decorator

Problem: zusätzliches Verhalten schichten.

Proxy

Problem: Zugriff kontrollieren oder verzögern.

Entscheidungsregel

Beide halten dieselbe Abstraktion. Decorator erweitert, Proxy vermittelt und kontrolliert den Zugriff.

Einsatzort: LoggingInvoiceDecorator / AuthorizationInvoiceProxy

InvoiceService.java
InvoiceService decorated =
    new LoggingInvoiceDecorator(realService);

InvoiceService protectedAccess =
    new AuthorizationInvoiceProxy(realService, allowed);
FrageDecoratorProxy
Was variiert?zusätzliches Verhalten schichtenZugriff kontrollieren oder verzögern
Risikounnötige Abstraktion bei nur einer VariantePattern-Missbrauch ohne echten Zustands-/Zugriffs-/Strukturbedarf
TestfokusVariante unabhängig testenÜbergang, Zugriff oder Strukturgrenze testen

06. Composite oder Visitor? Decision Lab

Entscheidungsdiagramm Composite oder Visitor?

Composite

Problem: Teil-Ganzes-Struktur einheitlich behandeln.

Visitor

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();
FrageCompositeVisitor
Was variiert?Teil-Ganzes-Struktur einheitlich behandelnneue Operationen über stabile Typen ergänzen
Risikounnötige Abstraktion bei nur einer VariantePattern-Missbrauch ohne echten Zustands-/Zugriffs-/Strukturbedarf
TestfokusVariante unabhängig testenÜbergang, Zugriff oder Strukturgrenze testen

07. Repository oder DAO? Decision Lab

Entscheidungsdiagramm Repository oder DAO?

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);
}
FrageRepositoryDAO
Was variiert?Domänenaggregate fachlich ladentechnische Tabellenoperationen kapseln
Risikounnötige Abstraktion bei nur einer VariantePattern-Missbrauch ohne echten Zustands-/Zugriffs-/Strukturbedarf
TestfokusVariante unabhängig testenÜbergang, Zugriff oder Strukturgrenze testen

08. Specification oder Predicate? Decision Lab

Entscheidungsdiagramm Specification oder Predicate?

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.

Einsatzort: Specification / Predicate

Specification.java
Specification<Claim> complete = Claim::documentsComplete;
Specification<Claim> belowLimit = claim -> claim.amountCents() <= 100_000;
Specification<Claim> autoApproval = complete.and(belowLimit);
FrageSpecificationPredicate
Was variiert?benannte kombinierbare Fachregellokale kleine Bedingung
Risikounnötige Abstraktion bei nur einer VariantePattern-Missbrauch ohne echten Zustands-/Zugriffs-/Strukturbedarf
TestfokusVariante unabhängig testenÜbergang, Zugriff oder Strukturgrenze testen
Kapitel 3 Entscheidungslabore: Erzeugung und Struktur8 Decision Labs

Acht weitere Pattern-Paare werden anhand ihrer tatsächlichen Absicht, Variationsachse und Risiken verglichen.

Pattern-Labs · Abschnitt 2

Pattern Decision Workbench

Acht weitere Pattern-Paare werden anhand ihrer tatsächlichen Absicht, Variationsachse und Risiken verglichen.

Pattern-Labs: 16/40 Pattern-Labs

24 Pattern-Labs offen.

Lab 09 · Template Method vs Strategy Importpipeline

Template Method vs Strategy

Template Method

Wähle es, wenn: gemeinsames Ablaufgerüst.

Strategy

Wähle es, wenn: austauschbarer Algorithmus.

Java-Beispiel: Template Method

Java-Beispiel: Template Method
package com.aydinsude.workbench.decision;
// Design Pattern: Template Method - stabiler Ablauf, variable Schritte.
public abstract class ImportTemplate { public final String run(String input){ return persist(validate(parse(input))); } protected abstract String parse(String in); protected String validate(String v){ if(v.isBlank()) throw new IllegalArgumentException(); return v;} protected String persist(String v){return "stored:"+v;} }

Java-Beispiel: Strategy

Java-Beispiel: Strategy
package com.aydinsude.workbench.decision;
// Design Pattern: Strategy - kompletter Algorithmus austauschbar.
public interface ImportStrategy { String importData(String input); }
FrageTemplate MethodStrategy
Primäre Absichtgemeinsames Ablaufgerüstaustauschbarer Algorithmus
Testfokusstabiler Vertragaustauschbares Verhalten
Risikozu starres Gerüstzu viele kleine Varianten

Lab 10 · Command vs Chain of Responsibility Freigabeprozess

Command vs Chain of Responsibility

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); }
FrageCommandChain of Responsibility
Primäre AbsichtOperation als Objektgestufte Bearbeitung
Testfokusstabiler Vertragaustauschbares Verhalten
Risikozu starres Gerüstzu viele kleine Varianten

Lab 11 · Mediator vs Observer UI-Workflow

Mediator vs Observer

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); }
FrageMediatorObserver
Primäre Absichtzentrale Koordinationverteilte Reaktion
Testfokusstabiler Vertragaustauschbares Verhalten
Risikozu starres Gerüstzu viele kleine Varianten

Lab 12 · Bridge vs Adapter Dokumentausgabe

Bridge vs Adapter

Bridge

Wähle es, wenn: zwei Dimensionen unabhängig.

Adapter

Wähle es, wenn: bestehende Schnittstelle übersetzen.

Java-Beispiel: Bridge

Java-Beispiel: Bridge
package com.aydinsude.workbench.decision;
// Design Pattern: Bridge - Abstraktion und Implementierung getrennt variieren.
public interface DocumentRenderer { byte[] render(String content); }

Java-Beispiel: Adapter

Java-Beispiel: Adapter
package com.aydinsude.workbench.decision;
// Design Pattern: Adapter - Legacy-Renderer kompatibel machen.
public final class LegacyRendererAdapter implements DocumentRenderer { public byte[] render(String c){return ("legacy:"+c).getBytes();} }
FrageBridgeAdapter
Primäre Absichtzwei Dimensionen unabhängigbestehende Schnittstelle übersetzen
Testfokusstabiler Vertragaustauschbares Verhalten
Risikozu starres Gerüstzu viele kleine Varianten

Lab 13 · Abstract Factory vs Factory Method Cloud Provider

Abstract Factory vs Factory Method

Abstract Factory

Wähle es, wenn: Produktfamilien.

Factory Method

Wähle es, wenn: ein Produkt erweitern.

Java-Beispiel: Abstract Factory

Java-Beispiel: Abstract Factory
package com.aydinsude.workbench.decision;
public record CloudResources(String queue,String storage) {}

Java-Beispiel: Factory Method

Java-Beispiel: Factory Method
package com.aydinsude.workbench.decision;
// Design Pattern: Abstract Factory - konsistente Produktfamilie erzeugen.
public interface CloudResourceFactory { CloudResources create(); }
FrageAbstract FactoryFactory Method
Primäre AbsichtProduktfamilienein Produkt erweitern
Testfokusstabiler Vertragaustauschbares Verhalten
Risikozu starres Gerüstzu viele kleine Varianten

Lab 14 · Prototype vs Builder Vertragskonfiguration

Prototype vs Builder

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);} }
FragePrototypeBuilder
Primäre AbsichtVorlage kopierenkomplex neu aufbauen
Testfokusstabiler Vertragaustauschbares Verhalten
Risikozu starres Gerüstzu viele kleine Varianten

Lab 15 · Memento vs Event Sourcing Editor/Aggregat

Memento vs Event Sourcing

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) {}
FrageMementoEvent Sourcing
Primäre AbsichtSnapshot wiederherstellenHistorie als Quelle
Testfokusstabiler Vertragaustauschbares Verhalten
Risikozu starres Gerüstzu viele kleine Varianten

Lab 16 · Null Object vs Optional Benachrichtigung/Kunde

Null Object vs Optional

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); }
FrageNull ObjectOptional
Primäre Absichtpolymorphes Standardverhaltenexplizite Abwesenheit
Testfokusstabiler Vertragaustauschbares Verhalten
Risikozu starres Gerüstzu 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 vs Object Pool

Flyweight

Wähle es, wenn: gemeinsamen unveränderlichen Zustand zwischen vielen Objekten teilen.

Object Pool

Wähle es, wenn: teure, wiederverwendbare Instanzen kontrolliert ausleihen.

Java-Beispiel: Flyweight

Java-Beispiel: Flyweight
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);} }
}
EntscheidungsfrageFlyweightObject Pool
Primäre Absichtgemeinsamen unveränderlichen Zustand zwischen vielen Objekten teilenteure, wiederverwendbare Instanzen kontrolliert ausleihen
Typischer Vorteilgezielte strukturelle Wirkungbewusst einfachere Alternative
Hauptrisikounnötige InfrastrukturGrenzen werden zu spät sichtbar
TestfokusVerhalten unter Last und FehlernKonsistenz des einfachen Pfads

Lab 18 · Interpreter vs Specification Sprache vs Regelmodell

Interpreter vs Specification

Interpreter

Wähle es, wenn: eine kleine Sprache oder Grammatik auswerten.

Specification

Wähle es, wenn: fachliche Regeln kombinierbar und testbar ausdrücken.

Java-Beispiel: Interpreter

Java-Beispiel: Interpreter
package com.aydinsude.workbench.decision.block3;
// Design Pattern: Interpreter - Knoten einer kleinen Ausdruckssprache.
public sealed interface Expression permits EqualsExpression, AndExpression { boolean evaluate(java.util.Map<String,String> context); }

Java-Beispiel: Specification

Java-Beispiel: Specification
package com.aydinsude.workbench.decision.block3;
// Design Pattern: Specification - kombinierbare fachliche Regel.
@FunctionalInterface public interface RuleSpecification<T>{ boolean isSatisfiedBy(T value); default RuleSpecification<T> and(RuleSpecification<T> other){return v->isSatisfiedBy(v)&&other.isSatisfiedBy(v);} }
EntscheidungsfrageInterpreterSpecification
Primäre Absichteine kleine Sprache oder Grammatik auswertenfachliche Regeln kombinierbar und testbar ausdrücken
Typischer Vorteilgezielte strukturelle Wirkungbewusst einfachere Alternative
Hauptrisikounnötige InfrastrukturGrenzen werden zu spät sichtbar
TestfokusVerhalten unter Last und FehlernKonsistenz des einfachen Pfads

Lab 19 · Event Sourcing vs Audit Log Quelle der Wahrheit

Event Sourcing vs Audit Log

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) {}
EntscheidungsfrageEvent SourcingAudit Log
Primäre Absichtden Zustand vollständig aus Ereignissen rekonstruierenÄnderungen zusätzlich zum aktuellen Zustand protokollieren
Typischer Vorteilgezielte strukturelle Wirkungbewusst einfachere Alternative
Hauptrisikounnötige InfrastrukturGrenzen werden zu spät sichtbar
TestfokusVerhalten unter Last und FehlernKonsistenz des einfachen Pfads

Lab 20 · CQRS vs CRUD Komplexität der Modelle

CQRS vs CRUD

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));} }
EntscheidungsfrageCQRSCRUD
Primäre AbsichtLese- und Schreibmodelle bewusst trennenein gemeinsames Modell für einfache Datenpflege nutzen
Typischer Vorteilgezielte strukturelle Wirkungbewusst einfachere Alternative
Hauptrisikounnötige InfrastrukturGrenzen werden zu spät sichtbar
TestfokusVerhalten unter Last und FehlernKonsistenz des einfachen Pfads

Lab 21 · Saga vs Two-Phase Commit Verfügbarkeit vs Atomarität

Saga vs Two-Phase Commit

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;} } }

Java-Beispiel: Two-Phase Commit

Java-Beispiel: Two-Phase Commit
package com.aydinsude.workbench.decision.block3;
// Pattern: Two-Phase Commit - Teilnehmer unterstützt Prepare/Commit.
public interface TwoPhaseParticipant { boolean prepare(); void commit(); void rollback(); }
EntscheidungsfrageSagaTwo-Phase Commit
Primäre Absichtverteilte Schritte mit Kompensationen koordinierenatomare Transaktionen über unterstützte Ressourcen erzwingen
Typischer Vorteilgezielte strukturelle Wirkungbewusst einfachere Alternative
Hauptrisikounnötige InfrastrukturGrenzen werden zu spät sichtbar
TestfokusVerhalten unter Last und FehlernKonsistenz des einfachen Pfads

Lab 22 · Transactional Outbox vs Dual Write Konsistenz bei Messaging

Transactional Outbox vs Dual Write

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();} }
EntscheidungsfrageTransactional OutboxDual Write
Primäre AbsichtDatenänderung und Event atomar in einer DB speichernDB und Broker direkt nacheinander schreiben
Typischer Vorteilgezielte strukturelle Wirkungbewusst einfachere Alternative
Hauptrisikounnötige InfrastrukturGrenzen werden zu spät sichtbar
TestfokusVerhalten unter Last und FehlernKonsistenz des einfachen Pfads

Lab 23 · Circuit Breaker vs Retry Schutz vs Wiederholung

Circuit Breaker vs Retry

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;} }
EntscheidungsfrageCircuit BreakerRetry
Primäre Absichtanhaltend fehlerhafte Abhängigkeiten zeitweise nicht aufrufenvorübergehende Fehler begrenzt erneut versuchen
Typischer Vorteilgezielte strukturelle Wirkungbewusst einfachere Alternative
Hauptrisikounnötige InfrastrukturGrenzen werden zu spät sichtbar
TestfokusVerhalten unter Last und FehlernKonsistenz des einfachen Pfads

Lab 24 · Dependency Injection vs Service Locator Explizitheit der Kopplung

Dependency Injection vs Service Locator

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));} }
EntscheidungsfrageDependency InjectionService Locator
Primäre AbsichtAbhängigkeiten explizit über Konstruktoren bereitstellenAbhängigkeiten zur Laufzeit aus einem globalen Container holen
Typischer Vorteilgezielte strukturelle Wirkungbewusst einfachere Alternative
Hauptrisikounnötige InfrastrukturGrenzen werden zu spät sichtbar
TestfokusVerhalten unter Last und FehlernKonsistenz 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 vs Layered Architecture

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); }
}
EntscheidungsfrageHexagonal ArchitectureLayered Architecture
Primäre Absichtfachliche Abhängigkeiten nach außen richteneine überschaubare Anwendung in technische Schichten gliedern
Typischer Einsatzhöhere Entkopplung oder explizite fachliche Modellierungdirekterer, häufig einfacherer Kontrollfluss
Hauptrisikozusätzliche Abstraktionen ohne echten Änderungsdruckzu enge Kopplung, sobald Varianten oder Last wachsen
TestfokusGrenzen, Austauschbarkeit und FehlerszenarienTransaktions- und Ablaufkonsistenz

Lab 26 · Domain Service vs Application Service Regel vs Orchestrierung

Domain Service vs Application Service

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"; }
}
EntscheidungsfrageDomain ServiceApplication Service
Primäre Absichteine domänenweite Regel ohne natürliche Entity-Zuordnung ausdrückeneinen Use Case, Transaktion und Ports koordinieren
Typischer Einsatzhöhere Entkopplung oder explizite fachliche Modellierungdirekterer, häufig einfacherer Kontrollfluss
Hauptrisikozusätzliche Abstraktionen ohne echten Änderungsdruckzu enge Kopplung, sobald Varianten oder Last wachsen
TestfokusGrenzen, Austauschbarkeit und FehlerszenarienTransaktions- und Ablaufkonsistenz

Lab 27 · Value Object vs Entity Wert vs Identität

Value Object vs Entity

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; }
}
EntscheidungsfrageValue ObjectEntity
Primäre Absichtfachliche Bedeutung durch unveränderliche Werte und Wertgleichheit ausdrückenein Objekt über eine stabile Identität und Lebenszyklus verfolgen
Typischer Einsatzhöhere Entkopplung oder explizite fachliche Modellierungdirekterer, häufig einfacherer Kontrollfluss
Hauptrisikozusätzliche Abstraktionen ohne echten Änderungsdruckzu enge Kopplung, sobald Varianten oder Last wachsen
TestfokusGrenzen, Austauschbarkeit und FehlerszenarienTransaktions- und Ablaufkonsistenz

Lab 28 · API Gateway vs Facade Systemgrenze vs Subsystem

API Gateway vs Facade

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; }
}
EntscheidungsfrageAPI GatewayFacade
Primäre Absichtexterne Requests routen und zentrale Edge-Funktionen anwendeneine komplexe interne API für einen Client vereinfachen
Typischer Einsatzhöhere Entkopplung oder explizite fachliche Modellierungdirekterer, häufig einfacherer Kontrollfluss
Hauptrisikozusätzliche Abstraktionen ohne echten Änderungsdruckzu enge Kopplung, sobald Varianten oder Last wachsen
TestfokusGrenzen, Austauschbarkeit und FehlerszenarienTransaktions- und Ablaufkonsistenz

Lab 29 · Bulkhead vs Rate Limiter Isolation vs Durchsatz

Bulkhead vs Rate Limiter

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; }
}
EntscheidungsfrageBulkheadRate Limiter
Primäre AbsichtFehler und Ressourcenverbrauch zwischen Abhängigkeiten isolierendie Anzahl von Aufrufen pro Zeitfenster begrenzen
Typischer Einsatzhöhere Entkopplung oder explizite fachliche Modellierungdirekterer, häufig einfacherer Kontrollfluss
Hauptrisikozusätzliche Abstraktionen ohne echten Änderungsdruckzu enge Kopplung, sobald Varianten oder Last wachsen
TestfokusGrenzen, Austauschbarkeit und FehlerszenarienTransaktions- und Ablaufkonsistenz

Lab 30 · Cache-Aside vs Read-Through Cache Anwendungssteuerung vs Cache-Abstraktion

Cache-Aside vs Read-Through Cache

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); }
EntscheidungsfrageCache-AsideRead-Through Cache
Primäre AbsichtLaden und Invalidierung explizit in der Anwendung steuerndas Nachladen vollständig hinter dem Cache kapseln
Typischer Einsatzhöhere Entkopplung oder explizite fachliche Modellierungdirekterer, häufig einfacherer Kontrollfluss
Hauptrisikozusätzliche Abstraktionen ohne echten Änderungsdruckzu enge Kopplung, sobald Varianten oder Last wachsen
TestfokusGrenzen, Austauschbarkeit und FehlerszenarienTransaktions- und Ablaufkonsistenz

Lab 31 · Optimistic Locking vs Pessimistic Locking Konflikterkennung vs Sperre

Optimistic Locking vs Pessimistic Locking

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();} }
}
EntscheidungsfrageOptimistic LockingPessimistic Locking
Primäre Absichtseltene Konflikte über Versionsprüfung beim Schreiben erkennenKonflikte durch exklusiven Zugriff vor der Änderung verhindern
Typischer Einsatzhöhere Entkopplung oder explizite fachliche Modellierungdirekterer, häufig einfacherer Kontrollfluss
Hauptrisikozusätzliche Abstraktionen ohne echten Änderungsdruckzu enge Kopplung, sobald Varianten oder Last wachsen
TestfokusGrenzen, Austauschbarkeit und FehlerszenarienTransaktions- und Ablaufkonsistenz

Lab 32 · Event-Driven vs Request-Response Entkopplung vs unmittelbare Antwort

Event-Driven vs Request-Response

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); }
EntscheidungsfrageEvent-DrivenRequest-Response
Primäre Absichtmehrere unabhängige Reaktionen asynchron oder entkoppelt auslöseneine direkte Antwort als Teil des Aufrufvertrags benötigen
Typischer Einsatzhöhere Entkopplung oder explizite fachliche Modellierungdirekterer, häufig einfacherer Kontrollfluss
Hauptrisikozusätzliche Abstraktionen ohne echten Änderungsdruckzu enge Kopplung, sobald Varianten oder Last wachsen
TestfokusGrenzen, Austauschbarkeit und FehlerszenarienTransaktions- 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 vs Registry

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) {}
}
EntscheidungsfrageFactoryRegistry
Primäre AbsichtObjekte anhand eines Erzeugungsfalls kapselnImplementierungen dynamisch über Schlüssel auffinden
HauptrisikoFactory kann mit wachsender Variantenanzahl zentral werden.Registry kann zu einem versteckten Service Locator entarten.
PrüffrageIst 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 vs Repository

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) {}
}
EntscheidungsfrageUnit of WorkRepository
Primäre Absichtmehrere Änderungen als atomare Arbeitseinheit koordinierenAggregate wie eine fachliche Sammlung laden und speichern
HauptrisikoUnit of Work kann Transaktionsgrenzen verschleiern.Repository kann zu generischem CRUD ohne Domänensprache verarmen.
PrüffrageIst 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 vs Integration Event

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.

Entscheidung

Domain Events dürfen domänennah sein; Integration Events brauchen einen stabilen, versionierten Vertrag.

Entscheidung
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) {}
}
EntscheidungsfrageDomain EventIntegration Event
Primäre Absichteine fachlich relevante Tatsache innerhalb einer Domäne ausdrückeneine stabile Nachricht an andere Systeme veröffentlichen
HauptrisikoDomain Events sollten nicht ungeprüft extern veröffentlicht werden.Integration Events dürfen das interne Modell nicht offenlegen.
PrüffrageIst 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 vs Adapter

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){}
}
EntscheidungsfrageAnti-Corruption LayerAdapter
Primäre Absichtdas eigene Domänenmodell gegen ein fremdes Modell schützeneine inkompatible technische Schnittstelle passend machen
HauptrisikoACL ist für triviale Integrationen oft zu schwer.Ein einzelner Adapter reicht bei semantischen Konflikten nicht.
PrüffrageIst 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 vs Big Bang Rewrite

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);}
}
EntscheidungsfrageStrangler FigBig Bang Rewrite
Primäre Absichtein Legacy-System schrittweise durch neue Pfade ersetzenein System vollständig neu bauen und zu einem Stichtag ersetzen
HauptrisikoStrangler erzeugt vorübergehend Doppelstrukturen.Big Bang birgt hohes fachliches und terminliches Risiko.
PrüffrageIst 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 vs Saga

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){}
}
EntscheidungsfrageProcess ManagerSaga
Primäre Absichteinen langlebigen fachlichen Prozess mit Zustand koordiniereneine verteilte Transaktion über lokale Schritte und Kompensationen realisieren
HauptrisikoProcess Manager kann zum zentralen God Object werden.Saga braucht explizite Kompensationen und Idempotenz.
PrüffrageIst 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 vs Cache

Materialized View

Wähle es, wenn: eine gezielt modellierte Leseprojektion persistent aufbauen.

Cache

Wähle es, wenn: teure Zugriffe temporär beschleunigen.

Entscheidung

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){}}
}
EntscheidungsfrageMaterialized ViewCache
Primäre Absichteine gezielt modellierte Leseprojektion persistent aufbauenteure Zugriffe temporär beschleunigen
HauptrisikoProjektionen brauchen Wiederaufbau und Konsistenzstrategie.Caches verursachen Invalidierungs- und Stale-Data-Probleme.
PrüffrageIst 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 vs Branch by Abstraction

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);}
}
EntscheidungsfrageFeature ToggleBranch by Abstraction
Primäre Absichtneues Verhalten zur Laufzeit kontrolliert aktiviereneine Implementierung hinter einer stabilen Abstraktion schrittweise austauschen
HauptrisikoAlte Toggles werden schnell zu dauerhafter Komplexität.Abstraktionen können ohne Migrationsziel unnötig bleiben.
PrüffrageIst 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

Strategy kompakt erklärt

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

  1. Änderungsachse und bestehendes Verhalten mit Tests sichern.
  2. Stabilen Vertrag extrahieren.
  3. Eine konkrete Variante hinter den Vertrag verschieben.
  4. 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);
        }
    }
}
VerwendenNicht verwendenTestfokus
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

State kompakt erklärt

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

  1. Änderungsachse und bestehendes Verhalten mit Tests sichern.
  2. Stabilen Vertrag extrahieren.
  3. Eine konkrete Variante hinter den Vertrag verschieben.
  4. 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;
        }
    }
}
VerwendenNicht verwendenTestfokus
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

Adapter kompakt erklärt

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

  1. Änderungsachse und bestehendes Verhalten mit Tests sichern.
  2. Stabilen Vertrag extrahieren.
  3. Eine konkrete Variante hinter den Vertrag verschieben.
  4. 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));
        }
    }
}
VerwendenNicht verwendenTestfokus
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

Facade kompakt erklärt

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

  1. Änderungsachse und bestehendes Verhalten mit Tests sichern.
  2. Stabilen Vertrag extrahieren.
  3. Eine konkrete Variante hinter den Vertrag verschieben.
  4. 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);
        }
    }
}
VerwendenNicht verwendenTestfokus
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

Builder kompakt erklärt

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

  1. Änderungsachse und bestehendes Verhalten mit Tests sichern.
  2. Stabilen Vertrag extrahieren.
  3. Eine konkrete Variante hinter den Vertrag verschieben.
  4. 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);
        }
    }
}
VerwendenNicht verwendenTestfokus
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

Factory Method kompakt erklärt

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

  1. Änderungsachse und bestehendes Verhalten mit Tests sichern.
  2. Stabilen Vertrag extrahieren.
  3. Eine konkrete Variante hinter den Vertrag verschieben.
  4. 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);
        };
    }
}
VerwendenNicht verwendenTestfokus
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

Decorator kompakt erklärt

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

  1. Änderungsachse und bestehendes Verhalten mit Tests sichern.
  2. Stabilen Vertrag extrahieren.
  3. Eine konkrete Variante hinter den Vertrag verschieben.
  4. 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);
            }
        }
    }
}
VerwendenNicht verwendenTestfokus
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

Observer kompakt erklärt

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

  1. Änderungsachse und bestehendes Verhalten mit Tests sichern.
  2. Stabilen Vertrag extrahieren.
  3. Eine konkrete Variante hinter den Vertrag verschieben.
  4. 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));
        }
    }
}
VerwendenNicht verwendenTestfokus
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

Specification kompakt erklärt

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

  1. Änderungsachse und bestehendes Verhalten mit Tests sichern.
  2. Stabilen Vertrag extrahieren.
  3. Eine konkrete Variante hinter den Vertrag verschieben.
  4. 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());
    }
}
VerwendenNicht verwendenTestfokus
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

Repository kompakt erklärt

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

  1. Änderungsachse und bestehendes Verhalten mit Tests sichern.
  2. Stabilen Vertrag extrahieren.
  3. Eine konkrete Variante hinter den Vertrag verschieben.
  4. 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);
        }
    }
}
VerwendenNicht verwendenTestfokus
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

Proxy kompakt erklärt

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

  1. Bestehendes Verhalten mit Characterization Tests sichern.
  2. Die konkrete Änderungsachse und den stabilen Vertrag benennen.
  3. Eine Verantwortung hinter den neuen Vertrag verschieben.
  4. 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);
        }
    }
}
VerwendenNicht verwendenTestfokus
Autorisierung, Lazy Loading, Remote- oder Caching-GrenzenWenn nur lokales Verhalten ergänzt wird und keine Stellvertretersemantik bestehtVertragstest für Proxy und Zielobjekt; Negativtest für verweigerten Zugriff

Karte 12 · Command Problem → Struktur → Java → Grenze

Command kompakt erklärt

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

  1. Bestehendes Verhalten mit Characterization Tests sichern.
  2. Die konkrete Änderungsachse und den stabilen Vertrag benennen.
  3. Eine Verantwortung hinter den neuen Vertrag verschieben.
  4. 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); }
}
VerwendenNicht verwendenTestfokus
Audit, Queueing, Retry, Undo oder entkoppelte AnwendungsfälleFür triviale direkte Methodenaufrufe ohne zusätzliche LebenszyklusanforderungenCommand-Handler isoliert testen; Idempotenz und Fehlerpfade prüfen

Karte 13 · Chain of Responsibility Problem → Struktur → Java → Grenze

Chain of Responsibility kompakt erklärt

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

  1. Bestehendes Verhalten mit Characterization Tests sichern.
  2. Die konkrete Änderungsachse und den stabilen Vertrag benennen.
  3. Eine Verantwortung hinter den neuen Vertrag verschieben.
  4. 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); }
    }
}
VerwendenNicht verwendenTestfokus
Validierung, Fraud Checks, Freigaben oder Support-RoutingWenn Reihenfolge und Anzahl der Schritte dauerhaft trivial bleibenJeden Handler separat und die Reihenfolge als Integrationskette testen

Karte 14 · Template Method Problem → Struktur → Java → Grenze

Template Method kompakt erklärt

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

  1. Bestehendes Verhalten mit Characterization Tests sichern.
  2. Die konkrete Änderungsachse und den stabilen Vertrag benennen.
  3. Eine Verantwortung hinter den neuen Vertrag verschieben.
  4. 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);
    }
}
VerwendenNicht verwendenTestfokus
Stabile Batch-, Import- oder Exportabläufe mit wenigen VariantenBei vielen unabhängigen Änderungsachsen oder tiefer VererbungshierarchieAblaufreihenfolge und jeden Hook über konkrete Testimplementierungen prüfen

Karte 15 · Composite Problem → Struktur → Java → Grenze

Composite kompakt erklärt

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

  1. Bestehendes Verhalten mit Characterization Tests sichern.
  2. Die konkrete Änderungsachse und den stabilen Vertrag benennen.
  3. Eine Verantwortung hinter den neuen Vertrag verschieben.
  4. 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);
        }
    }
}
VerwendenNicht verwendenTestfokus
Produktpakete, Berechtigungsbäume, Organisations- oder DokumentstrukturenWenn Hierarchie flach ist und Gruppen keine fachliche Bedeutung besitzenBlätter, verschachtelte Gruppen und leere Kompositionen testen

Karte 16 · Visitor Problem → Struktur → Java → Grenze

Visitor kompakt erklärt

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

  1. Bestehendes Verhalten mit Characterization Tests sichern.
  2. Die konkrete Änderungsachse und den stabilen Vertrag benennen.
  3. Eine Verantwortung hinter den neuen Vertrag verschieben.
  4. 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); }
}
VerwendenNicht verwendenTestfokus
Reporting, Export, Validierung oder Berechnung über stabile ElementtypenWenn häufig neue Elementtypen hinzukommen und Operationen stabil bleibenJede Element-Visitor-Kombination und vollständige Traversierung testen

Karte 17 · Bridge Problem → Struktur → Java → Grenze

Bridge kompakt erklärt

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

  1. Bestehendes Verhalten mit Characterization Tests sichern.
  2. Die konkrete Änderungsachse und den stabilen Vertrag benennen.
  3. Eine Verantwortung hinter den neuen Vertrag verschieben.
  4. 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"); }
    }
}
VerwendenNicht verwendenTestfokus
Dokumenttyp x Renderer, Fachprozess x Provider oder Gerät x PlattformWenn nur eine Änderungsachse existiert oder Kombinationen nicht unabhängig sindAbstraktion gegen mehrere Implementierungen als Vertragsmatrix testen

Karte 18 · Abstract Factory Problem → Struktur → Java → Grenze

Abstract Factory kompakt erklärt

Fachliches Problem

Zusammenpassende Familien technischer Komponenten konsistent erzeugen

Code Smell

Konfiguration mischt Provider-Komponenten verschiedener Familien

Struktur

Eine Factory liefert mehrere aufeinander abgestimmte Produkte

Alternative

Factory Method für nur einen Produkttyp; Builder für komplexe Einzelobjekte

Refactoring-Einstieg

  1. Bestehendes Verhalten mit Characterization Tests sichern.
  2. Die konkrete Änderungsachse und den stabilen Vertrag benennen.
  3. Eine Verantwortung hinter den neuen Vertrag verschieben.
  4. 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;
        }
    }
}
VerwendenNicht verwendenTestfokus
Cloud-Provider, Datenbankfamilien, UI-Themes oder TestumgebungenWenn nur ein unabhängiges Produkt erzeugt wirdJede Produktfamilie als konsistente Kombination testen

Karte 19 · Prototype Problem → Struktur → Java → Grenze

Prototype kompakt erklärt

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

  1. Bestehendes Verhalten mit Characterization Tests sichern.
  2. Die konkrete Änderungsachse und den stabilen Vertrag benennen.
  3. Eine Verantwortung hinter den neuen Vertrag verschieben.
  4. 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);
        }
    }
}
VerwendenNicht verwendenTestfokus
Vertrags-, Produkt- oder Workflowvorlagen mit vielen gemeinsamen WertenBei einfachen Objekten oder unklarer Tiefenkopie veränderlicher DatenKopie, Unabhängigkeit und unveränderte Vorlage prüfen

Karte 20 · Mediator Problem → Struktur → Java → Grenze

Mediator kompakt erklärt

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

  1. Bestehendes Verhalten mit Characterization Tests sichern.
  2. Die konkrete Änderungsachse und den stabilen Vertrag benennen.
  3. Eine Verantwortung hinter den neuen Vertrag verschieben.
  4. 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);
        }
    }
}
VerwendenNicht verwendenTestfokus
UI-Dialoge, komplexe Workflows oder ModulkoordinationWenn der Mediator selbst zur unstrukturierten God Class wirdInteraktionsregeln 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

Iterator Diagramm

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

Memento Diagramm

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

Flyweight Diagramm

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

Interpreter Diagramm

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

Null Object Diagramm

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

Object Pool Diagramm

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

Registry Diagramm

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

Unit of Work Diagramm

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(); }
  }
}

Karte 29 Domain Event Fachlich relevantes Geschehen explizit ausdrücken

Domain Event Diagramm

Code Smell

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

Transactional Outbox Diagramm

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

Saga Diagramm

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

Process Manager Diagramm

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; }
  }
}

Karte 33 Circuit Breaker Fehlerhafte Remote-Abhängigkeiten temporär abschirmen

Circuit Breaker Diagramm

Code Smell

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

Retry Diagramm

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

Bulkhead Diagramm

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

Rate Limiter Diagramm

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

Cache Aside Diagramm

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

CQRS Diagramm

Code Smell

Ein Modell muss gleichzeitig Invarianten und komplexe Abfragen optimieren

Struktur

Commands ändern Zustand, Queries lesen spezialisierte Projektionen

Verwenden

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

Event Sourcing Diagramm

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();
  }
}

Karte 40 Materialized View Teure Abfragen als vorab berechnete Sicht bereitstellen

Materialized View Diagramm

Code Smell

Jede Anfrage aggregiert große Datenmengen erneut

Struktur

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

API Gateway Diagramm

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

Backend for Frontend Diagramm

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

Service Mesh Diagramm

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

Anti-Corruption Layer Diagramm

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

Strangler Fig Diagramm

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

Branch by Abstraction Diagramm

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

Feature Toggle Diagramm

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; }
  }
}

Karte 48 Idempotent Consumer Mehrfach gelieferte Nachrichten genau einmal fachlich wirksam machen

Idempotent Consumer Diagramm

Code Smell

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

Competing Consumers Diagramm

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

Dead Letter Channel Diagramm

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.

Patternkarten: 60/100 Patternkarten abgeschlossen

40 Patternkarten offen.

Karte 51 Message Router Nachrichten anhand klarer Regeln an passende Kanäle leiten

Message Router Diagramm

Code Smell

Routing steckt verteilt in if/switch-Blöcken

Struktur

Ein Router kapselt Auswahlregeln und Zielkanäle

Verwenden

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);
    }
  }
}

Karte 52 Content-Based Router Nachrichten anhand fachlicher Inhalte dynamisch verteilen

Content-Based Router Diagramm

Code Smell

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);
    }
  }
}

Karte 53 Message Translator Unterschiedliche Nachrichtenmodelle explizit übersetzen

Message Translator Diagramm

Code Smell

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

Splitter Diagramm

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

Aggregator Diagramm

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));
    }
  }
}

Karte 56 Resequencer Außer Reihenfolge eingetroffene Nachrichten korrekt sortieren

Resequencer Diagramm

Code Smell

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

Claim Check Diagramm

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

Wire Tap Diagramm

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

Inbox Pattern Diagramm

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

Polling Consumer Diagramm

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

Layered Architecture Diagramm

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

Hexagonal Architecture Diagramm

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

Clean Architecture Diagramm

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

Onion Architecture Diagramm

Code Smell

Domänenobjekte referenzieren Services und Infrastruktur

Struktur

Domänenmodell innen, Domänendienste darum, Infrastruktur außen

Verwenden

DDD-Systeme mit reichhaltigem Domänenmodell

Nicht verwenden

Bei rein datengetriebenen CRUD-Modellen

Alternative

Clean Architecture bei stärkerem Use-Case-Fokus

Testfokus

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

Modular Monolith Diagramm

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

Microkernel Diagramm

Code Smell

Jede Produktvariante verändert denselben zentralen Code

Struktur

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

Service Layer Diagramm

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

Active Record Diagramm

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

Data Mapper Diagramm

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

Identity Map Diagramm

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(); }
  }
}
Kapitel 14 Pattern-Karten: Persistenz-Mapping10 Pattern-Karten

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

Lazy Load Diagramm

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

Query Object Diagramm

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

Table Data Gateway Diagramm

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

Row Data Gateway Diagramm

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

Metadata Mapping Diagramm

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

Embedded Value Diagramm

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) {}
}

Karte 77 Serialized LOB Komplexen, selten abgefragten Objektzustand als Ganzes speichern

Serialized LOB Diagramm

Code Smell

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

Single Table Inheritance Diagramm

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

Class Table Inheritance Diagramm

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

Concrete Table Inheritance Diagramm

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

Transaction Script Diagramm

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

Domain Model Diagramm

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

Aggregate Diagramm

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

Entity Diagramm

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

Value Object Diagramm

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

Domain Service Diagramm

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

Application Service Diagramm

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);}
  }
}

Karte 88 Policy Austauschbare fachliche Entscheidung explizit benennen

Policy Diagramm

Code Smell

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

Module Diagramm

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

Published Language Diagramm

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.

Patternkarten: 100/100 Patternkarten abgeschlossen

0 Patternkarten offen.

Karte 91 Event Notification Ein Ereignis signalisiert nur, dass sich etwas geändert hat

Event Notification Diagramm

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

Event-Carried State Transfer Diagramm

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

Request-Reply Diagramm

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

Correlation Identifier Diagramm

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

Return Address Diagramm

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

Normalizer Diagramm

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

Canonical Data Model Diagramm

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

Distributed Lock Diagramm

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

Leader Election Diagramm

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

Sidecar Diagramm

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.

Fachlich vertieft: 20 von 20 KernpatternsDeutschsprachige Quellen · Einsatzgrenzen · Abgrenzung · Java-Bezug
01

Strategy

Austauschbare fachliche Verfahren

Verhaltensmuster

Fachliche Einordnung

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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.
  • Übergangsregel: bestimmt, welcher Folgezustand entsteht.

Typischer Ablauf

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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.

Beteiligte Rollen

  • Prozesszustand: hält Korrelation und Fortschritt.
  • Event Handler: verarbeitet Rückmeldungen.
  • Command Sender: startet den nächsten Schritt.
  • Timeout/Recovery: behandelt ausbleibende Antworten.

Typischer Ablauf

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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.
  • Query Model: liefert abfrageoptimierte Projektionen.
  • Projection: aktualisiert Lesemodelle.
  • Synchronisationsmechanismus: definiert Konsistenz und Verzögerung.

Typischer Ablauf

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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.

Beteiligte Rollen

  • Event Store: persistiert geordnete Ereignisströme.
  • Aggregate: rekonstruiert Zustand und erzeugt neue Events.
  • Projection: baut Lesemodelle.
  • Snapshot optional: verkürzt lange Replays.

Typischer Ablauf

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Aufrufer startet einen fachlichen Vorgang.
  2. Die stabile Grenze nimmt typisierte Eingaben entgegen.
  3. Die konkrete Pattern-Rolle führt ihre klar abgegrenzte Verantwortung aus.
  4. Das Ergebnis oder Ereignis wird explizit an den nächsten Beteiligten übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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.

Fachlich vertieft: 40 zentrale Patterns20 zusätzliche Fachkapitel · deutschsprachige Quellen · keine übersetzten Ein-Satz-Texte
21

Template Method

Stabilen Ablauf vorgeben, einzelne Schritte gezielt variieren

Verhaltensmuster

Fachliche Einordnung

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

  1. Der Aufrufer startet genau eine öffentliche Template-Methode.
  2. Die Basisklasse prüft gemeinsame Vorbedingungen.
  3. Feste und variable Schritte werden in dokumentierter Reihenfolge ausgeführt.
  4. Hooks ergänzen Verhalten, ohne den Gesamtprozess neu zu ordnen.
  5. 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.

Deutschsprachige fachliche Grundlagen

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.

Beteiligte Rollen

  • Component: kleinster gemeinsamer fachlicher Vertrag.
  • 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

  1. Der Client startet eine Operation am Wurzelelement.
  2. Ein Blatt berechnet sein eigenes Ergebnis.
  3. Ein Composite delegiert an seine Kinder.
  4. Teilergebnisse werden nach einer klaren Aggregationsregel kombiniert.
  5. 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

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

  1. Der Client wählt einen Visitor für eine fachliche Aufgabe.
  2. Die Struktur traversiert ihre Elemente.
  3. Jedes Element ruft visitor.visit(this) auf.
  4. Der konkrete Visitor verarbeitet den exakten Typ.
  5. 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

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

  1. Der Client wählt eine fachliche Abstraktion.
  2. Bei der Konstruktion wird ein Implementor zugeordnet.
  3. Die Abstraktion entscheidet fachlich, was passieren soll.
  4. Der Implementor führt den technischen Teil aus.
  5. 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

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.

25

Abstract Factory

Zusammenpassende Objektfamilien konsistent erzeugen

Erzeugungsmuster

Fachliche Einordnung

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.
  • Concrete Products: zusammenpassende Implementierungen.

Typischer Ablauf

  1. Der Composition Root wählt eine konkrete Fabrik.
  2. Der Client fordert Produkte nur über abstrakte Methoden an.
  3. Die Fabrik erzeugt eine konsistente Familie.
  4. Der Client verwendet ausschließlich Produktverträge.
  5. 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

  1. Eine valide Vorlage wird erstellt oder aus einer Registry geladen.
  2. Der Client fordert eine Kopie an.
  3. Veränderliche Unterobjekte werden gemäß definierter Semantik kopiert.
  4. Erlaubte Abweichungen werden über typisierte with-Methoden gesetzt.
  5. 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.

Typische Fehlanwendungen

  • flache Kopien teilen unbeabsichtigt veränderliche Collections
  • technische IDs oder Auditdaten werden mitkopiert
  • 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

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

  1. Eine Komponente meldet eine fachlich benannte Aktion oder Änderung.
  2. Der Mediator entscheidet, welche anderen Komponenten reagieren müssen.
  3. Er ruft diese in einer expliziten Reihenfolge auf.
  4. Ergebnisse werden zusammengeführt oder weitergereicht.
  5. 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

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

  1. Der Client fordert eine Traversierung mit klarer Reihenfolge an.
  2. Der Iterator hält Cursor oder Stack.
  3. next liefert genau ein Element und bewegt den Zustand.
  4. Fehler, Ende und Ressourcenfreigabe sind definiert.
  5. 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

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

  1. Vor einer riskanten Änderung erzeugt der Originator ein Memento.
  2. Der Caretaker speichert es ohne interne Interpretation.
  3. Die Änderung wird ausgeführt.
  4. Bei Abbruch oder Undo wird das Memento zurückgegeben.
  5. 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

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

  1. Ein Schlüssel beschreibt den intrinsischen Zustand.
  2. Die Factory liefert eine vorhandene oder neue immutable Instanz.
  3. Der Client hält nur individuellen Kontext.
  4. Bei der Operation wird Kontext an das Flyweight übergeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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.

31

Null Object

Definiertes neutrales Verhalten statt verstreuter Null-Prüfungen

Verhaltensmuster

Fachliche Einordnung

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.

Beteiligte Rollen

  • Service- oder Policy-Vertrag.
  • Real Object: führt die normale Funktion aus.
  • Null Object: implementiert dokumentierte neutrale Semantik.
  • Composition Root: entscheidet explizit, welche Implementierung verwendet wird.

Typischer Ablauf

  1. Der Composition Root wählt reale oder neutrale Implementierung.
  2. Der Client ruft immer denselben Vertrag auf.
  3. Das Null Object liefert ein definiertes Ergebnis oder führt bewusst nichts aus.
  4. Metriken oder Audit machen den neutralen Pfad sichtbar, falls betrieblich relevant.

Wann sinnvoll?

  • optionale Benachrichtigung oder Audit-Senke besitzt klare No-op-Semantik
  • viele identische null-Prüfungen verschleiern den Ablauf
  • ein neutrales Ergebnis fachlich korrekt ist

Wann nicht?

  • Abwesenheit ein Fehler ist
  • der Aufrufer zwischen gefunden und nicht gefunden unterscheiden muss - dann ist Optional oder Result besser
  • No-op Verhalten kritische Vorgänge lautlos unterdrücken würde

Abgrenzung zu ähnlichen Mustern

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

  1. Der Application Service startet eine Arbeitseinheit.
  2. Mehrere Aggregate werden geladen und verändert.
  3. Repositories registrieren oder persistieren Änderungen im gemeinsamen Kontext.
  4. Die Unit of Work prüft Nebenläufigkeit und bestätigt atomar.
  5. 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

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.

33

Circuit Breaker

Wahrscheinlich fehlschlagende Remote-Aufrufe vorübergehend stoppen

Resilienzmuster

Fachliche Einordnung

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.
  • State Store: hält Zustand und Messfenster.
  • Fallback optional: liefert bewusst degradiertes Ergebnis.

Typischer Ablauf

  1. Im geschlossenen Zustand werden Aufrufe zugelassen und bewertet.
  2. Überschreitet die Fehlerquote den Grenzwert, öffnet der Breaker.
  3. Im offenen Zustand scheitern Aufrufe sofort.
  4. Nach einer Wartezeit werden wenige Testaufrufe halb offen zugelassen.
  5. 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

  1. Der erste Aufruf wird innerhalb einer Gesamtdauer gestartet.
  2. Ein Fehler wird klassifiziert.
  3. Nur transiente Fehler erhalten einen nächsten Versuch.
  4. Backoff und Jitter verhindern synchronisierte Lastspitzen.
  5. 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

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

  1. Ein Aufruf wird einer Workload-Klasse zugeordnet.
  2. Nur der zugehörige Pool darf Ressourcen bereitstellen.
  3. Bei Kapazität wird der Aufruf ausgeführt.
  4. Bei Erschöpfung wird früh abgelehnt oder begrenzt gepuffert.
  5. 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

  1. Ein Request wird einem Limit Key zugeordnet.
  2. Kosten oder Tokenbedarf werden berechnet.
  3. Die Policy prüft das verfügbare Budget atomar.
  4. Der Request wird zugelassen oder mit klarer Rückmeldung abgelehnt.
  5. 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

  1. Der Leser berechnet einen stabilen Cache-Key.
  2. Ein Hit wird nur verwendet, wenn Version und Gültigkeit passen.
  3. Bei Miss lädt der Store.
  4. Der Wert wird mit TTL gespeichert.
  5. 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

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

  1. Der Consumer empfängt Nachricht und Kennung.
  2. Inbox und fachliche Änderung werden in einer lokalen Transaktion koordiniert.
  3. Ist die Kennung neu, wird verarbeitet und als erledigt markiert.
  4. Ist sie bekannt, wird ohne erneute Wirkung bestätigt.
  5. Fehler lassen die Nachricht erneut zustellen oder gezielt quarantänisieren.

Wann sinnvoll?

  • Broker mindestens-einmal zustellt
  • Consumer seiteneffektbehaftete Operationen ausführt
  • 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.

Deutschsprachige fachliche Grundlagen

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

  1. Ein Verarbeitungsversuch schlägt fehl.
  2. Die Policy zählt Versuche und klassifiziert die Ursache.
  3. Nach Überschreiten des Budgets wird Nachricht mit Fehlerkontext verschoben.
  4. Monitoring erzeugt Alarm und Zuständigkeit.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Client fordert eine Ressource mit Deadline an.
  2. Der Pool liefert eine freie, validierte Instanz oder erzeugt innerhalb der Kapazitätsgrenze eine neue.
  3. Der Client verwendet die Ressource ausschließlich im vereinbarten Scope.
  4. Beim Schließen wird die Instanz validiert und zurückgesetzt.
  5. 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
  • Objekte tragen schwer zurücksetzbaren fachlichen Zustand
  • 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Client sendet eine Anfrage an den öffentlichen Endpunkt.
  2. Das Gateway validiert Transport- und Sicherheitsanforderungen.
  3. Eine Route bestimmt den zuständigen Dienst oder eine kontrollierte Aggregation.
  4. Antworten werden ohne Verlust fachlicher Fehlersemantik zurückgegeben.
  5. 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.

Deutschsprachige fachliche Grundlagen

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.
  • Domänendienste: bleiben gemeinsame fachliche Quelle.
  • Gateway/Identity Provider: übernimmt übergreifende Eintritts- und Identitätsaufgaben.

Typischer Ablauf

  1. Ein Client ruft sein eigenes BFF auf.
  2. Das BFF prüft clientbezogene Eingaben und baut benötigte Backend-Aufrufe.
  3. Mehrere fachliche Antworten werden bei Bedarf clientgerecht kombiniert.
  4. Das Ergebnis enthält nur die für diesen Client erforderlichen Daten.
  5. Ä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.

Deutschsprachige fachliche Grundlagen

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

  1. Ein Dienst adressiert einen anderen Dienst über dessen logischen Namen.
  2. Der lokale Proxy baut eine gesicherte Verbindung auf.
  3. Routing-, Retry- oder Timeoutregeln werden an der Infrastrukturgrenze angewendet.
  4. Der Zielproxy authentifiziert die Workload-Identität.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Producer legen unabhängige Arbeitseinheiten in die Queue.
  2. Freie Consumer konkurrieren um verfügbare Nachrichten.
  3. Eine Nachricht wird für die Bearbeitungsdauer einem Consumer zugeordnet.
  4. Erfolg wird bestätigt; Fehler führen zu Retry oder Dead Letter.
  5. 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.

Deutschsprachige fachliche Grundlagen

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.
  • Output Channels: repräsentieren klar benannte Zielpfade.
  • Unroutable/Invalid Channel: behandelt nicht zuordenbare Nachrichten.

Typischer Ablauf

  1. Der Router empfängt eine unveränderte Nachricht.
  2. Eine Policy liest nur die für die Entscheidung nötigen Merkmale.
  3. Genau eine oder bewusst mehrere Zielrouten werden bestimmt.
  4. Die Nachricht wird ohne versteckte Nebenwirkung weitergeleitet.
  5. 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.

Deutschsprachige fachliche Grundlagen

Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.

Content-Based Router

Zielkanäle anhand fachlich relevanter Nachrichteninhalte auswählen

Integrationsmuster

Fachliche Einordnung

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

  1. Die Nachricht wird gegen Schema und Pflichtfelder validiert.
  2. Routingmerkmale werden in ein kleines Decision Input übersetzt.
  3. Regeln werden in dokumentierter Priorität geprüft.
  4. Ein Ziel oder eine explizite Mehrfachzustellung wird ausgewählt.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Die Quellnachricht wird gegen ihr Schema validiert.
  2. Felder werden in semantische Zwischenwerte überführt.
  3. Einheiten, Codes und Strukturen werden gezielt übersetzt.
  4. Das Zielobjekt wird vollständig validiert.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Die Eingangsnachricht wird validiert.
  2. Eine Split-Policy bestimmt die fachlichen Einheiten.
  3. Für jedes Teil wird eine neue Nachricht mit Sequenzmetadaten erzeugt.
  4. Teile werden unabhängig verarbeitet.
  5. 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.

Typische Fehlanwendungen

  • fehlende Correlation- und Sequenzmetadaten
  • riesige Teilmengen überlasten den Broker
  • Teilnachrichten verlieren gemeinsamen fachlichen Kontext

Java- und Enterprise-Beispiel

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.

Deutschsprachige fachliche Grundlagen

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

  1. Eine Teilnachricht wird anhand ihrer Correlation-ID geladen.
  2. Duplikate werden erkannt und ignoriert oder aktualisiert.
  3. Der Zwischenstand wird atomar ergänzt.
  4. Die Completion Policy prüft Anzahl, Endsignal oder Deadline.
  5. 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.

Deutschsprachige fachliche Grundlagen

Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.

Resequencer

Außer Reihenfolge eingetroffene Nachrichten kontrolliert ordnen

Integrationsmuster

Fachliche Einordnung

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.
  • Correlation Key: trennt unabhängige Sequenzen.
  • Buffer Store: hält vorzeitig eingetroffene Nachrichten.
  • Gap/Timeout Policy: entscheidet über fehlende Positionen.

Typischer Ablauf

  1. Eine Nachricht wird ihrer Sequenzgruppe zugeordnet.
  2. Ist sie die nächste erwartete Position, wird sie freigegeben.
  3. Spätere Positionen werden gepuffert.
  4. Nach jeder Freigabe werden direkt folgende Pufferwerte ausgespielt.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Producer speichert die Nutzlast und erhält ein Token.
  2. Eine kleine Nachricht mit Token und Metadaten wird veröffentlicht.
  3. Der Consumer prüft Identität und Berechtigung.
  4. Die Nutzlast wird geladen und gegen Prüfsumme oder Version validiert.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Eine Nachricht passiert einen markierten Kanalpunkt.
  2. Der Tap erzeugt eine unveränderliche Kopie oder ein redigiertes Beobachtungsereignis.
  3. Der Hauptfluss wird unabhängig fortgesetzt.
  4. Der Beobachtungszweig verarbeitet asynchron.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Eine Nachricht wird mit stabiler ID empfangen.
  2. Ein Insert-if-absent registriert sie lokal.
  3. Neue Einträge werden fachlich verarbeitet.
  4. Fachänderung und Inbox-Status werden in einer lokalen Transaktion bestätigt.
  5. 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.

Deutschsprachige fachliche Grundlagen

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.
  • Checkpoint/Acknowledgement: bestätigt Fortschritt.

Typischer Ablauf

  1. Der Consumer prüft freie Verarbeitungskapazität.
  2. Er ruft einen begrenzten Batch mit Lease oder Cursor ab.
  3. Nachrichten werden verarbeitet und bestätigt.
  4. Bei leerer Quelle wird das Intervall kontrolliert erhöht.
  5. 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.

Deutschsprachige fachliche Grundlagen

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.
  • Context/Envelope: trägt notwendige Metadaten explizit.

Typischer Ablauf

  1. Ein Input wird in einen typisierten Pipelinevertrag überführt.
  2. Jeder Filter validiert seine Vorbedingungen.
  3. Das Ergebnis wird unverändert oder transformiert an den nächsten Schritt gegeben.
  4. Fehler stoppen, verzweigen oder kompensieren nach definierter Policy.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Ein Publisher erzeugt ein versioniertes Ereignis.
  2. Der Broker verteilt es an passende Abonnements.
  3. Jeder Subscriber verarbeitet seine eigene Zustellung.
  4. Fehler und Retries eines Subscribers beeinflussen andere nicht.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Requester erzeugt Request-ID, Deadline und Return Address.
  2. Die Anfrage wird veröffentlicht.
  3. Der Replier verarbeitet innerhalb der Deadline.
  4. Die Antwort trägt die Correlation-ID des Requests.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Am Beginn eines Vorgangs wird eine Correlation-ID bestimmt.
  2. Jede abgeleitete Nachricht übernimmt sie.
  3. Neue Message-IDs bleiben pro Nachricht eindeutig.
  4. Antworten und Teilresultate können dem Vorgang zugeordnet werden.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Requester wählt einen registrierten Antwortkanal.
  2. Die Adresse wird mit Request und Correlation-ID gesendet.
  3. Der Replier validiert die Adresse gegen seine Policy.
  4. Die Antwort wird an den angegebenen Kanal veröffentlicht.
  5. 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.

Deutschsprachige fachliche Grundlagen

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.
  • Canonical Contract: einheitliches internes Modell.
  • Validation Policy: prüft Semantik und Pflichtinformationen.

Typischer Ablauf

  1. Eingangstyp und Schema-Version werden erkannt.
  2. Der passende Translator wird ausgewählt.
  3. Codes, Einheiten und Strukturen werden normalisiert.
  4. Der gemeinsame Vertrag wird vollständig validiert.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Ein Adapter übersetzt die externe Anfrage in einen typisierten Use-Case-Aufruf.
  2. Die Anwendungsschicht koordiniert Domänenobjekte und notwendige Ports.
  3. Die Domäne entscheidet ohne Kenntnis von HTTP, SQL oder Frameworks.
  4. Infrastrukturadapter erfüllen die angeforderten technischen Leistungen.
  5. 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.

Deutschsprachige fachliche Grundlagen

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.
  • Driving Port: beschreibt aufrufbare Anwendungsleistungen.
  • Driven Port: beschreibt benötigte externe Leistungen.
  • Adapter: übersetzt Protokolle und Datenmodelle zwischen Port und Technik.

Typischer Ablauf

  1. Ein eingehender Adapter nimmt eine Anfrage entgegen.
  2. Er übersetzt sie in den Vertrag eines Driving Ports.
  3. Der Kern führt den Use Case aus und verwendet bei Bedarf Driven Ports.
  4. Ausgehende Adapter sprechen konkrete Infrastruktur an.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Ein äußerer Adapter erstellt ein Input Model.
  2. Ein Use Case verarbeitet es und arbeitet mit Entitäten.
  3. Benötigte Infrastruktur wird über innere Schnittstellen angefordert.
  4. Adapter implementieren diese Schnittstellen außen.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Ein äußerer Adapter ruft einen Application Service auf.
  2. Dieser koordiniert Domänenobjekte und Domain Services.
  3. Benötigte externe Leistungen werden über innere Interfaces beschrieben.
  4. Äußere Adapter implementieren diese Verträge.
  5. 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.

Deutschsprachige fachliche Grundlagen

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.
  • Architecture Tests: verhindern unerlaubte Querverbindungen.

Typischer Ablauf

  1. Ein Aufruf erreicht das zuständige Fachmodul.
  2. Andere Module verwenden ausschließlich dessen veröffentlichte API.
  3. Interne Typen und Tabellen bleiben verborgen.
  4. Domänenereignisse entkoppeln optionale Folgeaktionen.
  5. Die Anwendung wird gemeinsam gebaut und deployt.

Wann sinnvoll?

  • 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.

Deutschsprachige fachliche Grundlagen

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 API/SPI: versionierter Erweiterungsvertrag.
  • Plugin: implementiert optionale oder kundenspezifische Fähigkeit.
  • Registry/Loader: entdeckt, prüft und aktiviert Erweiterungen.

Typischer Ablauf

  1. Der Kern startet und lädt seine Basiskonfiguration.
  2. Ein Loader entdeckt verfügbare Plugins.
  3. Kompatibilität und Berechtigungen werden geprüft.
  4. Plugins registrieren ihre Fähigkeiten über das SPI.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Ein Adapter ruft eine benannte Anwendungsoperation auf.
  2. Der Service prüft anwendungsweite Vorbedingungen und startet den Transaktionsrahmen.
  3. Domänenobjekte treffen fachliche Entscheidungen.
  4. Ports speichern Ergebnisse oder veröffentlichen Ereignisse.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Ein Datensatz wird als Active-Record-Objekt geladen.
  2. Der Client ändert Felder oder ruft einfache Fachmethoden auf.
  3. save oder delete übersetzt den Zustand in SQL.
  4. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Ein Repository fordert ein Aggregate an.
  2. Der Mapper liest Datensätze und rekonstruiert Value Objects und Entities.
  3. Die Domäne arbeitet ohne Persistenzkenntnis.
  4. Beim Speichern extrahiert der Mapper den relevanten Zustand.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Ein Repository erhält eine ID.
  2. Es prüft die Identity Map des aktuellen Kontexts.
  3. Bei Treffer wird dieselbe Instanz zurückgegeben.
  4. Bei Fehlen wird geladen und registriert.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Ein Objekt wird mit ungeladenem Teilzustand erzeugt.
  2. Der erste Zugriff prüft den Ladezustand.
  3. Der Loader beschafft die Daten genau einmal.
  4. Der Wert wird im Objekt oder Kontext gespeichert.
  5. 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.

Deutschsprachige fachliche Grundlagen

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.

Beteiligte Rollen

  • Query Object: unveränderliche fachliche Suchbeschreibung.
  • Query Handler/Translator: übersetzt in konkrete Datenquellenabfrage.
  • Result Projection: definiert benötigte Ergebnisform.
  • Repository/Gateway: führt die übersetzte Abfrage aus.

Typischer Ablauf

  1. Der Client erstellt ein Query Object aus validierten Kriterien.
  2. Ein Handler wählt Datenquelle und Projektion.
  3. Ein Translator erzeugt Parameter und technische Query.
  4. Die Datenquelle führt paginiert aus.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Der Client ruft eine benannte Gatewayoperation auf.
  2. Das Gateway erzeugt parametriertes SQL.
  3. Die Datenbank liefert Zeilen.
  4. Das Gateway bildet sie in Records oder DTOs ab.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Ein Finder lädt eine Zeile.
  2. Er erzeugt ein Gateway mit Spaltenwerten und Identität.
  3. Der Client ändert Werte oder fordert delete an.
  4. Das Gateway führt ein parametriertes Update aus.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Beim Start werden Mappinginformationen geladen.
  2. Die Engine baut ein Metamodell auf.
  3. Queries und Schreiboperationen verwenden dieses Modell.
  4. Objektzustand wird reflektiv oder generiert übertragen.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Die Entity wird aus einer Tabellenzeile gelesen.
  2. Mehrere Spalten werden an den Value-Object-Konstruktor übergeben.
  3. Der Konstruktor prüft Invarianten.
  4. Änderungen ersetzen das unveränderliche Value Object vollständig.
  5. 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.

Typische Fehlanwendungen

  • öffentliche Setter erzeugen ungültige Teilzustände
  • Spaltennullbarkeit widerspricht den Invarianten
  • 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.

Deutschsprachige fachliche Grundlagen

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

  1. Die Anwendung erzeugt einen versionierten Snapshot.
  2. Ein Serializer schreibt JSON, XML oder Binärdaten.
  3. Der LOB wird atomar gespeichert.
  4. Beim Laden wird Format und Version geprüft.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Eine Query liest die gemeinsame Tabelle.
  2. Der Diskriminator bestimmt den Subtyp.
  3. Gemeinsame und relevante subtypeigene Spalten werden abgebildet.
  4. Ä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
  • Datenbankconstraints subtypeigene Pflichtfelder erzwingen sollen
  • Hierarchie häufig erweitert wird

Abgrenzung zu ähnlichen Mustern

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.

Deutschsprachige fachliche Grundlagen

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

  1. Eine polymorphe Query liest Basistabelle und relevante Subtabellen.
  2. Gemeinsame ID verbindet die Zeilen.
  3. Der Mapper erkennt den vorhandenen Subtyp.
  4. Ä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.

Typische Fehlanwendungen

  • Hierarchie wird zu tief
  • Fetchpläne laden unnötig alle Subtypen
  • Schemaänderungen erfordern komplexe koordinierte Migrationen

Java- und Enterprise-Beispiel

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.

Deutschsprachige fachliche Grundlagen

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.

Beteiligte Rollen

  • Concrete Tables: enthalten vollständige Daten jedes konkreten Typs.
  • 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

  1. Eine subtypebezogene Query greift auf genau eine Tabelle zu.
  2. Der Mapper erzeugt den konkreten Typ.
  3. Polymorphe Queries vereinigen mehrere Tabellen.
  4. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Eingabe wird validiert und in Fachwerte übersetzt.
  2. Benötigte Daten werden geladen.
  3. Regeln werden in definierter Reihenfolge ausgewertet.
  4. Änderungen werden innerhalb einer Transaktion gespeichert.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Ein Use Case lädt ein Aggregat.
  2. Fachwerte werden als Value Objects erzeugt.
  3. Das Aggregat führt eine benannte fachliche Operation aus.
  4. Invarianten werden innerhalb der Konsistenzgrenze geprüft.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Repository lädt die Root mit benötigtem Zustand.
  2. Aufrufer ruft eine fachlich benannte Operation auf.
  3. Root delegiert intern und prüft Invarianten.
  4. Änderungen werden atomar gespeichert.
  5. 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.

Typische Fehlanwendungen

  • Repository-Methode speichert interne Entität einzeln
  • 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.

Deutschsprachige fachliche Grundlagen

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

  1. Entity wird über Factory oder Aggregat erzeugt.
  2. Identität wird festgelegt.
  3. Änderungen erfolgen über fachliche Methoden.
  4. Methoden prüfen erlaubte Übergänge.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Rohdaten werden an der Systemgrenze eingelesen.
  2. Factory prüft Format und Invarianten.
  3. Use Cases verwenden den typisierten Wert.
  4. Berechnungen erzeugen neue Instanzen.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Application Service lädt benötigte Aggregate.
  2. Er übergibt Fachobjekte an den Domain Service.
  3. Der Service trifft eine fachliche Entscheidung.
  4. Aggregate übernehmen daraus zulässige Änderungen.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Eingabe wird autorisiert und validiert.
  2. Benötigte Aggregate werden geladen.
  3. Fachliche Operationen werden aufgerufen.
  4. Änderungen werden in einer Transaktion gespeichert.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Use Case baut den Entscheidungskontext auf.
  2. Policy bewertet relevante Fakten.
  3. Sie liefert Ergebnis und Begründung.
  4. Aggregate wenden die zulässige Entscheidung an.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Fachliche Begriffe werden nach Verantwortungsgebiet gruppiert.
  2. Nur benötigte Typen werden öffentlich gemacht.
  3. Andere Module verwenden die stabile Oberfläche.
  4. Interne Struktur kann unabhängig refaktoriert werden.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Ein Kontext definiert den öffentlichen Vertrag.
  2. Interne Modelle werden auf die veröffentlichte Sprache abgebildet.
  3. Consumer validieren und übersetzen den Vertrag.
  4. Änderungen erfolgen kompatibel oder über neue Version.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Sender ändert seinen Zustand.
  2. Nach Commit wird eine Benachrichtigung publiziert.
  3. Consumer korreliert und entscheidet, ob Daten benötigt werden.
  4. Consumer fragt den aktuellen Zustand ab.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Sender speichert Domänenänderung und Outbox atomar.
  2. Publisher veröffentlicht ein versioniertes Ereignis.
  3. Consumer dedupliziert die Nachricht.
  4. Payload wird in lokales Modell übersetzt.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Sender erzeugt Anfrage und Korrelation.
  2. Anfrage wird an den Bearbeiter übertragen.
  3. Bearbeiter erzeugt typisierte Antwort.
  4. Antwort wird zur angegebenen Adresse gesendet.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Ein Vorgang erhält eine stabile Kennung.
  2. Jede ausgehende Nachricht trägt sie im Envelope.
  3. Adapter übernehmen oder mappen die Kennung.
  4. Logs und Metriken referenzieren denselben Kontext.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Sender wählt einen erlaubten Rückkanal.
  2. Adresse wird im Envelope transportiert.
  3. Responder verarbeitet die Anfrage.
  4. Router validiert und verwendet die Rückadresse.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Adapter erkennt Format und Version.
  2. Parser validiert technische Struktur.
  3. Normalizer übersetzt fachliche Begriffe.
  4. Kanonisches Modell wird erneut validiert.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Quellsystem mappt sein lokales Modell auf den Kanon.
  2. Schema wird validiert und versioniert übertragen.
  3. Zielsystem mappt in sein lokales Modell.
  4. Inkompatible Änderungen werden früh erkannt.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Instanz fordert eine Lease an.
  2. Store vergibt monotonen Fencing Token.
  3. Operation akzeptiert nur aktuellen Token.
  4. Lease wird erneuert oder läuft aus.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Kandidaten versuchen atomar eine Lease zu erwerben.
  2. Gewinner erhält neue Epoche.
  3. Leader führt koordinierende Aufgaben aus.
  4. Follower beobachten Ablauf oder Heartbeat.
  5. 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.

Deutschsprachige fachliche Grundlagen

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

  1. Anwendung startet mit Sidecar in derselben Einheit.
  2. Aufrufe oder Telemetrie gehen über lokale Schnittstelle.
  3. Sidecar übernimmt technische Integration.
  4. Health Checks betrachten beide Prozesse.
  5. 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.

Deutschsprachige fachliche Grundlagen

Eigenständige fachredaktionelle Zusammenfassung auf Basis der verlinkten deutschsprachigen Quellen und der vorhandenen Java-Beispiele. Keine längeren Quellpassagen wurden übernommen.

⌂ Cockpit