300 vertiefte Refactorings

Refactoring Catalog

Alle zugehörigen Inhalte befinden sich auf dieser einen großen Seite. Kapitel und Beispiele sind standardmäßig geschlossen und lassen sich gezielt öffnen.

0 von 330 offen

30 Hauptkapitel · 300 enthaltene Lernbereiche · keine Navigation durch Einzeldateien nötig.

Qualitätsstatus:300 von 300 Refactorings ausführlich vertieft. Nur Hauptkapitel sind klappbar; alle Unterabschnitte bleiben sichtbar.
Kapitel 1 Methoden extrahieren und Abläufe zerlegen10 Refactorings

Zehn grundlegende Refactorings werden als sichere Transformationen gezeigt: vom Code Smell über kleine Schritte bis zum kompilierbaren Java-21-Zielcode und Test.

1. Extract Method

Extract Method

Fachliche Einordnung

Eine lange Methode vermischt Berechnung, Validierung und Ausgabe. Einzelne Abschnitte besitzen zwar eine erkennbare Aufgabe, sind aber nicht benannt.

Ein zusammenhängender Codeabschnitt wird in eine Methode mit fachlich aussagekräftigem Namen verschoben. Der Aufrufer beschreibt danach den Ablauf, die extrahierte Methode erklärt das Detail.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Zuerst durch Tests das beobachtbare Verhalten sichern
  2. Dann Ein- und Ausgaben des Fragments bestimmen, lokale Variablen als Parameter oder Rückgabewert behandeln, extrahieren, kompilieren und nach jedem kleinen Schritt testen.

Wann sinnvoll?

Sinnvoll bei wechselnden Abstraktionsebenen, wiederholten Kommentaren oder schwer isolierbaren Teilberechnungen. Nicht sinnvoll, wenn nur eine triviale Zeile ohne eigenen Begriff verschoben wird.

Risiken und Fehlanwendungen

Ein häufiger Fehler ist, mechanisch sehr kleine Methoden ohne fachlichen Namen zu erzeugen. Ebenso problematisch sind lange Parameterlisten; dann ist oft Introduce Parameter Object der nächste Schritt.

Enterprise-Java-Beispiel

In einem InvoiceService wird die Prüfung der Rabattberechtigung aus calculateInvoice() als determineDiscount(...) extrahiert. Dadurch kann die Regel separat getestet und später durch eine Policy ersetzt werden.

Vorhandener Java-21-Zielcode

InvoiceTotalCalculator.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
import java.util.List;
// Refactoring: Extract Method
// Zweck: Rabattberechnung benennen und die Hauptmethode auf einer Abstraktionsebene halten.
public final class InvoiceTotalCalculator {
  public BigDecimal total(List<BigDecimal> positions, boolean premium) {
    BigDecimal subtotal = positions.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
    return subtotal.subtract(discountFor(subtotal, premium));
  }
  private BigDecimal discountFor(BigDecimal subtotal, boolean premium) {
    return premium ? subtotal.multiply(new BigDecimal("0.10")) : BigDecimal.ZERO;
  }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

2. Inline Method

Inline Method

Fachliche Einordnung

Eine Methode delegiert nur, besitzt keinen eigenständigen Begriff oder versteckt den tatsächlichen Ablauf hinter unnötiger Indirektion.

Der Methodenrumpf wird an den Aufrufstellen eingesetzt und die bedeutungslose Methode entfernt. Das reduziert Navigation und macht eine spätere, sinnvollere Neuaufteilung möglich.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Alle Aufrufer ermitteln, Verhalten absichern, Methodenrumpf einsetzen, Namenskonflikte bereinigen, kompilieren und erst danach die alte Methode entfernen.

Wann sinnvoll?

Sinnvoll bei reinem Durchreichen oder nach missglückter Überextraktion. Nicht anwenden, wenn die Methode einen wichtigen Fachbegriff ausdrückt oder eine stabile Erweiterungsgrenze bildet.

Risiken und Fehlanwendungen

Gefährlich ist das Inlining, wenn dadurch Duplikate entstehen oder eine fachliche Entscheidung wieder in technische Abläufe zurückwandert.

Enterprise-Java-Beispiel

Ein CustomerService besitzt isPreferredCustomer(), das nur customer.preferred() zurückgibt. Wenn der Name keinen Mehrwert bietet, kann der Aufruf direkt verwendet werden.

Vorhandener Java-21-Zielcode

CustomerPriority.java
package com.aydinsude.workbench.catalog;
// Refactoring: Inline Method
// Zweck: Eine bedeutungslose Weiterleitung entfernen, wenn der Ausdruck selbst klarer ist.
public final class CustomerPriority {
  public boolean isPriority(int openCases, boolean vip) {
    return vip || openCases >= 5;
  }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

3. Extract Variable

Extract Variable

Fachliche Einordnung

Ein komplexer Ausdruck enthält mehrere fachliche Teilgedanken und muss beim Lesen mental zerlegt werden.

Teilresultate erhalten sprechende, unveränderliche Variablen. Der Code dokumentiert damit die Bedeutung der Berechnung und erleichtert Debugging sowie gezielte Tests.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Ausdruck schrittweise zerlegen, aussagekräftige Namen wählen, Seiteneffekte ausschließen, nach jeder Ersetzung kompilieren und Verhalten testen.

Wann sinnvoll?

Sinnvoll bei verschachtelten Bedingungen, langen Berechnungen oder wiederverwendeten Teilwerten. Nicht sinnvoll für offensichtliche Ausdrücke, deren Lesbarkeit durch zusätzliche Namen sinkt.

Risiken und Fehlanwendungen

Schlechte Namen wie tmp oder value verschieben nur die Unklarheit. Außerdem darf die Reihenfolge von Ausdrücken mit Seiteneffekten nicht unbemerkt verändert werden.

Enterprise-Java-Beispiel

Eine Preisberechnung benennt netAmount, taxAmount und grossAmount, statt alle Operationen in einem einzigen return-Ausdruck zu verschachteln.

Vorhandener Java-21-Zielcode

ShipmentEligibility.java
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Variable
// Zweck: Fachliche Teilbedingungen sichtbar und unabhängig prüfbar machen.
public final class ShipmentEligibility {
  public boolean eligible(int weightKg, boolean hazardous, boolean addressVerified) {
    boolean withinWeightLimit = weightKg <= 30;
    boolean safeGoods = !hazardous;
    return withinWeightLimit && safeGoods && addressVerified;
  }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

4. Inline Variable

Inline Variable

Fachliche Einordnung

Eine lokale Variable wiederholt nur einen klaren Ausdruck, wird einmal genutzt und erschwert andere Refactorings.

Die Verwendung wird durch den ursprünglichen Ausdruck ersetzt; die Variable entfällt.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Sicherstellen, dass der Ausdruck keine Seiteneffekte besitzt und billig reproduzierbar ist
  2. Dann Verwendung ersetzen, kompilieren, testen und Deklaration entfernen.

Wann sinnvoll?

Sinnvoll bei bedeutungslosen Zwischenvariablen oder wenn sie Extract Method verhindert. Nicht sinnvoll, wenn der Name fachliche Bedeutung trägt oder eine teure Berechnung mehrfach ausgeführt würde.

Risiken und Fehlanwendungen

Das Inlining darf keine mehrfachen Datenbank-, Zeit- oder Zufallsaufrufe erzeugen.

Enterprise-Java-Beispiel

Eine Variable eligible = policy.isEligible(order) kann bei einmaliger Nutzung inline stehen; ein berechneter Wechselkurs sollte dagegen bewusst gespeichert bleiben.

Vorhandener Java-21-Zielcode

OrderReference.java
package com.aydinsude.workbench.catalog;
// Refactoring: Inline Variable
// Zweck: Eine Variable entfernen, die keinen zusätzlichen Fachbegriff trägt.
public final class OrderReference {
  public String format(String tenant, long sequence) {
    return tenant.toUpperCase() + "-" + sequence;
  }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

5. Change Function Declaration

Change Function Declaration

Fachliche Einordnung

Name, Parameter oder Rückgabetyp einer Funktion drücken ihren Vertrag nicht klar aus.

Die Deklaration wird so geändert, dass sie den fachlichen Zweck, notwendige Eingaben und ein typisiertes Ergebnis eindeutig vermittelt.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Öffentliche Nutzung ermitteln, Tests ergänzen, neuen Vertrag parallel anbieten oder atomar umstellen, alle Aufrufer migrieren und alten Vertrag kontrolliert entfernen.

Wann sinnvoll?

Sinnvoll bei unklaren Namen, primitiven Parametern oder fehleranfälligen Rückgabewerten. Vorsicht bei veröffentlichten APIs; dort sind Kompatibilitätsadapter und Deprecation nötig.

Risiken und Fehlanwendungen

Ein bloßes Umbenennen ohne Verbesserung der Semantik bringt wenig. Zu viele Parameter sind oft ein Hinweis auf Parameter Object oder Context Object.

Enterprise-Java-Beispiel

process(String, int) wird zu approveClaim(ClaimId, ApprovalLimit) und liefert ApprovalDecision statt boolean.

Vorhandener Java-21-Zielcode

PaymentAuthorizer.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Change Function Declaration
// Zweck: Namen und Parameter machen den Autorisierungsvertrag explizit.
public final class PaymentAuthorizer {
  public Authorization authorizePayment(String accountId, BigDecimal amount) {
    if (accountId == null || accountId.isBlank()) throw new IllegalArgumentException("accountId");
    return new Authorization(accountId, amount, amount.signum() > 0);
  }
  public record Authorization(String accountId, BigDecimal amount, boolean approved) {}
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

6. Encapsulate Variable

Encapsulate Variable

Fachliche Einordnung

Ein veränderlicher Wert ist direkt von vielen Stellen erreichbar; Änderungen und Invarianten sind nicht kontrollierbar.

Zugriffe laufen über eine klar definierte API. Lesen und Schreiben können validiert, protokolliert oder später durch eine andere Speicherung ersetzt werden.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Direkte Zugriffe suchen, Getter oder fachliche Operationen einführen, Schreibzugriffe migrieren, Feldzugriff einschränken und Tests für Invarianten ergänzen.

Wann sinnvoll?

Sinnvoll bei globalem Zustand, öffentlich veränderbaren Feldern oder Konfiguration. Nicht als Ausrede für wahllose Getter/Setter verwenden.

Risiken und Fehlanwendungen

Technische Setter ohne Fachsprache kapseln die Variable nur syntaktisch. Besser sind Operationen wie activate(), reserve() oder changeAddress().

Enterprise-Java-Beispiel

Ein öffentliches creditLimit wird privat; Änderungen erfolgen nur noch über adjustCreditLimit(...) mit Berechtigungs- und Grenzprüfung.

Vorhandener Java-21-Zielcode

ExchangeRateTable.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal; import java.util.Map; import java.util.concurrent.ConcurrentHashMap;
// Refactoring: Encapsulate Variable
// Zweck: Änderungen validieren und interne Repräsentation verbergen.
public final class ExchangeRateTable {
  private final Map<String, BigDecimal> rates = new ConcurrentHashMap<>();
  public void update(String currency, BigDecimal rate) {
    if (rate.signum() <= 0) throw new IllegalArgumentException("rate");
    rates.put(currency, rate);
  }
  public BigDecimal rateFor(String currency) { return rates.get(currency); }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

7. Rename Variable

Rename Variable

Fachliche Einordnung

Ein Variablenname beschreibt Herkunft, Einheit oder fachliche Bedeutung nicht zuverlässig.

Der Name wird so gewählt, dass Leser Zweck und Einheit ohne zusätzliche Kommentare verstehen.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Bedeutung im Kontext klären, alle Verwendungen automatisiert umbenennen, öffentliche Verträge separat betrachten und Tests ausführen.

Wann sinnvoll?

Sinnvoll bei Abkürzungen, generischen Namen und verwechselbaren Einheiten. Nicht überpräzisieren, wenn der enge Kontext die Bedeutung bereits eindeutig macht.

Risiken und Fehlanwendungen

Namen dürfen keine falsche Sicherheit erzeugen: amountInCents ist nur korrekt, wenn der Typ und alle Aufrufer tatsächlich Cent verwenden.

Enterprise-Java-Beispiel

amt wird zu invoiceTotal, timeout zu requestTimeoutMillis oder besser zu Duration requestTimeout.

Vorhandener Java-21-Zielcode

RiskScore.java
package com.aydinsude.workbench.catalog;
// Refactoring: Rename Variable
// Zweck: Fachliche Bedeutung statt Implementierungsabkürzungen ausdrücken.
public final class RiskScore {
  public int calculate(int missedPayments, int fraudSignals) {
    int paymentRiskPoints = missedPayments * 10;
    int fraudRiskPoints = fraudSignals * 25;
    return paymentRiskPoints + fraudRiskPoints;
  }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

8. Introduce Parameter Object

Introduce Parameter Object

Fachliche Einordnung

Mehrere Parameter treten wiederholt gemeinsam auf und bilden fachlich eine Einheit.

Die zusammengehörigen Werte werden in einem unveränderlichen Typ gebündelt. Dadurch wird der Vertrag kürzer und Regeln finden einen natürlichen Ort.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Parametergruppe identifizieren, Value Object oder Record mit Validierung anlegen, neuen Parameter hinzufügen, Aufrufer schrittweise migrieren und alte Parameter entfernen.

Wann sinnvoll?

Sinnvoll bei Data Clumps und langen Signaturen. Nicht sinnvoll, wenn Werte nur zufällig einmal gemeinsam vorkommen oder der neue Typ keine kohärente Bedeutung besitzt.

Risiken und Fehlanwendungen

Ein bloßer Datencontainer kann zum Sammelbecken werden. Der Typ sollte Invarianten, Einheiten und sinnvolle Operationen ausdrücken.

Enterprise-Java-Beispiel

startDate, endDate und timezone werden zu BillingPeriod; der Typ verhindert ein Enddatum vor dem Startdatum.

Vorhandener Java-21-Zielcode

DeliveryPlanner.java
package com.aydinsude.workbench.catalog;
import java.time.LocalDate;
// Refactoring: Introduce Parameter Object
// Pattern: Value Object
// Zweck: Zusammengehörige Lieferparameter konsistent validieren und weiterreichen.
public final class DeliveryPlanner {
  public String plan(DeliveryRequest request) {
    return request.postalCode()+":"+request.requestedDate()+":"+request.express();
  }
  public record DeliveryRequest(String postalCode, LocalDate requestedDate, boolean express) {
    public DeliveryRequest { if (postalCode == null || postalCode.isBlank()) throw new IllegalArgumentException("postalCode"); }
  }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

9. Combine Functions into Class

Combine Functions into Class

Fachliche Einordnung

Mehrere Funktionen arbeiten ständig auf denselben Daten und reichen dieselben Parameter weiter.

Daten und eng zusammengehörige Operationen werden in einer Klasse gebündelt. Gemeinsamer Zustand und Fachsprache werden explizit.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Gemeinsame Daten identifizieren, Klasse oder Value Object anlegen, Funktionen schrittweise verschieben, Aufrufer migrieren und Duplikate entfernen.

Wann sinnvoll?

Sinnvoll bei hoher Kohäsion und wiederkehrenden Parametergruppen. Nicht sinnvoll, wenn Funktionen unabhängig bleiben oder die Klasse nur ein Utility-Sammelbecken würde.

Risiken und Fehlanwendungen

Die neue Klasse darf nicht zur God Class wachsen. Verantwortungsgrenzen und unveränderlicher Zustand sind entscheidend.

Enterprise-Java-Beispiel

PricingContext bündelt Positionen, Kundensegment und Datum; calculateBasePrice(), discount() und tax() werden als klar getrennte Operationen modelliert.

Vorhandener Java-21-Zielcode

TemperatureReading.java
package com.aydinsude.workbench.catalog;
// Refactoring: Combine Functions into Class
// Zweck: Berechnungen besitzen einen gemeinsamen unveränderlichen Fachkontext.
public record TemperatureReading(double celsius) {
  public double fahrenheit() { return celsius * 9 / 5 + 32; }
  public boolean freezing() { return celsius <= 0; }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

10. Combine Functions into Transform

Combine Functions into Transform

Fachliche Einordnung

Mehrere Ableitungen werden aus denselben Eingangsdaten berechnet und sollen gemeinsam als Ergebnis verfügbar sein.

Eine Transformationsfunktion erzeugt ein neues, unveränderliches Ergebnisobjekt mit allen abgeleiteten Werten.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Eingabe und gewünschte Ableitungen definieren, Result-Typ einführen, Berechnungen in eine reine Transformation verschieben und Aufrufer auf das Ergebnisobjekt umstellen.

Wann sinnvoll?

Sinnvoll für Berichte, Mapping und reine Ableitungen. Nicht sinnvoll, wenn Zustand geändert oder fachliche Lebenszyklen gesteuert werden.

Risiken und Fehlanwendungen

Versteckte I/O oder Mutation macht die Transformation schwer testbar. Außerdem darf das Resultat nicht zum unstrukturierten Allzweckobjekt werden.

Enterprise-Java-Beispiel

Aus OrderData entsteht OrderSummary mit net, tax, gross und margin; alle Werte werden in einem deterministischen Schritt berechnet.

Vorhandener Java-21-Zielcode

OrderSummaryTransform.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Combine Functions into Transform
// Zweck: Abgeleitete Werte in einem deterministischen Transformationsschritt bündeln.
public final class OrderSummaryTransform {
  public Summary transform(BigDecimal net, BigDecimal taxRate) {
    BigDecimal tax = net.multiply(taxRate);
    return new Summary(net, tax, net.add(tax));
  }
  public record Summary(BigDecimal net, BigDecimal tax, BigDecimal gross) {}
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

Kapitel 2 Phasen trennen und Datenflüsse klären10 Refactorings

Daten-, Schleifen- und Vertragsrefactorings als kleine, kompilierbare Transformationen.

11. Split Phase

Split Phase

Fachliche Einordnung

Eine Methode vermischt zwei Verarbeitungsschritte mit unterschiedlichen Regeln, Fehlern oder Änderungsgründen.

Der Ablauf wird in klar getrennte Phasen mit einem expliziten Zwischenmodell zerlegt.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Phasengrenze bestimmen, Zwischenobjekt einführen, erste Phase extrahieren, zweite Phase auf Zwischenobjekt umstellen und Fehlerverträge je Phase testen.

Wann sinnvoll?

Sinnvoll bei Parsing plus Fachentscheidung, Laden plus Berechnung oder Validierung plus Persistenz. Nicht sinnvoll bei sehr kurzen linearen Abläufen ohne eigenständige Grenze.

Risiken und Fehlanwendungen

Ein untypisiertes Map-Zwischenmodell verschiebt das Problem. Besser ist ein validierter Record mit klarer Semantik.

Enterprise-Java-Beispiel

CSV-Import wird in parseRow() und createOrderCommand() geteilt; technische Syntaxfehler und fachliche Ablehnungen bleiben unterscheidbar.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Split Phase
// Zweck: Technische Aufbereitung und fachliche Entscheidung getrennt verändern und testen.
public final class ShipmentImportPhases {
  public ImportResult importLine(String line) {
    ParsedShipment parsed = parse(line);
    return classify(parsed);
  }
  private ParsedShipment parse(String line) {
    String[] parts = line.split(";");
    return new ParsedShipment(parts[0].trim(), Integer.parseInt(parts[1].trim()));
  }
  private ImportResult classify(ParsedShipment shipment) {
    return new ImportResult(shipment.reference(), shipment.weightKg() <= 30);
  }
  record ParsedShipment(String reference, int weightKg) {}
  public record ImportResult(String reference, boolean standardRoute) {}
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

12. Replace Derived Variable with Query

Replace Derived Variable with Query

Fachliche Einordnung

Ein abgeleiteter Wert wird als veränderlicher Zustand gespeichert und kann gegenüber seinen Quelldaten veralten.

Der Wert wird bei Bedarf aus der maßgeblichen Quelle berechnet. Dadurch entfällt Synchronisationslogik.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Alle Schreibstellen des abgeleiteten Feldes finden, reine Berechnungsfunktion einführen, Leser umstellen und Feld samt Aktualisierungslogik entfernen.

Wann sinnvoll?

Sinnvoll bei billig berechenbaren Werten. Nicht sinnvoll bei teuren Berechnungen, die bewusst als Cache mit klarer Invalidierung modelliert sind.

Risiken und Fehlanwendungen

Eine Query darf keine Seiteneffekte besitzen. Bei Performanceproblemen braucht es eine explizite Cache-Policy statt eines versteckten Feldes.

Enterprise-Java-Beispiel

Order speichert total nicht mehr separat, sondern berechnet total() aus den Positionen.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal; import java.util.ArrayList; import java.util.List;
// Refactoring: Replace Derived Variable with Query
// Zweck: Keine synchron zu haltende Gesamtsumme speichern.
public final class CartTotalQuery {
  private final List<BigDecimal> prices = new ArrayList<>();
  public void add(BigDecimal price) { prices.add(price); }
  public BigDecimal total() { return prices.stream().reduce(BigDecimal.ZERO, BigDecimal::add); }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

13. Change Reference to Value

Change Reference to Value

Fachliche Einordnung

Ein gemeinsam referenziertes Objekt besitzt keine echte Identität und erzeugt unnötige Lebenszyklus- oder Mutationskopplung.

Das Objekt wird zu einem unveränderlichen Value Object mit Wertgleichheit. Kopien sind gleichwertig und können sicher geteilt werden.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Identitätsnutzung prüfen, Mutation entfernen, Konstruktion validieren, equals/hashCode oder Record-Semantik einführen und Referenzregister auflösen.

Wann sinnvoll?

Sinnvoll für Geld, Adressen, Zeiträume und Codes. Nicht sinnvoll, wenn Historie, individuelle Identität oder unabhängiger Lebenszyklus relevant sind.

Risiken und Fehlanwendungen

Ein Value Object darf keine versteckte Mutation enthalten. Datenbank-IDs allein machen ein Fachobjekt nicht automatisch zur Entity.

Enterprise-Java-Beispiel

CurrencyCode wird als validierter Record modelliert; zwei Instanzen mit EUR sind fachlich gleich.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Change Reference to Value
// Pattern: Value Object
// Zweck: Adressen werden unveränderlich und anhand ihres Inhalts verglichen.
public record CustomerAddressValue(String street, String city, String postalCode) {
  public CustomerAddressValue {
    if (street == null || city == null || postalCode == null) throw new IllegalArgumentException("address");
  }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

14. Change Value to Reference

Change Value to Reference

Fachliche Einordnung

Mehrere Value-Instanzen repräsentieren tatsächlich dasselbe identifizierbare Fachobjekt und müssen konsistent geändert werden.

Ein Repository oder Identity Map liefert eine kanonische Referenz mit stabiler Identität.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Fachliche Identität definieren, Repository einführen, Erzeugung zentralisieren, Aufrufer migrieren und Gleichheitsregeln anpassen.

Wann sinnvoll?

Sinnvoll bei Kunden, Verträgen oder Produkten mit eigenem Lebenszyklus. Nicht sinnvoll für reine Werte ohne Identität.

Risiken und Fehlanwendungen

Ein globales Registry-Singleton erzeugt versteckte Kopplung. Die Referenzquelle sollte explizit injiziert und testbar sein.

Enterprise-Java-Beispiel

Aus mehrfach kopierten Customer-Daten wird eine Customer-Entity, die über CustomerRepository anhand CustomerId geladen wird.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Map; import java.util.concurrent.ConcurrentHashMap;
// Refactoring: Change Value to Reference
// Pattern: Identity Map / Registry
// Zweck: Pro Kunden-ID wird eine kanonische Instanz verwendet.
public final class CustomerReferenceRegistry {
  private final Map<String, Customer> customers = new ConcurrentHashMap<>();
  public Customer customer(String id, String name) { return customers.computeIfAbsent(id, key -> new Customer(key,name)); }
  public static final class Customer { private final String id; private final String name; Customer(String id,String name){this.id=id;this.name=name;} public String id(){return id;} public String name(){return name;} }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

15. Split Loop

Split Loop

Fachliche Einordnung

Eine Schleife berechnet mehrere unabhängige Resultate und vermischt verschiedene Zwecke.

Die Schleife wird in getrennte Durchläufe mit jeweils einem klaren Ziel aufgeteilt.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Verhalten und Performance messen, unabhängige Resultate identifizieren, erste Schleife kopieren, Aufgaben trennen und Tests vergleichen.

Wann sinnvoll?

Sinnvoll, wenn Verständlichkeit und spätere Extraktion gewinnen. Nicht sinnvoll bei sehr großen Datenmengen oder Stream-Quellen, bei denen zusätzliche Durchläufe teuer sind.

Risiken und Fehlanwendungen

Nicht blind anwenden: Bei Performancekritik kann eine Single-Pass-Aggregation mit benanntem Accumulator besser sein.

Enterprise-Java-Beispiel

Eine Schleife, die Summe und Risikoindikatoren berechnet, wird in calculateTotal() und collectRiskFlags() geteilt.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal; import java.util.List;
// Refactoring: Split Loop
// Zweck: Zwei unabhängige Fragen getrennt lesbar und veränderbar machen.
public final class OrderAnalysisLoops {
  public BigDecimal total(List<BigDecimal> amounts) { BigDecimal total=BigDecimal.ZERO; for (var amount:amounts) total=total.add(amount); return total; }
  public long expensiveCount(List<BigDecimal> amounts) { long count=0; for (var amount:amounts) if(amount.compareTo(new BigDecimal("100"))>0) count++; return count; }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

16. Slide Statements

Slide Statements

Fachliche Einordnung

Zusammengehörige Anweisungen liegen auseinander und erschweren das Erkennen einer Einheit oder Extraktion.

Anweisungen werden ohne Verhaltensänderung räumlich zusammengeführt.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Datenabhängigkeiten und Seiteneffekte prüfen, Anweisung schrittweise verschieben, nach jedem Schritt kompilieren und Tests ausführen.

Wann sinnvoll?

Sinnvoll als Vorbereitung für Extract Method oder zur besseren lokalen Lesbarkeit. Nicht verschieben, wenn Reihenfolge fachlich oder technisch relevant ist.

Risiken und Fehlanwendungen

Besonders kritisch sind Logging, Transaktionen, Locks und I/O. Ihre Reihenfolge darf nicht nur aufgrund optischer Nähe verändert werden.

Enterprise-Java-Beispiel

Validierung und Aufbau eines PaymentCommand werden direkt vor dem Versand zusammengeführt und anschließend extrahiert.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Slide Statements
// Zweck: Validierung und Normalisierung stehen direkt vor ihrer Verwendung.
public final class InvoicePreparation {
  public BigDecimal prepare(BigDecimal net, BigDecimal taxRate) {
    if (net.signum() < 0) throw new IllegalArgumentException("net");
    BigDecimal normalizedRate = taxRate.stripTrailingZeros();
    return net.add(net.multiply(normalizedRate));
  }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

17. Replace Loop with Pipeline

Replace Loop with Pipeline

Fachliche Einordnung

Eine Schleife enthält mehrere klar trennbare Schritte wie Filtern, Abbilden und Aggregieren.

Der Kontrollfluss wird als deklarative Pipeline aus benannten Operationen formuliert.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Seiteneffekte entfernen, Schritte identifizieren, Filter/Map/Reduce oder eigene Pipeline-Stufen einführen und Ergebnis gegen bestehende Tests prüfen.

Wann sinnvoll?

Sinnvoll bei reinen Transformationen. Nicht sinnvoll, wenn komplexe Abbruchlogik, mutable Zustände oder Debugbarkeit durch eine Pipeline leiden.

Risiken und Fehlanwendungen

Streams sind kein Selbstzweck. Tiefe, verschachtelte Pipelines können schlechter lesbar sein als eine einfache Schleife.

Enterprise-Java-Beispiel

OrderLines werden gefiltert, in Money-Werte abgebildet und mit Money::add aggregiert.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal; import java.util.List;
// Refactoring: Replace Loop with Pipeline
// Zweck: Filter, Abbildung und Aggregation als deklarative Schritte sichtbar machen.
public final class ActiveCustomerRevenue {
  public BigDecimal calculate(List<Customer> customers) {
    return customers.stream().filter(Customer::active).map(Customer::revenue).reduce(BigDecimal.ZERO,BigDecimal::add);
  }
  public record Customer(boolean active, BigDecimal revenue) {}
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

18. Remove Dead Code

Remove Dead Code

Fachliche Einordnung

Code ist nicht erreichbar, nicht mehr aufgerufen oder durch alte Featurepfade überholt.

Nicht benötigte Bestandteile werden vollständig entfernt, statt sie aus Angst dauerhaft mitzuschleppen.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Nutzung statisch und dynamisch prüfen, Tests und Telemetrie auswerten, öffentlichen Vertrag beachten, entfernen und Build sowie Regressionstests ausführen.

Wann sinnvoll?

Sinnvoll bei bestätigter Nichtnutzung. Nicht vorschnell bei Reflection, Framework-Konfiguration oder externen API-Aufrufern.

Risiken und Fehlanwendungen

Auskommentierter Code gehört ebenfalls entfernt; Versionsverwaltung ist das Archiv. Bei öffentlichen APIs ist zuerst Deprecation nötig.

Enterprise-Java-Beispiel

Ein alter LegacyPricingAdapter wird nach vollständiger Migration und Nutzungsprüfung entfernt.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Remove Dead Code
// Zweck: Nur die tatsächlich unterstützte Preisregel bleibt Teil des Vertrags.
public final class CurrentPricingRule {
  public BigDecimal price(BigDecimal base, boolean premium) { return premium ? base.multiply(new BigDecimal("0.90")) : base; }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

19. Separate Query from Modifier

Separate Query from Modifier

Fachliche Einordnung

Eine Methode liefert Informationen und verändert gleichzeitig Zustand; Aufrufer können Folgen nicht erkennen.

Lesende Query und verändernder Command werden getrennt. Der Vertrag macht Seiteneffekte explizit.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Beobachtbares Verhalten sichern, Rückgabewert und Mutation analysieren, Query extrahieren, Command separat benennen und Aufrufer in klarer Reihenfolge umstellen.

Wann sinnvoll?

Sinnvoll bei überraschenden Seiteneffekten und schwer testbaren Methoden. Nicht zwingend, wenn eine fachliche Operation natürlich ein Ergebnis ihrer Änderung zurückgibt, etwa reserve() mit ReservationResult.

Risiken und Fehlanwendungen

Eine künstliche Trennung kann Race Conditions erzeugen, wenn Query und Command atomar zusammengehören. Dann ist ein Decision Object als Ergebnis der Operation besser.

Enterprise-Java-Beispiel

getAndMarkInvoicePaid() wird zu paymentStatus() und markPaid(...), sofern beide nicht atomar gekoppelt sein müssen.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.HashSet; import java.util.Set;
// Refactoring: Separate Query from Modifier
// Command-Query Separation: Abfrage und Zustandsänderung sind getrennt.
public final class ReservationLedger {
  private final Set<String> reserved = new HashSet<>();
  public boolean isReserved(String id) { return reserved.contains(id); }
  public void reserve(String id) { if(!reserved.add(id)) throw new IllegalStateException("already reserved"); }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

20. Parameterize Function

Parameterize Function

Fachliche Einordnung

Mehrere nahezu gleiche Funktionen unterscheiden sich nur durch einen Wert oder eine kleine Strategie.

Die Unterschiede werden als expliziter Parameter modelliert; Duplikate verschwinden und der gemeinsame Ablauf bleibt sichtbar.

Erkennungsmerkmale

  • Lesefluss oder Änderbarkeit leiden sichtbar.
  • Mehrere Gründe zur Änderung liegen in derselben Struktur.
  • Der vorhandene Code erschwert isolierte Tests oder sichere Erweiterungen.

Sicherer Ablauf

  1. Duplikate vergleichen, echte Variationsachse bestimmen, Typ oder Enum statt primitiver Flags wählen, gemeinsame Funktion bilden und Aufrufer migrieren.

Wann sinnvoll?

Sinnvoll bei stabiler gemeinsamer Struktur. Nicht sinnvoll, wenn die Varianten unterschiedliche Verantwortung besitzen oder ein wachsender Flag-Parameter entsteht.

Risiken und Fehlanwendungen

Boolean-Parameter verschlechtern oft die Lesbarkeit. Besser sind fachliche Enums, Value Objects oder Strategy-Objekte.

Enterprise-Java-Beispiel

calculateDomesticFee() und calculateInternationalFee() werden zu calculateFee(ShippingZone zone), solange der Ablauf tatsächlich gemeinsam bleibt.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Parameterize Function
// Strategy-Parameter: Der Grenzwert ist Teil des Aufrufvertrags statt duplizierter Methoden.
public final class ThresholdClassifier {
  public boolean reaches(int score, int threshold) { return score >= threshold; }
}
Teststrategie: Vor der Änderung das äußere Verhalten mit Characterization Tests sichern. Nach jedem kleinen Transformationsschritt kompilieren und gezielte Unit-Tests ausführen. Bei öffentlichen Verträgen zusätzlich Kompatibilitäts- oder Contract-Tests verwenden.

Deutschsprachige fachliche Grundlagen

Die Erklärung ist eine eigenständige, auf das konkrete Workbench-Beispiel bezogene Zusammenfassung. Die Quellen dienen zur fachlichen Einordnung von Refactoring, Code Smells, Verhaltenssicherung und schrittweisem Vorgehen.

Kapitel 3 Parameter, Abfragen und Rückgabewerte vereinfachen10 Refactorings

API-, Parameter-, Command- und Lebenszyklusrefactorings als kleine, kompilierbare Transformationen.

21. Replace Parameter with Query

Replace Parameter with Query

Fachliches Ausgangsproblem

Ein Aufrufer übergibt einen Wert, den die aufgerufene Methode aus einem bereits vorhandenen Objekt oder Dienst selbst zuverlässig bestimmen kann. Dadurch müssen mehrere Aufrufer dieselbe Ableitungslogik kennen und können widersprüchliche Werte liefern.

Erkennungsmerkmale

Typische Hinweise sind Parameter, die unmittelbar vor dem Aufruf aus einem anderen Argument berechnet werden, identische Berechnungen an mehreren Aufrufstellen oder Parameter, deren Wert stets aus dem Empfängerobjekt stammt.

Nutzen der Transformation

Der Vertrag wird kürzer, die Wissensverantwortung liegt an einer Stelle und inkonsistente Aufruferberechnungen verschwinden.

Sicherer Refactoring-Ablauf

  1. Verhalten mit Charakterisierungstests absichern.
  2. Die interne Abfrage zunächst zusätzlich zum Parameter einführen und beide Werte vergleichen.
  3. Alle Aufrufer auf den vereinfachten Vertrag umstellen.
  4. Den redundanten Parameter entfernen und die Abfrage hinter einer klar benannten Methode kapseln.

Einsatzgrenze

Nicht anwenden, wenn die Methode bewusst von außen steuerbar bleiben soll, die Abfrage teuer ist oder ein versteckter Zugriff auf Infrastruktur entstehen würde.

Risiken und Fehlanwendungen

Eine häufige Fehlanwendung ersetzt einen sichtbaren Parameter durch globalen Zustand oder eine Datenbankabfrage. Das reduziert die Signatur, erhöht aber die versteckte Kopplung.

Verwandte Techniken

Verwandt mit Preserve Whole Object, Extract Query Object und Introduce Parameter Object.

Enterprise-Java-Einordnung

Eine Versandberechnung liest Expressstatus und Kundensegment aus dem typisierten Auftrag, statt einen extern berechneten Multiplikator entgegenzunehmen.

Teststrategie

Tests sollten Standard-, Express- und Grenzfälle abdecken und beweisen, dass kein Aufrufer mehr einen abweichenden Faktor einschleusen kann.

ShippingFeeQuery.java

ShippingFeeQuery.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Replace Parameter with Query
// Zweck: Der Gebührenfaktor wird aus dem Auftrag statt vom Aufrufer geliefert.
public final class ShippingFeeQuery {
  public BigDecimal fee(Order order) {
    return order.baseFee().multiply(order.express() ? new BigDecimal("1.50") : BigDecimal.ONE);
  }
  public record Order(BigDecimal baseFee, boolean express) {}
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

22. Replace Query with Parameter

Replace Query with Parameter

Fachliches Ausgangsproblem

Eine Methode greift selbst auf veränderlichen Zustand, Zeit, Konfiguration oder Infrastruktur zu. Ihr Ergebnis hängt dadurch von unsichtbaren Bedingungen ab und lässt sich nur schwer reproduzieren.

Erkennungsmerkmale

Warnsignale sind direkte Aufrufe von Uhr, Zufall, Systemkonfiguration, Security Context oder Repository innerhalb einer eigentlich fachlichen Berechnung.

Nutzen der Transformation

Abhängigkeiten werden sichtbar; die Methode wird deterministisch, leichter testbar und unabhängig von Laufzeitumgebung oder Thread-Kontext.

Sicherer Refactoring-Ablauf

  1. Die versteckte Abfrage identifizieren und ihren fachlichen Typ bestimmen.
  2. Den Wert zunächst als zusätzlichen Parameter einführen.
  3. Aufrufer so umbauen, dass sie den Wert an einer klaren Systemgrenze beschaffen.
  4. Den internen Zugriff entfernen und Tests mit expliziten Werten ergänzen.

Einsatzgrenze

Nicht anwenden, wenn der abgefragte Wert Teil der stabilen Identität des Empfängerobjekts ist oder die Übergabe nur eine lange Kette bedeutungsloser Parameter erzeugt.

Risiken und Fehlanwendungen

Zu viele primitive Parameter verschlechtern den Vertrag. In diesem Fall ist ein Context Object oder Value Object meist der bessere nächste Schritt.

Verwandte Techniken

Verwandt mit Extract Clock, Introduce Context Object und Dependency Injection.

Enterprise-Java-Einordnung

Eine Fristberechnung erhält ein Instant oder eine Clock-Schnittstelle, statt LocalDateTime.now() tief im Domain-Code aufzurufen.

Teststrategie

Tests verwenden feste Zeitpunkte, verschiedene Zeitzonen und Grenzwerte rund um Fristwechsel.

TaxCalculationParameter.java

TaxCalculationParameter.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Replace Query with Parameter
// Dependency Injection: Der Steuersatz ist explizite Eingabe statt versteckter Abfrage.
public final class TaxCalculationParameter {
  public BigDecimal gross(BigDecimal net, BigDecimal taxRate) {
    return net.add(net.multiply(taxRate));
  }
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

23. Preserve Whole Object

Preserve Whole Object

Fachliches Ausgangsproblem

Ein Aufrufer zerlegt ein fachliches Objekt in mehrere Einzelwerte und reicht diese gemeinsam weiter. Die aufgerufene Methode verliert dadurch Kontext und ihre Signatur wächst bei jeder neuen Regel.

Erkennungsmerkmale

Mehrere Parameter werden immer aus demselben Objekt gelesen, ändern sich gemeinsam oder bilden eine bekannte fachliche Einheit.

Nutzen der Transformation

Die Methode erhält den fachlichen Zusammenhang, Signaturen bleiben stabiler und neue Regeln können ohne wiederholte Parameterketten ergänzt werden.

Sicherer Refactoring-Ablauf

  1. Die benötigten Werte und ihre Quelle dokumentieren.
  2. Das vollständige Objekt zusätzlich übergeben und intern zunächst nur lesend verwenden.
  3. Einzelparameter schrittweise durch Objektzugriffe ersetzen.
  4. Redundante Parameter entfernen und die Abhängigkeit auf eine schmale fachliche Sicht begrenzen.

Einsatzgrenze

Nicht anwenden, wenn dadurch eine zu breite Abhängigkeit entsteht, sensible Daten unnötig weitergereicht werden oder nur ein einzelner stabiler Wert benötigt wird.

Risiken und Fehlanwendungen

Das ganze Objekt darf nicht als bequemer Datenbeutel missbraucht werden. Besser ist gegebenenfalls ein fokussiertes Interface oder Value Object.

Verwandte Techniken

Verwandt mit Introduce Parameter Object, Extract Interface und Replace Parameter with Query.

Enterprise-Java-Einordnung

Eine Kreditprüfung erhält den LoanApplication-Kontext statt Einkommen, Laufzeit, Betrag und Kundengruppe als lose Einzelparameter.

Teststrategie

Tests prüfen, dass nur relevante Felder ausgewertet werden und Änderungen an irrelevanten Feldern das Ergebnis nicht beeinflussen.

EligibilityPreserveWholeObject.java

EligibilityPreserveWholeObject.java
package com.aydinsude.workbench.catalog;
// Refactoring: Preserve Whole Object
// Zweck: Die Regel erhält den vollständigen Antrag und bleibt bei neuen Kriterien stabil.
public final class EligibilityPreserveWholeObject {
  public boolean eligible(Application application) {
    return application.age() >= 18 && application.annualIncome() >= 30000 && !application.blocked();
  }
  public record Application(int age, int annualIncome, boolean blocked) {}
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

24. Remove Flag Argument

Remove Flag Argument

Fachliches Ausgangsproblem

Ein boolescher oder codierter Parameter schaltet innerhalb einer Methode zwischen deutlich verschiedenen Abläufen um. Der Aufruf verrät ohne Blick in die Implementierung nicht, welches Verhalten gewählt wird.

Erkennungsmerkmale

Aufrufe wie process(order, true), save(entity, false) oder mehrere Verzweigungen auf demselben Flag sind klare Hinweise.

Nutzen der Transformation

Aufrufe werden selbsterklärend, Varianten können getrennt getestet und später unabhängig entwickelt werden.

Sicherer Refactoring-Ablauf

  1. Alle Flagwerte und Aufrufstellen erfassen.
  2. Für die fachlich unterschiedlichen Absichten sprechende Methoden einführen.
  3. Gemeinsame Teile extrahieren, ohne die Varianten künstlich zusammenzuhalten.
  4. Aufrufer migrieren und das Flag entfernen.

Einsatzgrenze

Nicht anwenden, wenn das Argument echten Datencharakter besitzt und nicht die Auswahl eines Verfahrens darstellt.

Risiken und Fehlanwendungen

Nur zwei Wrapper um dieselbe riesige Flag-Methode zu legen reicht nicht. Die Verzweigung muss tatsächlich in klar benannte Abläufe oder Strategien überführt werden.

Verwandte Techniken

Verwandt mit Replace Conditional with Polymorphism, Strategy und Command.

Enterprise-Java-Einordnung

Aus approve(claim, true) und approve(claim, false) werden approveAutomatically(claim) und requestManualApproval(claim).

Teststrategie

Für jede neue Methode eigene Vertrags- und Negativtests anlegen; gemeinsame Invarianten zusätzlich als Contract Test sichern.

DocumentDeliveryCommands.java

DocumentDeliveryCommands.java
package com.aydinsude.workbench.catalog;
// Refactoring: Remove Flag Argument
// Command-Methoden: Jede Variante besitzt einen sprechenden Vertrag.
public final class DocumentDeliveryCommands {
  public String sendImmediately(String documentId) { return "sent:" + documentId; }
  public String queueForReview(String documentId) { return "queued:" + documentId; }
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

25. Replace Constructor with Factory Function

Replace Constructor with Factory Function

Fachliches Ausgangsproblem

Ein öffentlicher Konstruktor muss technische Details, Variantenwahl oder Validierungsreihenfolge offenlegen. Der Typname allein beschreibt nicht, welche Erzeugungsabsicht vorliegt.

Erkennungsmerkmale

Mehrere Konstruktoren mit ähnlichen Signaturen, optionale Nullwerte, technische Parameter oder nachgelagerte Initialisierung weisen darauf hin.

Nutzen der Transformation

Benannte Erzeugung macht Absicht und Variante sichtbar, kann konkrete Implementierungen verbergen und garantiert vollständige Objekte.

Sicherer Refactoring-Ablauf

  1. Eine benannte Factory Function parallel zum Konstruktor einführen.
  2. Invarianten und Standardwerte in die Factory verlagern.
  3. Aufrufer schrittweise migrieren.
  4. Konstruktorzugriff einschränken, sobald keine externe Nutzung mehr besteht.

Einsatzgrenze

Nicht anwenden, wenn ein einfacher Konstruktor den Vertrag klar ausdrückt und keine Varianten oder Invarianten verborgen werden müssen.

Risiken und Fehlanwendungen

Factories dürfen nicht zu allwissenden Service Locators werden. Externe Abhängigkeiten gehören in einen Composition Root oder eine eigene Factory-Klasse.

Verwandte Techniken

Verwandt mit Factory Method, Static Factory, Builder und Introduce Value Object.

Enterprise-Java-Einordnung

Payment.ofCard(...) und Payment.ofBankTransfer(...) erzeugen typisierte Zahlungsvarianten mit geprüften Pflichtdaten.

Teststrategie

Tests prüfen Invarianten, Standardwerte und die Ablehnung unvollständiger Erzeugungsdaten.

PaymentRequestFactory.java

PaymentRequestFactory.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Replace Constructor with Factory Function
// Pattern: Factory Method
public record PaymentRequestFactory(String reference, BigDecimal amount, String currency) {
  public static PaymentRequestFactory euroPayment(String reference, BigDecimal amount) {
    if (amount.signum() <= 0) throw new IllegalArgumentException("amount");
    return new PaymentRequestFactory(reference, amount, "EUR");
  }
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

26. Replace Function with Command

Replace Function with Command

Fachliches Ausgangsproblem

Eine Funktion benötigt immer mehr Zustand, optionale Schritte, Wiederholung, Audit oder Rücknahme. Parameter und Kontrollfluss werden unübersichtlich.

Erkennungsmerkmale

Lange Parameterlisten, mehrere Hilfsmethoden, temporärer Zustand und der Wunsch nach Retry, Queueing oder Undo sind typische Hinweise.

Nutzen der Transformation

Der Vorgang wird als Objekt adressierbar, protokollierbar, warteschlangenfähig und isoliert testbar.

Sicherer Refactoring-Ablauf

  1. Den Funktionsaufruf als unveränderliches Command-Objekt modellieren.
  2. Eingaben im Konstruktor oder Record bündeln.
  3. Ausführung in execute() oder handle() verschieben.
  4. Nebenwirkungen über Ports führen und Ergebnis explizit typisieren.

Einsatzgrenze

Nicht anwenden, wenn eine reine, kurze Funktion ohne Lebenszyklus oder Nebenwirkungen ausreicht.

Risiken und Fehlanwendungen

Ein Command darf nicht gleichzeitig Datenmodell, Transaktion, Repository und Event Bus besitzen. Die Ausführung bleibt an klaren Ports ausgerichtet.

Verwandte Techniken

Verwandt mit Command Pattern, Application Service und Process Manager.

Enterprise-Java-Einordnung

CloseAccountCommand enthält AccountId, Grund und Antragsteller; ein Handler koordiniert Validierung, Persistenz und Domain Event.

Teststrategie

Tests prüfen erfolgreiche Ausführung, Ablehnung, Idempotenz und Wiederholung mit identischer Command-ID.

ApproveClaimCommand.java

ApproveClaimCommand.java
package com.aydinsude.workbench.catalog;
// Refactoring: Replace Function with Command
// Pattern: Command
public final class ApproveClaimCommand {
  private final String claimId; private boolean executed;
  public ApproveClaimCommand(String claimId) { this.claimId=claimId; }
  public Result execute() { if(executed) throw new IllegalStateException("already executed"); executed=true; return new Result(claimId,"APPROVED"); }
  public record Result(String claimId, String status) {}
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

27. Replace Command with Function

Replace Command with Function

Fachliches Ausgangsproblem

Eine Command-Klasse besitzt keinen eigenen Zustand oder Lebenszyklus und kapselt lediglich eine kurze, reine Berechnung. Die zusätzliche Objektstruktur erschwert das Lesen.

Erkennungsmerkmale

Das Objekt wird unmittelbar erzeugt und einmal ausgeführt, besitzt keine Ports, kein Audit, kein Retry und keine austauschbaren Implementierungen.

Nutzen der Transformation

Weniger Typen, direkterer Kontrollfluss und geringere Navigationskosten.

Sicherer Refactoring-Ablauf

  1. Reinheit und Seiteneffektfreiheit durch Tests belegen.
  2. Berechnung als benannte Funktion oder statische Methode anbieten.
  3. Aufrufer migrieren.
  4. Command-Klasse und unnötige Factory entfernen.

Einsatzgrenze

Nicht anwenden, wenn der Vorgang als Nachricht, Warteschlangenauftrag, Transaktionsgrenze oder Erweiterungspunkt gebraucht wird.

Risiken und Fehlanwendungen

Das Entfernen darf keinen fachlichen Begriff zerstören. Eine gute Funktion braucht weiterhin einen präzisen Namen und typisierte Ein- und Ausgaben.

Verwandte Techniken

Verwandt mit Inline Class, Substitute Algorithm und Pure Function.

Enterprise-Java-Einordnung

Ein VatCalculationCommand ohne Zustand wird zu calculateVat(Money, TaxRate).

Teststrategie

Property- und Grenzwerttests sichern die reine Berechnung; keine Mock-Interaktionen nötig.

NormalizeCustomerName.java

NormalizeCustomerName.java
package com.aydinsude.workbench.catalog;
import java.util.Locale;
// Refactoring: Replace Command with Function
// Zweck: Eine zustandslose Transformation bleibt eine kleine reine Funktion.
public final class NormalizeCustomerName {
  public String normalize(String value) { return value.trim().replaceAll("\\s+"," ").toUpperCase(Locale.ROOT); }
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

28. Return Modified Value

Return Modified Value

Fachliches Ausgangsproblem

Eine Methode verändert einen übergebenen Wert oder ein umgebendes Objekt, ohne dass der Aufruf diese Änderung deutlich macht. Leser müssen Seiteneffekte erraten.

Erkennungsmerkmale

Void-Methoden mit Mutationen, gemeinsam genutzte Akkumulatoren oder veränderte Parameter sind typische Anzeichen.

Nutzen der Transformation

Datenfluss wird sichtbar, Methoden lassen sich leichter kombinieren und unveränderliche Modelle werden möglich.

Sicherer Refactoring-Ablauf

  1. Den tatsächlich erzeugten oder veränderten Wert bestimmen.
  2. Die Methode so umstellen, dass sie den neuen Wert zurückgibt.
  3. Aufrufer auf explizite Zuweisung migrieren.
  4. Verdeckte Mutation reduzieren oder durch unveränderliche Typen ersetzen.

Einsatzgrenze

Nicht anwenden, wenn die Operation bewusst ein zustandsbehaftetes Aggregate verändert und dessen Invarianten nur intern geschützt werden können.

Risiken und Fehlanwendungen

Ein Rückgabewert allein macht eine mutierende Methode noch nicht rein. Dokumentation und Typmodell müssen klar zeigen, ob zusätzlich Zustand verändert wird.

Verwandte Techniken

Verwandt mit Immutable Value Object, Combine Functions into Transform und Command-Query Separation.

Enterprise-Java-Einordnung

normalize(address) liefert eine neue Address zurück, statt das übergebene DTO still zu verändern.

Teststrategie

Tests vergleichen ursprünglichen und neuen Wert und prüfen, dass die Eingabe unverändert bleibt.

RiskScoreUpdate.java

RiskScoreUpdate.java
package com.aydinsude.workbench.catalog;
// Refactoring: Return Modified Value
// Zweck: Der Datenfluss des neuen Risikowerts ist am Aufruf sichtbar.
public final class RiskScoreUpdate {
  public Assessment applyDocumentRisk(Assessment current, int documentRisk) {
    return new Assessment(current.customerId(), current.score() + documentRisk);
  }
  public record Assessment(String customerId, int score) {}
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

29. Replace Inline Code with Function Call

Replace Inline Code with Function Call

Fachliches Ausgangsproblem

Ein Codeabschnitt implementiert bereits ein bekanntes Konzept, ruft aber nicht die dafür vorhandene Funktion auf. Dadurch entstehen semantische Duplikate.

Erkennungsmerkmale

Mehrere Stellen enthalten fast gleiche Bedingungen, Normalisierungen oder Berechnungen, obwohl ein passender zentraler Vertrag existiert.

Nutzen der Transformation

Ein Begriff besitzt nur noch eine Implementierung; Fehlerkorrekturen und Regeländerungen wirken konsistent.

Sicherer Refactoring-Ablauf

  1. Verhalten von Inline-Code und Zielfunktion mit Tests vergleichen.
  2. Abweichungen sichtbar machen und entscheiden, welche Semantik korrekt ist.
  3. Inline-Code durch Funktionsaufruf ersetzen.
  4. Duplikate und veraltete Hilfswerte entfernen.

Einsatzgrenze

Nicht anwenden, wenn die vorhandene Funktion nur zufällig ähnlich ist oder einen anderen fachlichen Vertrag besitzt.

Risiken und Fehlanwendungen

Das Refactoring darf Unterschiede nicht glattbügeln. Vor der Ersetzung müssen Vorbedingungen, Einheiten und Fehlerfälle identisch sein.

Verwandte Techniken

Verwandt mit Extract Method, Remove Dead Code und Introduce Value Object.

Enterprise-Java-Einordnung

Eine manuelle IBAN-Normalisierung wird durch Iban.normalize(raw) ersetzt.

Teststrategie

Differentialtests vergleichen bisherige Inline-Logik und zentrale Funktion für repräsentative Datensätze.

PremiumCustomerRule.java

PremiumCustomerRule.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Replace Inline Code with Function Call
// Specification-artige Regel: Die fachliche Aussage besitzt einen Namen.
public final class PremiumCustomerRule {
  public boolean isPremium(Customer customer) { return customer.orders() >= 10 && customer.revenue().compareTo(new BigDecimal("5000")) >= 0; }
  public record Customer(int orders, BigDecimal revenue) {}
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

30. Remove Setting Method

Remove Setting Method

Fachliches Ausgangsproblem

Ein Objekt kann nach seiner Erzeugung über einen Setter in einen ungültigen oder nur teilweise initialisierten Zustand gebracht werden.

Erkennungsmerkmale

Setter für Pflichtwerte, nur einmal gesetzte Felder oder Werte mit komplexen Invarianten sind typische Hinweise.

Nutzen der Transformation

Objekte sind nach Erzeugung gültig, Invarianten werden lokal geschützt und nebenläufige Nutzung wird sicherer.

Sicherer Refactoring-Ablauf

  1. Schreibzugriffe und Lebenszyklus des Feldes analysieren.
  2. Pflichtwert in Konstruktor oder Factory aufnehmen.
  3. Fachliche Änderungen durch benannte Operationen modellieren.
  4. Setter entfernen und Feld möglichst final machen.

Einsatzgrenze

Nicht anwenden, wenn ein Framework zwingend einen Setter benötigt; dann ist ein Persistence DTO oder Adapter statt eines offenen Domain-Setters sinnvoll.

Risiken und Fehlanwendungen

Ein generischer setStatus(...) sollte nicht bloß durch einen privaten Setter ersetzt werden. Zustandswechsel brauchen Methoden wie approve(), cancel() oder suspend().

Verwandte Techniken

Verwandt mit Encapsulate Variable, Introduce Factory und Immutable Object.

Enterprise-Java-Einordnung

Eine Order erhält currency im Konstruktor; Statusänderungen laufen ausschließlich über geprüfte Domain-Operationen.

Teststrategie

Tests prüfen, dass ungültige Zwischenzustände nicht erzeugbar sind und erlaubte Übergänge weiterhin funktionieren.

ImmutableContractNumber.java

ImmutableContractNumber.java
package com.aydinsude.workbench.catalog;
// Refactoring: Remove Setting Method
// Value Object: Vertragsnummer ist nach Erzeugung unveränderlich.
public record ImmutableContractNumber(String value) {
  public ImmutableContractNumber { if(value==null || value.isBlank()) throw new IllegalArgumentException("value"); }
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

Kapitel 4 Funktionen und Verantwortlichkeiten verschieben10 Refactorings

Verantwortungsverschiebung, Typcodes und Hierarchie-Refactorings mit kompilierbarem Java-21-Zielcode.

31. Move Function

Move Function

Fachliches Ausgangsproblem

Eine Methode verwendet überwiegend Daten und Verhalten einer anderen Klasse. Ihr aktueller Ort erhöht Kopplung und zwingt den Aufrufer, fremdes Wissen zu besitzen.

Erkennungsmerkmale

Feature Envy zeigt sich durch viele Zugriffe auf ein anderes Objekt, lange Navigationsketten oder Parameter, die fast ausschließlich Daten des Zielobjekts transportieren.

Nutzen der Transformation

Verantwortung und Daten liegen näher zusammen; Klassen werden kohärenter und Schnittstellen kleiner.

Sicherer Refactoring-Ablauf

  1. Abhängigkeiten der Methode messen und Zielklasse bestimmen.
  2. Methode zunächst im Ziel duplizieren und durch Tests vergleichen.
  3. Aufrufer delegieren lassen.
  4. Alte Implementierung entfernen und Sichtbarkeiten verkleinern.

Einsatzgrenze

Nicht verschieben, wenn die Methode mehrere Aggregate koordiniert oder bewusst eine Application-Service-Verantwortung besitzt.

Risiken und Fehlanwendungen

Einfaches Verschieben kann zyklische Abhängigkeiten erzeugen. Dann ist eine neue Domain Service-, Policy- oder Mapper-Grenze besser.

Verwandte Techniken

Verwandt mit Move Field, Extract Class und Domain Service.

Enterprise-Java-Einordnung

discountFor(customer) wandert aus OrderService in CustomerPricingPolicy oder Customer, abhängig von der fachlichen Invariante.

Teststrategie

Vorher-/Nachher-Contract-Tests und Architekturtests sichern Verhalten sowie erlaubte Paketabhängigkeiten.

CustomerRiskPolicy.java

CustomerRiskPolicy.java
package com.aydinsude.workbench.catalog;
// Refactoring: Move Function
// Zweck: Die Risikoberechnung liegt beim fachlichen Profil statt in einem fremden Service.
public final class CustomerRiskPolicy {
  public Risk assess(CustomerProfile profile) { return profile.risk(); }
  public record CustomerProfile(int overdueInvoices, boolean politicallyExposed) {
    public Risk risk() { return politicallyExposed || overdueInvoices > 2 ? Risk.HIGH : Risk.NORMAL; }
  }
  public enum Risk { NORMAL, HIGH }
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

32. Move Field

Move Field

Fachliches Ausgangsproblem

Ein Feld befindet sich in einer Klasse, die weder seine Invarianten noch den größten Teil seiner Nutzung besitzt.

Erkennungsmerkmale

Andere Klassen lesen oder ändern den Wert häufiger; der Wert wird ständig weitergereicht oder bildet zusammen mit fremden Daten eine Einheit.

Nutzen der Transformation

Datenhoheit wird klarer und fachliche Regeln können am richtigen Ort geschützt werden.

Sicherer Refactoring-Ablauf

  1. Nutzung und Eigentümerschaft klären.
  2. Feld im Zieltyp einführen und synchron halten.
  3. Leser und Schreiber schrittweise migrieren.
  4. Altes Feld entfernen und Persistenzmapping anpassen.

Einsatzgrenze

Nicht verschieben, wenn der Wert Teil einer anderen Aggregate-Grenze ist und dadurch unerlaubte direkte Kopplung entstehen würde.

Risiken und Fehlanwendungen

Bei persistierten Systemen müssen Migration, Serialisierung und Rückwärtskompatibilität berücksichtigt werden.

Verwandte Techniken

Verwandt mit Move Function, Encapsulate Record und Aggregate Design.

Enterprise-Java-Einordnung

creditLimit wandert aus CustomerProfile in CreditAccount, weil dort Freigaben und Ausschöpfung verwaltet werden.

Teststrategie

Tests prüfen Datenmigration, Serialisierung und alle Invarianten am neuen Eigentümer.

ContractRenewalDate.java

ContractRenewalDate.java
package com.aydinsude.workbench.catalog;
import java.time.LocalDate;
// Refactoring: Move Field
// Zweck: Das Verlängerungsdatum gehört zum Vertragszeitraum.
public record ContractRenewalDate(String contractId, Term term) {
  public record Term(LocalDate startsOn, LocalDate renewsOn) {
    public boolean renewalDue(LocalDate today) { return !renewsOn.isAfter(today); }
  }
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

33. Move Statements into Function

Move Statements into Function

Fachliches Ausgangsproblem

Vor oder nach einem Funktionsaufruf stehen an mehreren Stellen dieselben vorbereitenden oder abschließenden Anweisungen. Der tatsächliche Vertrag der Funktion ist unvollständig.

Erkennungsmerkmale

Wiederkehrende Validierung, Normalisierung, Logging- oder Abschlusslogik direkt an mehreren Aufrufstellen weist darauf hin.

Nutzen der Transformation

Der Funktionsvertrag bildet den vollständigen Vorgang ab und Aufrufer können keinen notwendigen Schritt vergessen.

Sicherer Refactoring-Ablauf

  1. Gemeinsame Statements und ihre Reihenfolge sichern.
  2. Seiteneffekte und Fehlerfälle analysieren.
  3. Statements in die Funktion verschieben.
  4. Aufrufstellen vereinfachen und Duplikate entfernen.

Einsatzgrenze

Nicht anwenden, wenn einzelne Aufrufer bewusst unterschiedliche Transaktions-, Logging- oder Sicherheitsgrenzen besitzen.

Risiken und Fehlanwendungen

Technische Querschnittsbelange sollten eventuell als Decorator oder Interceptor modelliert werden, nicht in jede Fachfunktion wandern.

Verwandte Techniken

Verwandt mit Move Statements to Callers, Extract Function und Decorator.

Enterprise-Java-Einordnung

Eine normalize-and-validate-Sequenz wird Bestandteil von registerCustomer(...), weil jede Registrierung sie zwingend benötigt.

Teststrategie

Tests prüfen, dass alle bisherigen Aufrufvarianten weiterhin normalisiert und validiert werden.

InvoiceDispatchPreparation.java

InvoiceDispatchPreparation.java
package com.aydinsude.workbench.catalog;
// Refactoring: Move Statements into Function
// Template Method: Vorbereitung und Versand bilden einen stabilen Ablauf.
public final class InvoiceDispatchPreparation {
  public String dispatch(String invoiceId) { return prepare(invoiceId) + ":sent"; }
  private String prepare(String invoiceId) { return "validated:" + invoiceId; }
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

34. Move Statements to Callers

Move Statements to Callers

Fachliches Ausgangsproblem

Eine Funktion enthält Schritte, die nur ein Teil der Aufrufer benötigt. Ihr allgemeiner Vertrag wird dadurch überraschend oder zu spezialisiert.

Erkennungsmerkmale

Bedingungen auf Aufruferart, optionale Seiteneffekte oder spezielle Formatierung am Ende einer allgemein benannten Funktion sind Hinweise.

Nutzen der Transformation

Der gemeinsame Vertrag wird kleiner; Spezialfälle bleiben an der Stelle sichtbar, die sie tatsächlich benötigt.

Sicherer Refactoring-Ablauf

  1. Gemeinsamen Kern und aufruferspezifische Schritte trennen.
  2. Speziallogik an betroffene Aufrufer verschieben.
  3. Gemeinsame Funktion neu benennen oder vereinfachen.
  4. Duplikate nur dort extrahieren, wo ein echter Begriff besteht.

Einsatzgrenze

Nicht anwenden, wenn dadurch ein obligatorischer Schritt leicht vergessen werden kann oder Sicherheitsregeln auf viele Aufrufer verteilt werden.

Risiken und Fehlanwendungen

Das Verschieben darf keine fachliche Invariante aus dem Aggregate herauslösen.

Verwandte Techniken

Verwandt mit Split Phase, Command und Application Service.

Enterprise-Java-Einordnung

generateInvoice() erzeugt nur das Dokument; Versand und Archivierung werden von den jeweiligen Anwendungsfällen orchestriert.

Teststrategie

Contract-Tests sichern den Kern; spezifische Aufrufertests prüfen Versand und Archivierung getrennt.

CustomerNotificationTemplate.java

CustomerNotificationTemplate.java
package com.aydinsude.workbench.catalog;
// Refactoring: Move Statements to Callers
// Strategy am Aufrufer: Der gemeinsame Text bleibt zentral, der Kanal ergänzt seine Variante.
public final class CustomerNotificationTemplate {
  public String baseMessage(String customerId) { return "customer:" + customerId; }
  public String email(String customerId) { return baseMessage(customerId) + ":email-footer"; }
  public String sms(String customerId) { return baseMessage(customerId) + ":sms-short"; }
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

35. Replace Type Code with Subclasses

Replace Type Code with Subclasses

Fachliches Ausgangsproblem

Ein Typcode steuert zahlreiche Verzweigungen und beschreibt Varianten mit unterschiedlichem Verhalten. Neue Varianten erfordern Änderungen an vielen switch- oder if-Stellen.

Erkennungsmerkmale

Wiederkehrende Abfragen desselben Codes, variantenspezifische Felder und ungültige Kombinationen sind typische Hinweise.

Nutzen der Transformation

Varianten besitzen eigene Regeln, ungültige Zustände werden schwerer erzeugbar und Erweiterungen betreffen weniger Stellen.

Sicherer Refactoring-Ablauf

  1. Varianten und gemeinsame Invarianten erfassen.
  2. Eine gemeinsame Abstraktion einführen.
  3. Je Variante Unterklasse oder versiegelten Typ anlegen.
  4. Erzeugung zentralisieren und Verzweigungen schrittweise durch Polymorphie ersetzen.

Einsatzgrenze

Nicht anwenden, wenn der Code nur Daten klassifiziert, Varianten kein eigenes Verhalten besitzen oder sich Kombinationen dynamisch überlagern.

Risiken und Fehlanwendungen

Eine tiefe Vererbungshierarchie kann das Problem verschärfen. In Java sind sealed interfaces, Strategy oder Composition oft geeigneter.

Verwandte Techniken

Verwandt mit Replace Conditional with Polymorphism, State und Strategy.

Enterprise-Java-Einordnung

PaymentMethod wird als versiegelte Hierarchie CardPayment, BankTransfer und InvoicePayment modelliert.

Teststrategie

Contract-Tests gelten für alle Varianten; zusätzliche Tests prüfen variantenspezifische Invarianten.

PaymentMethodHierarchy.java

PaymentMethodHierarchy.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Replace Type Code with Subclasses
// Pattern: Polymorphism
public sealed interface PaymentMethodHierarchy permits PaymentMethodHierarchy.Card, PaymentMethodHierarchy.Transfer {
  BigDecimal fee(BigDecimal amount);
  record Card() implements PaymentMethodHierarchy { public BigDecimal fee(BigDecimal amount){ return amount.multiply(new BigDecimal("0.02")); } }
  record Transfer() implements PaymentMethodHierarchy { public BigDecimal fee(BigDecimal amount){ return new BigDecimal("0.50"); } }
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

36. Remove Subclass

Remove Subclass

Fachliches Ausgangsproblem

Eine Unterklasse unterscheidet sich kaum noch von ihrer Oberklasse oder trägt nur einen einzelnen Datenwert. Die Hierarchie erzeugt mehr Navigation als Bedeutung.

Erkennungsmerkmale

Leere Überschreibungen, nur ein zusätzliches Feld oder Unterklassen mit identischem Verhalten weisen darauf hin.

Nutzen der Transformation

Die Hierarchie wird flacher und der Typbaum entspricht wieder echten substituierbaren Varianten.

Sicherer Refactoring-Ablauf

  1. Öffentliche Unterschiede und Instanziierungen erfassen.
  2. Unterscheidungsdaten in die Oberklasse oder ein Value Object überführen.
  3. Factory und Aufrufer migrieren.
  4. Unterklasse entfernen und Verträge vereinfachen.

Einsatzgrenze

Nicht anwenden, wenn die Unterklasse einen stabilen Fachbegriff, eigene Invarianten oder zukünftige unabhängige Entwicklung besitzt.

Risiken und Fehlanwendungen

Alle Unterschiede wahllos in Flags zu verwandeln führt zurück zu Typcode-Verzweigungen.

Verwandte Techniken

Verwandt mit Collapse Hierarchy, Replace Subclass with Delegate und Introduce Special Case.

Enterprise-Java-Einordnung

DomesticCustomer und InternationalCustomer werden zu Customer mit einer geprüften MarketRegion, sofern das Verhalten identisch geworden ist.

Teststrategie

Tests vergleichen Serialisierung, Factory-Auswahl und alle bisherigen Varianten.

UnifiedCustomerAccount.java

UnifiedCustomerAccount.java
package com.aydinsude.workbench.catalog;
// Refactoring: Remove Subclass
// Value Object: Der Kontotyp ist Datenwert statt leerer Unterklasse.
public record UnifiedCustomerAccount(String id, AccountType type) {
  public boolean business() { return type == AccountType.BUSINESS; }
  public enum AccountType { PRIVATE, BUSINESS }
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

37. Extract Superclass

Extract Superclass

Fachliches Ausgangsproblem

Mehrere Klassen besitzen dieselben Felder, Methoden und Invarianten, ohne dass diese gemeinsame Struktur ausdrücklich modelliert ist.

Erkennungsmerkmale

Duplizierte Implementierungen mit gleicher Bedeutung und substituierbare Objekte deuten auf eine gemeinsame Oberklasse hin.

Nutzen der Transformation

Duplikation sinkt und ein gemeinsamer, typisierter Vertrag wird sichtbar.

Sicherer Refactoring-Ablauf

  1. Gemeinsamen Vertrag und echte Ist-ein-Beziehung prüfen.
  2. Identische Methoden schrittweise hochziehen.
  3. Gemeinsame Felder kapseln und Konstruktion vereinheitlichen.
  4. Substituierbarkeit mit Contract-Tests sichern.

Einsatzgrenze

Nicht anwenden, wenn Gemeinsamkeit nur technisch ist oder Varianten nicht substituierbar sind. Dann sind Interface, Delegation oder Hilfsobjekt besser.

Risiken und Fehlanwendungen

Eine Oberklasse darf keine optionalen Felder und leeren Hooks sammeln. Das verletzt meist das Substitutionsprinzip.

Verwandte Techniken

Verwandt mit Pull Up Method, Extract Interface und Template Method.

Enterprise-Java-Einordnung

EmailNotification und SmsNotification können eine abstrakte Notification mit gemeinsamer Empfänger- und Auditlogik erhalten, sofern ihre Verträge übereinstimmen.

Teststrategie

Contract-Tests prüfen die Oberklasseninvarianten für jede Unterklasse.

AuditedOperation.java

AuditedOperation.java
package com.aydinsude.workbench.catalog;
import java.time.Instant;
// Refactoring: Extract Superclass
// Template Method: Gemeinsames Audit-Gerüst, fachliche Aktion bleibt abstrakt.
public abstract class AuditedOperation {
  public final Result execute(String actor) { return new Result(actor,Instant.EPOCH,perform()); }
  protected abstract String perform();
  public record Result(String actor, Instant at, String value) {}
  public static final class CloseOrder extends AuditedOperation { protected String perform(){ return "ORDER_CLOSED"; } }
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

38. Collapse Hierarchy

Collapse Hierarchy

Fachliches Ausgangsproblem

Ober- und Unterklasse unterscheiden sich fachlich nicht mehr. Die Hierarchie erschwert Navigation, Konstruktion und Persistenzmapping ohne Nutzen.

Erkennungsmerkmale

Keine relevante Überschreibung, identische Invarianten und eine Eins-zu-eins-Nutzung der Typen sind Hinweise.

Nutzen der Transformation

Die Modellstruktur wird einfacher und vermeidet künstliche Abstraktionsschichten.

Sicherer Refactoring-Ablauf

  1. Substituierbarkeit und Nutzung beider Typen prüfen.
  2. Felder und Methoden in den verbleibenden Typ verschieben.
  3. Factories, Serialisierung und Persistenz migrieren.
  4. Überflüssigen Typ entfernen und API-Kompatibilität behandeln.

Einsatzgrenze

Nicht anwenden, wenn die Hierarchie eine veröffentlichte API oder bewusst getrennte Fachbegriffe repräsentiert.

Risiken und Fehlanwendungen

Ein vorschnelles Zusammenlegen kann spätere unabhängige Entwicklung erschweren; Entscheidung und Migrationspfad dokumentieren.

Verwandte Techniken

Verwandt mit Remove Subclass, Inline Class und Replace Inheritance with Delegation.

Enterprise-Java-Einordnung

BaseContract und Contract werden zusammengeführt, nachdem keine alternative Unterklasse und kein abweichendes Verhalten mehr existiert.

Teststrategie

Regressionstests sichern JSON-, Datenbank- und API-Kompatibilität.

CollapsedApprovalRule.java

CollapsedApprovalRule.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Collapse Hierarchy
// Zweck: Eine unnötige Vererbungsebene wird entfernt.
public final class CollapsedApprovalRule {
  private final BigDecimal limit;
  public CollapsedApprovalRule(BigDecimal limit){ this.limit=limit; }
  public boolean approves(BigDecimal amount){ return amount.compareTo(limit) <= 0; }
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

39. Replace Subclass with Delegate

Replace Subclass with Delegate

Fachliches Ausgangsproblem

Vererbung wird nur verwendet, um eine optionale Fähigkeit oder einzelne Variante zu erhalten. Kombinationen sind nicht möglich und die Basisklasse wird durch Unterklassenwissen belastet.

Erkennungsmerkmale

Viele kleine Unterklassen, Mehrfachkombinationen oder Überschreibungen, die nur an ein Hilfsobjekt delegieren könnten, sind Hinweise.

Nutzen der Transformation

Verhalten lässt sich kombinieren und zur Laufzeit austauschen; die Hauptklasse bleibt stabil.

Sicherer Refactoring-Ablauf

  1. Variantenspezifisches Verhalten in ein Delegate-Interface extrahieren.
  2. Delegate in der bisherigen Basisklasse halten.
  3. Unterklassen schrittweise durch konfigurierte Instanzen ersetzen.
  4. Hierarchie entfernen und Erzeugung zentralisieren.

Einsatzgrenze

Nicht anwenden, wenn eine echte Ist-ein-Beziehung mit gemeinsamem Vertrag und starken Invarianten besteht.

Risiken und Fehlanwendungen

Ein Delegate ohne klare Verantwortung kann zu beliebiger Strategie-Injektion führen. Seine Schnittstelle muss fachlich eng bleiben.

Verwandte Techniken

Verwandt mit Strategy, State, Decorator und Favor Composition over Inheritance.

Enterprise-Java-Einordnung

PremiumOrder-Unterklasse wird durch PricingPolicy und FulfillmentPolicy ersetzt, die unabhängig kombinierbar sind.

Teststrategie

Contract-Tests prüfen alle Delegate-Implementierungen und ausgewählte Kombinationen.

DeliveryServiceDelegate.java

DeliveryServiceDelegate.java
package com.aydinsude.workbench.catalog;
// Refactoring: Replace Subclass with Delegate
// Pattern: Strategy/Delegation
public final class DeliveryServiceDelegate {
  private final RoutePolicy policy;
  public DeliveryServiceDelegate(RoutePolicy policy){ this.policy=policy; }
  public String route(String destination){ return policy.route(destination); }
  public interface RoutePolicy { String route(String destination); }
  public static final class Express implements RoutePolicy { public String route(String d){ return "EXPRESS:"+d; } }
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

40. Replace Superclass with Delegate

Replace Superclass with Delegate

Fachliches Ausgangsproblem

Eine Klasse erbt von einer technischen oder fachfremden Oberklasse nur, um deren Funktionen zu nutzen. Dadurch wird ein falscher Ist-ein-Vertrag veröffentlicht.

Erkennungsmerkmale

Aufrufer sehen geerbte Methoden, die nicht zum Fachtyp gehören; Überschreibungen blockieren Verhalten oder die Oberklasse kann nicht unabhängig gewechselt werden.

Nutzen der Transformation

Der öffentliche Typvertrag entspricht wieder der Fachdomäne; technische Implementierung kann ausgetauscht werden.

Sicherer Refactoring-Ablauf

  1. Tatsächlich benötigte Funktionen der Oberklasse erfassen.
  2. Ein fokussiertes Delegate-Feld und eine schmale Schnittstelle einführen.
  3. Aufrufe auf Delegation umstellen.
  4. Vererbung entfernen und nicht passende API verschwinden lassen.

Einsatzgrenze

Nicht anwenden, wenn die Unterklasse vollständig substituierbar ist und die Oberklasse tatsächlich den stabilen Fachvertrag definiert.

Risiken und Fehlanwendungen

Bloße Eins-zu-eins-Weiterleitung kann eine unnötige Mittelschicht erzeugen. Nur fachlich benötigte Operationen sollten angeboten werden.

Verwandte Techniken

Verwandt mit Encapsulate Collection, Adapter und Replace Inheritance with Delegation.

Enterprise-Java-Einordnung

CustomerReport erbt nicht mehr von ArrayList, sondern besitzt eine List<ReportRow> und bietet nur fachliche Operationen an.

Teststrategie

Tests prüfen, dass nur erlaubte Operationen verfügbar sind und Delegatwechsel dasselbe Fachverhalten liefern.

CustomerScoringDelegate.java

CustomerScoringDelegate.java
package com.aydinsude.workbench.catalog;
// Refactoring: Replace Superclass with Delegate
// Pattern: Delegation over inheritance
public final class CustomerScoringDelegate {
  private final ScoreNormalizer normalizer;
  public CustomerScoringDelegate(ScoreNormalizer normalizer){ this.normalizer=normalizer; }
  public int score(int raw){ return normalizer.normalize(raw); }
  public static final class ScoreNormalizer { public int normalize(int raw){ return Math.max(0,Math.min(100,raw)); } }
}

Deutschsprachige fachliche Grundlagen

Die Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das vorhandene Java-Beispiel übertragen. Die Quellen dienen als fachliche Grundlage; der Text ist keine Übersetzung eines englischen Eintrags.

Kapitel 5 Primitive Werte durch Fachtypen ersetzen10 Refactorings

Datenkapselung, Bedingungen und Kontrollfluss mit kompilierbarem Java-21-Zielcode.

41. Replace Primitive with Object

Replace Primitive with Object

Fachliches Ausgangsproblem

Ein fachlich bedeutsamer Wert wird nur als String, Zahl oder Boolean weitergereicht. Dadurch liegen Validierung, Formatierung und Regeln an vielen Aufrufstellen verteilt.

Erkennungsmerkmale

Viele Parameter gleichen primitiven Typs, wiederholte Prüfungen, unklare Einheiten und Methoden wie calculate(double amount, String currency).

Nutzen der Transformation

Ein eigener Werttyp bündelt Bedeutung, Gültigkeit und Verhalten. Der Compiler verhindert Verwechslungen und Fachregeln erhalten einen eindeutigen Ort.

Sicherer Refactoring-Ablauf

  1. Money, EmailAddress oder CustomerNumber als unveränderliches Value Object einführen; zunächst Konstruktion absichern, dann Signaturen schrittweise umstellen.

Einsatzgrenze

Nicht für rein technische, lokal begrenzte Werte ohne fachliche Semantik. Ein Typ pro beliebiger Zahl erzeugt unnötige Zerlegung.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Value Object, Encapsulate Variable, Change Function Declaration

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Replace Primitive with Object nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

MoneyValue.java

MoneyValue.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
import java.util.Objects;
// Refactoring: Replace Primitive with Object
// Value Object: Money bündelt Betrag, Währung und fachliche Invarianten.
public record MoneyValue(BigDecimal amount, String currency) {
  public MoneyValue {
    Objects.requireNonNull(amount); Objects.requireNonNull(currency);
    if (currency.isBlank()) throw new IllegalArgumentException("currency");
  }
  public MoneyValue add(MoneyValue other) {
    if (!currency.equals(other.currency)) throw new IllegalArgumentException("currency mismatch");
    return new MoneyValue(amount.add(other.amount), currency);
  }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.math.BigDecimal; import org.junit.jupiter.api.Test;
class MoneyValueTest { @Test void primitive_becomes_value_object(){ var sum=new MoneyValue(new BigDecimal("10.00"),"EUR").add(new MoneyValue(new BigDecimal("2.50"),"EUR")); assertEquals(new BigDecimal("12.50"),sum.amount()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

42. Replace Temp with Query

Replace Temp with Query

Fachliches Ausgangsproblem

Ein temporäres Ergebnis wird gespeichert und später mehrfach verwendet. Die Variable verlängert den Zustand der Methode und erschwert das Zerlegen in kleinere Funktionen.

Erkennungsmerkmale

Temporäre Variablen werden weit entfernt von ihrer Berechnung gelesen; Änderungen der Berechnungsgrundlage erfordern manuelle Synchronisierung.

Nutzen der Transformation

Eine seiteneffektfreie Abfrage macht die Berechnung an jedem Verwendungspunkt sichtbar und erleichtert Extract Method.

Sicherer Refactoring-Ablauf

  1. Berechnung in eine kleine Query extrahieren, Tests sichern, temporäre Variable ersetzen und erst danach weitere Methoden abtrennen.

Einsatzgrenze

Nicht anwenden, wenn die Berechnung teuer, nicht deterministisch oder seiteneffektbehaftet ist. Dann ist ein bewusstes Snapshot-Objekt besser.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Extract Method, Memoization, Replace Derived Variable with Query

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Replace Temp with Query nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

OrderNetAmountQuery.java

OrderNetAmountQuery.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Replace Temp with Query
// Query Object: Die Berechnung ist benannt, wiederverwendbar und ohne Seiteneffekt.
public final class OrderNetAmountQuery {
  public BigDecimal net(BigDecimal gross, BigDecimal discount) { return gross.subtract(discount); }
  public boolean requiresApproval(BigDecimal gross, BigDecimal discount) { return net(gross,discount).compareTo(new BigDecimal("1000")) > 0; }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.math.BigDecimal; import org.junit.jupiter.api.Test;
class OrderNetAmountQueryTest { @Test void temp_is_query(){ assertTrue(new OrderNetAmountQuery().requiresApproval(new BigDecimal("1200"),new BigDecimal("100"))); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

43. Encapsulate Record

Encapsulate Record

Fachliches Ausgangsproblem

Eine Map, ein Array oder ein frei veränderbarer Datenträger repräsentiert ein Fachobjekt. Schlüssel, Pflichtfelder und Invarianten bleiben implizit.

Erkennungsmerkmale

String-Schlüssel an vielen Stellen, Casts, fehlende Felder erst zur Laufzeit und direkte Mutation durch beliebige Aufrufer.

Nutzen der Transformation

Ein typisierter Record oder eine Klasse macht Struktur und Bedeutung explizit und bietet einen kontrollierten Migrationspunkt.

Sicherer Refactoring-Ablauf

  1. Zunächst lesenden Wrapper einführen, dann Erzeugung zentralisieren, Schreibzugriffe in Methoden überführen und rohe Struktur aus der API entfernen.

Einsatzgrenze

Für echte dynamische Dokumente mit bewusst offenem Schema kann eine Map korrekt sein; dann sollte zumindest eine Validierungsgrenze existieren.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Replace Array with Object, Data Mapper, Value Object

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Encapsulate Record nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

CustomerRecord.java

CustomerRecord.java
package com.aydinsude.workbench.catalog;
import java.util.Map;
// Refactoring: Encapsulate Record
// Facade: Benannte Fachzugriffe verbergen die untypisierte Legacy-Struktur.
public final class CustomerRecord {
  private final Map<String,String> data;
  public CustomerRecord(Map<String,String> data){ this.data=Map.copyOf(data); }
  public String customerId(){ return required("customerId"); }
  public String email(){ return required("email"); }
  private String required(String key){ var value=data.get(key); if(value==null||value.isBlank()) throw new IllegalStateException(key); return value; }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.Map; import org.junit.jupiter.api.Test;
class CustomerRecordTest { @Test void record_is_encapsulated(){ var c=new CustomerRecord(Map.of("customerId","C1","email","a@example.test")); assertEquals("C1",c.customerId()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

44. Encapsulate Collection

Encapsulate Collection

Fachliches Ausgangsproblem

Eine interne Collection wird direkt herausgegeben. Aufrufer können Elemente hinzufügen, entfernen oder die Reihenfolge verändern und umgehen dabei Fachregeln.

Erkennungsmerkmale

Getter liefern modifizierbare Listen; add/remove geschieht außerhalb des Aggregats; Duplikate und Grenzen werden inkonsistent behandelt.

Nutzen der Transformation

Das besitzende Objekt kontrolliert Mutation und kann Invarianten, Ereignisse und Kopien zuverlässig verwalten.

Sicherer Refactoring-Ablauf

  1. Defensive Sicht zurückgeben, fachliche add/remove-Methoden einführen, Aufrufer migrieren und direkte Collection-Mutation sperren.

Einsatzgrenze

Nicht jede Liste braucht ein reiches Domänenobjekt. Bei reinem Transfer genügt eine unveränderliche Kopie.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Aggregate, Domain Collection, Defensive Copy

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Encapsulate Collection nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

OrderLineCollection.java

OrderLineCollection.java
package com.aydinsude.workbench.catalog;
import java.util.ArrayList; import java.util.List;
// Refactoring: Encapsulate Collection
// Aggregate: Änderungen an Positionen laufen ausschließlich über die Aggregate-Grenze.
public final class OrderLineCollection {
  private final List<String> lines=new ArrayList<>();
  public void add(String sku){ if(sku==null||sku.isBlank()) throw new IllegalArgumentException("sku"); lines.add(sku); }
  public List<String> lines(){ return List.copyOf(lines); }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class OrderLineCollectionTest { @Test void collection_is_protected(){ var o=new OrderLineCollection(); o.add("SKU-1"); assertThrows(UnsupportedOperationException.class,()->o.lines().add("SKU-2")); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

45. Replace Magic Literal with Symbolic Constant

Replace Magic Literal with Symbolic Constant

Fachliches Ausgangsproblem

Eine Zahl oder Zeichenkette trägt fachliche Bedeutung, die im Code nicht sichtbar ist. Gleiche Werte können zudem verschiedene Bedeutungen haben.

Erkennungsmerkmale

Verstreute 30, 0.19 oder 'VIP'; Kommentare erklären Zahlen; Änderungen erfordern globale Suche.

Nutzen der Transformation

Eine benannte Konstante oder ein Value Object dokumentiert die Regel und verhindert uneinheitliche Anpassungen.

Sicherer Refactoring-Ablauf

  1. Bedeutung klären, Vorkommen fachlich unterscheiden, Konstante nahe am Besitzer definieren und Tests für Grenzwerte ergänzen.

Einsatzgrenze

Konstanten sind ungeeignet für konfigurierbare oder zeitabhängige Regeln. Dann Policy oder Configuration Snapshot einsetzen.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Introduce Value Object, Extract Policy, Replace Primitive with Object

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Replace Magic Literal with Symbolic Constant nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

ApprovalThreshold.java

ApprovalThreshold.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Replace Magic Literal with Symbolic Constant
// Policy: Der Schwellenwert besitzt einen fachlichen Namen und eine zentrale Heimat.
public final class ApprovalThreshold {
  public static final BigDecimal MANUAL_APPROVAL_LIMIT=new BigDecimal("10000.00");
  public boolean manualApprovalRequired(BigDecimal amount){ return amount.compareTo(MANUAL_APPROVAL_LIMIT)>0; }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.math.BigDecimal; import org.junit.jupiter.api.Test;
class ApprovalThresholdTest { @Test void symbolic_constant_explains_rule(){ assertTrue(new ApprovalThreshold().manualApprovalRequired(new BigDecimal("10000.01"))); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

46. Introduce Assertion

Introduce Assertion

Fachliches Ausgangsproblem

Der Code setzt eine interne Bedingung voraus, dokumentiert sie aber nicht. Fehler zeigen sich später an einer fachlich entfernten Stelle.

Erkennungsmerkmale

Implizite Vorbedingungen, rechnerisch unmögliche Zustände und Kommentare wie 'kann nie null sein'.

Nutzen der Transformation

Eine Assertion macht die interne Annahme ausführbar und lokalisiert Programmierfehler früh.

Sicherer Refactoring-Ablauf

  1. Invariante identifizieren, Assertion an der Entstehungs- oder Nutzungskante platzieren und fachliche Eingabefehler weiterhin regulär behandeln.

Einsatzgrenze

Assertions ersetzen keine Validierung externer Daten und keine fachlichen Ablehnungen. Sie dürfen nicht Teil normaler Kontrollflüsse werden.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Guard Clause, Bean Validation, Design by Contract

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Introduce Assertion nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

SettlementInvariant.java

SettlementInvariant.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Introduce Assertion
// Invariant: Settlement darf nur für positive, bereits abgeglichene Beträge entstehen.
public final class SettlementInvariant {
  public String settle(BigDecimal amount, boolean reconciled){
    if(amount.signum()<=0) throw new IllegalStateException("amount must be positive");
    if(!reconciled) throw new IllegalStateException("payment must be reconciled");
    return "SETTLED:"+amount;
  }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.math.BigDecimal; import org.junit.jupiter.api.Test;
class SettlementInvariantTest { @Test void invalid_state_fails_near_source(){ assertThrows(IllegalStateException.class,()->new SettlementInvariant().settle(BigDecimal.TEN,false)); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

47. Replace Nested Conditional with Guard Clauses

Replace Nested Conditional with Guard Clauses

Fachliches Ausgangsproblem

Die Hauptlogik steckt tief in verschachtelten if-Blöcken. Sonderfälle dominieren die visuelle Struktur und erhöhen die kognitive Last.

Erkennungsmerkmale

Mehrere Einrückungsebenen, else-Zweige nach return-fähigen Bedingungen und schwer erkennbare Happy-Path-Logik.

Nutzen der Transformation

Frühe Rückgaben behandeln Sonderfälle zuerst; der normale Ablauf bleibt linear und lesbar.

Sicherer Refactoring-Ablauf

  1. Bedingungen einzeln charakterisieren, negierte Logik verständlich benennen, Guard Clauses schrittweise einführen und Verhalten testen.

Einsatzgrenze

Bei gemeinsamem Abschluss oder notwendiger Ressourcenfreigabe darf die Methode nicht unkontrolliert fragmentiert werden.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Decompose Conditional, Introduce Special Case, Extract Method

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Replace Nested Conditional with Guard Clauses nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

ClaimEligibilityGuards.java

ClaimEligibilityGuards.java
package com.aydinsude.workbench.catalog;
// Refactoring: Replace Nested Conditional with Guard Clauses
// Specification-artige Guards: Jeder Ablehnungsgrund ist früh und separat sichtbar.
public final class ClaimEligibilityGuards {
  public String decide(boolean activePolicy, boolean completeDocuments, int amount){
    if(!activePolicy) return "REJECTED_POLICY";
    if(!completeDocuments) return "REJECTED_DOCUMENTS";
    if(amount<=0) return "REJECTED_AMOUNT";
    return "ACCEPTED";
  }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class ClaimEligibilityGuardsTest { @Test void guards_keep_happy_path_flat(){ assertEquals("ACCEPTED",new ClaimEligibilityGuards().decide(true,true,100)); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

48. Replace Conditional with Polymorphism

Replace Conditional with Polymorphism

Fachliches Ausgangsproblem

Ein wachsender switch oder if-Block wählt Verhalten nach Typ, Status oder Kanal. Jede neue Variante verändert dieselbe zentrale Stelle.

Erkennungsmerkmale

Wiederholte Typprüfungen in mehreren Methoden, parallele switch-Blöcke und hohe Änderungsrate bei neuen Varianten.

Nutzen der Transformation

Polymorphe Typen oder Handler kapseln variierendes Verhalten; der Aufrufer arbeitet gegen einen stabilen Vertrag.

Sicherer Refactoring-Ablauf

  1. Variationsachse bestimmen, Interface extrahieren, eine Variante nach der anderen verschieben und Auswahl in Factory oder Registry isolieren.

Einsatzgrenze

Bei wenigen stabilen Fällen kann ein klarer switch verständlicher sein. Polymorphie ist kein Selbstzweck und darf Datenvarianten nicht künstlich aufblasen.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Strategy, State, Factory, Replace Type Code with Subclasses

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Replace Conditional with Polymorphism nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

DeliveryPricingPolymorphism.java

DeliveryPricingPolymorphism.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Replace Conditional with Polymorphism
// Design Pattern: Strategy - Versandpreise sind austauschbare Varianten.
public final class DeliveryPricingPolymorphism {
  public BigDecimal price(DeliveryPolicy policy, BigDecimal base){ return policy.price(base); }
  public sealed interface DeliveryPolicy permits Standard, Express { BigDecimal price(BigDecimal base); }
  public record Standard() implements DeliveryPolicy { public BigDecimal price(BigDecimal base){ return base.add(new BigDecimal("4.90")); } }
  public record Express() implements DeliveryPolicy { public BigDecimal price(BigDecimal base){ return base.add(new BigDecimal("14.90")); } }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.math.BigDecimal; import org.junit.jupiter.api.Test;
class DeliveryPricingPolymorphismTest { @Test void conditional_becomes_strategy(){ assertEquals(new BigDecimal("24.90"),new DeliveryPricingPolymorphism().price(new DeliveryPricingPolymorphism.Express(),new BigDecimal("10.00"))); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

49. Introduce Special Case

Introduce Special Case

Fachliches Ausgangsproblem

Ein besonderer Wert wie null, UNKNOWN oder GUEST führt an vielen Stellen zu denselben Prüfungen und Standardreaktionen.

Erkennungsmerkmale

Wiederholte Null- oder Sonderwertprüfungen, Standardtexte und alternative Berechnungen über mehrere Klassen verteilt.

Nutzen der Transformation

Ein Special-Case-Objekt kapselt das abweichende Verhalten und lässt Aufrufer den normalen Vertrag verwenden.

Sicherer Refactoring-Ablauf

  1. Gemeinsame Sonderfalllogik sammeln, passendes Objekt einführen, Erzeugung zentralisieren und Prüfungen schrittweise entfernen.

Einsatzgrenze

Wenn das Fehlen eines Werts fachlich relevant und explizit behandelt werden muss, ist Optional oder Result oft ehrlicher als ein scheinbar normales Objekt.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Null Object, Optional, Result Type

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Introduce Special Case nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

CustomerContactSpecialCase.java

CustomerContactSpecialCase.java
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Special Case
// Design Pattern: Null Object - anonyme Kunden besitzen explizites Standardverhalten.
public sealed interface CustomerContactSpecialCase permits CustomerContactSpecialCase.Known, CustomerContactSpecialCase.Anonymous {
  String displayName(); boolean contactable();
  record Known(String displayName, String email) implements CustomerContactSpecialCase { public boolean contactable(){ return email!=null&&!email.isBlank(); } }
  record Anonymous() implements CustomerContactSpecialCase { public String displayName(){ return "Anonymous"; } public boolean contactable(){ return false; } }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class CustomerContactSpecialCaseTest { @Test void special_case_removes_null_checks(){ CustomerContactSpecialCase c=new CustomerContactSpecialCase.Anonymous(); assertEquals("Anonymous",c.displayName()); assertFalse(c.contactable()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

50. Replace Control Flag with Break

Replace Control Flag with Break

Fachliches Ausgangsproblem

Eine boolesche Variable steuert, ob eine Schleife oder ein Zweig weiterläuft. Der eigentliche Abbruchgrund wird dadurch indirekt dargestellt.

Erkennungsmerkmale

Flags wie found, done oder continueProcessing werden an mehreren Stellen gesetzt und später geprüft.

Nutzen der Transformation

Direkter break, continue oder return bildet die Kontrollabsicht klarer ab und verkürzt den Gültigkeitsbereich von Zustand.

Sicherer Refactoring-Ablauf

  1. Flag-Verwendungen analysieren, einfachen Abbruch ersetzen, komplexe Schleife gegebenenfalls in eine benannte Suchfunktion extrahieren.

Einsatzgrenze

Nicht anwenden, wenn nach der Schleife der Abbruchgrund benötigt wird; dann ein typisiertes Ergebnis statt eines nackten Flags zurückgeben.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Replace Loop with Pipeline, Extract Method, Decision Object

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Replace Control Flag with Break nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

FraudSignalSearch.java

FraudSignalSearch.java
package com.aydinsude.workbench.catalog;
import java.util.List;
// Refactoring: Replace Control Flag with Break
// Zweck: Der Abbruch ist direkt am fachlichen Treffer sichtbar.
public final class FraudSignalSearch {
  public String firstBlocked(List<String> signals){
    for(String signal:signals){ if(signal.startsWith("BLOCK:")) return signal.substring(6); }
    return "NONE";
  }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.List; import org.junit.jupiter.api.Test;
class FraudSignalSearchTest { @Test void control_flag_is_removed(){ assertEquals("device",new FraudSignalSearch().firstBlocked(List.of("WARN:ip","BLOCK:device","BLOCK:account"))); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

Kapitel 6 Klassen extrahieren und Verantwortungen schneiden10 Refactorings

Klassen-, Delegations-, Algorithmus- und Conditional-Refactorings mit kompilierbarem Java-21-Zielcode.

51. Extract Class

Extract Class

Fachliches Ausgangsproblem

Eine Klasse trägt mehrere voneinander unabhängige Verantwortlichkeiten und ändert sich aus unterschiedlichen Gründen.

Erkennungsmerkmale

Viele Feldergruppen, Methodencluster, häufige Teiländerungen und Namen wie Manager, Processor oder Service mit breitem Auftrag.

Nutzen der Transformation

Eine neue Klasse schafft Kohäsion, begrenzt Abhängigkeiten und macht Verantwortlichkeiten separat testbar.

Sicherer Refactoring-Ablauf

  1. Methoden- und Feldcluster identifizieren, neue Klasse einführen, delegieren, Aufrufer migrieren und Schnittstelle anschließend verengen.

Einsatzgrenze

Zu frühes Extrahieren kann anämische Kleinstklassen und Weiterleitungsketten erzeugen. Verantwortlichkeit muss fachlich oder technisch eigenständig sein.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Single Responsibility, Move Function, Extract Service

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Extract Class nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

CustomerCreditProfile.java

CustomerCreditProfile.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Extract Class
// Design Pattern: Value Object - Kreditdaten und Invarianten bilden eine eigene Fachgrenze.
public record CustomerCreditProfile(BigDecimal limit, BigDecimal exposure) {
  public CustomerCreditProfile { if(limit.signum()<0||exposure.signum()<0) throw new IllegalArgumentException("negative credit values"); }
  public BigDecimal available(){ return limit.subtract(exposure); }
  public boolean canReserve(BigDecimal amount){ return amount.signum()>0 && available().compareTo(amount)>=0; }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.math.BigDecimal; import org.junit.jupiter.api.Test;
class CustomerCreditProfileTest { @Test void extracted_class_owns_credit_rule(){ assertTrue(new CustomerCreditProfile(new BigDecimal("1000"),new BigDecimal("250")).canReserve(new BigDecimal("700"))); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

52. Inline Class

Inline Class

Fachliches Ausgangsproblem

Eine Klasse besitzt keine eigenständige Verantwortung mehr und dient nur noch als dünne Hülle oder Datenablage.

Erkennungsmerkmale

Fast alle Methoden delegieren, Lebenszyklus ist identisch mit einer anderen Klasse und Änderungen erfolgen stets gemeinsam.

Nutzen der Transformation

Das Zusammenführen reduziert Indirektion und macht das Modell wieder proportional zur tatsächlichen Komplexität.

Sicherer Refactoring-Ablauf

  1. Öffentliche Nutzung prüfen, Verhalten und Daten in Besitzer verschieben, Aufrufer umstellen und überflüssigen Typ entfernen.

Einsatzgrenze

Nicht inlinen, wenn die Klasse eine stabile Grenze, ein Sicherheitskonzept oder einen zukünftigen unabhängigen Lebenszyklus repräsentiert.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Remove Middle Man, Collapse Hierarchy, Inline Method

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Inline Class nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

ShippingLabel.java

ShippingLabel.java
package com.aydinsude.workbench.catalog;
// Refactoring: Inline Class
// Value Object: Die kleine Adresshülle ist direkt Teil des Versandetiketts.
public record ShippingLabel(String recipient, String street, String city) {
  public ShippingLabel { if(recipient.isBlank()||street.isBlank()||city.isBlank()) throw new IllegalArgumentException("complete address required"); }
  public String printable(){ return recipient+" | "+street+" | "+city; }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class ShippingLabelTest { @Test void tiny_class_is_inlined(){ assertEquals("Aydin | Main 1 | Vienna",new ShippingLabel("Aydin","Main 1","Vienna").printable()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

53. Hide Delegate

Hide Delegate

Fachliches Ausgangsproblem

Aufrufer navigieren durch Objektketten und kennen interne Beziehungen, etwa order.customer().address().country().

Erkennungsmerkmale

Message Chains, wiederholte Navigationspfade und Änderungen der internen Struktur schlagen auf viele Clients durch.

Nutzen der Transformation

Das besitzende Objekt bietet eine fachliche Operation und verbirgt den Delegaten. Dadurch sinkt strukturelle Kopplung.

Sicherer Refactoring-Ablauf

  1. Häufige Navigation identifizieren, fachlich benannte Methode am passenden Besitzer ergänzen und Clients schrittweise umstellen.

Einsatzgrenze

Eine vollständige Abschirmung kann zur überladenen Fassade führen. Nicht jede Navigation ist ein Problem; entscheidend ist die Stabilität der Grenze.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Law of Demeter, Facade, Remove Middle Man

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Hide Delegate nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

OrderCustomerFacade.java

OrderCustomerFacade.java
package com.aydinsude.workbench.catalog;
// Refactoring: Hide Delegate
// Facade: Order verbirgt die interne Customer-Contact-Kette.
public final class OrderCustomerFacade {
  private final Customer customer;
  public OrderCustomerFacade(Customer customer){ this.customer=customer; }
  public String customerEmail(){ return customer.contact().email(); }
  public record Customer(Contact contact) {}
  public record Contact(String email) { public Contact { if(email==null||email.isBlank()) throw new IllegalArgumentException("email"); } }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class OrderCustomerFacadeTest { @Test void delegate_chain_is_hidden(){ var o=new OrderCustomerFacade(new OrderCustomerFacade.Customer(new OrderCustomerFacade.Contact("a@example.test"))); assertEquals("a@example.test",o.customerEmail()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

54. Remove Middle Man

Remove Middle Man

Fachliches Ausgangsproblem

Eine Klasse leitet fast jede Operation unverändert an einen Delegaten weiter. Die zusätzliche Ebene liefert keinen eigenen Vertrag mehr.

Erkennungsmerkmale

Viele triviale Einzeiler, parallele API zum Delegaten und häufige Anpassungen an beiden Klassen.

Nutzen der Transformation

Direkter Zugriff oder eine kleinere gezielte Grenze reduziert Boilerplate und macht Verantwortlichkeiten klarer.

Sicherer Refactoring-Ablauf

  1. Weiterleitungen kategorisieren, fachlich wertvolle behalten, triviale Clients direkt anbinden und alte Methoden deprecaten.

Einsatzgrenze

Nicht entfernen, wenn die Ebene Sicherheit, Transaktion, Caching, Beobachtbarkeit oder eine stabile externe API kapselt.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Hide Delegate, Facade, Adapter

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Remove Middle Man nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

PaymentGatewayAccess.java

PaymentGatewayAccess.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Remove Middle Man
// Port and Adapter: Der Use Case arbeitet direkt gegen den stabilen PaymentPort.
public final class PaymentGatewayAccess {
  private final PaymentPort port;
  public PaymentGatewayAccess(PaymentPort port){ this.port=port; }
  public String charge(String orderId, BigDecimal amount){ return port.charge(orderId,amount); }
  public interface PaymentPort { String charge(String orderId, BigDecimal amount); }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.math.BigDecimal; import org.junit.jupiter.api.Test;
class PaymentGatewayAccessTest { @Test void meaningless_middle_man_is_removed(){ var u=new PaymentGatewayAccess((id,a)->"PAID:"+id+":"+a); assertEquals("PAID:O1:12.50",u.charge("O1",new BigDecimal("12.50"))); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

55. Substitute Algorithm

Substitute Algorithm

Fachliches Ausgangsproblem

Ein Algorithmus ist unnötig komplex, fehleranfällig oder nicht mehr passend, obwohl sein externer Vertrag stabil bleiben soll.

Erkennungsmerkmale

Viele Sonderfälle, schwer beweisbare Schleifen, bekannte Standardlösung vorhanden oder schlechte Performance bei typischen Daten.

Nutzen der Transformation

Ein klarerer Algorithmus verbessert Verständlichkeit und Wartbarkeit, ohne die öffentliche Schnittstelle zu verändern.

Sicherer Refactoring-Ablauf

  1. Verhaltensbaseline und Randfälle sichern, neuen Algorithmus parallel implementieren, Ergebnisse vergleichen und kontrolliert umschalten.

Einsatzgrenze

Nicht ohne ausreichende Tests und Messdaten austauschen. Ein kürzerer Algorithmus ist nicht automatisch korrekter oder schneller.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Strategy, Differential Test, Performance Budget

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Substitute Algorithm nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

PrioritySortAlgorithm.java

PrioritySortAlgorithm.java
package com.aydinsude.workbench.catalog;
import java.util.Comparator; import java.util.List;
// Refactoring: Substitute Algorithm
// Strategy: Comparator beschreibt die Sortierregel explizit.
public final class PrioritySortAlgorithm {
  public List<Ticket> sort(List<Ticket> tickets){ return tickets.stream().sorted(Comparator.comparingInt(Ticket::priority).reversed().thenComparing(Ticket::id)).toList(); }
  public record Ticket(String id,int priority) {}
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.List; import org.junit.jupiter.api.Test;
class PrioritySortAlgorithmTest { @Test void standard_algorithm_is_clear(){ var s=new PrioritySortAlgorithm(); assertEquals(List.of("B","A"),s.sort(List.of(new PrioritySortAlgorithm.Ticket("A",1),new PrioritySortAlgorithm.Ticket("B",2))).stream().map(PrioritySortAlgorithm.Ticket::id).toList()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

56. Decompose Conditional

Decompose Conditional

Fachliches Ausgangsproblem

Eine komplexe Bedingung vermischt Entscheidung, Berechnung und Ergebnisbehandlung in einem großen Block.

Erkennungsmerkmale

Lange boolesche Ausdrücke, mehrfach verschachtelte Zweige und fachliche Regeln, die nur aus Operatoren erschlossen werden können.

Nutzen der Transformation

Benannte Methoden für Bedingung und Zweige machen die Fachentscheidung lesbar und separat testbar.

Sicherer Refactoring-Ablauf

  1. Bedingung benennen, then/else-Blöcke extrahieren, gemeinsame Datenübergabe reduzieren und anschließend weitere Pattern prüfen.

Einsatzgrenze

Kleine offensichtliche Bedingungen sollten nicht in bedeutungslose Methoden zerlegt werden.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Consolidate Conditional Expression, Specification, Guard Clauses

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Decompose Conditional nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

ContractPricingDecision.java

ContractPricingDecision.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal; import java.time.LocalDate;
// Refactoring: Decompose Conditional
// Policy: Benannte Teilentscheidungen machen die Preisregel lesbar.
public final class ContractPricingDecision {
  public BigDecimal price(LocalDate today, LocalDate validUntil, boolean premium, BigDecimal base){
    if(isExpired(today,validUntil)) return BigDecimal.ZERO;
    return premiumPrice(premium,base);
  }
  private boolean isExpired(LocalDate today, LocalDate validUntil){ return today.isAfter(validUntil); }
  private BigDecimal premiumPrice(boolean premium, BigDecimal base){ return premium?base.multiply(new BigDecimal("0.90")):base; }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.math.BigDecimal; import java.time.LocalDate; import org.junit.jupiter.api.Test;
class ContractPricingDecisionTest { @Test void conditional_parts_are_named(){ assertEquals(new BigDecimal("90.00"),new ContractPricingDecision().price(LocalDate.of(2026,1,1),LocalDate.of(2026,12,31),true,new BigDecimal("100.00"))); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

57. Consolidate Conditional Expression

Consolidate Conditional Expression

Fachliches Ausgangsproblem

Mehrere aufeinanderfolgende Bedingungen führen zum gleichen Ergebnis, werden aber getrennt geprüft.

Erkennungsmerkmale

Serien von if-return mit identischem Rückgabewert und schwer sichtbarer gemeinsamer fachlicher Bedeutung.

Nutzen der Transformation

Eine gemeinsame, benannte Bedingung zeigt, dass die Fälle dieselbe Entscheidung repräsentieren.

Sicherer Refactoring-Ablauf

  1. Gleiches Ergebnis verifizieren, Bedingungen kombinieren, Ausdruck benennen und Wahrheitstabelle testen.

Einsatzgrenze

Nicht zusammenführen, wenn Bedingungen unterschiedliche fachliche Ursachen mit eigener Diagnose oder Auditierung darstellen.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Extract Method, Specification, Decision Table

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Consolidate Conditional Expression nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

EligibilityRules.java

EligibilityRules.java
package com.aydinsude.workbench.catalog;
// Refactoring: Consolidate Conditional Expression
// Specification: Die gesamte Zulässigkeit besitzt einen Namen und eine Testgrenze.
public final class EligibilityRules {
  public boolean eligible(int age, boolean activeAccount, boolean sanctionsHit){ return hasMinimumAge(age)&&activeAccount&&!sanctionsHit; }
  private boolean hasMinimumAge(int age){ return age>=18; }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class EligibilityRulesTest { @Test void conditions_are_consolidated(){ assertTrue(new EligibilityRules().eligible(30,true,false)); assertFalse(new EligibilityRules().eligible(17,true,false)); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

58. Consolidate Duplicate Conditional Fragments

Consolidate Duplicate Conditional Fragments

Fachliches Ausgangsproblem

Gleicher Code steht am Anfang oder Ende beider Zweige eines Conditionals und verdeckt den tatsächlichen Unterschied.

Erkennungsmerkmale

Identische Aufrufe in then und else, duplizierte Nachbereitung oder wiederholte Ressourcenfreigabe.

Nutzen der Transformation

Gemeinsame Fragmente werden vor oder nach die Entscheidung gezogen; der Conditional zeigt nur noch die Variation.

Sicherer Refactoring-Ablauf

  1. Duplikat auf Seiteneffekte prüfen, gemeinsame Reihenfolge sichern, Fragment verschieben und beide Pfade testen.

Einsatzgrenze

Nicht verschieben, wenn Ausführungshäufigkeit, Reihenfolge oder Transaktionskontext zwischen den Zweigen verschieden ist.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Slide Statements, Extract Method, Template Method

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Consolidate Duplicate Conditional Fragments nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

NotificationDispatch.java

NotificationDispatch.java
package com.aydinsude.workbench.catalog;
// Refactoring: Consolidate Duplicate Conditional Fragments
// Template Method-artiger Ablauf: Varianten unterscheiden nur den Kanal, Abschluss bleibt gemeinsam.
public final class NotificationDispatch {
  public String dispatch(boolean urgent,String message){
    String channel=urgent?"SMS":"EMAIL";
    String sent=channel+":"+message;
    return audit(sent);
  }
  private String audit(String sent){ return "AUDITED:"+sent; }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class NotificationDispatchTest { @Test void duplicate_fragments_are_consolidated(){ assertEquals("AUDITED:SMS:alert",new NotificationDispatch().dispatch(true,"alert")); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

59. Replace Error Code with Exception

Replace Error Code with Exception

Fachliches Ausgangsproblem

Eine Methode meldet Fehler über Zahlen, Sonderwerte oder Booleans. Aufrufer können die Prüfung vergessen oder Bedeutung verwechseln.

Erkennungsmerkmale

Rückgabewerte wie -1, null oder false mit zusätzlichem lastError; Erfolg und Fehler teilen denselben Rückgabetyp.

Nutzen der Transformation

Eine typisierte Exception trennt außergewöhnlichen Fehlerpfad vom normalen Ergebnis und bewahrt Ursache sowie Kontext.

Sicherer Refactoring-Ablauf

  1. Fehlerklassen definieren, Aufrufer einzeln migrieren, Ursache erhalten und API-Vertrag dokumentieren.

Einsatzgrenze

Erwartbare fachliche Ablehnungen sind oft Result oder Decision, nicht Exception. Exceptions dürfen keinen normalen Geschäftsfluss modellieren.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Exception Translation, Result Type, Replace Exception with Precheck

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Replace Error Code with Exception nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

InventoryReservation.java

InventoryReservation.java
package com.aydinsude.workbench.catalog;
// Refactoring: Replace Error Code with Exception
// Domain Exception: Fehlschlag transportiert fachlichen Kontext statt Zahlencode.
public final class InventoryReservation {
  public String reserve(String sku,int requested,int available){
    if(requested<=0) throw new IllegalArgumentException("requested");
    if(available<requested) throw new InsufficientStockException(sku,requested,available);
    return "RESERVED:"+sku+":"+requested;
  }
  public static final class InsufficientStockException extends RuntimeException {
    public InsufficientStockException(String sku,int requested,int available){ super("sku="+sku+", requested="+requested+", available="+available); }
  }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class InventoryReservationTest { @Test void error_code_becomes_exception(){ assertThrows(InventoryReservation.InsufficientStockException.class,()->new InventoryReservation().reserve("S1",5,2)); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

60. Replace Exception with Precheck

Replace Exception with Precheck

Fachliches Ausgangsproblem

Eine erwartbare, häufige Situation wird über eine Exception erkannt, obwohl sie vorab günstig und zuverlässig geprüft werden kann.

Erkennungsmerkmale

Exceptions bei normalem Ablauf, etwa fehlender Map-Schlüssel oder nicht vorhandene optionale Datei; auffällige Catch-Blöcke in Schleifen.

Nutzen der Transformation

Eine explizite Vorprüfung macht den normalen Kontrollfluss sichtbar und vermeidet unnötige Ausnahmebehandlung.

Sicherer Refactoring-Ablauf

  1. Atomare oder konsistente Precheck-Möglichkeit prüfen, Bedingung extrahieren und Race Conditions berücksichtigen.

Einsatzgrenze

Nicht anwenden, wenn zwischen Prüfung und Aktion ein Zeitfenster entsteht oder die Operation selbst die einzige verlässliche Prüfung ist.

Risiken und Fehlanwendungen

Das Refactoring muss in kleinen, kompilierbaren Schritten erfolgen. Vor allem öffentliche Verträge, Nebenwirkungen, Transaktionsgrenzen und Nebenläufigkeit dürfen nicht stillschweigend verändert werden.

Verwandte Techniken

Guard Clause, Try-Operation, Result Type

Enterprise-Java-Einordnung

In einer Enterprise-Anwendung wird Replace Exception with Precheck nicht isoliert angewendet, sondern an einer klaren fachlichen oder technischen Grenze. Der vorhandene Java-21-Code zeigt eine kleine, kompilierbare Zielstruktur; in Spring oder Jakarta EE bleibt die Fachlogik dabei unabhängig vom Framework.

Teststrategie

Vor der Änderung sichern Characterization Tests das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfall, Grenzwerte und Fehlerpfade; bei öffentlichen APIs kommen Vertrags- oder Integrationstests hinzu.

DocumentLookup.java

DocumentLookup.java
package com.aydinsude.workbench.catalog;
import java.util.Map; import java.util.Optional;
// Refactoring: Replace Exception with Precheck
// Repository Query: Erwartbare Abwesenheit wird als Optional modelliert.
public final class DocumentLookup {
  private final Map<String,String> documents;
  public DocumentLookup(Map<String,String> documents){ this.documents=Map.copyOf(documents); }
  public Optional<String> find(String id){ if(id==null||id.isBlank()) return Optional.empty(); return Optional.ofNullable(documents.get(id)); }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.Map; import org.junit.jupiter.api.Test;
class DocumentLookupTest { @Test void expected_absence_is_not_exceptional(){ assertTrue(new DocumentLookup(Map.of("D1","content")).find("missing").isEmpty()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Die Quellen dienen als Grundlage; der Text ist keine wörtliche Übersetzung.

Kapitel 7 Variablen, Bedingungen und Kontrollfluss klären10 Refactorings

Variablen-, Feld-, Vererbungs- und Delegations-Refactorings mit kompilierbarem Java-21-Zielcode.

61. Split Variable

Split Variable

Fachliches Ausgangsproblem

Eine Variable wird während einer Methode für mehrere unterschiedliche Bedeutungen wiederverwendet. Dadurch ist nicht mehr erkennbar, welcher Wert zu welchem Verarbeitungsschritt gehört.

Erkennungsmerkmale

Mehrfache Zuweisungen mit wechselnder Semantik, Namen wie temp oder value und schwer nachvollziehbare Datenflüsse.

Nutzen der Transformation

Jede Bedeutung erhält eine eigene Variable. Dadurch werden Lebensdauer, Zweck und Abhängigkeiten sichtbar und weitere Extraktionen werden sicherer.

Sicherer Refactoring-Ablauf

  1. Zuweisungen und Verwendungen gruppieren, für jede Bedeutung einen präzisen Namen einführen, schrittweise ersetzen und anschließend unnötige Mutationen entfernen.

Einsatzgrenze

Nicht jede Neuzuweisung ist problematisch. Schleifenzähler oder bewusst akkumulierte Werte brauchen keine künstliche Aufteilung.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Rename Variable, Extract Variable, Replace Temp with Query

Enterprise-Java-Einordnung

Split Variable wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

PricingCalculation.java

PricingCalculation.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Split Variable
// Value Object-orientiert: Jede Rechenstufe besitzt eine eindeutige Bedeutung.
public final class PricingCalculation {
  public BigDecimal total(BigDecimal unitPrice,int quantity,BigDecimal discountRate){
    BigDecimal gross=unitPrice.multiply(BigDecimal.valueOf(quantity));
    BigDecimal discount=gross.multiply(discountRate);
    BigDecimal net=gross.subtract(discount);
    return net;
  }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.math.BigDecimal; import org.junit.jupiter.api.Test;
class PricingCalculationTest { @Test void variables_have_single_meaning(){ assertEquals(new BigDecimal("180.00"),new PricingCalculation().total(new BigDecimal("100.00"),2,new BigDecimal("0.10"))); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

62. Rename Field

Rename Field

Fachliches Ausgangsproblem

Ein Feldname beschreibt seine fachliche Bedeutung oder Einheit nicht mehr korrekt und führt dadurch zu Fehlinterpretationen.

Erkennungsmerkmale

Abkürzungen, historische Namen, widersprüchliche Begriffe zwischen Code und Fachsprache oder Felder wie statusFlag und data.

Nutzen der Transformation

Ein präziser Name macht Invarianten, Einheit und Verantwortlichkeit sichtbar und reduziert den Bedarf an Kommentaren.

Sicherer Refactoring-Ablauf

  1. Verwendungen und Serialisierungsverträge ermitteln, Feld kontrolliert umbenennen, Mapper und Tests anpassen und externe Kompatibilität bewusst sichern.

Einsatzgrenze

Bei öffentlichen JSON-, Datenbank- oder Messaging-Verträgen darf die technische Feldbezeichnung nicht ohne Migrationsplan geändert werden.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Rename Variable, Encapsulate Field, Data Mapper

Enterprise-Java-Einordnung

Rename Field wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

CustomerAccount.java

CustomerAccount.java
package com.aydinsude.workbench.catalog;
// Refactoring: Rename Field
// Ubiquitous Language: Der Feldname spiegelt den Fachbegriff wider.
public record CustomerAccount(String accountStatus) {
  public CustomerAccount { if(accountStatus==null||accountStatus.isBlank()) throw new IllegalArgumentException("accountStatus"); }
  public boolean active(){ return "ACTIVE".equals(accountStatus); }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class CustomerAccountTest { @Test void field_name_expresses_domain(){ assertTrue(new CustomerAccount("ACTIVE").active()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

63. Pull Up Method

Pull Up Method

Fachliches Ausgangsproblem

Mehrere Unterklassen enthalten identische oder nahezu identische Methoden, obwohl das Verhalten zur gemeinsamen Abstraktion gehört.

Erkennungsmerkmale

Duplizierte Implementierungen, parallele Fehlerkorrekturen und Methoden mit identischem Vertrag in Geschwisterklassen.

Nutzen der Transformation

Gemeinsames Verhalten erhält einen einzigen Ort; Unterklassen konzentrieren sich auf echte Unterschiede.

Sicherer Refactoring-Ablauf

  1. Vorbedingungen vergleichen, Unterschiede beseitigen oder parametrisieren, Methode in die Oberklasse verschieben und Unterklassen schrittweise vereinfachen.

Einsatzgrenze

Nicht hochziehen, wenn die Ähnlichkeit nur zufällig ist oder die Methode fachlich unterschiedliche Invarianten besitzt.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Extract Superclass, Template Method, Parameterize Function

Enterprise-Java-Einordnung

Pull Up Method wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

NotificationTemplate.java

NotificationTemplate.java
package com.aydinsude.workbench.catalog;
// Refactoring: Pull Up Method
// Template Method: Gemeinsame Normalisierung liegt in der Basisklasse.
public abstract class NotificationTemplate {
  public final String render(String message){ return channel()+":"+normalize(message); }
  protected abstract String channel();
  protected String normalize(String message){ return message.trim().replaceAll("\\s+"," "); }
  public static final class Email extends NotificationTemplate { protected String channel(){ return "EMAIL"; } }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class NotificationTemplateTest { @Test void common_method_is_pulled_up(){ assertEquals("EMAIL:hello world",new NotificationTemplate.Email().render(" hello   world ")); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

64. Pull Up Field

Pull Up Field

Fachliches Ausgangsproblem

Mehrere Unterklassen besitzen dasselbe Feld mit gleicher Bedeutung und gleichem Lebenszyklus.

Erkennungsmerkmale

Identische Felder, doppelte Initialisierung und parallele Getter in Geschwisterklassen.

Nutzen der Transformation

Gemeinsamer Zustand wird zentral verwaltet und kann konsistent geschützt werden.

Sicherer Refactoring-Ablauf

  1. Semantik und Initialisierung vergleichen, Feld in die Oberklasse verschieben, Konstruktoren anpassen und Sichtbarkeit möglichst klein halten.

Einsatzgrenze

Nicht anwenden, wenn gleiche Namen unterschiedliche fachliche Bedeutungen verdecken oder die Oberklasse dadurch unnötigen Zustand erhält.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Pull Up Constructor Body, Extract Superclass, Encapsulate Variable

Enterprise-Java-Einordnung

Pull Up Field wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

AuditedCommand.java

AuditedCommand.java
package com.aydinsude.workbench.catalog;
// Refactoring: Pull Up Field
// Command: Gemeinsamer Korrelationskontext wird zentral verwaltet.
public abstract class AuditedCommand {
  private final String correlationId;
  protected AuditedCommand(String correlationId){ if(correlationId==null||correlationId.isBlank()) throw new IllegalArgumentException("correlationId"); this.correlationId=correlationId; }
  public String correlationId(){ return correlationId; }
  public static final class ApproveOrder extends AuditedCommand { public ApproveOrder(String id){ super(id); } }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class AuditedCommandTest { @Test void common_field_is_pulled_up(){ assertEquals("corr-1",new AuditedCommand.ApproveOrder("corr-1").correlationId()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

65. Pull Up Constructor Body

Pull Up Constructor Body

Fachliches Ausgangsproblem

Unterklassen wiederholen denselben Initialisierungsschritt, obwohl er Teil der gemeinsamen Oberklasseninvariante ist.

Erkennungsmerkmale

Gleiche Validierungen, Normalisierungen oder Feldzuweisungen in mehreren Konstruktoren.

Nutzen der Transformation

Die Oberklasse stellt ihre Invariante selbst her; Unterklassen liefern nur ihre spezifischen Werte.

Sicherer Refactoring-Ablauf

  1. Gemeinsame Initialisierung identifizieren, geschützten Oberklassenkonstruktor einführen, super-Aufrufe ergänzen und Duplikate entfernen.

Einsatzgrenze

Eine Oberklasse darf keine unterklassenspezifischen Annahmen erzwingen. Dann ist Komposition oder Factory oft geeigneter.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Pull Up Field, Extract Superclass, Factory Method

Enterprise-Java-Einordnung

Pull Up Constructor Body wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

BaseImportJob.java

BaseImportJob.java
package com.aydinsude.workbench.catalog;
// Refactoring: Pull Up Constructor Body
// Template Method: Gemeinsame Initialisierung liegt in der Basisklasse.
public abstract class BaseImportJob {
  private final String source;
  protected BaseImportJob(String source){ if(source==null||source.isBlank()) throw new IllegalArgumentException("source"); this.source=source.trim(); }
  public String source(){ return source; }
  public static final class CsvImportJob extends BaseImportJob { public CsvImportJob(String source){ super(source); } }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class BaseImportJobTest { @Test void constructor_body_is_pulled_up(){ assertEquals("orders.csv",new BaseImportJob.CsvImportJob(" orders.csv ").source()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

66. Push Down Method

Push Down Method

Fachliches Ausgangsproblem

Eine Methode der Oberklasse wird nur von wenigen Unterklassen sinnvoll verwendet und belastet alle anderen Typen mit einem unpassenden Vertrag.

Erkennungsmerkmale

Leere Implementierungen, UnsupportedOperationException oder Methoden, die nur nach instanceof sinnvoll sind.

Nutzen der Transformation

Der Vertrag wird auf die Typen begrenzt, die ihn tatsächlich erfüllen können.

Sicherer Refactoring-Ablauf

  1. Nutzer der Methode ermitteln, Methode in passende Unterklasse verschieben, Aufrufer auf präzisere Typen umstellen und Oberklassenvertrag verkleinern.

Einsatzgrenze

Wenn Aufrufer weiterhin polymorph auf das Verhalten angewiesen sind, ist eine neue Rolle oder ein Interface besser als bloßes Verschieben.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Push Down Field, Extract Interface, Replace Inheritance with Delegation

Enterprise-Java-Einordnung

Push Down Method wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

ExportFormat.java

ExportFormat.java
package com.aydinsude.workbench.catalog;
// Refactoring: Push Down Method
// Polymorphism: Variantenspezifisches Verhalten bleibt bei der Variante.
public sealed interface ExportFormat permits CsvExport, PdfExport { String mediaType(); }
final class CsvExport implements ExportFormat { public String mediaType(){ return "text/csv"; } public char delimiter(){ return ';'; } }
final class PdfExport implements ExportFormat { public String mediaType(){ return "application/pdf"; } }

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class ExportFormatTest { @Test void specialized_method_is_pushed_down(){ assertEquals(';',new CsvExport().delimiter()); assertEquals("application/pdf",new PdfExport().mediaType()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

67. Push Down Field

Push Down Field

Fachliches Ausgangsproblem

Ein Feld in der Oberklasse ist nur für einzelne Unterklassen relevant und erzeugt bei anderen Typen bedeutungslosen Zustand.

Erkennungsmerkmale

Null- oder Standardwerte in manchen Unterklassen, bedingte Nutzung und Speicherzustand ohne fachliche Bedeutung.

Nutzen der Transformation

Jede Unterklasse trägt nur den Zustand, den ihre Invarianten wirklich benötigen.

Sicherer Refactoring-Ablauf

  1. Verwendungen analysieren, Feld und Initialisierung in die betroffenen Unterklassen verschieben und Oberklassenkonstruktor vereinfachen.

Einsatzgrenze

Wenn das Feld eine gemeinsame zukünftige Fähigkeit ausdrückt, kann ein separates Capability-Objekt geeigneter sein.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Push Down Method, Extract Class, Capability Interface

Enterprise-Java-Einordnung

Push Down Field wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

DeliveryOption.java

DeliveryOption.java
package com.aydinsude.workbench.catalog;
import java.time.Duration;
// Refactoring: Push Down Field
// Polymorphism: Express-spezifische SLA liegt nur in ExpressDelivery.
public sealed interface DeliveryOption permits StandardDelivery, ExpressDelivery { String code(); }
final class StandardDelivery implements DeliveryOption { public String code(){ return "STANDARD"; } }
record ExpressDelivery(Duration serviceLevel) implements DeliveryOption { public ExpressDelivery { if(serviceLevel.isNegative()||serviceLevel.isZero()) throw new IllegalArgumentException("serviceLevel"); } public String code(){ return "EXPRESS"; } }

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.Duration; import org.junit.jupiter.api.Test;
class DeliveryOptionTest { @Test void specialized_field_is_pushed_down(){ assertEquals(Duration.ofHours(4),new ExpressDelivery(Duration.ofHours(4)).serviceLevel()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

68. Replace Delegation with Inheritance

Replace Delegation with Inheritance

Fachliches Ausgangsproblem

Eine Klasse delegiert nahezu jede Operation an einen stabilen Basistyp und soll dessen vollständigen Vertrag bewusst übernehmen.

Erkennungsmerkmale

Viele reine Weiterleitungsmethoden, identischer Lebenszyklus und echte substituierbare Beziehung.

Nutzen der Transformation

Direkte Vererbung kann unnötige Weiterleitung entfernen und Polymorphie vereinfachen.

Sicherer Refactoring-Ablauf

  1. Liskov-Substitution prüfen, Basisklasse kontrollierbar machen, Delegation schrittweise ersetzen und Verhalten mit Vertragstests absichern.

Einsatzgrenze

Dieses Refactoring ist selten. Bei bloßer Wiederverwendung oder instabiler Basisklasse bleibt Delegation meist die bessere Wahl.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Replace Inheritance with Delegation, Liskov Substitution, Adapter

Enterprise-Java-Einordnung

Replace Delegation with Inheritance wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

CsvReportFormatter.java

CsvReportFormatter.java
package com.aydinsude.workbench.catalog;
// Refactoring: Replace Delegation with Inheritance
// Template Method: CSV-Formatter ist eine echte Spezialisierung des Basisformatters.
public class CsvReportFormatter extends ReportFormatter {
  @Override protected String separator(){ return ";"; }
}
class ReportFormatter {
  protected String separator(){ return ","; }
  public String row(String left,String right){ return left+separator()+right; }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class CsvReportFormatterTest { @Test void specialization_uses_inheritance(){ assertEquals("A;B",new CsvReportFormatter().row("A","B")); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

69. Replace Inheritance with Delegation

Replace Inheritance with Delegation

Fachliches Ausgangsproblem

Eine Unterklasse erbt Verhalten und Zustand, obwohl sie fachlich kein echtes Exemplar des Basistyps ist oder nur einen Teil seiner Fähigkeiten benötigt.

Erkennungsmerkmale

Überschriebene Methoden mit leerem Verhalten, verletzte Invarianten, fragile Basisklasse und Änderungen mit unerwarteten Nebenwirkungen.

Nutzen der Transformation

Delegation trennt Wiederverwendung von Typbeziehung und erlaubt gezielte, austauschbare Zusammenarbeit.

Sicherer Refactoring-Ablauf

  1. Benötigte Fähigkeiten identifizieren, Delegat einführen, Aufrufe schrittweise weiterleiten, öffentlichen Vertrag verkleinern und Vererbung entfernen.

Einsatzgrenze

Delegation kann zu bloßen Weiterleitungsketten führen. Der Delegat sollte eine klar benannte Verantwortung besitzen.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Decorator, Strategy, Adapter, Collapse Hierarchy

Enterprise-Java-Einordnung

Replace Inheritance with Delegation wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

DiscountedPrice.java

DiscountedPrice.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Replace Inheritance with Delegation
// Strategy: Preisberechnung wird als kleiner Port delegiert.
public final class DiscountedPrice {
  private final PricePolicy policy;
  public DiscountedPrice(PricePolicy policy){ this.policy=policy; }
  public BigDecimal calculate(BigDecimal base){ return policy.apply(base); }
  public interface PricePolicy { BigDecimal apply(BigDecimal base); }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.math.BigDecimal; import org.junit.jupiter.api.Test;
class DiscountedPriceTest { @Test void delegation_replaces_inheritance(){ var p=new DiscountedPrice(v->v.multiply(new BigDecimal("0.80"))); assertEquals(new BigDecimal("80.0000"),p.calculate(new BigDecimal("100.00"))); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

70. Replace Type Code with State/Strategy

Replace Type Code with State/Strategy

Fachliches Ausgangsproblem

Ein Typ- oder Statuscode steuert wiederholt unterschiedliches Verhalten und erzeugt wachsende switch-Blöcke.

Erkennungsmerkmale

Parallele Bedingungen in mehreren Methoden, neue Varianten ändern viele Stellen und ungültige Kombinationen bleiben darstellbar.

Nutzen der Transformation

State oder Strategy kapseln variierendes Verhalten hinter einem stabilen Vertrag; Auswahl und Verhalten werden getrennt.

Sicherer Refactoring-Ablauf

  1. Variationsachse bestimmen, Interface einführen, Varianten schrittweise extrahieren, Auswahl zentralisieren und alte Typcodes aus Verhalten entfernen.

Einsatzgrenze

Für reine Datenklassifikation oder wenige stabile Fälle kann ein Enum mit klarer Logik ausreichend sein.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Strategy, State, Replace Conditional with Polymorphism

Enterprise-Java-Einordnung

Replace Type Code with State/Strategy wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

PaymentLifecycle.java

PaymentLifecycle.java
package com.aydinsude.workbench.catalog;
// Refactoring: Replace Type Code with State/Strategy
// State Pattern: Jeder Zustand kontrolliert seine erlaubten Übergänge.
public final class PaymentLifecycle {
  private State state=new Pending();
  public void authorize(){ state=state.authorize(); }
  public String status(){ return state.name(); }
  private interface State { State authorize(); String name(); }
  private static final class Pending implements State { public State authorize(){ return new Authorized(); } public String name(){ return "PENDING"; } }
  private static final class Authorized implements State { public State authorize(){ return this; } public String name(){ return "AUTHORIZED"; } }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class PaymentLifecycleTest { @Test void state_replaces_type_code(){ var p=new PaymentLifecycle(); p.authorize(); assertEquals("AUTHORIZED",p.status()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

Kapitel 8 Kapselung, Felder und Zugriffsgrenzen verbessern10 Refactorings

Datenmodell-, Assoziations- und Typisierungs-Refactorings mit kompilierbarem Java-21-Zielcode.

71. Self Encapsulate Field

Self Encapsulate Field

Fachliches Ausgangsproblem

Eine Klasse greift direkt auf ihr eigenes Feld zu, obwohl Zugriff, Normalisierung oder spätere Ableitung kontrolliert werden sollen.

Erkennungsmerkmale

Viele direkte Lese- und Schreibzugriffe, schwer einführbare Invarianten und spätere Umstellung auf berechnete Werte.

Nutzen der Transformation

Interne Zugriffsmethoden schaffen einen einzigen Kontrollpunkt und erleichtern spätere Änderungen der Repräsentation.

Sicherer Refactoring-Ablauf

  1. Private Getter/Setter oder fachliche Methoden einführen, interne Zugriffe schrittweise ersetzen und Setter möglichst durch intentionale Operationen begrenzen.

Einsatzgrenze

Mechanische Getter und Setter ohne fachlichen Nutzen erhöhen nur die Indirektion. Kapselung braucht einen konkreten Änderungsgrund.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Encapsulate Variable, Replace Primitive with Object, Introduce Assertion

Enterprise-Java-Einordnung

Self Encapsulate Field wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

CustomerBalance.java

CustomerBalance.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Self Encapsulate Field
// Encapsulation: Invarianten bleiben an einem kontrollierten Zugriffspunkt.
public final class CustomerBalance {
  private BigDecimal amount = BigDecimal.ZERO;
  public BigDecimal amount(){ return amount; }
  public void changeTo(BigDecimal value){
    if(value==null || value.signum()<0) throw new IllegalArgumentException("amount");
    amount=value;
  }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.math.BigDecimal; import org.junit.jupiter.api.Test;
class CustomerBalanceTest { @Test void field_is_self_encapsulated(){ var b=new CustomerBalance(); b.changeTo(new BigDecimal("25.00")); assertEquals(new BigDecimal("25.00"),b.amount()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

72. Replace Array with Object

Replace Array with Object

Fachliches Ausgangsproblem

Ein Array speichert heterogene Fachwerte an positionsabhängigen Indizes. Bedeutung und Gültigkeit bleiben unsichtbar.

Erkennungsmerkmale

Zugriffe wie row[0], row[3], Casts und Kommentare zur Spaltenreihenfolge.

Nutzen der Transformation

Ein typisiertes Objekt macht Felder, Einheiten und Invarianten explizit und verhindert Indexfehler.

Sicherer Refactoring-Ablauf

  1. Bedeutung jeder Position dokumentieren, Record oder Klasse einführen, Konvertierung an der Grenze zentralisieren und Aufrufer schrittweise migrieren.

Einsatzgrenze

Für homogene numerische Vektoren oder bewusst tabellarische technische Daten bleibt ein Array angemessen.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Encapsulate Record, Data Mapper, Replace Stringly-Typed Map with Typed Record

Enterprise-Java-Einordnung

Replace Array with Object wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

ShipmentWindow.java

ShipmentWindow.java
package com.aydinsude.workbench.catalog;
import java.time.LocalDate;
// Refactoring: Replace Array with Object
// Value Object: Benannte Komponenten ersetzen fragile Indexkonventionen.
public record ShipmentWindow(LocalDate from, LocalDate to) {
  public ShipmentWindow { if(from==null||to==null||to.isBefore(from)) throw new IllegalArgumentException("window"); }
  public long days(){ return java.time.temporal.ChronoUnit.DAYS.between(from,to)+1; }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.LocalDate; import org.junit.jupiter.api.Test;
class ShipmentWindowTest { @Test void object_replaces_array(){ var w=new ShipmentWindow(LocalDate.of(2026,7,1),LocalDate.of(2026,7,3)); assertEquals(3,w.days()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

73. Change Unidirectional Association to Bidirectional

Change Unidirectional Association to Bidirectional

Fachliches Ausgangsproblem

Zwei Objekte müssen konsistent voneinander navigierbar sein, aber nur eine Seite kennt die Beziehung.

Erkennungsmerkmale

Teure Rückwärtssuchen, externe Maps oder wiederholte Queries, um den Besitzer eines Objekts zu finden.

Nutzen der Transformation

Eine kontrollierte bidirektionale Beziehung ermöglicht Navigation in beide Richtungen und kann fachliche Invarianten lokal halten.

Sicherer Refactoring-Ablauf

  1. Verantwortlichen Besitzer festlegen, Änderungsmethoden auf einer Seite zentralisieren, beide Seiten atomar aktualisieren und Konsistenztests ergänzen.

Einsatzgrenze

Bidirektionale Beziehungen erhöhen Kopplung und Fehlergefahr. Ohne echten Navigationsbedarf sollte die Beziehung einseitig bleiben.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Aggregate, Encapsulate Collection, Association Object

Enterprise-Java-Einordnung

Change Unidirectional Association to Bidirectional wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

OrderCustomerRelation.java

OrderCustomerRelation.java
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Change Unidirectional Association to Bidirectional
// Aggregate consistency: Eine Operation hält beide Seiten synchron.
public final class OrderCustomerRelation {
  public static final class Customer { private final List<Order> orders=new ArrayList<>(); public List<Order> orders(){ return List.copyOf(orders); } }
  public static final class Order { private Customer customer; public Customer customer(){ return customer; } }
  public static void assign(Order order, Customer customer){
    if(order.customer!=null) order.customer.orders.remove(order);
    order.customer=Objects.requireNonNull(customer); if(!customer.orders.contains(order)) customer.orders.add(order);
  }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class OrderCustomerRelationTest { @Test void both_sides_stay_consistent(){ var o=new OrderCustomerRelation.Order(); var c=new OrderCustomerRelation.Customer(); OrderCustomerRelation.assign(o,c); assertSame(c,o.customer()); assertEquals(1,c.orders().size()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

74. Change Bidirectional Association to Unidirectional

Change Bidirectional Association to Unidirectional

Fachliches Ausgangsproblem

Beide Objekte kennen einander, obwohl nur eine Navigationsrichtung benötigt wird. Dadurch entstehen Kopplung, Synchronisationsaufwand und Serialisierungszyklen.

Erkennungsmerkmale

Gegenseitige Setter, Endlosschleifen bei JSON-Ausgabe und schwer kontrollierbare Lebenszyklen.

Nutzen der Transformation

Eine gerichtete Beziehung vereinfacht Eigentum, Laden, Persistenz und Konsistenz.

Sicherer Refactoring-Ablauf

  1. Tatsächliche Navigationsnutzung messen, überflüssige Rückreferenz entfernen, Queries oder Repository-Zugriff an der richtigen Grenze ergänzen und Tests anpassen.

Einsatzgrenze

Wenn die Rücknavigation eine echte Aggregate-Invariante trägt, darf sie nicht nur zur technischen Vereinfachung entfernt werden.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Data Mapper, Repository, Aggregate Boundary

Enterprise-Java-Einordnung

Change Bidirectional Association to Unidirectional wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

InvoiceAccountLink.java

InvoiceAccountLink.java
package com.aydinsude.workbench.catalog;
// Refactoring: Change Bidirectional Association to Unidirectional
// Simplicity: Nur die fachlich benötigte Richtung bleibt bestehen.
public final class InvoiceAccountLink {
  public record Account(String id) { public Account { if(id==null||id.isBlank()) throw new IllegalArgumentException("id"); } }
  public record Invoice(String number, Account account) { public Invoice { if(number==null||number.isBlank()||account==null) throw new IllegalArgumentException(); } }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class InvoiceAccountLinkTest { @Test void only_required_direction_remains(){ var a=new InvoiceAccountLink.Account("A1"); var i=new InvoiceAccountLink.Invoice("I1",a); assertEquals("A1",i.account().id()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

75. Introduce Foreign Method

Introduce Foreign Method

Fachliches Ausgangsproblem

Eine fremde Klasse benötigt eine kleine zusätzliche Operation, kann aber nicht verändert oder erweitert werden.

Erkennungsmerkmale

Wiederholte Hilfslogik direkt neben Aufrufen einer Bibliotheksklasse oder eines unveränderbaren Legacy-Typs.

Nutzen der Transformation

Eine klar benannte Hilfsmethode lokalisiert die fehlende Operation und dokumentiert die fachliche Absicht.

Sicherer Refactoring-Ablauf

  1. Operation nahe am nutzenden Code als statische oder private Methode einführen, Parameter explizit halten und Duplikate entfernen.

Einsatzgrenze

Bei mehreren Operationen oder dauerhaftem Erweiterungsbedarf ist Local Extension, Adapter oder Wrapper geeigneter.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Introduce Local Extension, Adapter, Extension Method

Enterprise-Java-Einordnung

Introduce Foreign Method wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

LegacyDateSupport.java

LegacyDateSupport.java
package com.aydinsude.workbench.catalog;
import java.time.LocalDate;
// Refactoring: Introduce Foreign Method
// Adapter-artige Erweiterung: Fremdtyp bleibt unverändert.
public final class LegacyDateSupport {
  private LegacyDateSupport(){}
  public static LocalDate nextBusinessDay(LocalDate date){
    var next=date.plusDays(1); while(next.getDayOfWeek().getValue()>=6) next=next.plusDays(1); return next;
  }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.LocalDate; import org.junit.jupiter.api.Test;
class LegacyDateSupportTest { @Test void foreign_method_extends_external_type(){ assertEquals(LocalDate.of(2026,7,13),LegacyDateSupport.nextBusinessDay(LocalDate.of(2026,7,10))); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

76. Introduce Local Extension

Introduce Local Extension

Fachliches Ausgangsproblem

Eine fremde Klasse benötigt mehrere zusätzliche Operationen oder eine stabilere lokale Schnittstelle, kann aber nicht verändert werden.

Erkennungsmerkmale

Viele Foreign Methods, wiederholte Konvertierung und direkte Abhängigkeit von Bibliotheksdetails.

Nutzen der Transformation

Wrapper oder Unterklasse bietet eine lokale, fachlich passende Schnittstelle und begrenzt Fremdabhängigkeiten.

Sicherer Refactoring-Ablauf

  1. Benötigten Vertrag definieren, Wrapper einführen, Konvertierung zentralisieren und Aufrufer schrittweise auf die lokale Erweiterung umstellen.

Einsatzgrenze

Vererbung ist nur bei wirklich erweiterbarer Basisklasse sicher. Häufig ist ein Adapter mit Komposition robuster.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Adapter, Anti-Corruption Layer, Introduce Foreign Method

Enterprise-Java-Einordnung

Introduce Local Extension wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

MoneyExtension.java

MoneyExtension.java
package com.aydinsude.workbench.catalog;
import java.math.*;
// Refactoring: Introduce Local Extension
// Wrapper/Value Object: Lokale Erweiterungen erhalten eine kohärente API.
public record MoneyExtension(BigDecimal value) {
  public MoneyExtension { value=value.setScale(2,RoundingMode.HALF_UP); }
  public MoneyExtension add(MoneyExtension other){ return new MoneyExtension(value.add(other.value)); }
  public boolean positive(){ return value.signum()>0; }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.math.BigDecimal; import org.junit.jupiter.api.Test;
class MoneyExtensionTest { @Test void local_extension_groups_operations(){ var sum=new MoneyExtension(new BigDecimal("10")).add(new MoneyExtension(new BigDecimal("2.50"))); assertEquals(new BigDecimal("12.50"),sum.value()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

77. Replace Data Class with Rich Domain Object

Replace Data Class with Rich Domain Object

Fachliches Ausgangsproblem

Ein Fachobjekt enthält nur Felder und Getter; Regeln und Zustandsänderungen liegen in Services verteilt.

Erkennungsmerkmale

Anämische Modelle, Setter für jeden Zustand und Services, die fremde Daten manipulieren.

Nutzen der Transformation

Das Domänenobjekt schützt seine Invarianten und bietet intentionale Operationen statt beliebiger Mutation.

Sicherer Refactoring-Ablauf

  1. Invarianten und zustandsnahe Regeln identifizieren, fachliche Methoden einführen, Setter entfernen und orchestrierende Services auf Koordination reduzieren.

Einsatzgrenze

Nicht jede DTO- oder Persistenzklasse soll reich werden. Transfermodelle und Read Models dürfen bewusst datenorientiert bleiben.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Domain Model, Aggregate, Move Function

Enterprise-Java-Einordnung

Replace Data Class with Rich Domain Object wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

ServiceAgreement.java

ServiceAgreement.java
package com.aydinsude.workbench.catalog;
import java.time.LocalDate;
// Refactoring: Replace Data Class with Rich Domain Object
// Aggregate: Invarianten und Verhalten liegen beim Fachobjekt.
public final class ServiceAgreement {
  private LocalDate validUntil;
  public ServiceAgreement(LocalDate validUntil){ this.validUntil=java.util.Objects.requireNonNull(validUntil); }
  public void extendTo(LocalDate newDate){ if(!newDate.isAfter(validUntil)) throw new IllegalArgumentException("newDate"); validUntil=newDate; }
  public boolean activeOn(LocalDate date){ return !date.isAfter(validUntil); }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.LocalDate; import org.junit.jupiter.api.Test;
class ServiceAgreementTest { @Test void rich_object_protects_transition(){ var a=new ServiceAgreement(LocalDate.of(2026,7,31)); a.extendTo(LocalDate.of(2026,8,31)); assertTrue(a.activeOn(LocalDate.of(2026,8,1))); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

78. Replace Boolean with Enum

Replace Boolean with Enum

Fachliches Ausgangsproblem

Ein Boolean steht für mehr als eine fachliche Bedeutung oder wird mit unklaren true/false-Aufrufen verwendet.

Erkennungsmerkmale

Methodenaufrufe wie send(true), mehrere kombinierte Flags oder Kommentare zur Bedeutung von false.

Nutzen der Transformation

Ein Enum benennt erlaubte Zustände, verhindert Verwechslungen und kann später Verhalten aufnehmen.

Sicherer Refactoring-Ablauf

  1. Bedeutungen sammeln, Enum einführen, Signaturen und Persistenzmapping migrieren und ungültige Kombinationen mit Tests ausschließen.

Einsatzgrenze

Für eine wirklich binäre, lokal klare Aussage kann Boolean die einfachere Darstellung bleiben.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Remove Flag Argument, Replace Type Code with State/Strategy, Value Object

Enterprise-Java-Einordnung

Replace Boolean with Enum wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

DeliveryConfirmation.java

DeliveryConfirmation.java
package com.aydinsude.workbench.catalog;
// Refactoring: Replace Boolean with Enum
// Type-safe domain model: Benannte Varianten ersetzen mehrdeutige Flags.
public record DeliveryConfirmation(ConfirmationMode mode) {
  public enum ConfirmationMode { NONE, SIGNATURE, ONE_TIME_CODE }
  public boolean requiresCustomerAction(){ return mode!=ConfirmationMode.NONE; }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class DeliveryConfirmationTest { @Test void enum_replaces_boolean(){ assertTrue(new DeliveryConfirmation(DeliveryConfirmation.ConfirmationMode.SIGNATURE).requiresCustomerAction()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

79. Replace Null with Optional Result

Replace Null with Optional Result

Fachliches Ausgangsproblem

Eine Abfrage signalisiert ein reguläres Nichtvorhandensein durch null; Aufrufer vergessen Prüfungen oder verwechseln Fehler mit Abwesenheit.

Erkennungsmerkmale

NullPointerException, verstreute Nullprüfungen und unklare Verträge von Finder-Methoden.

Nutzen der Transformation

Optional macht Abwesenheit im Rückgabetyp sichtbar und zwingt zu einer bewussten Behandlung.

Sicherer Refactoring-Ablauf

  1. Nur regulär fehlende Ergebnisse umstellen, Rückgabetyp ändern, Aufrufer migrieren und Fehlerfälle weiterhin als Result oder Exception modellieren.

Einsatzgrenze

Optional ist ungeeignet für Felder, Parameter oder komplexe Fehlerdiagnosen. Dort sind Null Object oder Result Type oft besser.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Introduce Special Case, Result Type, Null Object

Enterprise-Java-Einordnung

Replace Null with Optional Result wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

CustomerLookupResult.java

CustomerLookupResult.java
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Replace Null with Optional Result
// Explicit result contract: Abwesenheit ist Teil der Signatur.
public final class CustomerLookupResult {
  private final Map<String,String> names=Map.of("C1","Ada");
  public Optional<String> findName(String id){ return Optional.ofNullable(names.get(id)); }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class CustomerLookupResultTest { @Test void optional_replaces_null(){ var l=new CustomerLookupResult(); assertEquals("Ada",l.findName("C1").orElseThrow()); assertTrue(l.findName("missing").isEmpty()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

80. Replace Stringly-Typed Map with Typed Record

Replace Stringly-Typed Map with Typed Record

Fachliches Ausgangsproblem

Fachwerte werden in einer Map mit String-Schlüsseln transportiert. Strukturfehler werden erst zur Laufzeit erkannt.

Erkennungsmerkmale

Tippfehler in Schlüsseln, Casts, uneinheitliche Pflichtfelder und fehlende IDE-Unterstützung.

Nutzen der Transformation

Ein Record oder typisiertes DTO macht den Vertrag kompilierbar, dokumentierbar und validierbar.

Sicherer Refactoring-Ablauf

  1. Schema und Pflichtfelder bestimmen, Record einführen, Mapper an der Systemgrenze erstellen und interne Aufrufer schrittweise migrieren.

Einsatzgrenze

Bei bewusst offenen Metadaten oder Plugin-Daten kann eine Map bleiben; der stabile Kern sollte dennoch typisiert sein.

Risiken und Fehlanwendungen

Die Änderung muss in kleinen, kompilierbaren Schritten erfolgen. Öffentliche Verträge, Persistenzmapping, Serialisierung, Nebenwirkungen und Nebenläufigkeit werden ausdrücklich mitgeprüft.

Verwandte Techniken

Encapsulate Record, Data Mapper, Anti-Corruption Layer

Enterprise-Java-Einordnung

Replace Stringly-Typed Map with Typed Record wird in Enterprise-Java-Anwendungen an einer klaren Fach-, Persistenz- oder Integrationsgrenze eingesetzt. Der vorhandene Java-21-Code zeigt die Zielstruktur ohne Frameworkkopplung; Spring und Jakarta EE bleiben Adapter um die Fachlogik.

Teststrategie

Characterization Tests sichern zunächst das beobachtbare Verhalten. Danach prüfen fokussierte Unit-Tests Normalfälle, Grenzwerte und Fehlerpfade; bei öffentlichen Verträgen kommen Mapper-, Serialisierungs- oder Integrationstests hinzu.

ClaimSubmission.java

ClaimSubmission.java
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Replace Stringly-Typed Map with Typed Record
// Value Object: Compiler und Konstruktor sichern das Datenmodell.
public record ClaimSubmission(String claimId, String customerId, BigDecimal amount) {
  public ClaimSubmission {
    if(claimId==null||claimId.isBlank()||customerId==null||customerId.isBlank()||amount==null||amount.signum()<=0) throw new IllegalArgumentException("claim");
  }
}

Java-21-Zielcode.java

Java-21-Zielcode.java
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.math.BigDecimal; import org.junit.jupiter.api.Test;
class ClaimSubmissionTest { @Test void typed_record_replaces_map(){ var c=new ClaimSubmission("CL1","CU1",new BigDecimal("99.00")); assertEquals("CL1",c.claimId()); } }

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul- und Fachquellen redaktionell zusammengeführt und auf das konkrete Java-Beispiel übertragen. Der Text ist keine wörtliche Übersetzung.

Kapitel 9 Interfaces, Hierarchien und Polymorphie ordnen10 Refactorings

Abhängigkeits-, Integrations- und Persistenzgrenzen mit kompilierbarem Java-21-Zielcode.

81. Extract Interface

Extract Interface

Fachliches Ausgangsproblem

Eine Fach- oder Anwendungsklasse hängt von einer umfangreichen konkreten Providerklasse ab, obwohl sie nur einen kleinen Ausschnitt ihres Verhaltens benötigt. Dadurch werden technische Details, Lebenszyklusentscheidungen und zusätzliche Methoden ungewollt Teil der fachlichen Kopplung.

Erkennungsmerkmale

Konstruktoren erwarten konkrete SDK-, Client- oder Frameworktypen; Tests müssen große Providerobjekte aufbauen; Änderungen am Provider erzwingen Anpassungen in fachlich unabhängigen Klassen; der Aufrufer nutzt nur ein bis drei Methoden des Typs.

Nutzen der Transformation

Eine fachlich benannte, schmale Schnittstelle macht den tatsächlich benötigten Vertrag sichtbar. Die Abhängigkeit zeigt danach auf eine stabile Fähigkeit statt auf die gesamte technische Implementierung. Das erleichtert Adapter, Fakes und getrennte Weiterentwicklung.

Sicherer Refactoring-Ablauf

  1. Verhalten des bestehenden Aufrufers mit Characterization Tests sichern.
  2. Alle tatsächlich verwendeten Provideroperationen und benötigten Datentypen erfassen.
  3. Eine kleine, aus Sicht des Verbrauchers benannte Schnittstelle einführen.
  4. Die konkrete Providerklasse oder einen Adapter diese Schnittstelle implementieren lassen.
  5. Den Aufrufer auf die Schnittstelle umstellen und überflüssige Providerkenntnisse entfernen.

Einsatzgrenze

Nicht jede Klasse braucht ein Interface. Wenn exakt eine stabile Implementierung existiert, keine Austausch- oder Testgrenze benötigt wird und der konkrete Typ selbst der fachliche Vertrag ist, erzeugt eine zusätzliche Schnittstelle nur Navigation ohne Nutzen.

Risiken und Fehlanwendungen

Ein zu breites Interface konserviert die alte Kopplung. Ein Interface pro Klasse ohne fachliche Bedeutung führt zu sogenannter Interface-Verschmutzung. Die Schnittstelle sollte vom Verbraucher und nicht vom Providerdesign bestimmt werden.

Verwandte Techniken

Dependency Inversion, Interface Segregation, Extract Adapter und Introduce Port.

Abgrenzung und Entscheidungshilfe

Extract Interface trennt einen benötigten Vertrag aus einem vorhandenen Typ. Extract Adapter übersetzt zusätzlich zwischen zwei nicht passenden Modellen. Beides kann aufeinander folgen.

Enterprise-Java-Einordnung

Ein Zahlungsanwendungsfall kennt nur PaymentAuthorizationPort. Ein Stripe- oder Bankadapter übersetzt technische Requests und Responses. Die Fachlogik bleibt unabhängig von SDK-Versionen, HTTP-Fehlern und Provider-Metadaten.

Teststrategie

Contract-Tests prüfen den Portvertrag für jede Adapterimplementierung. Unit-Tests des Anwendungsfalls verwenden einen kleinen Fake und prüfen Fachentscheidungen ohne Netzwerkzugriff.

PaymentAuthorizationPort.java
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Interface
// Port Pattern: Der Use Case hängt nur vom benötigten Vertrag ab.
public interface PaymentAuthorizationPort {
  Authorization authorize(String paymentId, long cents);
  record Authorization(String providerReference, boolean approved) {}
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

82. Replace Static Utility with Injected Service

Replace Static Utility with Injected Service

Fachliches Ausgangsproblem

Eine statische Utility-Klasse enthält veränderliche fachliche oder technische Entscheidungen. Aufrufer können die Abhängigkeit weder im Konstruktor erkennen noch im Test kontrollieren. Globale Zustände, Zeit, Zufall oder Konfiguration werden häufig indirekt gelesen.

Erkennungsmerkmale

Statische Methoden greifen auf Systemzeit, Umgebungsvariablen, globale Konfiguration oder externe Ressourcen zu; Tests benötigen statische Mocking-Werkzeuge; unterschiedliche Mandanten oder Regionen brauchen Varianten; Aufrufer lassen ihre Abhängigkeit nicht erkennen.

Nutzen der Transformation

Ein injizierter Service macht die Abhängigkeit explizit, ermöglicht unterschiedliche Implementierungen und begrenzt Seiteneffekte. Fachlich reine, unveränderliche Hilfsfunktionen dürfen dagegen weiterhin statisch bleiben.

Sicherer Refactoring-Ablauf

  1. Statische Aufrufe inventarisieren und reine von zustands- oder umgebungsabhängigen Funktionen trennen.
  2. Einen kleinen Service-Vertrag für die veränderliche Fähigkeit definieren.
  3. Die vorhandene Logik zunächst unverändert in eine Implementierung verschieben.
  4. Den Service über den Konstruktor an die Verbraucher übergeben.
  5. Statische Zugriffspunkte nach schrittweiser Migration entfernen.

Einsatzgrenze

Reine Funktionen wie mathematische Umrechnungen oder nullsichere Stringoperationen benötigen nicht automatisch einen Service. Die Transformation ist sinnvoll, wenn Austauschbarkeit, Lebenszyklus oder Seiteneffekte relevant sind.

Risiken und Fehlanwendungen

Ein UniversalService als Ersatz für eine UniversalUtility verbessert nichts. Ebenso problematisch ist ein Service, der nur statische Methoden umhüllt, aber weiterhin global bezogen wird.

Verwandte Techniken

Introduce Dependency Injection, Extract Clock, Extract ID Generator und Replace Global State with Context Object.

Abgrenzung und Entscheidungshilfe

Der Kern ist nicht Objektorientierung um jeden Preis, sondern explizite Kontrolle über eine relevante Abhängigkeit.

Enterprise-Java-Einordnung

Steuerberechnung wird als TaxCalculationService injiziert. Regionale Regeln, Stichtage und Rundungsverfahren können separat implementiert und getestet werden, ohne den Bestellservice neu zu verdrahten.

Teststrategie

Unit-Tests injizieren kontrollierte Implementierungen. Contract-Tests sichern gemeinsame Regeln. Ein Integrationstest prüft die produktive Verdrahtung im Composition Root.

TaxCalculationService.java
package com.aydinsude.workbench.catalog;
import java.math.*;
// Refactoring: Replace Static Utility with Injected Service
// Strategy + Dependency Injection: Steuerregeln sind austauschbar und testbar.
public final class TaxCalculationService {
  public interface TaxPolicy { BigDecimal taxFor(BigDecimal net); }
  private final TaxPolicy policy;
  public TaxCalculationService(TaxPolicy policy){ this.policy=java.util.Objects.requireNonNull(policy); }
  public BigDecimal gross(BigDecimal net){ return net.add(policy.taxFor(net)).setScale(2,RoundingMode.HALF_UP); }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

83. Replace Global State with Context Object

Replace Global State with Context Object

Fachliches Ausgangsproblem

Mehrere Klassen lesen und verändern globale statische Variablen. Das Verhalten hängt dadurch von einer unsichtbaren Reihenfolge vorheriger Aufrufe ab und kann zwischen Tests, Threads oder Mandanten auslaufen.

Erkennungsmerkmale

Statische mutable Maps, globale CurrentUser- oder Tenant-Felder, Tests mit reset()-Aufrufen, sporadische Parallelitätsfehler und Methoden, deren Ergebnis ohne sichtbare Parameter variiert.

Nutzen der Transformation

Ein explizites Context Object bündelt zusammengehörige Laufzeitinformationen und wird bewusst übergeben. Abhängigkeiten, Gültigkeitsdauer und Besitz werden sichtbar. Unveränderliche Snapshots vermeiden unkontrollierte Änderungen.

Sicherer Refactoring-Ablauf

  1. Globale Felder und alle Lese-/Schreibstellen erfassen.
  2. Zusammengehörige Werte in einem unveränderlichen Kontexttyp modellieren.
  3. Den Kontext zunächst zusätzlich an eine äußere Anwendungsgrenze übergeben.
  4. Aufrufer schrittweise von globalem Zugriff auf den Parameter umstellen.
  5. Globale Schreiboperationen entfernen und Kontextwechsel explizit modellieren.

Einsatzgrenze

Ein Kontext darf nicht zum beliebigen Datenbeutel werden. Werte, die nur eine einzelne Methode benötigt, bleiben direkte Parameter. Technische Thread-Kontexte benötigen klare Propagationsregeln.

Risiken und Fehlanwendungen

Ein riesiges Context Object versteckt Abhängigkeiten erneut. Mutable Kontexte erzeugen weiterhin Reihenfolgeprobleme. Besonders kritisch ist die Speicherung in ThreadLocal bei asynchronen oder virtuellen Threads.

Verwandte Techniken

Introduce Parameter Object, Immutable Snapshot, Correlation Context und Security Context Snapshot.

Abgrenzung und Entscheidungshilfe

Ein Context Object macht Laufzeitkontext sichtbar. Ein Service Locator liefert dagegen versteckt beliebige Dienste und ist kein Ersatz.

Enterprise-Java-Einordnung

RequestContext enthält TenantId, CorrelationId, Locale und Actor in unveränderlicher Form. Anwendungskommandos erhalten diesen Kontext explizit; Adapter erzeugen ihn an der Systemgrenze.

Teststrategie

Tests erzeugen kleine Kontextinstanzen und prüfen Mandantenisolation sowie unveränderte Weitergabe. Parallelitätstests stellen sicher, dass keine Daten zwischen Requests geteilt werden.

RequestExecutionContext.java
package com.aydinsude.workbench.catalog;
import java.time.Instant;
// Refactoring: Replace Global State with Context Object
// Context Object: Laufzeitdaten sind explizit, unveränderlich und requestgebunden.
public record RequestExecutionContext(String tenantId, String correlationId, Instant startedAt) {
  public RequestExecutionContext {
    if(tenantId==null||tenantId.isBlank()||correlationId==null||correlationId.isBlank()||startedAt==null) throw new IllegalArgumentException("context");
  }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

84. Introduce Dependency Injection

Introduce Dependency Injection

Fachliches Ausgangsproblem

Eine Klasse erzeugt benötigte Dienste selbst und entscheidet damit gleichzeitig über Fachlogik, technische Implementierung und Objektlebenszyklus. Änderungen an Infrastruktur führen zu Änderungen im Anwendungsfall.

Erkennungsmerkmale

new-Aufrufe für Repository, HTTP-Client oder Clock innerhalb fachlicher Methoden; Konfigurationszugriffe im Konstruktor; Tests müssen reale Infrastruktur starten; Varianten werden mit if-Abfragen ausgewählt.

Nutzen der Transformation

Dependency Injection verlagert Erzeugung und Verdrahtung an eine äußere Stelle. Die Klasse beschreibt nur noch, welche Fähigkeiten sie benötigt. Constructor Injection sichert vollständige, unveränderliche Objekte.

Sicherer Refactoring-Ablauf

  1. Direkte Objekterzeugung und versteckte Dienstzugriffe identifizieren.
  2. Für jede relevante Fähigkeit einen konkreten oder abstrakten Vertrag bestimmen.
  3. Abhängigkeiten als Konstruktorparameter einführen und Felder final machen.
  4. Erzeugung in einen Composition Root oder Framework-Konfiguration verschieben.
  5. Service-Locator- und Fallback-Pfade entfernen, sobald alle Aufrufer migriert sind.

Einsatzgrenze

Value Objects und interne Implementierungsdetails müssen nicht injiziert werden. Eine Dependency ist typischerweise ein externer Dienst, eine Policy oder eine austauschbare Kollaboration.

Risiken und Fehlanwendungen

Zu viele Konstruktorparameter weisen häufig auf zu viele Verantwortlichkeiten hin. Field Injection versteckt Unvollständigkeit und erschwert reine Unit-Tests. Ein DI-Container darf nicht in Fachcode hineinreichen.

Verwandte Techniken

Replace Service Locator with Constructor Injection, Introduce Composition Root und Extract Interface.

Abgrenzung und Entscheidungshilfe

Dependency Injection ist die Übergabe von Abhängigkeiten; ein DI-Framework ist nur eine mögliche technische Umsetzung.

Enterprise-Java-Einordnung

OrderApplicationService erhält OrderRepository, PaymentPort, Clock und EventPublisher im Konstruktor. Spring oder CDI verdrahtet die produktiven Adapter außerhalb des Fachmoduls.

Teststrategie

Unit-Tests erstellen den Service direkt mit Fakes. Ein schlanker Container-Smoke-Test prüft, dass die produktive Verdrahtung vollständig und eindeutig ist.

ShipmentUseCase.java
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Dependency Injection
// Constructor Injection: Abhängigkeiten sind vollständig und unveränderlich.
public final class ShipmentUseCase {
  public interface ShipmentRepository { void save(String shipmentId); }
  public interface NotificationPort { void sent(String shipmentId); }
  private final ShipmentRepository repository; private final NotificationPort notifications;
  public ShipmentUseCase(ShipmentRepository repository, NotificationPort notifications){ this.repository=repository; this.notifications=notifications; }
  public void ship(String id){ repository.save(id); notifications.sent(id); }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

85. Replace Service Locator with Constructor Injection

Replace Service Locator with Constructor Injection

Fachliches Ausgangsproblem

Klassen rufen einen globalen Service Locator auf, um Abhängigkeiten bei Bedarf zu suchen. Die Signatur behauptet Selbstständigkeit, obwohl das Objekt von mehreren externen Diensten abhängt.

Erkennungsmerkmale

ServiceLocator.get(), ApplicationContext.getBean() oder CDI.current() im Anwendungs- und Fachcode; Laufzeitfehler wegen fehlender Registrierung; Tests müssen globale Containerzustände vorbereiten.

Nutzen der Transformation

Constructor Injection macht alle Pflichtabhängigkeiten beim Erzeugen sichtbar. Ungültige Objekte können nicht entstehen, IDE und Compiler unterstützen Umbenennung und Navigation, Tests benötigen keinen globalen Container.

Sicherer Refactoring-Ablauf

  1. Alle Locator-Aufrufe pro Klasse erfassen.
  2. Pflicht- und optionale Abhängigkeiten unterscheiden.
  3. Pflichtabhängigkeiten als Konstruktorparameter einführen.
  4. Composition Root oder Framework-Konfiguration auf die neue Signatur umstellen.
  5. Locator-Aufrufe entfernen und globale Registrierungen zurückbauen.

Einsatzgrenze

Dynamische Pluginverzeichnisse können eine Registry als fachlichen Mechanismus benötigen. Auch dann sollte die Registry explizit injiziert werden und nicht global erreichbar sein.

Risiken und Fehlanwendungen

Setter Injection für Pflichtabhängigkeiten erzeugt zeitweise unvollständige Objekte. Ein einziger injizierter Locator mit neuem Namen behebt die versteckte Kopplung nicht.

Verwandte Techniken

Introduce Dependency Injection, Introduce Composition Root und Strategy Registry.

Abgrenzung und Entscheidungshilfe

Eine explizit injizierte, fachlich eng begrenzte Registry ist nicht dasselbe wie ein universeller Service Locator.

Enterprise-Java-Einordnung

Ein ExportUseCase erhält DocumentStore und ExporterRegistry direkt. Die Auswahl eines Exporters bleibt dynamisch, aber die Registry ist eine sichtbare Abhängigkeit.

Teststrategie

Konstruktionstests zeigen fehlende Abhängigkeiten sofort. Unit-Tests übergeben Fakes; ein Integrationstest prüft die Containerkonfiguration.

CustomerRegistration.java
package com.aydinsude.workbench.catalog;
// Refactoring: Replace Service Locator with Constructor Injection
// Explicit Dependencies: Der Vertrag zeigt, was der Use Case wirklich benötigt.
public final class CustomerRegistration {
  public interface CustomerStore { void add(String customerId); }
  public interface WelcomeSender { void send(String customerId); }
  private final CustomerStore store; private final WelcomeSender sender;
  public CustomerRegistration(CustomerStore store, WelcomeSender sender){ this.store=store; this.sender=sender; }
  public void register(String id){ store.add(id); sender.send(id); }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

86. Extract Adapter

Extract Adapter

Fachliches Ausgangsproblem

Fachcode verwendet Datentypen, Fehlercodes und Aufrufreihenfolgen eines externen Systems direkt. Änderungen des Fremdsystems breiten sich über viele Klassen aus.

Erkennungsmerkmale

SDK-Klassen in Domänenpaketen, wiederholte Mappinglogik, Providerfehler in Use-Case-Signaturen, technische Retry-Details in Fachmethoden und schwer testbare externe Aufrufe.

Nutzen der Transformation

Ein Adapter übersetzt zwischen internem Port und fremder Schnittstelle. Er kapselt Mapping, Fehlerübersetzung, Protokolldetails und Anbieterbesonderheiten an einer klaren Systemgrenze.

Sicherer Refactoring-Ablauf

  1. Intern benötigte Fähigkeit aus Sicht des Anwendungsfalls als Port formulieren.
  2. Fremde Typen und Fehler auf eine Adapterklasse begrenzen.
  3. Mapping und Fehlerübersetzung aus den Verbrauchern in den Adapter verschieben.
  4. Verbraucher auf den Port umstellen.
  5. Adapter mit Contract- und Integrationstests absichern.

Einsatzgrenze

Kein Adapter ist nötig, wenn die externe Schnittstelle bereits exakt dem stabilen internen Vertrag entspricht und keine Übersetzung stattfindet. Eine bloße Weiterleitungsfassade ohne Grenze erzeugt wenig Nutzen.

Risiken und Fehlanwendungen

Ein undichter Adapter gibt Providerklassen oder Fehlercodes weiter. Ein übermächtiger Adapter kann mehrere unabhängige Fähigkeiten vermischen.

Verwandte Techniken

Extract Interface, Anti-Corruption Layer, Gateway und Exception Translation.

Abgrenzung und Entscheidungshilfe

Ein Adapter passt eine Schnittstelle an. Eine Facade vereinfacht ein Subsystem, ohne zwingend inkompatible Modelle zu übersetzen.

Enterprise-Java-Einordnung

BankPaymentAdapter implementiert PaymentPort, übersetzt Money und PaymentRequest in SDK-Objekte und bildet Timeout, Ablehnung und Providerstörung auf typisierte interne Ergebnisse ab.

Teststrategie

Contract-Tests sichern den Port. Mappingtests prüfen Grenzwerte und Fehlerübersetzung. Ein Integrationstest gegen Sandbox oder Stub prüft das echte Protokoll.

LegacyCustomerAdapter.java
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Adapter
// Adapter Pattern: Legacy-Sprache endet an der Integrationsgrenze.
public final class LegacyCustomerAdapter {
  public interface LegacyClient { LegacyCustomer load(String id); }
  public record LegacyCustomer(String number, String activeFlag) {}
  public record Customer(String id, boolean active) {}
  private final LegacyClient client;
  public LegacyCustomerAdapter(LegacyClient client){ this.client=client; }
  public Customer find(String id){ var raw=client.load(id); return new Customer(raw.number(),"Y".equals(raw.activeFlag())); }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

87. Extract Gateway

Extract Gateway

Fachliches Ausgangsproblem

Mehrere Anwendungsfälle enthalten wiederholte technische Logik für den Zugriff auf ein entferntes System, eine Datenbankfunktion oder einen fremden Dienst. Endpunkte, Authentisierung, Timeouts und Mapping sind verteilt.

Erkennungsmerkmale

HTTP-URLs, SQL oder SDK-Aufrufe in Use Cases; wiederholte Header- und Fehlerbehandlung; technische DTOs in Fachsignaturen; uneinheitliche Timeout- oder Retryregeln.

Nutzen der Transformation

Ein Gateway bildet einen klaren Zugang zu einem externen System. Es bündelt Protokollwissen, technische Policies und Datentransformationen, während der Anwendungsfall mit fachlich benannten Operationen arbeitet.

Sicherer Refactoring-Ablauf

  1. Verteilte Zugriffslogik und gemeinsame Operationen sammeln.
  2. Eine fachlich benannte Gateway-Schnittstelle definieren.
  3. Protokoll- und Mappinglogik in eine Implementierung verschieben.
  4. Aufrufer schrittweise umstellen und Duplikate entfernen.
  5. Timeout-, Retry- und Observability-Regeln am Gateway zentral testen.

Einsatzgrenze

Ein Gateway sollte keine komplette fremde API spiegeln. Es modelliert nur die vom eigenen System benötigten Fähigkeiten. Für reine Persistenz fachlicher Aggregate ist meist Repository passender.

Risiken und Fehlanwendungen

Ein God Gateway mit dutzenden unabhängigen Operationen wird zum neuen Kopplungsknoten. Werden fachliche Entscheidungen hineingeschoben, verschwimmt die Schichtgrenze.

Verwandte Techniken

Adapter, Repository, Facade, Circuit Breaker und Anti-Corruption Layer.

Abgrenzung und Entscheidungshilfe

Gateway bezeichnet den Zugang zu einem externen System; Adapter betont die Übersetzung inkompatibler Schnittstellen. Häufig ist eine Klasse beides.

Enterprise-Java-Einordnung

CustomerRegistryGateway kapselt Abfrage und Aktualisierung eines behördlichen Registers. Der Use Case sieht CustomerStatus statt HTTP-Statuscodes und JSON-Strukturen.

Teststrategie

Gateway-Contract-Tests, Protokolltests mit Stubserver sowie Fehlerklassifikation für Timeout, Nichtgefunden und fachliche Ablehnung.

FraudScreeningGateway.java
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Gateway
// Gateway Pattern: Transportdetails bleiben außerhalb der Fachlogik.
public interface FraudScreeningGateway {
  ScreeningResult screen(String customerId, long amountInCents);
  enum ScreeningResult { ACCEPT, REVIEW, REJECT }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

88. Introduce Repository

Introduce Repository

Fachliches Ausgangsproblem

Anwendungs- oder Domänenlogik enthält SQL, ORM-Abfragen oder technische Persistenzoperationen. Fachliche Regeln hängen dadurch von Tabellen, Sessions und Ladeverhalten ab.

Erkennungsmerkmale

EntityManager oder JdbcTemplate im Use Case, Querystrings neben Fachlogik, technische Persistence Entities außerhalb des Adapters, wiederholte Abfragen für dieselben Aggregate.

Nutzen der Transformation

Ein Repository stellt aus Sicht der Domäne eine Sammlung fachlicher Aggregate dar. Es kapselt Persistenzzugriff und erlaubt fachlich benannte Lade- und Speicheroperationen.

Sicherer Refactoring-Ablauf

  1. Aggregate und deren Identitätsgrenzen bestimmen.
  2. Nur benötigte fachliche Repositoryoperationen definieren.
  3. Bestehende SQL-/ORM-Logik in einen Adapter verschieben.
  4. Mapping zwischen Persistenz- und Domänenmodell an der Grenze etablieren.
  5. Use Cases auf den Repositoryvertrag umstellen.

Einsatzgrenze

Nicht jede Tabelle benötigt ein Repository. Read-only-Auswertungen können über Query Objects oder spezialisierte Read Models besser modelliert werden. Ein Repository sollte Aggregate, nicht beliebige Tabellenzeilen, verwalten.

Risiken und Fehlanwendungen

Generische CRUD-Repositories geben technische Operationen als Fachvertrag aus. Leaky Repositories liefern Streams, Sessions oder JPA Entities und machen die Domäne wieder abhängig.

Verwandte Techniken

Data Mapper, Unit of Work, Query Object, Aggregate und Gateway.

Abgrenzung und Entscheidungshilfe

DAO ist oft datenquellen- und tabellenorientiert. Repository ist domänenorientiert und spricht in Aggregaten und fachlichen Suchbegriffen.

Enterprise-Java-Einordnung

OrderRepository lädt und speichert Order-Aggregate. Ein JpaOrderRepository übernimmt EntityGraph, Mapping und optimistisches Locking, ohne diese Details an den Use Case weiterzugeben.

Teststrategie

Repository-Contract-Tests laufen gegen In-Memory-Fake und echte Datenbankimplementierung. Integrationstests prüfen Mapping, Nebenläufigkeit und Transaktionsverhalten.

OrderRepositoryBoundary.java
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Repository
// Repository Pattern: Persistenz wird als Aggregat-Sammlung modelliert.
public interface OrderRepositoryBoundary {
  Optional<Order> findById(String id); void save(Order order);
  record Order(String id, String status) { public Order { Objects.requireNonNull(id); Objects.requireNonNull(status); } }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

89. Introduce Unit of Work

Introduce Unit of Work

Fachliches Ausgangsproblem

Ein Geschäftsfall verändert mehrere Objekte und Persistenzoperationen, ohne dass klar ist, welche Änderungen gemeinsam erfolgreich sein müssen. Einzelne Saves können Teilzustände hinterlassen.

Erkennungsmerkmale

Mehrere Repository.save-Aufrufe in einem Use Case, manuelle Transaktionssteuerung an vielen Stellen, unklare Reihenfolge von Inserts/Updates, doppelte Speicherung desselben Objekts.

Nutzen der Transformation

Eine Unit of Work verfolgt Änderungen innerhalb eines Geschäftsfalls und koordiniert deren atomaren Abschluss. Die Transaktionsgrenze wird explizit und technisch gebündelt.

Sicherer Refactoring-Ablauf

  1. Geschäftliche Konsistenzgrenze und beteiligte Aggregate bestimmen.
  2. Aktuelle Schreiboperationen und Commitpunkte erfassen.
  3. Eine Unit-of-Work-Abstraktion oder klaren Transaction Runner einführen.
  4. Schreiboperationen innerhalb dieser Grenze ausführen.
  5. Commit, Rollback und Fehlerübersetzung zentralisieren.

Einsatzgrenze

Eine Unit of Work darf nicht über lange Benutzerinteraktionen oder entfernte Systemgrenzen offen bleiben. Für verteilte Abläufe sind Saga, Outbox oder Process Manager geeigneter.

Risiken und Fehlanwendungen

Eine globale Unit of Work für den gesamten Request kann unnötig viele Objekte binden. Versteckte automatische Flushes erschweren Vorhersagbarkeit. Transaktionen dürfen keine langsamen Remote-Aufrufe umschließen.

Verwandte Techniken

Repository, Transaction Boundary, Transactional Outbox und Aggregate.

Abgrenzung und Entscheidungshilfe

Repository kapselt Zugriff auf Aggregate. Unit of Work koordiniert mehrere Änderungen und ihren gemeinsamen Commit.

Enterprise-Java-Einordnung

ApproveClaimUseCase ändert Claim und PaymentInstruction in einer lokalen Transaktion. Ein Outbox-Ereignis wird im selben Commit geschrieben; die externe Auszahlung erfolgt danach.

Teststrategie

Integrationstests erzwingen Fehler zwischen Schreibschritten und prüfen vollständigen Rollback. Nebenläufigkeitstests prüfen Konflikte und Wiederholung.

SettlementUnitOfWork.java
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Unit of Work
// Unit of Work Pattern: Zusammengehörige Änderungen werden atomar gesammelt.
public final class SettlementUnitOfWork {
  private final List<Runnable> changes=new ArrayList<>();
  public void register(Runnable change){ changes.add(Objects.requireNonNull(change)); }
  public void commit(){ var snapshot=List.copyOf(changes); snapshot.forEach(Runnable::run); changes.clear(); }
  public int pending(){ return changes.size(); }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

90. Introduce Composition Root

Introduce Composition Root

Fachliches Ausgangsproblem

Objekte werden an vielen Stellen erzeugt und verdrahtet. Konfiguration, konkrete Adapter und Fachlogik vermischen sich, wodurch der tatsächliche Objektgraph schwer nachvollziehbar ist.

Erkennungsmerkmale

new-Aufrufe für Services in Controllern und Use Cases, Containerzugriff aus Fachcode, mehrere konkurrierende Konfigurationsorte, Laufzeitfehler durch uneindeutige Beans.

Nutzen der Transformation

Ein Composition Root ist der zentrale äußere Ort, an dem konkrete Implementierungen gewählt und der Objektgraph aufgebaut wird. Fachmodule bleiben frei von Container- und Umgebungswissen.

Sicherer Refactoring-Ablauf

  1. Alle Erzeugungs- und Verdrahtungsstellen erfassen.
  2. Abhängigkeiten in Fach- und Anwendungsklassen explizit machen.
  3. Produktive Adapter in einer Bootstrap- oder Konfigurationskomponente zusammenführen.
  4. Umgebungs- und Variantenentscheidungen ausschließlich dort treffen.
  5. Direkte Containerzugriffe und verstreute Fabriken entfernen.

Einsatzgrenze

Bei großen modularen Systemen kann jedes Feature einen lokalen Composition Root besitzen, der von einem übergeordneten Bootstrap zusammengesetzt wird. Zentral bedeutet nicht zwingend eine einzige riesige Datei.

Risiken und Fehlanwendungen

Ein Composition Root mit Fachlogik wird unübersichtlich. Reflektive Magie und automatische Scans können die sichtbare Verdrahtung wieder verbergen. Zyklen sollten nicht durch Lazy-Auflösung kaschiert werden.

Verwandte Techniken

Dependency Injection, Factory, Module und Feature Composition.

Abgrenzung und Entscheidungshilfe

Composition Root ist ein Ort im Systemdesign. Ein DI-Container ist ein Werkzeug, das dort verwendet werden kann.

Enterprise-Java-Einordnung

Eine Spring-Konfiguration verbindet OrderApplicationService mit JpaOrderRepository, BankPaymentAdapter, SystemClock und OutboxPublisher. Das Domänenmodul kennt Spring nicht.

Teststrategie

Ein Context-Smoke-Test prüft den vollständigen Objektgraphen. Architekturrules verhindern Frameworkabhängigkeiten in Kernmodulen.

BillingCompositionRoot.java
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Composition Root
// Composition Root: Nur hier werden konkrete Adapter mit dem Use Case verdrahtet.
public final class BillingCompositionRoot {
  public interface InvoiceStore { void save(String invoiceId); }
  public static final class BillingUseCase { private final InvoiceStore store; BillingUseCase(InvoiceStore store){ this.store=store; } public void create(String id){ store.save(id); } }
  private BillingCompositionRoot(){}
  public static BillingUseCase create(InvoiceStore store){ return new BillingUseCase(store); }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

Kapitel 10 Value Objects und fachliche Modelle einführen10 Refactorings

Fachtypen, Regeln und Variantenachsen mit kompilierbarem Java-21-Zielcode.

91. Introduce Value Object

Introduce Value Object

Fachliches Ausgangsproblem

Fachlich bedeutsame Werte werden als String, int oder BigDecimal weitergegeben. Einheit, Format, Gültigkeit und Vergleichsregeln sind nicht am Typ erkennbar.

Erkennungsmerkmale

Parameter wie String email, String country, BigDecimal amount; wiederholte Validierung und Normalisierung; vertauschte Parameter gleichen Typs; primitive Obsession.

Nutzen der Transformation

Ein Value Object bündelt Wert, Invarianten und fachliche Operationen. Gleichheit basiert auf dem Inhalt, der Typ verhindert Verwechslungen und ungültige Zustände werden früh abgewiesen.

Sicherer Refactoring-Ablauf

  1. Einen stabilen fachlichen Begriff mit klaren Invarianten auswählen.
  2. Einen unveränderlichen Typ mit validierender Fabrik oder Konstruktor einführen.
  3. Zunächst eine Signatur auf den neuen Typ umstellen.
  4. Verteilte Validierung und Formatierung in den Typ verschieben.
  5. Persistenz- und Transportmapping an den Systemgrenzen ergänzen.

Einsatzgrenze

Ein Value Object sollte klein, unveränderlich und wertbasiert sein. Objekte mit eigener Identität und Lebenszyklus sind Entities. Nicht jeder String benötigt einen Wrapper.

Risiken und Fehlanwendungen

Ein anämisches Wrapperobjekt ohne Regeln bringt wenig. Zu aggressive Validierung kann Import- und Migrationsfälle blockieren; dann sind Parse-Ergebnis und validierter Wert getrennt zu modellieren.

Verwandte Techniken

Replace Primitive with Object, Embedded Value, Record und Domain Model.

Abgrenzung und Entscheidungshilfe

Value Objects sind nicht bloß DTOs: Sie schützen Invarianten und besitzen fachlich sinnvolles Verhalten.

Enterprise-Java-Einordnung

Money enthält Betrag und Währung, verhindert währungsübergreifende Addition und kapselt Rundungsregeln. EmailAddress normalisiert und validiert Adressen an der Eingangsgrenze.

Teststrategie

Äquivalenz, Hashcode, Grenzwerte, ungültige Konstruktion und fachliche Operationen werden direkt am Typ getestet.

EmailAddress.java
package com.aydinsude.workbench.catalog;
import java.util.Locale;
// Refactoring: Introduce Value Object
// Value Object Pattern: Identität entsteht ausschließlich aus dem fachlichen Wert.
public record EmailAddress(String value) {
  public EmailAddress {
    if (value == null) throw new IllegalArgumentException("email");
    value = value.trim().toLowerCase(Locale.ROOT);
    if (!value.matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$")) throw new IllegalArgumentException("invalid email");
  }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

92. Replace Collection with Domain Collection

Replace Collection with Domain Collection

Fachliches Ausgangsproblem

Eine rohe List oder Set enthält fachlich zusammengehörige Elemente, während Regeln über Anzahl, Duplikate, Reihenfolge und Aggregation in verschiedenen Services verteilt sind.

Erkennungsmerkmale

Wiederholte Schleifen über dieselbe Sammlung, size()-Prüfungen an vielen Stellen, Entfernen oder Hinzufügen ohne Invarianten, Methoden mit List<DomainType> in zahlreichen Signaturen.

Nutzen der Transformation

Eine Domain Collection gibt der Menge einen fachlichen Namen und kapselt Invarianten sowie häufige Operationen. Aufrufer sprechen über OrderLines oder Approvers statt über technische Listen.

Sicherer Refactoring-Ablauf

  1. Wiederkehrende Regeln und Operationen der Sammlung erfassen.
  2. Einen unveränderlichen oder kontrolliert mutierbaren Sammlungstyp einführen.
  3. Erzeugung und Änderungsoperationen mit Invarianten versehen.
  4. Signaturen schrittweise auf den Domänentyp umstellen.
  5. Direkte Freigabe der internen Collection durch defensive Sichten ersetzen.

Einsatzgrenze

Bei rein technischen Zwischenlisten ohne eigene Regeln genügt die Standard-Collection. Eine Domain Collection ist sinnvoll, wenn die Menge einen eigenen Begriff und Verhalten besitzt.

Risiken und Fehlanwendungen

Das bloße Delegieren sämtlicher List-Methoden erzeugt einen unnötigen Wrapper. Mutierbare interne Listen dürfen nicht nach außen leaken.

Verwandte Techniken

Encapsulate Collection, Value Object, Aggregate und Composite.

Abgrenzung und Entscheidungshilfe

Eine Domain Collection ist ein Fachtyp; ein Repository repräsentiert dagegen eine persistierte Sammlung von Aggregaten.

Enterprise-Java-Einordnung

OrderLines verhindert doppelte Positionsnummern, berechnet Total und liefert nur fachliche Operationen addProduct, removeLine und taxableTotal.

Teststrategie

Invarianten bei Erzeugung und Änderung, defensive Kopien, Aggregationen und Gleichheit der Collection testen.

OrderLines.java
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Replace Collection with Domain Collection
// Domain Collection: Invarianten und Aggregatoperationen liegen bei der Sammlung.
public final class OrderLines {
  public record Line(String productId,int quantity,long unitCents){ public Line { if(productId==null||productId.isBlank()||quantity<1||unitCents<0) throw new IllegalArgumentException("line"); } }
  private final List<Line> lines;
  public OrderLines(List<Line> lines){ if(lines==null||lines.isEmpty()) throw new IllegalArgumentException("empty"); var ids=new HashSet<String>(); for(var l:lines) if(!ids.add(l.productId())) throw new IllegalArgumentException("duplicate"); this.lines=List.copyOf(lines); }
  public long totalCents(){ return lines.stream().mapToLong(l->l.unitCents()*l.quantity()).sum(); }
  public List<Line> asList(){ return lines; }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

93. Extract Domain Service

Extract Domain Service

Fachliches Ausgangsproblem

Eine fachliche Operation gehört zu keinem einzelnen Entity oder Value Object, weil sie mehrere Aggregate oder externe Fachinformationen kombiniert. Sie landet deshalb in einem technischen Application Service oder Utility.

Erkennungsmerkmale

Fachformeln in Controllern, statische DomainUtils, Methoden mit mehreren fachlichen Objekten ohne Infrastruktur, Logik die künstlich in eine Entity gedrückt wurde.

Nutzen der Transformation

Ein Domain Service erhält einen fachlichen Namen und kapselt domänenreine Logik, die nicht natürlich einem Objekt gehört. Das Modell bleibt ausdrucksstark, ohne Entities zu überladen.

Sicherer Refactoring-Ablauf

  1. Prüfen, ob das Verhalten wirklich in Entity oder Value Object gehört.
  2. Benötigte fachliche Eingaben und Ergebnis als klare Typen bestimmen.
  3. Eine zustandslose, domänensprachlich benannte Serviceklasse einführen.
  4. Logik aus technischen Schichten verschieben.
  5. Infrastrukturzugriffe über vorbereitete Werte oder Ports außerhalb halten.

Einsatzgrenze

Orchestrierung, Transaktionen und technische Aufrufe gehören in Application Services. Ein Domain Service ist für fachliche Regeln, nicht als Sammelplatz für beliebige Logik.

Risiken und Fehlanwendungen

Zu viele Domain Services führen zu anämischen Entities. Ein Service mit Repository-, HTTP- und Messagingzugriff ist meist Application Service oder Use Case.

Verwandte Techniken

Domain Model, Policy Object, Specification und Application Service.

Abgrenzung und Entscheidungshilfe

Domain Service entscheidet fachlich. Application Service koordiniert Ablauf, Transaktion und Ports.

Enterprise-Java-Einordnung

CreditLimitService berechnet aus Kundenrisiko, offenen Forderungen und beantragtem Betrag eine fachliche Kreditentscheidung, ohne Datenbank oder HTTP zu kennen.

Teststrategie

Reine Unit-Tests decken Entscheidungstabellen, Grenzwerte und Symmetrieeigenschaften ab. Keine Frameworkumgebung erforderlich.

TransferDomainService.java
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Domain Service
// Domain Service Pattern: Eine fachliche Operation koordiniert mehrere Entities.
public final class TransferDomainService {
  public static final class Account { private long cents; public Account(long cents){ if(cents<0) throw new IllegalArgumentException(); this.cents=cents; } public long balance(){ return cents; } void debit(long amount){ if(amount<=0||amount>cents) throw new IllegalArgumentException("funds"); cents-=amount; } void credit(long amount){ if(amount<=0) throw new IllegalArgumentException("amount"); cents+=amount; } }
  public void transfer(Account source,Account target,long cents){ if(source==target) throw new IllegalArgumentException("same account"); source.debit(cents); target.credit(cents); }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

94. Extract Policy Object

Extract Policy Object

Fachliches Ausgangsproblem

Eine veränderliche Geschäftsregel ist als verschachtelte Bedingung in einem großen Service eingebettet. Varianten nach Land, Produkt, Risiko oder Kundengruppe führen zu wachsenden if-Ketten.

Erkennungsmerkmale

Bedingungen mit vielen fachlichen Parametern, häufige Regeländerungen, mehrere Versionen derselben Entscheidung, schwer benennbare Booleans.

Nutzen der Transformation

Ein Policy Object macht eine Regel als eigenständiges, testbares Konzept sichtbar. Eingaben und Entscheidung werden typisiert, Varianten können gezielt ausgetauscht oder versioniert werden.

Sicherer Refactoring-Ablauf

  1. Die fachliche Entscheidung und ihre Einflussgrößen isolieren.
  2. Einen benannten Eingabekontext und typisiertes Ergebnis definieren.
  3. Die bestehende Regel unverändert in ein Policy Object verschieben.
  4. Aufrufer über einen klaren Vertrag anbinden.
  5. Varianten nur bei echtem Änderungsdruck als Strategien ergänzen.

Einsatzgrenze

Eine triviale, stabile Bedingung benötigt kein eigenes Objekt. Policy Objects sind besonders wertvoll bei eigenständigem Vokabular, Versionierung oder mehreren Varianten.

Risiken und Fehlanwendungen

Eine Policy darf keine Orchestrierung oder Persistenz übernehmen. Dutzende winzige Policies ohne erkennbare Fachgrenze erschweren den Ablauf.

Verwandte Techniken

Strategy, Specification, Decision Object und Domain Service.

Abgrenzung und Entscheidungshilfe

Eine Specification beantwortet typischerweise, ob ein Objekt ein Kriterium erfüllt. Eine Policy kann eine reichere Entscheidung mit Begründung und Folgeschritten liefern.

Enterprise-Java-Einordnung

RefundPolicy entscheidet anhand Betrag, Lieferstatus, Kundensegment und Frist über automatische Erstattung, manuelle Prüfung oder Ablehnung.

Teststrategie

Entscheidungstabellen decken alle Regelkombinationen ab. Versions- und Mandantenvarianten erhalten gemeinsame Contract-Tests.

RefundPolicy.java
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Extract Policy Object
// Policy Pattern: Veränderliche Fachregeln werden als explizite Entscheidung gekapselt.
public final class RefundPolicy {
  public record Decision(boolean allowed,String reason) {}
  private final Duration window;
  public RefundPolicy(Duration window){ if(window==null||window.isNegative()||window.isZero()) throw new IllegalArgumentException("window"); this.window=window; }
  public Decision evaluate(Instant paidAt,Instant requestedAt,boolean consumed){ if(consumed) return new Decision(false,"already-consumed"); if(requestedAt.isAfter(paidAt.plus(window))) return new Decision(false,"window-expired"); return new Decision(true,"eligible"); }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

95. Replace If-Else Chain with Strategy

Replace If-Else Chain with Strategy

Fachliches Ausgangsproblem

Eine Methode wählt anhand eines Typs oder Merkmals zwischen mehreren vollständigen Algorithmen. Jede neue Variante verändert dieselbe zentrale if-else-Kette.

Erkennungsmerkmale

Lange Kette auf paymentType, channel, country oder customerType; Varianten haben eigene Berechnungsschritte; Tests benötigen viele Verzweigungskombinationen; häufige Merge-Konflikte.

Nutzen der Transformation

Strategy trennt austauschbare Algorithmen hinter einem gemeinsamen Vertrag. Jede Variante kann unabhängig entwickelt und getestet werden; die Auswahl wird von der Ausführung getrennt.

Sicherer Refactoring-Ablauf

  1. Gemeinsamen Eingabe- und Ergebnisvertrag der Varianten bestimmen.
  2. Eine Strategy-Schnittstelle einführen.
  3. Eine bestehende Verzweigung in eine konkrete Strategy verschieben.
  4. Schrittweise weitere Varianten extrahieren.
  5. Auswahl über Resolver, Registry oder Composition Root zentralisieren.

Einsatzgrenze

Bei zwei sehr kleinen, stabilen Ausdrücken ist eine Strategiehierarchie übertrieben. Wenn Verhalten vom internen Lebenszykluszustand abhängt, ist State oft passender.

Risiken und Fehlanwendungen

Ein großer Context mit vielen optionalen Feldern zeigt einen zu breiten Vertrag. Eine Registry darf unbekannte Varianten nicht stillschweigend auf einen Default abbilden.

Verwandte Techniken

Replace Conditional with Polymorphism, State, Factory und Registry.

Abgrenzung und Entscheidungshilfe

Strategy wird vom Aufrufer oder Resolver ausgewählt. State wechselt typischerweise aufgrund interner Zustandsübergänge des Contexts.

Enterprise-Java-Einordnung

ShippingCostStrategy berechnet Kosten für Standard, Express und Abholung. Ein Resolver wählt anhand des Lieferprodukts, während der Checkout nur den Strategy-Vertrag kennt.

Teststrategie

Contract-Tests prüfen gemeinsame Invarianten aller Strategien. Jede Variante erhält fokussierte Grenzwerttests; Resolvertests prüfen eindeutige Auswahl.

DeliveryPricing.java
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Replace If-Else Chain with Strategy
// Strategy Pattern: Varianten werden ohne wachsende Verzweigung ergänzt.
public final class DeliveryPricing {
  public enum Mode { STANDARD, EXPRESS, PICKUP }
  public interface Strategy { long price(long basketCents); }
  private final Map<Mode,Strategy> strategies;
  public DeliveryPricing(Map<Mode,Strategy> strategies){ this.strategies=Map.copyOf(strategies); }
  public long price(Mode mode,long basketCents){ var strategy=strategies.get(mode); if(strategy==null) throw new IllegalArgumentException("mode"); return strategy.price(basketCents); }
  public static DeliveryPricing defaults(){ return new DeliveryPricing(Map.of(Mode.STANDARD,b->b>=5000?0:490,Mode.EXPRESS,b->990,Mode.PICKUP,b->0)); }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

96. Replace Switch with Polymorphic Handler

Replace Switch with Polymorphic Handler

Fachliches Ausgangsproblem

Ein switch über einen Befehlstyp, Ereignisnamen oder Dokumenttyp enthält pro Fall Validierung, Ausführung und Fehlerbehandlung. Neue Typen verändern eine zentrale Dispatch-Methode.

Erkennungsmerkmale

switch auf enum oder String in einem Dispatcher, wiederholte Casts, pro Fall deutlich unterschiedliche Abhängigkeiten, zentraler Code mit hoher Änderungsfrequenz.

Nutzen der Transformation

Polymorphe Handler kapseln typbezogenes Verhalten und ihre Abhängigkeiten. Ein Dispatcher ordnet Typ zu Handler, ohne die Implementierung jedes Falls zu kennen.

Sicherer Refactoring-Ablauf

  1. Die Fälle und ihren gemeinsamen Dispatch-Vertrag erfassen.
  2. Einen typisierten Handlervertrag definieren.
  3. Einen Fall vollständig in einen Handler verschieben.
  4. Handler über eine explizite Registry registrieren.
  5. Switch entfernen, sobald alle Fälle migriert und unbekannte Typen definiert behandelt sind.

Einsatzgrenze

Ein switch zur einfachen Wertabbildung kann lesbarer sein. Handler lohnen sich, wenn Fälle eigenes Verhalten, Abhängigkeiten oder Tests besitzen.

Risiken und Fehlanwendungen

Stringbasierte Handlerkeys bleiben fehleranfällig. Eine automatische Classpath-Suche kann Mehrdeutigkeiten verstecken. Handler dürfen keine gemeinsame Transaktionsregel widersprüchlich implementieren.

Verwandte Techniken

Command, Strategy Registry, Factory und Message Router.

Abgrenzung und Entscheidungshilfe

Der polymorphe Handler ist besonders für typisierte Nachrichten oder Befehle geeignet; Strategy beschreibt eher austauschbare Algorithmen für denselben Kontext.

Enterprise-Java-Einordnung

DocumentHandler verarbeitet Invoice, Contract und ClaimDocument. Jeder Handler validiert und transformiert seinen Typ; die Registry lehnt unbekannte Typen explizit ab.

Teststrategie

Contract-Tests prüfen Handlerverträge. Registrytests prüfen doppelte und fehlende Registrierung. Jeder Handler erhält eigene Fachtests.

DocumentExportHandlers.java
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Replace Switch with Polymorphic Handler
// Polymorphism + Registry: Jeder Handler besitzt genau ein Formatverhalten.
public final class DocumentExportHandlers {
  public interface Handler { String format(); byte[] export(List<String> rows); }
  private final Map<String,Handler> handlers;
  public DocumentExportHandlers(List<Handler> handlers){ var map=new HashMap<String,Handler>(); for(var h:handlers) if(map.put(h.format(),h)!=null) throw new IllegalArgumentException("duplicate"); this.handlers=Map.copyOf(map); }
  public byte[] export(String format,List<String> rows){ var h=handlers.get(format); if(h==null) throw new IllegalArgumentException("format"); return h.export(List.copyOf(rows)); }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

97. Introduce Explicit State Machine

Introduce Explicit State Machine

Fachliches Ausgangsproblem

Zustandsabhängiges Verhalten ist über Statusfelder, Booleans und verstreute Bedingungen verteilt. Ungültige Übergänge sind möglich und erlaubte Folgeaktionen nicht zentral erkennbar.

Erkennungsmerkmale

Mehrere Felder wie approved, paid, closed; switch(status) in vielen Methoden; direkte setStatus-Aufrufe; Produktionsfehler durch übersprungene Zustände.

Nutzen der Transformation

Eine explizite State Machine modelliert Zustände, Ereignisse, erlaubte Übergänge und Aktionen als überprüfbaren Vertrag. Ungültige Übergänge werden an einer Stelle abgewiesen.

Sicherer Refactoring-Ablauf

  1. Bestehende Zustände und tatsächlich beobachtete Übergänge dokumentieren.
  2. Characterization Tests für gültige und ungültige Abläufe erstellen.
  3. Einen zentralen Übergangsvertrag mit Ereignis und Ergebnis einführen.
  4. Direkte Statusänderungen schrittweise durch transition(event) ersetzen.
  5. Nebenwirkungen von Zustandsänderung trennen und nach erfolgreichem Übergang auslösen.

Einsatzgrenze

Für einen linearen Ablauf mit zwei stabilen Zuständen genügt oft eine validierende Methode. Eine State Machine ist sinnvoll bei mehreren Übergängen, Rollen oder zeitlichen Regeln.

Risiken und Fehlanwendungen

Diagramm und Code dürfen nicht auseinanderlaufen. Versteckte Seiteneffekte in Transitionen erschweren Wiederholung. Persistierte Zustände benötigen Migrationsstrategie.

Verwandte Techniken

State Pattern, Workflow Object, Process Manager und Decision Object.

Abgrenzung und Entscheidungshilfe

State Pattern verteilt Verhalten auf Zustandsobjekte. Eine explizite State Machine kann auch tabellen- oder regelbasiert implementiert sein.

Enterprise-Java-Einordnung

ClaimStateMachine erlaubt SUBMIT, APPROVE, REJECT und PAY abhängig vom aktuellen ClaimStatus. Jede Ablehnung enthält einen fachlichen Grund.

Teststrategie

Eine Übergangsmatrix prüft jede Kombination aus Zustand und Ereignis. Property-Tests sichern, dass geschlossene Zustände keine Änderungen mehr erlauben.

TicketStateMachine.java
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Explicit State Machine
// State Machine Pattern: Erlaubte Übergänge sind zentral und ausführbar dokumentiert.
public final class TicketStateMachine {
  public enum State { NEW, ASSIGNED, RESOLVED, CLOSED }
  private static final Map<State,Set<State>> ALLOWED=Map.of(State.NEW,Set.of(State.ASSIGNED),State.ASSIGNED,Set.of(State.RESOLVED),State.RESOLVED,Set.of(State.CLOSED),State.CLOSED,Set.of());
  public State transition(State from,State to){ if(!ALLOWED.getOrDefault(from,Set.of()).contains(to)) throw new IllegalStateException(from+" -> "+to); return to; }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

98. Introduce Null Object

Introduce Null Object

Fachliches Ausgangsproblem

Ein optionaler Kollaborator führt zu wiederholten null-Prüfungen. Der fehlende Fall besitzt dabei ein wohldefiniertes neutrales Verhalten, etwa keine Benachrichtigung oder keinen Rabatt.

Erkennungsmerkmale

if (service != null) an vielen Stellen, optionale No-op-Konfiguration, Tests für identische Nullpfade, NullPointer-Risiken nach neuen Aufrufen.

Nutzen der Transformation

Ein Null Object implementiert denselben Vertrag mit bewusst neutralem Verhalten. Aufrufer können den Vertrag ohne Sonderprüfung verwenden und bleiben frei von Nullzweigen.

Sicherer Refactoring-Ablauf

  1. Prüfen, ob für das Fehlen wirklich ein eindeutiges neutrales Verhalten existiert.
  2. Einen kleinen Vertrag und eine No-op-Implementierung definieren.
  3. Erzeugungs- oder Verdrahtungsstelle so ändern, dass niemals null geliefert wird.
  4. Nullprüfungen schrittweise entfernen.
  5. Observability prüfen, damit wichtige Fehlkonfigurationen nicht unsichtbar werden.

Einsatzgrenze

Wenn das Fehlen fachlich relevant ist oder vom Aufrufer behandelt werden muss, sind Optional, Result Type oder Special Case besser. Null Object darf Fehler nicht verschlucken.

Risiken und Fehlanwendungen

Ein stilles Null Object kann fehlende Konfiguration verbergen. Neutrales Verhalten muss tatsächlich alle Invarianten wahren und darf keine falschen Erfolgsannahmen erzeugen.

Verwandte Techniken

Introduce Special Case, Optional Result, Result Type und No-op Adapter.

Abgrenzung und Entscheidungshilfe

Optional macht Abwesenheit sichtbar. Null Object macht sie für Aufrufer transparent. Die Wahl hängt davon ab, ob der fehlende Fall behandelt werden muss.

Enterprise-Java-Einordnung

NoOpAuditPublisher ist nur in lokalen Tests zulässig; produktive Profile verlangen einen echten AuditPublisher. Ein NoDiscountPolicy liefert Money.zero statt null.

Teststrategie

Contract-Tests prüfen, dass das Null Object den Vertrag erfüllt, keine Seiteneffekte auslöst und definierte neutrale Ergebnisse liefert.

CustomerDiscount.java
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Null Object
// Null Object Pattern: Fehlende Variante besitzt neutrales Verhalten statt null.
public final class CustomerDiscount {
  public interface Discount { long apply(long cents); }
  public static final Discount NONE=cents->cents;
  private final Discount discount;
  public CustomerDiscount(Discount discount){ this.discount=discount==null?NONE:discount; }
  public long finalPrice(long cents){ if(cents<0) throw new IllegalArgumentException("price"); return discount.apply(cents); }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

99. Extract Specification

Extract Specification

Fachliches Ausgangsproblem

Fachliche Auswahl- oder Zulassungsregeln sind als lange Prädikate in Services, Repositories und Controllern dupliziert. Kombinationen aus UND, ODER und NICHT sind schwer lesbar.

Erkennungsmerkmale

Wiederholte Bedingungen über dasselbe Fachobjekt, Methoden wie isEligible mit vielen Klauseln, inkonsistente Filter zwischen UI und Backend, Regeln mit eigenem Fachnamen.

Nutzen der Transformation

Eine Specification kapselt ein benanntes Kriterium und kann mit anderen Spezifikationen kombiniert werden. Fachsprache und Wiederverwendung werden sichtbar, ohne die Entity mit jeder Abfrage zu belasten.

Sicherer Refactoring-Ablauf

  1. Eine wiederkehrende fachliche Bedingung mit klarem Namen auswählen.
  2. Einen Specification-Vertrag über den relevanten Typ einführen.
  3. Die Bedingung unverändert in eine konkrete Specification verschieben.
  4. Komposition für and, or und not kontrolliert ergänzen.
  5. Duplikate in Services und Queries schrittweise ersetzen.

Einsatzgrenze

Ein einmaliges triviales Predicate benötigt keine Klasse. Für Entscheidungen mit mehreren Ergebnissen und Begründungen ist Policy oder Decision Object geeigneter.

Risiken und Fehlanwendungen

Eine generische Specification-Bibliothek kann Fachsprache verdrängen. Datenbankübersetzung und In-Memory-Auswertung dürfen nicht unbemerkt unterschiedliche Semantik haben.

Verwandte Techniken

Predicate, Policy Object, Query Object und Decision Table.

Abgrenzung und Entscheidungshilfe

Specification beschreibt ein Kriterium. Strategy führt einen Algorithmus aus. Policy kann eine reichere Entscheidung treffen.

Enterprise-Java-Einordnung

EligibleForAutomaticRefund kombiniert WithinReturnWindow, NotFraudSuspected und AmountBelowLimit. Die Regel kann im Use Case erklärt und separat getestet werden.

Teststrategie

Beispiel- und Grenzwerttests je Specification, Wahrheitstabellen für Kompositionen und Konsistenztests zwischen In-Memory- und Persistenzabfrage.

OrderEligibilitySpecification.java
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Specification
// Specification Pattern: Fachregeln werden benannt, kombiniert und separat getestet.
public interface OrderEligibilitySpecification {
  record Candidate(boolean active,long outstandingCents,String country) {}
  boolean isSatisfiedBy(Candidate candidate);
  default OrderEligibilitySpecification and(OrderEligibilitySpecification other){ return c->isSatisfiedBy(c)&&other.isSatisfiedBy(c); }
  static OrderEligibilitySpecification active(){ return Candidate::active; }
  static OrderEligibilitySpecification debtBelow(long limit){ return c->c.outstandingCents()<limit; }
  static OrderEligibilitySpecification country(String value){ return c->value.equals(c.country()); }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

100. Replace Boolean Return with Decision Object

Replace Boolean Return with Decision Object

Fachliches Ausgangsproblem

Eine Methode liefert true oder false, obwohl der Aufrufer zusätzlich Grund, Regelversion, Grenzwert oder nächsten Schritt benötigt. Daraufhin wird die Entscheidung an anderer Stelle erneut berechnet oder mit Zusatzabfragen ergänzt.

Erkennungsmerkmale

Methoden wie canApprove oder isValid mit anschließenden if-Blöcken, Logmeldungen ohne konkreten Grund, parallele Methoden reasonForRejection, mehrere Booleans für denselben Entscheidungsprozess.

Nutzen der Transformation

Ein Decision Object macht Ergebnis und Begründung atomar. Es kann Status, Reason Code, erklärende Fakten und empfohlene Aktion typisiert transportieren, ohne dass Aufrufer Regeln duplizieren.

Sicherer Refactoring-Ablauf

  1. Alle Informationen erfassen, die Aufrufer nach der Boolean-Entscheidung benötigen.
  2. Einen kleinen unveränderlichen Decision-Typ mit Status und fachlichem Grund definieren.
  3. Bestehende Methode intern auf Decision umstellen und vorübergehend einen Boolean-Adapter anbieten.
  4. Aufrufer schrittweise auf die reichere Entscheidung migrieren.
  5. Alte Boolean-Methode entfernen, sobald keine Doppelberechnung mehr existiert.

Einsatzgrenze

Wenn die Antwort wirklich vollständig binär ist und kein Grund oder Folgeschritt benötigt wird, bleibt boolean angemessen. Decision Objects dürfen nicht zu beliebigen Diagnosecontainern anwachsen.

Risiken und Fehlanwendungen

Freitextgründe erschweren Automatisierung und Übersetzung; besser sind typisierte Reason Codes plus optionale Parameter. Das Decision Object darf keine spätere mutable Prozessakte werden.

Verwandte Techniken

Result Type, Policy Object, Validation Result und State Transition Result.

Abgrenzung und Entscheidungshilfe

Ein Result Type modelliert häufig Erfolg oder technischen/fachlichen Fehler einer Operation. Ein Decision Object beschreibt bewusst das Ergebnis einer Regelbewertung.

Enterprise-Java-Einordnung

CreditDecision enthält APPROVED, MANUAL_REVIEW oder REJECTED, einen ReasonCode, verwendete PolicyVersion und Limit. API, Audit und Workflow nutzen dasselbe Ergebnis.

Teststrategie

Entscheidungstabellen prüfen Status und Reason Code gemeinsam. Serialisierungs- und Kompatibilitätstests sichern öffentliche Decision-Verträge.

CreditDecision.java
package com.aydinsude.workbench.catalog;
// Refactoring: Replace Boolean Return with Decision Object
// Decision Object Pattern: Ergebnis, Begründung und nächster Schritt bleiben zusammen.
public final class CreditDecision {
  public enum Status { APPROVED, REVIEW, REJECTED }
  public record Decision(Status status,String reason,String nextAction) {}
  public Decision decide(int score,long exposureCents){ if(score<500) return new Decision(Status.REJECTED,"score-too-low","notify-customer"); if(exposureCents>500000) return new Decision(Status.REVIEW,"high-exposure","manual-review"); return new Decision(Status.APPROVED,"policy-satisfied","create-contract"); }
}

Deutschsprachige fachliche Grundlagen

Die fachliche Erklärung wurde aus deutschsprachigen Hochschul-, Hersteller- und Überblicksquellen redaktionell zusammengeführt und auf das vorhandene Java-21-Beispiel übertragen. Sie ist keine wortwörtliche Übersetzung oder Kopie einer einzelnen Quelle.

Kapitel 11 Events explizit machen und Ereignisobjekte einführen10 Refactorings

Vertrags-, Fehler-, Workflow- und Integrationsrefactorings mit kompilierbarem Java-21-Zielcode.

101. Introduce Event Object Refactoring

Introduce Event Object

Ausgangsproblem

Viele Methoden reichen dieselben Event-Felder einzeln weiter; Reihenfolge und Bedeutung der Parameter sind leicht zu verwechseln.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Bündele zusammengehörige Eventdaten in einem unveränderlichen Fachobjekt und validiere Pflichtfelder am Konstruktor.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

OrderPlacedEvent.java
package com.aydinsude.workbench.catalog;
import java.time.Instant;
// Refactoring: Introduce Event Object
// Event Object Pattern: Zusammengehörige Ereignisdaten bilden einen unveränderlichen Vertrag.
public record OrderPlacedEvent(String orderId, String customerId, long totalCents, Instant occurredAt) {
  public OrderPlacedEvent {
    if (orderId == null || orderId.isBlank()) throw new IllegalArgumentException("orderId");
    if (customerId == null || customerId.isBlank()) throw new IllegalArgumentException("customerId");
    if (totalCents < 0) throw new IllegalArgumentException("totalCents");
    if (occurredAt == null) throw new IllegalArgumentException("occurredAt");
  }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.Instant; import org.junit.jupiter.api.Test;
class OrderPlacedEventTest { @Test void event_owns_contract(){ var e=new OrderPlacedEvent("O-1","C-1",1200,Instant.EPOCH); assertEquals(1200,e.totalCents()); assertThrows(IllegalArgumentException.class,()->new OrderPlacedEvent("","C",0,Instant.EPOCH)); } }
Wirkung: Eventdaten werden zu einem stabilen, typisierten Vertrag.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Ein fachlich bedeutsames Geschehen wird nur durch einen Methodenaufruf, mehrere lose Parameter oder eine technische Nachricht dargestellt. Zeitpunkt, Verursacher, fachliche Identität und relevante Fakten sind nicht gemeinsam modelliert. Dadurch können nachgelagerte Reaktionen das Geschehen weder zuverlässig protokollieren noch unabhängig verarbeiten.

Erkennungsmerkmale

Mehrere Empfänger erhalten dieselben losen Parameter; Methoden heißen notifySomething oder handleChange, ohne das Ereignis als Typ auszudrücken; Audit, Messaging und Fachreaktionen bauen jeweils eigene Datenstrukturen; spätere Erweiterungen verändern viele Signaturen.

Ziel und Nutzen

Ein unveränderliches Event Object hält die fachlich relevanten Tatsachen eines bereits eingetretenen Ereignisses zusammen. Es gibt dem Geschehen einen Namen, stabilisiert den Vertrag und trennt die Entstehung des Ereignisses von seinen Reaktionen.

Sicherer Refactoring-Ablauf

  1. Das fachlich eingetretene Geschehen in der Vergangenheitsform benennen.
  2. Nur Fakten aufnehmen, die zum Ereigniszeitpunkt feststanden; keine Services oder veränderlichen Entitäten einbetten.
  3. Eindeutige Ereigniskennung, Zeitpunkt und fachliche Referenzen typisieren.
  4. Bisherige lose Parameter schrittweise durch das Event Object ersetzen.
  5. Verbraucher über eigene Handler anbinden und Versionierungsregeln festlegen.

Wann sinnvoll - und wann nicht

Ein Event Object ist sinnvoll, wenn ein Geschehen mehrere Reaktionen, Auditierbarkeit oder zeitliche Entkopplung benötigt. Für einen rein lokalen Funktionsaufruf ohne fachliche Bedeutung wäre ein eigener Ereignistyp unnötig.

Risiken und typische Fehlanwendungen

Ein Event darf nicht zum vollständigen Objekt-Snapshot anwachsen. Werden Befehle als Ereignisse benannt oder zukünftige Absichten aufgenommen, verschwimmt die Semantik. Öffentliche Ereignisse brauchen besonders stabile Verträge.

Abgrenzung

Ein Command fordert eine Handlung an und kann abgelehnt werden. Ein Event beschreibt eine bereits eingetretene Tatsache.

Verwandte Refactorings und Muster

Domain Event, Integration Event, Event Envelope, Observer und Transactional Outbox.

Enterprise-Java-Einordnung

Nach erfolgreicher Policierung erzeugt das Aggregat PolicyIssued mit PolicyId, CustomerId, Tarifversion und Ausstellungszeitpunkt. Rechnung, Dokumenterzeugung und Audit reagieren getrennt darauf.

Teststrategie

Tests prüfen Ereignisinhalt und Entstehungsbedingung. Contract-Tests sichern Serialisierung und Versionierung; Handler-Tests prüfen jede Reaktion unabhängig.

Deutschsprachige Fachgrundlagen

102. Extract Mapper Refactoring

Extract Mapper

Ausgangsproblem

Mappingcode zwischen API-DTO und Domänenobjekt ist über Controller und Service verteilt.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Extrahiere eine reine Mapper-Komponente ohne I/O; halte Mappingregeln zentral und testbar.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

CustomerMapper.java
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Mapper
// Mapper Pattern: Übersetzung zwischen externem Vertrag und Domänenmodell ist isoliert.
public final class CustomerMapper {
  public record CustomerRequest(String firstName,String lastName,String email) {}
  public record Customer(String fullName,EmailAddress email) {}
  public Customer toDomain(CustomerRequest request){
    if(request==null) throw new IllegalArgumentException("request");
    return new Customer((request.firstName()+" "+request.lastName()).trim(),new EmailAddress(request.email()));
  }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class CustomerMapperTest { @Test void mapper_translates_contract(){ var c=new CustomerMapper().toDomain(new CustomerMapper.CustomerRequest("Ada","Lovelace","ADA@EXAMPLE.ORG")); assertEquals("Ada Lovelace",c.fullName()); assertEquals("ada@example.org",c.email().value()); } }
Wirkung: Eine reine Übersetzungsgrenze schützt das Domänenmodell.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Konvertierungen zwischen API-, Persistenz-, Fremdsystem- und Domänenmodellen sind in Controllern, Repositories oder Use Cases verteilt. Feldzuordnungen, Standardwerte und Typkonvertierungen werden wiederholt und vermischen sich mit Fachlogik.

Erkennungsmerkmale

Viele Konstruktoraufrufe mit Feld-für-Feld-Kopien; technische DTOs gelangen in die Domäne; dieselbe Zuordnung existiert in mehreren Richtungen; Mappingfehler werden erst in End-to-End-Tests sichtbar.

Ziel und Nutzen

Ein expliziter Mapper konzentriert die Übersetzung an einer Systemgrenze. Fachlogik bleibt frei von Transport- und Persistenzformaten; Änderungen am äußeren Modell lassen sich an einer Stelle behandeln.

Sicherer Refactoring-Ablauf

  1. Die Grenze und beide beteiligten Modelle eindeutig bestimmen.
  2. Vorhandene Feldzuordnungen und Sonderregeln mit Tests sichern.
  3. Einen richtungsbezogenen Mapper mit klar benannten Methoden einführen.
  4. Konvertierungen aus Controller, Repository oder Use Case in den Mapper verschieben.
  5. Technische DTOs aus inneren Signaturen entfernen.

Wann sinnvoll - und wann nicht

Bei einer einmaligen, trivialen Zuordnung von zwei oder drei Feldern kann eine lokale Factory lesbarer sein. Ein Mapper ist besonders wertvoll, wenn Modelle bewusst unabhängig bleiben oder mehrere Konvertierungsregeln existieren.

Risiken und typische Fehlanwendungen

Bidirektionale Universalmapper verschleiern unterschiedliche Regeln je Richtung. Reflexionsbasierte Automatik kann fachlich relevante Konvertierungen unsichtbar machen. Mapper dürfen keine Fachentscheidungen treffen.

Abgrenzung

Ein Mapper übersetzt Datenrepräsentationen. Ein Adapter übersetzt einen ganzen Verhaltensvertrag und kann Mapper intern verwenden.

Verwandte Refactorings und Muster

Data Mapper, Adapter, Anti-Corruption Layer und Factory Method.

Enterprise-Java-Einordnung

CustomerApiMapper übersetzt REST-Anfragen in RegisterCustomerCommand und CustomerView in Response-DTOs. Ein JpaCustomerMapper trennt zusätzlich Persistenzobjekte vom Aggregat.

Teststrategie

Beispiel- und Grenzwerttests prüfen jede Richtung. Tests müssen Null-, Enum-, Zeit- und Geldkonvertierungen sowie unbekannte Werte abdecken.

Deutschsprachige Fachgrundlagen

103. Replace Mutable DTO with Record Refactoring

Replace Mutable DTO with Record

Ausgangsproblem

Ein veränderliches DTO kann nach Validierung unbemerkt geändert werden.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Ersetze Getter/Setter-Datencontainer durch ein validierendes Record mit defensiven Kopien.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

ShipmentRequest.java
package com.aydinsude.workbench.catalog;
import java.util.List;
// Refactoring: Replace Mutable DTO with Record
// Immutable Data Transfer Object: Der Transportvertrag kann nach Erzeugung nicht driften.
public record ShipmentRequest(String orderId, List<String> packageIds) {
  public ShipmentRequest {
    if(orderId==null||orderId.isBlank()) throw new IllegalArgumentException("orderId");
    packageIds=List.copyOf(packageIds);
    if(packageIds.isEmpty()||packageIds.stream().anyMatch(String::isBlank)) throw new IllegalArgumentException("packages");
  }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.*; import org.junit.jupiter.api.Test;
class ShipmentRequestTest { @Test void record_is_immutable(){ var source=new ArrayList<>(List.of("P1")); var r=new ShipmentRequest("O1",source); source.add("P2"); assertEquals(List.of("P1"),r.packageIds()); } }
Wirkung: Ein Record reduziert veränderlichen Zustand und stärkt den Vertrag.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Ein reines Transportobjekt besitzt Setter, einen leeren Konstruktor und veränderliche Felder, obwohl es nach der Erzeugung nur Daten transportieren soll. Unvollständige Zwischenzustände und spätere Änderungen sind dadurch möglich.

Erkennungsmerkmale

DTOs mit vielen Settern; Werte werden nach Übergabe verändert; equals/hashCode fehlen oder sind inkonsistent; Tests bauen Objekte in mehreren Schritten auf; parallele Verarbeitung benötigt defensive Kopien.

Ziel und Nutzen

Ein Java Record beschreibt einen unveränderlichen, transparenten Datenträger mit kompakter Syntax und konsistenter Wertsemantik. Pflichtfelder werden beim Erzeugen übergeben; der gültige Zustand ist leichter erkennbar.

Sicherer Refactoring-Ablauf

  1. Prüfen, ob der Typ tatsächlich Daten transportiert und keine Identität oder veränderliches Verhalten besitzt.
  2. Alle Erzeugungsstellen und Frameworkanforderungen erfassen.
  3. Felder in Record-Komponenten überführen und Validierung in einen kompakten Konstruktor legen.
  4. Setter-basierte Erzeugung durch Konstruktor, Factory oder Builder ersetzen.
  5. Serialisierungs- und Kompatibilitätstests ausführen.

Wann sinnvoll - und wann nicht

Records eignen sich nicht automatisch für JPA-Entities, langlebige Aggregate oder Objekte mit bewusst veränderlichem Lebenszyklus. Für sehr viele optionale Felder kann ein Builder weiterhin sinnvoll sein.

Risiken und typische Fehlanwendungen

Unveränderlichkeit ist nur flach: mutable Collections müssen defensiv kopiert werden. Ein Record mit Fachidentität wird nicht allein durch die Syntax zum Value Object. Framework-Kompatibilität muss geprüft werden.

Abgrenzung

Ein Record ist ein Java-Sprachmittel. Ein Value Object ist ein fachliches Modellkonzept und kann als Record umgesetzt werden, benötigt aber fachliche Invarianten.

Verwandte Refactorings und Muster

Immutable DTO, Value Object, Defensive Copy und Builder.

Enterprise-Java-Einordnung

PaymentRequest wird als Record mit PaymentId, Money, CustomerReference und RequestedAt modelliert. Der Adapter validiert die Eingabe einmal und übergibt einen vollständigen Wert an den Use Case.

Teststrategie

Konstruktorvalidierung, Wertgleichheit, defensive Kopien und JSON-Kompatibilität prüfen. Mutationstests zeigen, ob Invarianten wirklich abgesichert sind.

Deutschsprachige Fachgrundlagen

104. Introduce Result Type Refactoring

Introduce Result Type

Ausgangsproblem

Erwartbare fachliche Ablehnungen werden als Exceptions oder Boolean ohne Begründung transportiert.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Führe einen abgeschlossenen Result-Typ mit Success und Rejected ein; zwinge Aufrufer zur expliziten Behandlung.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

RegistrationResult.java
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Result Type
// Result Pattern: Erwartbare Ergebnisse sind explizit und exhaustiv behandelbar.
public sealed interface RegistrationResult permits RegistrationResult.Accepted,RegistrationResult.Rejected {
  record Accepted(String customerId) implements RegistrationResult { public Accepted { if(customerId==null||customerId.isBlank()) throw new IllegalArgumentException(); } }
  record Rejected(String code,String message) implements RegistrationResult { public Rejected { if(code==null||code.isBlank()||message==null||message.isBlank()) throw new IllegalArgumentException(); } }
  static RegistrationResult evaluate(boolean duplicate){ return duplicate?new Rejected("DUPLICATE","E-Mail bereits registriert"):new Accepted("C-100"); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class RegistrationResultTest { @Test void result_explains_rejection(){ var r=RegistrationResult.evaluate(true); assertInstanceOf(RegistrationResult.Rejected.class,r); assertEquals("DUPLICATE",((RegistrationResult.Rejected)r).code()); } }
Wirkung: Ein algebraischer Result-Typ macht erwartbare Ablehnungen sichtbar.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Erwartbare fachliche Ablehnungen werden als Exceptions, null oder mehrdeutige Booleans signalisiert. Aufrufer können Erfolg, Ablehnungsgrund und Folgeaktion nicht typsicher unterscheiden.

Erkennungsmerkmale

catch-Blöcke für normale Geschäftsentscheidungen; null bedeutet verschiedene Dinge; parallele Methoden liefern reason oder status; Fehlertexte werden ausgewertet; jeder Aufrufer interpretiert Rückgaben anders.

Ziel und Nutzen

Ein Result Type modelliert die endlichen möglichen Ausgänge einer Operation ausdrücklich. Erfolg und erwartbare Ablehnung tragen jeweils passende Daten; Compiler und Pattern Matching unterstützen eine vollständige Behandlung.

Sicherer Refactoring-Ablauf

  1. Mögliche fachliche und technische Ausgänge der Operation erfassen.
  2. Erwartbare Ergebnisse von unerwarteten Programm- oder Infrastrukturfehlern trennen.
  3. Einen geschlossenen Result-Vertrag mit typisierten Varianten definieren.
  4. Bestehende Rückgabe intern adaptieren und Aufrufer schrittweise umstellen.
  5. Textbasierte Statusauswertung und normale Kontrollfluss-Exceptions entfernen.

Wann sinnvoll - und wann nicht

Unerwartete technische Defekte oder verletzte interne Invarianten müssen nicht in jeden Result Type aufgenommen werden. Ein Result ist sinnvoll, wenn der Aufrufer fachlich auf mehrere erwartbare Ausgänge reagieren soll.

Risiken und typische Fehlanwendungen

Ein allumfassendes Result mit beliebigem Fehlerobjekt verliert Typsicherheit. Tief verschachtelte Result-Typen erschweren APIs. Technische Details dürfen nicht unkontrolliert bis zur Fachgrenze durchgereicht werden.

Abgrenzung

Ein Decision Object beschreibt das Ergebnis einer Regelbewertung. Ein Result Type beschreibt den Ausgang einer Operation und kann Erfolg oder erwartbare Fehlervarianten enthalten.

Verwandte Refactorings und Muster

Decision Object, Either, Validation Result und Exception Translation.

Enterprise-Java-Einordnung

ReserveInventory liefert Reserved, InsufficientStock oder ProductUnknown. Der Orchestrator entscheidet je Variante über Fortsetzung, Kompensation oder manuelle Prüfung.

Teststrategie

Tests prüfen jede Variante einschließlich Nutzdaten. Exhaustive Switches und Contract-Tests sichern, dass neue Varianten bewusst behandelt werden.

Deutschsprachige Fachgrundlagen

105. Translate Infrastructure Exception Refactoring

Translate Infrastructure Exception

Ausgangsproblem

SQL- oder HTTP-Ausnahmen entweichen bis in fachliche Schichten und koppeln Aufrufer an Infrastrukturdetails.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Fange technische Fehler am Adapter ab und übersetze sie in eine stabile fachliche/Anwendungs-Ausnahme mit erhaltener Ursache.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

CustomerLoadGateway.java
package com.aydinsude.workbench.catalog;
// Refactoring: Translate Infrastructure Exception
// Exception Translation Pattern: Technische Fehler werden an der Adaptergrenze stabilisiert.
public final class CustomerLoadGateway {
  public interface Client { String fetch(String id) throws Exception; }
  public static final class CustomerUnavailable extends RuntimeException { public CustomerUnavailable(String id,Throwable cause){ super("customer unavailable: "+id,cause); } }
  private final Client client;
  public CustomerLoadGateway(Client client){ this.client=client; }
  public String load(String id){ try { return client.fetch(id); } catch(Exception e){ throw new CustomerUnavailable(id,e); } }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class CustomerLoadGatewayTest { @Test void gateway_translates_and_preserves_cause(){ var cause=new Exception("down"); var g=new CustomerLoadGateway(id->{throw cause;}); var e=assertThrows(CustomerLoadGateway.CustomerUnavailable.class,()->g.load("C1")); assertSame(cause,e.getCause()); } }
Wirkung: Exception Translation hält technische Details hinter der Adaptergrenze.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

SQL-, HTTP-, Messaging- oder SDK-Ausnahmen gelangen aus Adaptern in Anwendung und Domäne. Innere Schichten müssen technische Fehlerklassen kennen und können nicht stabil zwischen fachlich relevanten Fehlerkategorien unterscheiden.

Erkennungsmerkmale

Use Cases fangen SQLException, WebClientException oder SDKExceptions; Controller kennen Datenbankcodes; Retryentscheidungen basieren auf Herstellermeldungen; technische Bibliotheksupdates verändern Fachcode.

Ziel und Nutzen

Exception Translation übersetzt technische Fehler an der Adaptergrenze in einen kleinen, stabilen Fehlervertrag. Innere Schichten reagieren auf Bedeutung wie vorübergehend nicht verfügbar, Konflikt oder nicht gefunden statt auf Herstellerdetails.

Sicherer Refactoring-Ablauf

  1. Tatsächlich auftretende technische Fehler und ihre Semantik inventarisieren.
  2. Stabile Fehlerkategorien aus Sicht des inneren Vertrags definieren.
  3. Im Adapter Ursache und relevante Metadaten erhalten und übersetzen.
  4. Aufrufer auf die neuen Kategorien umstellen.
  5. Logging, Retry und API-Mapping getrennt nach Kategorie testen.

Wann sinnvoll - und wann nicht

Nicht jede Exception muss übersetzt werden. Programmierfehler und verletzte Invarianten sollten sichtbar bleiben. Die Übersetzung gehört an eine echte Abhängigkeitsgrenze, nicht in beliebige Zwischenklassen.

Risiken und typische Fehlanwendungen

Eine einzige generische InfrastructureException vernichtet Information. Das Weglassen der Ursache erschwert Diagnose. Fehlercodes aus Fremdsystemen dürfen nicht ungefiltert zum öffentlichen Fachvertrag werden.

Abgrenzung

Exception Translation stabilisiert Fehlersemantik. Exception Mapping an einer REST-Grenze übersetzt anschließend interne Fehler in HTTP-Antworten.

Verwandte Refactorings und Muster

Adapter, Gateway, Anti-Corruption Layer, Result Type und Retry Policy.

Enterprise-Java-Einordnung

Ein PaymentProviderAdapter übersetzt ProviderTimeout in PaymentTemporarilyUnavailable und DuplicateRequest in PaymentAlreadyProcessed. Der Use Case kennt weder HTTP-Status noch SDK-Typen.

Teststrategie

Adaptertests provozieren typische Providerfehler und prüfen Kategorie, Ursache und Metadaten. Höhere Tests prüfen Retry- oder API-Reaktionen ohne technische Exceptionklassen.

Deutschsprachige Fachgrundlagen

106. Extract Validator Refactoring

Extract Validator

Ausgangsproblem

Validierungsregeln sind in Controller, Entity und Persistenzcode dupliziert.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Extrahiere einen kompositionsfähigen Validator mit strukturierten Verletzungen statt verstreuter Guard-Logik.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

OrderValidator.java
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Validator
// Validator + Composite Pattern: Regeln werden einzeln testbar und gemeinsam ausführbar.
public final class OrderValidator {
  public record Order(String customerId,long totalCents) {}
  public record Violation(String code,String message) {}
  public interface Rule { Optional<Violation> validate(Order order); }
  private final List<Rule> rules;
  public OrderValidator(List<Rule> rules){ this.rules=List.copyOf(rules); }
  public List<Violation> validate(Order order){ return rules.stream().map(r->r.validate(order)).flatMap(Optional::stream).toList(); }
  public static OrderValidator defaults(){ return new OrderValidator(List.of(o->o.customerId()==null||o.customerId().isBlank()?Optional.of(new Violation("CUSTOMER","Kunde fehlt")):Optional.empty(),o->o.totalCents()<0?Optional.of(new Violation("TOTAL","Summe negativ")):Optional.empty())); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class OrderValidatorTest { @Test void validator_collects_structured_violations(){ var v=OrderValidator.defaults().validate(new OrderValidator.Order("",-1)); assertEquals(2,v.size()); assertEquals("CUSTOMER",v.getFirst().code()); } }
Wirkung: Komponierbare Regeln liefern strukturierte, testbare Verletzungen.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Validierungsregeln sind über Controller, Services, Entitäten und Persistenzcode verteilt. Dieselbe Eingabe wird mehrfach oder in unterschiedlicher Reihenfolge geprüft; Fehlermeldungen und Schweregrade sind inkonsistent.

Erkennungsmerkmale

Lange if-Ketten vor einem Use Case; wiederholte Null-, Format- und Fachprüfungen; frühe Exceptions verhindern das Sammeln mehrerer Befunde; Regeln lassen sich nicht isoliert testen.

Ziel und Nutzen

Ein Validator bündelt zusammengehörige Prüfungen an einer klaren Grenze und liefert ein typisiertes Ergebnis. Die eigentliche Verarbeitung kann von gültigen Voraussetzungen ausgehen.

Sicherer Refactoring-Ablauf

  1. Syntaktische, strukturelle und fachliche Regeln voneinander unterscheiden.
  2. Die Regeln einer konkreten Eingabe oder Operation zuordnen.
  3. Einen Validator mit typisierten Violations und stabilen Codes einführen.
  4. Bestehende Prüfungen schrittweise verschieben und Duplikate entfernen.
  5. Reihenfolge, Sammelstrategie und Abbruchregeln ausdrücklich festlegen.

Wann sinnvoll - und wann nicht

Invarianten eines Aggregats bleiben im Aggregat; ein externer Validator darf sie nicht ersetzen. Bean Validation eignet sich gut für strukturelle Grenzen, komplexe Fachentscheidungen benötigen häufig Policies oder Specifications.

Risiken und typische Fehlanwendungen

Ein zentraler Universalvalidator wird zum neuen God Object. Freitextfehler erschweren APIs und Übersetzung. Validierung darf keine Daten ändern oder Seiteneffekte auslösen.

Abgrenzung

Ein Validator meldet Verstöße für eine Eingabe oder Operation. Eine Specification beschreibt meist ein wiederverwendbares fachliches Kriterium und ist kombinierbar.

Verwandte Refactorings und Muster

Specification, Bean Validation Boundary, Policy Object und Result Type.

Enterprise-Java-Einordnung

RegisterSupplierValidator prüft Pflichtangaben, IBAN-Format und erlaubte Länderkombinationen. Das Supplier-Aggregat schützt weiterhin seine eigenen Invarianten.

Teststrategie

Entscheidungstabellen prüfen Regeln, Kombinationen und stabile Violation Codes. Tests sichern, dass der Validator rein ist und keine Eingabe verändert.

Deutschsprachige Fachgrundlagen

107. Replace Temporal Coupling with Workflow Object Refactoring

Replace Temporal Coupling with Workflow Object

Ausgangsproblem

Mehrere Methoden müssen in einer unsichtbaren Reihenfolge aufgerufen werden, sonst entsteht ein inkonsistenter Zustand.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Kapsle den Ablauf in einem Workflow-Objekt, das Reihenfolge und Zwischenzustand explizit kontrolliert.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

InvoiceWorkflow.java
package com.aydinsude.workbench.catalog;
// Refactoring: Replace Temporal Coupling with Workflow Object
// Workflow Pattern: Reihenfolge und erlaubte Übergänge werden explizit modelliert.
public final class InvoiceWorkflow {
  public enum Stage { CREATED, CALCULATED, SENT }
  private Stage stage=Stage.CREATED; private long total;
  public Stage stage(){ return stage; } public long total(){ return total; }
  public void calculate(long net,long tax){ require(Stage.CREATED); if(net<0||tax<0) throw new IllegalArgumentException(); total=net+tax; stage=Stage.CALCULATED; }
  public String send(){ require(Stage.CALCULATED); stage=Stage.SENT; return "sent:"+total; }
  private void require(Stage expected){ if(stage!=expected) throw new IllegalStateException("expected "+expected+", was "+stage); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class InvoiceWorkflowTest { @Test void workflow_enforces_order(){ var w=new InvoiceWorkflow(); assertThrows(IllegalStateException.class,w::send); w.calculate(100,20); assertEquals("sent:120",w.send()); assertEquals(InvoiceWorkflow.Stage.SENT,w.stage()); } }
Wirkung: Ein Workflow-Objekt macht zeitliche Kopplung sichtbar und prüfbar.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Mehrere Methoden müssen in einer bestimmten, nicht typisierten Reihenfolge aufgerufen werden. Ein Objekt ist zwischen den Aufrufen teilweise initialisiert; falsche Reihenfolge oder ausgelassene Schritte führen erst zur Laufzeit zu Fehlern.

Erkennungsmerkmale

Kommentare wie zuerst validate, dann reserve, dann commit; Boolean-Felder steuern erlaubte Methoden; öffentliche Setter bauen Zwischenzustände auf; Aufrufer duplizieren dieselbe Sequenz.

Ziel und Nutzen

Ein Workflow Object macht den Prozess und seine Übergänge explizit. Es hält benötigten Zustand kontrolliert, benennt Schritte fachlich und verhindert oder typisiert ungültige Reihenfolgen.

Sicherer Refactoring-Ablauf

  1. Die tatsächliche Sequenz, Zustände und Abbruchpfade dokumentieren.
  2. Einen Workflow mit klarer Eingabe, Ergebnis und expliziten Zuständen einführen.
  3. Die bestehende Reihenfolge zunächst unverändert kapseln.
  4. Aufrufer auf eine einzige Startoperation oder typisierte Schritte umstellen.
  5. Versteckte Statusfelder und öffentliche Zwischenmethoden entfernen.

Wann sinnvoll - und wann nicht

Ein einfacher synchroner Ablauf mit wenigen lokalen Anweisungen benötigt kein Workflow Object. Bei langlebigen oder verteilten Prozessen können Process Manager oder Saga geeigneter sein.

Risiken und typische Fehlanwendungen

Ein Workflow darf nicht jede beteiligte Fachlogik übernehmen. Persistierter Workflowzustand erfordert Versionierung und Wiederanlauf. Zu generische Workflow-Engines können Fachsprache verdrängen.

Abgrenzung

Eine State Machine fokussiert erlaubte Zustandsübergänge eines Objekts. Ein Workflow Object orchestriert eine Folge von Arbeitsschritten und deren Ergebnisse.

Verwandte Refactorings und Muster

Process Manager, Saga, State Machine, Command und Template Method.

Enterprise-Java-Einordnung

CustomerOnboardingWorkflow koordiniert Identitätsprüfung, Kontoanlage und Begrüßung. Jeder Schritt liefert ein typisiertes Ergebnis; Wiederaufnahme und Kompensation sind sichtbar.

Teststrategie

Szenariotests prüfen Happy Path, Abbruch, Wiederholung und ungültige Übergänge. Ein deterministischer Clock- und ID-Port hält Tests reproduzierbar.

Deutschsprachige Fachgrundlagen

108. Introduce Idempotency Key Refactoring

Introduce Idempotency Key

Ausgangsproblem

Wiederholte Requests führen zu doppelten Zahlungen oder Aufträgen.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Führe einen typisierten Idempotency-Key und einen atomaren Store ein; gib bei Wiederholung dasselbe Ergebnis zurück.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

IdempotentPaymentService.java
package com.aydinsude.workbench.catalog;
import java.util.concurrent.ConcurrentHashMap;
// Refactoring: Introduce Idempotency Key
// Idempotent Receiver Pattern: Gleicher Schlüssel liefert dieselbe fachliche Wirkung.
public final class IdempotentPaymentService {
  public record IdempotencyKey(String value){ public IdempotencyKey { if(value==null||value.isBlank()) throw new IllegalArgumentException(); } }
  private final ConcurrentHashMap<IdempotencyKey,String> results=new ConcurrentHashMap<>();
  public String charge(IdempotencyKey key,long cents){ if(cents<=0) throw new IllegalArgumentException("cents"); return results.computeIfAbsent(key,k->"PAY-"+k.value()+"-"+cents); }
  public int executionCount(){ return results.size(); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class IdempotentPaymentServiceTest { @Test void duplicate_key_has_single_effect(){ var s=new IdempotentPaymentService(); var k=new IdempotentPaymentService.IdempotencyKey("K1"); assertEquals(s.charge(k,500),s.charge(k,500)); assertEquals(1,s.executionCount()); } }
Wirkung: Ein Idempotency-Key stabilisiert wiederholte oder parallele Aufrufe.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Ein Befehl oder externer Request kann durch Retry, Timeout oder doppelte Zustellung mehrfach eintreffen. Ohne stabile Identität werden Zahlungen, Buchungen oder Nachrichten doppelt ausgeführt.

Erkennungsmerkmale

Clients wiederholen Requests nach Timeout; Consumer erhält At-least-once-Zustellung; Duplikate werden nur über zufällige Zeitfenster erkannt; Seiteneffekte besitzen keine Wiederholungssemantik.

Ziel und Nutzen

Ein Idempotency Key identifiziert die fachlich gleiche Ausführungsabsicht über Wiederholungen hinweg. Die Verarbeitung kann ein gespeichertes Ergebnis zurückgeben oder konkurrierende Ausführungen kontrolliert ablehnen.

Sicherer Refactoring-Ablauf

  1. Die fachliche Einheit bestimmen, die genau einmal wirksam werden soll.
  2. Den Schlüssel am Ursprung erzeugen und über alle Grenzen unverändert propagieren.
  3. Speicherung von Status und Ergebnis atomar mit der Ausführung koordinieren.
  4. Wiederholungen mit gleichem Schlüssel und anderem Payload als Konflikt behandeln.
  5. Aufbewahrung, Ablauf und Parallelitätsverhalten festlegen.

Wann sinnvoll - und wann nicht

Ein zufälliger Schlüssel pro Wiederholungsversuch löst das Problem nicht. Für reine, ohnehin idempotente Leseoperationen ist zusätzliche Speicherung oft unnötig.

Risiken und typische Fehlanwendungen

Check-then-act ohne atomare Reservierung bleibt rennanfällig. Zu kurze Aufbewahrung erlaubt späte Duplikate; unbegrenzte Speicherung wächst dauerhaft. Schlüssel dürfen nicht unkontrolliert Mandantengrenzen überschreiten.

Abgrenzung

Ein Correlation Identifier verbindet zusammengehörige Nachrichten zur Beobachtung. Ein Idempotency Key verhindert die doppelte Wirkung derselben Absicht.

Verwandte Refactorings und Muster

Inbox Pattern, Idempotent Receiver, Transactional Outbox und Deduplication Store.

Enterprise-Java-Einordnung

Der Payment-API-Client sendet eine stabile Idempotency-Key-ID. Der Orchestrator reserviert sie atomar, speichert PaymentResult und liefert bei Wiederholung exakt dieses Ergebnis.

Teststrategie

Parallelitätstests senden denselben Schlüssel gleichzeitig. Contract-Tests prüfen identischen und abweichenden Payload, Wiederholung nach Fehler sowie Ablaufregeln.

Deutschsprachige Fachgrundlagen

109. Extract Retry Policy Refactoring

Extract Retry Policy

Ausgangsproblem

Retry-Schleifen, Backoff und Fehlerklassifizierung sind über mehrere Integrationen kopiert.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Extrahiere eine Policy, die Versuchszahl und Wiederholbarkeit entscheidet; halte I/O im Adapter.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

RetryPolicy.java
package com.aydinsude.workbench.catalog;
import java.time.Duration;
// Refactoring: Extract Retry Policy
// Policy Pattern: Wiederholungsentscheidung ist von der Ausführung getrennt.
public record RetryPolicy(int maxAttempts, Duration delay) {
  public RetryPolicy { if(maxAttempts<1||delay==null||delay.isNegative()) throw new IllegalArgumentException(); }
  public boolean shouldRetry(int attempt,Throwable failure){ return attempt<maxAttempts && failure instanceof TransientFailure; }
  public static final class TransientFailure extends RuntimeException { public TransientFailure(String message){ super(message); } }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.Duration; import org.junit.jupiter.api.Test;
class RetryPolicyTest { @Test void policy_classifies_failure_and_attempt(){ var p=new RetryPolicy(3,Duration.ofMillis(50)); assertTrue(p.shouldRetry(1,new RetryPolicy.TransientFailure("timeout"))); assertFalse(p.shouldRetry(3,new RetryPolicy.TransientFailure("timeout"))); assertFalse(p.shouldRetry(1,new IllegalArgumentException())); } }
Wirkung: Eine Policy trennt Retry-Entscheidung von Transport und Warte-Mechanik.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Wiederholungslogik ist als Schleife mit festen Pausen in mehreren Adaptern verteilt. Fehlerklassifikation, maximale Dauer und Abbruchbedingungen unterscheiden sich oder verursachen Wiederholungsstürme.

Erkennungsmerkmale

for-Schleifen um Remoteaufrufe; catch(Exception) mit sleep; identische Retries auf allen Fehlern; verschachtelte Wiederholungen in SDK, Client und Use Case; fehlende Deadline.

Ziel und Nutzen

Eine Retry Policy macht ausdrücklich, welche Fehler vorübergehend sind, wie oft und mit welchem Backoff wiederholt wird und wann sofort abgebrochen werden muss. Sie lässt sich testen und mit Deadline sowie Circuit Breaker kombinieren.

Sicherer Refactoring-Ablauf

  1. Fehler nach vorübergehend, dauerhaft und unbekannt klassifizieren.
  2. Maximale Versuche oder besser ein Zeitbudget definieren.
  3. Backoff, Jitter und serverseitige Retry-After-Hinweise modellieren.
  4. Policy am äußeren Adapter platzieren und doppelte Retryschichten entfernen.
  5. Metriken für Versuche, Erfolge und Erschöpfung ergänzen.

Wann sinnvoll - und wann nicht

Nicht-idempotente Operationen dürfen nur mit geeigneter Idempotenzsicherung wiederholt werden. Validierungsfehler, Berechtigungsfehler und dauerhafte Ablehnungen sind keine Retryfälle.

Risiken und typische Fehlanwendungen

Unbegrenzte oder synchronisierte Retries verstärken Ausfälle. Retry innerhalb einer langen Transaktion bindet Ressourcen. Mehrere unsichtbare Retryebenen vervielfachen Versuche.

Abgrenzung

Retry behandelt erwartbar vorübergehende Fehler durch erneute Versuche. Circuit Breaker stoppt Aufrufe, wenn ein Ziel wahrscheinlich weiterhin ausfällt.

Verwandte Refactorings und Muster

Circuit Breaker, Deadline, Idempotency Key, Backoff und Bulkhead.

Enterprise-Java-Einordnung

InventoryGateway wiederholt nur Timeouts und 503-Antworten innerhalb einer Gesamtdeadline. 400- und 409-Antworten werden sofort fachlich übersetzt.

Teststrategie

Deterministische Tests verwenden Fake Clock und berechnen den Retryplan ohne reale Wartezeiten. Fehlerklassifikation, Jittergrenzen und Deadlineabbruch werden separat geprüft.

Deutschsprachige Fachgrundlagen

110. Extract Audit Port Refactoring

Extract Audit Port

Ausgangsproblem

Audit-Logging wird direkt mit Logger- oder Datenbankaufrufen in Geschäftslogik vermischt.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Extrahiere einen typisierten Audit-Port und injiziere ihn; Fachlogik beschreibt nur das Ereignis.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

AuditedApprovalService.java
package com.aydinsude.workbench.catalog;
import java.time.Instant;
// Refactoring: Extract Audit Port
// Ports and Adapters: Audit-I/O liegt hinter einem typisierten Ausgangsport.
public final class AuditedApprovalService {
  public record AuditEntry(String action,String subject,Instant at) {}
  public interface AuditPort { void append(AuditEntry entry); }
  private final AuditPort audit; private final java.time.Clock clock;
  public AuditedApprovalService(AuditPort audit,java.time.Clock clock){ this.audit=audit; this.clock=clock; }
  public boolean approve(String claimId,long cents){ if(claimId==null||claimId.isBlank()||cents<0) throw new IllegalArgumentException(); boolean approved=cents<=10_000; audit.append(new AuditEntry(approved?"APPROVED":"MANUAL_REVIEW",claimId,clock.instant())); return approved; }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.*; import java.util.*; import org.junit.jupiter.api.Test;
class AuditedApprovalServiceTest { @Test void service_emits_typed_audit_entry(){ var entries=new ArrayList<AuditedApprovalService.AuditEntry>(); var s=new AuditedApprovalService(entries::add,Clock.fixed(Instant.EPOCH,ZoneOffset.UTC)); assertTrue(s.approve("CL-1",500)); assertEquals("APPROVED",entries.getFirst().action()); assertEquals(Instant.EPOCH,entries.getFirst().at()); } }
Wirkung: Ein Audit-Port entkoppelt Fachentscheidung von Logging- und Speichertechnik.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Fach- und Anwendungscode schreibt direkt in Logdateien, Datenbanktabellen oder Audit-SDKs. Dadurch vermischen sich Nachweispflichten mit Speicherformat, Transport und technischer Fehlerbehandlung.

Erkennungsmerkmale

Logger- oder JDBC-Aufrufe mitten in Fachmethoden; Audittexte werden frei zusammengesetzt; unterschiedliche Module protokollieren gleiche Vorgänge anders; Tests müssen Logs durchsuchen.

Ziel und Nutzen

Ein Audit Port beschreibt fachlich relevante Nachweise als typisierten Vertrag. Adapter entscheiden über unveränderliche Speicherung, Signierung oder Weiterleitung; die Fachlogik bleibt unabhängig vom Auditprodukt.

Sicherer Refactoring-Ablauf

  1. Pflichtige Auditereignisse und benötigte Fakten mit Fach- und Compliance-Seite klären.
  2. Einen schmalen Port mit typisierten Audit Records definieren.
  3. Direkte Log- und Persistenzaufrufe in einen Adapter verschieben.
  4. Aufrufer schrittweise auf den Port umstellen.
  5. Fehlerstrategie, Datenschutz, Aufbewahrung und Unveränderlichkeit festlegen.

Wann sinnvoll - und wann nicht

Technisches Debug-Logging ist nicht automatisch Audit. Ein Audit Port ist für nachweispflichtige, fachlich benannte Vorgänge gedacht. Kritische Audits benötigen eine explizite Zustell- und Fehlerstrategie.

Risiken und typische Fehlanwendungen

Freitext-Audits sind schwer auswertbar. Sensible Daten dürfen nicht ungefiltert aufgenommen werden. Fire-and-forget kann Nachweise verlieren; synchrone Kopplung kann dagegen den Fachablauf blockieren.

Abgrenzung

Observability dient Betrieb und Diagnose. Audit dient nachvollziehbaren fachlichen oder regulatorischen Nachweisen mit strengeren Anforderungen.

Verwandte Refactorings und Muster

Security Audit Event, Event Object, Outbox und Observability Context.

Enterprise-Java-Einordnung

ApprovalService veröffentlicht ApprovalAuditRecord mit Vorgang, Akteur, Entscheidung, Grundcode und Policyversion. Ein Adapter schreibt manipulationsgeschützt in das zentrale Audit-System.

Teststrategie

Unit-Tests prüfen, welches Audit Record bei welcher Fachentscheidung entsteht. Adaptertests sichern Persistenz, Reihenfolge, Maskierung und Fehlerverhalten.

Deutschsprachige Fachgrundlagen

Kapitel 12 Publisher-Ports und ausgehende Nachrichten entkoppeln10 Refactorings

Technische Grenzen, kontrollierbare Infrastruktur und Resilienz mit kompilierbarem Java-21-Zielcode.

111. Extract Publisher Port Port / Publisher

Extract Publisher Port

Ausgangsproblem

Geschäftslogik ruft Kafka-, JMS- oder HTTP-Publisher direkt auf und kennt technische Zustellparameter.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Extrahiere einen typisierten Ausgangsport. Die Anwendung publiziert ein Fachereignis; der technische Adapter übernimmt Serialisierung und Transport.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

OrderEventPublisher.java
package com.aydinsude.workbench.catalog;
import java.time.Instant;
// Refactoring: Extract Publisher Port
// Ports and Adapters: Fachcode publiziert über einen typisierten Ausgangsport.
public final class OrderEventPublisher {
  public record OrderConfirmed(String orderId, Instant occurredAt) {
    public OrderConfirmed { if(orderId==null||orderId.isBlank()||occurredAt==null) throw new IllegalArgumentException(); }
  }
  public interface EventPublisher { void publish(OrderConfirmed event); }
  private final EventPublisher publisher; private final java.time.Clock clock;
  public OrderEventPublisher(EventPublisher publisher, java.time.Clock clock){ this.publisher=publisher; this.clock=clock; }
  public void confirm(String orderId){ publisher.publish(new OrderConfirmed(orderId,clock.instant())); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.*; import java.util.*; import org.junit.jupiter.api.Test;
class OrderEventPublisherTest { @Test void publishes_typed_event(){ var events=new ArrayList<OrderEventPublisher.OrderConfirmed>(); var s=new OrderEventPublisher(events::add,Clock.fixed(Instant.EPOCH,ZoneOffset.UTC)); s.confirm("O-1"); assertEquals(new OrderEventPublisher.OrderConfirmed("O-1",Instant.EPOCH),events.getFirst()); } }
Einsatzgrenze: Die neue Abstraktion muss eine echte technische oder fachliche Grenze stabilisieren. Ein Port, eine Policy oder ein Kontext ohne konkrete Änderungsachse erhöht nur die Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Anwendungsfälle verwenden direkt Brokerclients, Topics, Header und Serialisierung. Fachliche Verarbeitung ist dadurch an ein Messagingprodukt und dessen Zustellsemantik gekoppelt.

Erkennungsmerkmale

KafkaTemplate, JMSProducer oder SDK-Client im Use Case; Topicnamen im Fachcode; technische Header werden manuell gesetzt; Tests benötigen Broker oder umfangreiche Mocks.

Ziel und Nutzen

Ein Publisher Port beschreibt die fachliche Veröffentlichungsabsicht unabhängig vom Transport. Ein Adapter übernimmt Event Envelope, Serialisierung, Routing und Brokerfehler.

Sicherer Refactoring-Ablauf

  1. Fachliche Veröffentlichungsfälle und benötigte Garantien erfassen.
  2. Einen Port mit fachlich typisierten Nachrichten oder Events definieren.
  3. Broker- und Serialisierungslogik in einen Adapter verschieben.
  4. Direkte Clientaufrufe schrittweise ersetzen.
  5. Zustellstrategie, Outbox und Fehlerbehandlung bewusst entscheiden.

Wann sinnvoll - und wann nicht

Ein Publisher Port garantiert allein keine atomare Veröffentlichung zusammen mit Datenänderungen. Dafür ist häufig Transactional Outbox nötig. Rein interne synchrone Reaktionen können über Domain Events erfolgen.

Risiken und typische Fehlanwendungen

Ein generischer publish(Object) Port versteckt Verträge. Topic- und Schemaentscheidungen dürfen nicht unkontrolliert in Aufrufer zurückwandern. Falsche Erfolgsannahmen bei asynchroner Zustellung sind gefährlich.

Abgrenzung

Ein Publisher Port abstrahiert die Fähigkeit zu veröffentlichen. Event Object oder Integration Event definiert den Inhalt der Nachricht.

Verwandte Refactorings und Muster

Transactional Outbox, Domain Event, Integration Event, Event Envelope und Adapter.

Enterprise-Java-Einordnung

OrderApplicationService ruft OrderEventPublisher.publish(OrderConfirmed) auf. Der Kafka-Adapter erzeugt Envelope, Partition Key und Schema-Version.

Teststrategie

Use-Case-Tests verwenden Recording Publisher. Adaptertests prüfen Topic, Key, Header, Serialisierung und Brokerfehler; Contract-Tests sichern Eventschemas.

Deutschsprachige Fachgrundlagen

112. Introduce Correlation Identifier Correlation Identifier

Introduce Correlation Identifier

Ausgangsproblem

Korrelation wird als optionaler String durch Logger, Header und Methoden verteilt und geht an asynchronen Grenzen verloren.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Führe einen validierten Correlation-Identifier und einen expliziten Request-Kontext ein. Übergib ihn bewusst über jede Prozessgrenze.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

CorrelatedRequest.java
package com.aydinsude.workbench.catalog;
import java.util.UUID;
// Refactoring: Introduce Correlation Identifier
// Correlation Identifier Pattern: Ein stabiler Fachtyp verbindet zusammengehörige Aufrufe.
public record CorrelatedRequest(CorrelationId correlationId, String payload) {
  public CorrelatedRequest { if(correlationId==null||payload==null) throw new IllegalArgumentException(); }
  public record CorrelationId(String value) {
    public CorrelationId { if(value==null||value.isBlank()) throw new IllegalArgumentException("correlationId"); }
    public static CorrelationId newId(){ return new CorrelationId(UUID.randomUUID().toString()); }
  }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class CorrelatedRequestTest { @Test void correlation_is_explicit(){ var id=new CorrelatedRequest.CorrelationId("trace-42"); var r=new CorrelatedRequest(id,"payload"); assertEquals("trace-42",r.correlationId().value()); assertThrows(IllegalArgumentException.class,()->new CorrelatedRequest.CorrelationId("")); } }
Einsatzgrenze: Die neue Abstraktion muss eine echte technische oder fachliche Grenze stabilisieren. Ein Port, eine Policy oder ein Kontext ohne konkrete Änderungsachse erhöht nur die Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Ein fachlicher Vorgang durchläuft mehrere Threads, Dienste und Nachrichten, ohne eine gemeinsame Kennung. Logs, Traces und Antworten lassen sich nicht zuverlässig demselben Ablauf zuordnen.

Erkennungsmerkmale

Support sucht über Zeitstempel und Kundennummern; neue IDs entstehen an jeder Grenze; Message-Header fehlen; asynchrone Antworten können nicht dem Request zugeordnet werden.

Ziel und Nutzen

Ein Correlation Identifier verbindet technisch oder fachlich zusammengehörige Schritte über Prozessgrenzen. Er verbessert Diagnose, Request-Reply-Zuordnung und Auditbezug, ohne Fachidentitäten zu ersetzen.

Sicherer Refactoring-Ablauf

  1. Lebensdauer und Startpunkt des zu korrelierenden Vorgangs bestimmen.
  2. Einen typisierten, unveränderlichen Identifier einführen.
  3. An der Eingangsgrenze vorhandene Kennung validieren oder neu erzeugen.
  4. Über HTTP, Messaging und asynchrone Tasks bewusst propagieren.
  5. Logging, Tracing und Antworten auf dieselbe Kennung ausrichten.

Wann sinnvoll - und wann nicht

CorrelationId ist weder Entity-ID noch Idempotency Key. Eine einzige globale Kennung für alle Unterprozesse kann zu grob sein; bei Verzweigungen können Causation IDs ergänzen.

Risiken und typische Fehlanwendungen

ThreadLocal-basierte Propagation bricht bei asynchroner Verarbeitung. Ungeprüfte externe Kennungen können Log-Injection oder Kollisionen verursachen. Personenbezogene Daten gehören nicht in die ID.

Abgrenzung

Correlation Identifier verbindet einen Ablauf. Idempotency Key identifiziert eine wiederholbare Absicht; Trace IDs werden häufig vom Observability-System verwaltet.

Verwandte Refactorings und Muster

Observability Context, Request-Reply, Causation Identifier und Event Envelope.

Enterprise-Java-Einordnung

Ein Schadenfall-Upload erhält CorrelationId am API-Gateway. Dokumentprüfung, Virenscan, Extraktion und Ergebnisnachricht tragen dieselbe Kennung.

Teststrategie

Boundary-Tests prüfen Erzeugung, Validierung und Weitergabe. Asynchrone Tests stellen sicher, dass Kontext nicht zwischen Vorgängen ausläuft.

Deutschsprachige Fachgrundlagen

113. Replace Direct Call with Command Bus Command Bus

Replace Direct Call with Command Bus

Ausgangsproblem

Ein Use Case ruft mehrere konkrete Handler direkt auf; Routing, Audit und Erweiterung sind über den Aufrufer verteilt.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Modelliere Commands und Handler als typisierte Verträge. Ein kleiner Bus übernimmt ausschließlich Auflösung und Dispatch.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

CommandBus.java
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Replace Direct Call with Command Bus
// Command + Registry Pattern: Dispatch wird vom fachlichen Aufrufer getrennt.
public final class CommandBus {
  public interface Command<R> {}
  public interface Handler<C extends Command<R>,R> { Class<C> type(); R handle(C command); }
  private final Map<Class<?>,Handler<?,?>> handlers;
  public CommandBus(List<Handler<?,?>> handlers){ var map=new HashMap<Class<?>,Handler<?,?>>(); for(var h:handlers) if(map.put(h.type(),h)!=null) throw new IllegalArgumentException("duplicate handler"); this.handlers=Map.copyOf(map); }
  @SuppressWarnings("unchecked") public <C extends Command<R>,R> R dispatch(C command){ var h=(Handler<C,R>)handlers.get(command.getClass()); if(h==null) throw new IllegalArgumentException("no handler"); return h.handle(command); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.*; import org.junit.jupiter.api.Test;
class CommandBusTest { record Ping(String value) implements CommandBus.Command<String>{} @Test void bus_dispatches_by_type(){ CommandBus.Handler<Ping,String> h=new CommandBus.Handler<>(){ public Class<Ping> type(){return Ping.class;} public String handle(Ping p){return "pong:"+p.value();} }; assertEquals("pong:x",new CommandBus(List.of(h)).dispatch(new Ping("x"))); } }
Einsatzgrenze: Die neue Abstraktion muss eine echte technische oder fachliche Grenze stabilisieren. Ein Port, eine Policy oder ein Kontext ohne konkrete Änderungsachse erhöht nur die Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Ein Aufrufer kennt konkrete Handler und deren Abhängigkeiten. Querschnittliche Regeln wie Transaktion, Autorisierung, Metrik oder Idempotenz werden an vielen Aufrufstellen wiederholt.

Erkennungsmerkmale

Controller instanziieren oder injizieren zahlreiche Use Cases; große Dispatcher mit instanceof oder switch; Decoratorlogik wird pro Handler manuell verdrahtet; neue Befehle verändern zentrale Aufrufer.

Ziel und Nutzen

Ein Command Bus trennt das Senden eines typisierten Befehls von dessen Handler. Eine kontrollierte Pipeline kann Querschnittsregeln einheitlich anwenden, während jeder Handler einen einzelnen Anwendungsfall umsetzt.

Sicherer Refactoring-Ablauf

  1. Befehle und jeweils genau einen verantwortlichen Handler identifizieren.
  2. Typisierte Command- und Handlerverträge definieren.
  3. Eine explizite Registry oder Composition-Root-Zuordnung aufbauen.
  4. Direkte Handleraufrufe schrittweise über den Bus führen.
  5. Pipeline-Verhalten wie Transaktion und Autorisierung klein und sichtbar halten.

Wann sinnvoll - und wann nicht

Für wenige direkte Aufrufe kann ein Command Bus unnötige Indirektion erzeugen. Innerhalb eines Aggregats ersetzt er keine normalen Methoden. Query und Command sollten nicht unklar vermischt werden.

Risiken und typische Fehlanwendungen

Ein magischer Reflection-Bus erschwert Navigation und Fehleranalyse. Ein untypisiertes Object-API verliert Compilerhilfe. Zu viele Pipelinebehaviors können tatsächlichen Ablauf verstecken.

Abgrenzung

Ein Command Bus dispatcht Befehle an Handler. Ein Event Bus verteilt ein eingetretenes Ereignis typischerweise an mehrere unabhängige Empfänger.

Verwandte Refactorings und Muster

Command, Mediator, Chain of Responsibility, CQRS und Composition Root.

Enterprise-Java-Einordnung

REST-Adapter sendet ApproveClaimCommand. Der Bus findet ApproveClaimHandler und führt davor Authorization-, Transaction- und Audit-Behavior aus.

Teststrategie

Handler werden direkt unit-getestet. Bustests prüfen eindeutige Registrierung, Pipeline-Reihenfolge und unbekannte Commands; Architekturtests verhindern direkte Umgehung.

Deutschsprachige Fachgrundlagen

114. Extract Clock Dependency Injection / Clock

Extract Clock

Ausgangsproblem

Fachlogik liest die Systemzeit direkt. Tests werden zeitabhängig und Grenzfälle sind schwer reproduzierbar.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Injiziere java.time.Clock und leite alle Zeitentscheidungen aus genau dieser Quelle ab.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

ExpiryService.java
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Extract Clock
// Dependency Injection: Zeit wird als kontrollierbare Abhängigkeit behandelt.
public final class ExpiryService {
  private final Clock clock;
  public ExpiryService(Clock clock){ this.clock=clock; }
  public boolean expired(Instant validUntil){ if(validUntil==null) throw new IllegalArgumentException(); return !clock.instant().isBefore(validUntil); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.*; import org.junit.jupiter.api.Test;
class ExpiryServiceTest { @Test void boundary_is_deterministic(){ var now=Instant.parse("2026-07-11T10:00:00Z"); var s=new ExpiryService(Clock.fixed(now,ZoneOffset.UTC)); assertTrue(s.expired(now)); assertFalse(s.expired(now.plusSeconds(1))); } }
Einsatzgrenze: Die neue Abstraktion muss eine echte technische oder fachliche Grenze stabilisieren. Ein Port, eine Policy oder ein Kontext ohne konkrete Änderungsachse erhöht nur die Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Fachlogik liest System.currentTimeMillis, Instant.now oder LocalDate.now direkt. Tests hängen von realer Zeit ab; Zeitzonen, Stichtage und Ablaufregeln sind schwer reproduzierbar.

Erkennungsmerkmale

Flaky Tests um Mitternacht; Thread.sleep in Tests; Zeit wird mehrfach innerhalb einer Operation gelesen; Fachentscheidungen ändern sich je Testlauf.

Ziel und Nutzen

Eine injizierte Clock macht Zeit zu einer expliziten Abhängigkeit. Produktiv liefert sie die Systemzeit, Tests verwenden feste oder steuerbare Zeitpunkte. Ein Vorgang kann einen konsistenten Jetzt-Snapshot verwenden.

Sicherer Refactoring-Ablauf

  1. Direkte Zeitabfragen inventarisieren und fachliche Zeiteinheit bestimmen.
  2. java.time.Clock oder einen fachlich benannten Zeitport einführen.
  3. Clock per Konstruktor injizieren und innerhalb einer Operation einmal lesen.
  4. Tests auf feste Zeitpunkte und Zeitzonen umstellen.
  5. Sleep-basierte Tests durch kontrolliertes Fortschalten ersetzen.

Wann sinnvoll - und wann nicht

Zeitwerte, die bereits als Ereignis- oder Befehlsdaten vorliegen, sollten nicht erneut über Clock gelesen werden. Eine globale veränderliche Fake-Zeit kann wiederum Tests koppeln.

Risiken und typische Fehlanwendungen

Vermischung von Instant und lokaler Zeit erzeugt Zeitzonenfehler. Mehrfaches now() innerhalb derselben Entscheidung kann inkonsistente Grenzfälle schaffen. Clock darf nicht als beliebiger Scheduler missbraucht werden.

Abgrenzung

Clock liefert Zeit. Ein Scheduler plant Ausführung; eine Deadline beschreibt das verbleibende Zeitbudget eines Vorgangs.

Verwandte Refactorings und Muster

Deterministic Clock Boundary, Deadline, Scheduler und Immutable Snapshot.

Enterprise-Java-Einordnung

SubscriptionRenewalPolicy erhält Clock und entscheidet reproduzierbar über Kulanzfrist, Fälligkeit und Kündigungsdatum.

Teststrategie

Feste Clock prüft Grenzwerte vor, auf und nach dem Stichtag. Zeitzonentests decken Sommerzeit und lokale Geschäftstage ab.

Deutschsprachige Fachgrundlagen

115. Extract ID Generator Factory Port

Extract ID Generator

Ausgangsproblem

UUID-Erzeugung ist im Use Case versteckt. Tests können konkrete Ergebnisse nicht stabil prüfen.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Extrahiere einen ID-Generator-Port. Produktion verwendet UUID, Tests eine deterministische Sequenz.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

CustomerRegistration.java
package com.aydinsude.workbench.catalog;
// Refactoring: Extract ID Generator
// Factory Port: Identitätserzeugung wird als Abhängigkeit sichtbar.
public final class CustomerRegistration {
  public interface IdGenerator { String nextId(); }
  public record Customer(String id,String name) {}
  private final IdGenerator ids;
  public CustomerRegistration(IdGenerator ids){ this.ids=ids; }
  public Customer register(String name){ if(name==null||name.isBlank()) throw new IllegalArgumentException("name"); return new Customer(ids.nextId(),name.trim()); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class CustomerRegistrationTest { @Test void generated_identity_is_testable(){ var s=new CustomerRegistration(()->"C-100"); assertEquals(new CustomerRegistration.Customer("C-100","Ada"),s.register(" Ada ")); } }
Einsatzgrenze: Die neue Abstraktion muss eine echte technische oder fachliche Grenze stabilisieren. Ein Port, eine Policy oder ein Kontext ohne konkrete Änderungsachse erhöht nur die Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Fach- oder Anwendungscode erzeugt IDs direkt über UUID.randomUUID, Datenbanksequenzen oder statische Utilities. Tests können erwartete Referenzen nicht kontrollieren und unterschiedliche ID-Regeln sind verteilt.

Erkennungsmerkmale

Zufällige IDs erschweren Assertions; Aggregate kennen Datenbanksequenzen; Formate werden per Stringkonkatenation gebaut; Mandanten oder Regionen benötigen unterschiedliche Präfixe.

Ziel und Nutzen

Ein ID Generator Port macht Identitätserzeugung explizit und austauschbar. Produktive Adapter können UUID, ULID, Sequenz oder externe Nummernkreise verwenden; Tests liefern deterministische Werte.

Sicherer Refactoring-Ablauf

  1. Arten und Gültigkeitsbereiche der IDs erfassen.
  2. Für jede fachlich verschiedene Identität einen schmalen Generatorvertrag definieren.
  3. Direkte statische Erzeugung in einen Adapter verschieben.
  4. Generator in den erzeugenden Use Case oder die Factory injizieren.
  5. Kollision, Format und Sortierbarkeit als technische Verträge testen.

Wann sinnvoll - und wann nicht

Bereits vorhandene externe Identitäten werden nicht neu generiert. Value Objects ohne Identität benötigen keinen Generator. Ein generischer StringIdGenerator kann fachlich unterschiedliche IDs vermischen.

Risiken und typische Fehlanwendungen

Vorhersagbare IDs können Sicherheitsrisiken erzeugen. Datenbank- und Anwendungssequenzen dürfen nicht doppelt konkurrieren. ID-Format sollte nicht unnötig Fachlogik steuern.

Abgrenzung

Ein Generator erzeugt neue Identitäten. Ein Mapper oder Parser wandelt bestehende externe Identifikatoren in interne Value Objects um.

Verwandte Refactorings und Muster

Factory, Composition Root, Deterministic ID Generator und Entity Identity.

Enterprise-Java-Einordnung

ClaimIdGenerator liefert eine typisierte ClaimId. Der produktive ULID-Adapter unterstützt zeitliche Sortierung; Tests verwenden eine Sequenz aus vorbereiteten IDs.

Teststrategie

Unit-Tests injizieren feste IDs. Contract-Tests prüfen Eindeutigkeit, Format, Parallelität und gegebenenfalls monotone Sortierung.

Deutschsprachige Fachgrundlagen

116. Introduce Transaction Boundary Unit of Work / Transaction Boundary

Introduce Transaction Boundary

Ausgangsproblem

Repository-Aufrufe committen unabhängig; Teilfehler können einen fachlich inkonsistenten Zwischenstand hinterlassen.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Führe eine explizite Transaction-Boundary ein, die den gesamten Use Case atomar ausführt.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

TransferApplicationService.java
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Transaction Boundary
// Unit of Work Pattern: Ein fachlicher Use Case besitzt genau eine Commit-Grenze.
public final class TransferApplicationService {
  public interface TransactionBoundary { <T> T required(java.util.function.Supplier<T> work); }
  public interface AccountPort { long balance(String id); void save(String id,long cents); }
  private final TransactionBoundary tx; private final AccountPort accounts;
  public TransferApplicationService(TransactionBoundary tx,AccountPort accounts){ this.tx=tx; this.accounts=accounts; }
  public void transfer(String from,String to,long cents){ tx.required(()->{ long source=accounts.balance(from); if(cents<=0||source<cents) throw new IllegalArgumentException("funds"); accounts.save(from,source-cents); accounts.save(to,accounts.balance(to)+cents); return null; }); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.*; import org.junit.jupiter.api.Test;
class TransferApplicationServiceTest { @Test void whole_use_case_runs_in_one_boundary(){ var data=new HashMap<String,Long>(Map.of("A",1000L,"B",100L)); var calls=new int[1]; TransferApplicationService.TransactionBoundary tx=w->{calls[0]++;return w.get();}; TransferApplicationService.AccountPort p=new TransferApplicationService.AccountPort(){public long balance(String id){return data.get(id);} public void save(String id,long c){data.put(id,c);}}; new TransferApplicationService(tx,p).transfer("A","B",400); assertEquals(1,calls[0]); assertEquals(600L,data.get("A")); assertEquals(500L,data.get("B")); } }
Einsatzgrenze: Die neue Abstraktion muss eine echte technische oder fachliche Grenze stabilisieren. Ein Port, eine Policy oder ein Kontext ohne konkrete Änderungsachse erhöht nur die Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Transaktionen beginnen implizit in Controllern, Repositories oder über breit gesetzte Annotationen. Mehrere fachliche Schritte laufen entweder ungewollt getrennt oder halten eine Transaktion während langsamer Remoteaufrufe offen.

Erkennungsmerkmale

@Transactional auf ganzen Klassen; Repositories committen selbst; Lazy Loading bestimmt fachlichen Ablauf; Netzwerkaufrufe innerhalb einer Datenbanktransaktion; unklare Rollbackregeln.

Ziel und Nutzen

Eine explizite Transaction Boundary umfasst genau die atomare lokale Zustandsänderung eines Anwendungsfalls. Besitz, Dauer und Rollback werden sichtbar; Remoteintegration wird über geeignete Muster entkoppelt.

Sicherer Refactoring-Ablauf

  1. Die fachlich atomare lokale Änderung bestimmen.
  2. Aktuelle Start-, Commit- und Rollbackpunkte dokumentieren.
  3. Transaktionssteuerung an die Application-Service- oder Unit-of-Work-Grenze verschieben.
  4. Langsame Remoteaufrufe aus der lokalen Transaktion herauslösen.
  5. Nebenläufigkeit, Events und Outbox bewusst integrieren.

Wann sinnvoll - und wann nicht

Eine lokale Datenbanktransaktion kann keine verteilte Atomarität über mehrere Dienste garantieren. Für verteilte Abläufe sind Saga, Outbox oder Kompensation nötig.

Risiken und typische Fehlanwendungen

Zu breite Transaktionen erhöhen Sperren und Latenz; zu schmale Grenzen verletzen Invarianten. Selbstaufrufe bei Proxy-basierten Frameworks können Annotationen umgehen.

Abgrenzung

Transaction Boundary definiert lokale Atomarität. Workflow oder Saga koordinieren mehrere lokale Transaktionen über Zeit.

Verwandte Refactorings und Muster

Unit of Work, Transactional Outbox, Saga und Optimistic Locking.

Enterprise-Java-Einordnung

ApproveClaim lädt den Schadenfall, wendet die Entscheidung an, speichert Aggregate und Outbox-Eintrag in einer lokalen Transaktion. Auszahlung erfolgt später asynchron.

Teststrategie

Integrationstests prüfen Commit und Rollback sowie Outbox-Atomarität. Nebenläufigkeitstests decken Konflikte und Wiederholung ab.

Deutschsprachige Fachgrundlagen

117. Replace Synchronized Method with Lock Object Monitor Object / Lock

Replace Synchronized Method with Lock Object

Ausgangsproblem

Eine synchronisierte Methode sperrt das gesamte Objekt und hält den Monitor auch während unabhängiger Arbeit.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Nutze ein privates Lock-Objekt und halte den kritischen Abschnitt so klein wie möglich.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

InventoryReservation.java
package com.aydinsude.workbench.catalog;
import java.util.concurrent.locks.ReentrantLock;
// Refactoring: Replace Synchronized Method with Lock Object
// Monitor Object: Lock-Ownership und kritischer Abschnitt werden explizit.
public final class InventoryReservation {
  private final ReentrantLock lock=new ReentrantLock(); private int available;
  public InventoryReservation(int available){ if(available<0) throw new IllegalArgumentException(); this.available=available; }
  public boolean reserve(int quantity){ if(quantity<=0) throw new IllegalArgumentException(); lock.lock(); try { if(available<quantity) return false; available-=quantity; return true; } finally { lock.unlock(); } }
  public int available(){ lock.lock(); try{return available;} finally{lock.unlock();} }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class InventoryReservationTest { @Test void reservation_is_atomic(){ var stock=new InventoryReservation(5); assertTrue(stock.reserve(4)); assertFalse(stock.reserve(2)); assertEquals(1,stock.available()); } }
Einsatzgrenze: Die neue Abstraktion muss eine echte technische oder fachliche Grenze stabilisieren. Ein Port, eine Policy oder ein Kontext ohne konkrete Änderungsachse erhöht nur die Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Eine gesamte öffentliche Methode ist synchronized, obwohl nur ein kleiner Zustandsübergang geschützt werden muss. Langsame Berechnung oder I/O hält den Monitor und blockiert unabhängige Aufrufer.

Erkennungsmerkmale

synchronized an langen Methoden; Remote- oder Dateiaufrufe innerhalb des Monitors; keine Timeout- oder Interruptstrategie; unklare Zuordnung, welcher Zustand geschützt wird.

Ziel und Nutzen

Ein dediziertes Lock Object macht Schutzbereich und Besitz explizit. Kritische Abschnitte können verkleinert, mit try/finally abgesichert und bei Bedarf mit tryLock oder mehreren unabhängigen Locks gestaltet werden.

Sicherer Refactoring-Ablauf

  1. Gemeinsam veränderlichen Zustand und zu schützende Invariante bestimmen.
  2. Langsame oder reine Arbeit aus dem kritischen Abschnitt verschieben.
  3. Ein privates finales Lock Object einführen.
  4. Lock und Unlock strikt in try/finally kapseln.
  5. Parallelität, Fairness, Timeout und Deadlockrisiken prüfen.

Wann sinnvoll - und wann nicht

Ein Lock ist nicht automatisch die beste Lösung. Unveränderlichkeit, atomare Datenstrukturen oder Actor-/Queue-Verarbeitung können geteilter Mutation ganz vermeiden.

Risiken und typische Fehlanwendungen

Mehrere Locks erzeugen Ordnungs- und Deadlockprobleme. Öffentlich erreichbare Lockobjekte dürfen nicht verwendet werden. Das Herausziehen von Arbeit darf die atomare Invariante nicht aufbrechen.

Abgrenzung

synchronized nutzt den Objektmonitor implizit. Ein Lock Object bietet explizitere Kontrolle, beseitigt aber nicht die Notwendigkeit eines korrekten Nebenläufigkeitsmodells.

Verwandte Refactorings und Muster

Monitor Object, Bulkhead, Concurrent Repository und Immutable Snapshot.

Enterprise-Java-Einordnung

InventoryReservation schützt nur die atomare Änderung einer SKU-Menge. Datenbank- oder Eventaufrufe erfolgen nach Freigabe des lokalen Locks.

Teststrategie

Parallelitätstests synchronisieren Startpunkte und prüfen Invarianten unter Last. Timeouts helfen, Deadlocks sichtbar zu machen; Tests dürfen nicht nur zufällig auf Rennen hoffen.

Deutschsprachige Fachgrundlagen

118. Introduce Immutable Snapshot Immutable Snapshot / Copy-on-Write

Introduce Immutable Snapshot

Ausgangsproblem

Mehrere Threads lesen eine veränderliche Konfigurations-Map während eines Reloads und sehen Mischzustände.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Publiziere vollständig gebaute immutable Snapshots atomar. Leser arbeiten immer auf genau einer Version.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

RoutingConfiguration.java
package com.aydinsude.workbench.catalog;
import java.util.*; import java.util.concurrent.atomic.AtomicReference;
// Refactoring: Introduce Immutable Snapshot
// Copy-on-Write Snapshot: Leser sehen stets eine vollständige Konfigurationsversion.
public final class RoutingConfiguration {
  public record Snapshot(long version,Map<String,String> routes){ public Snapshot { routes=Map.copyOf(routes); } }
  private final AtomicReference<Snapshot> current=new AtomicReference<>(new Snapshot(0,Map.of()));
  public Snapshot current(){ return current.get(); }
  public void replace(long version,Map<String,String> routes){ var next=new Snapshot(version,routes); current.updateAndGet(old->{ if(version<=old.version()) throw new IllegalArgumentException("version"); return next; }); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.*; import org.junit.jupiter.api.Test;
class RoutingConfigurationTest { @Test void snapshot_is_atomic_and_immutable(){ var c=new RoutingConfiguration(); var source=new HashMap<String,String>(Map.of("AT","eu")); c.replace(1,source); source.put("DE","eu"); assertEquals(Map.of("AT","eu"),c.current().routes()); assertThrows(IllegalArgumentException.class,()->c.replace(1,Map.of())); } }
Einsatzgrenze: Die neue Abstraktion muss eine echte technische oder fachliche Grenze stabilisieren. Ein Port, eine Policy oder ein Kontext ohne konkrete Änderungsachse erhöht nur die Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Mehrere Threads oder Verarbeitungsschritte lesen eine Konfiguration oder einen Zustand, während dieser schrittweise verändert wird. Leser können eine Mischung aus alten und neuen Werten beobachten.

Erkennungsmerkmale

Viele volatile Einzelfelder; Getter lesen mutable Maps; Reload verändert Collections in-place; Tests sehen inkonsistente Kombinationen; defensive Kopien fehlen.

Ziel und Nutzen

Ein Immutable Snapshot bündelt einen konsistenten Stand in einem unveränderlichen Objekt. Aktualisierung erzeugt vollständig einen neuen Snapshot und veröffentlicht ihn atomar; Leser benötigen keine feingranularen Locks.

Sicherer Refactoring-Ablauf

  1. Zusammengehörige Werte und ihre gemeinsame Konsistenzgrenze bestimmen.
  2. Einen unveränderlichen Snapshottyp mit defensiven Kopien definieren.
  3. Neue Konfiguration vollständig validieren und außerhalb des kritischen Pfads aufbauen.
  4. Snapshot über eine atomare Referenz austauschen.
  5. In-place-Mutationen und einzelne volatile Felder entfernen.

Wann sinnvoll - und wann nicht

Snapshots eignen sich für überwiegend gelesene Zustände mit vergleichsweise seltenen Aktualisierungen. Sehr große oder hochfrequent veränderte Daten können Copy-on-Write zu teuer machen.

Risiken und typische Fehlanwendungen

Flache Unveränderlichkeit reicht nicht, wenn enthaltene Collections mutable bleiben. Alte Snapshots können bei falscher Referenzhaltung Speicher binden. Versions- und Aktualisierungssemantik müssen sichtbar sein.

Abgrenzung

Ein Snapshot ist ein unveränderlicher konsistenter Stand. Ein Cache kann Werte einzeln laden und ablaufen lassen und bietet nicht automatisch gemeinsame Konsistenz.

Verwandte Refactorings und Muster

Copy-on-Write, Configuration Snapshot, Value Object und Atomic Reference.

Enterprise-Java-Einordnung

PricingConfigurationSnapshot enthält Tarifversion, Rundungsregeln und Limits. Reload baut und validiert einen neuen Snapshot, bevor er atomar für neue Requests aktiviert wird.

Teststrategie

Tests prüfen defensive Kopien, atomare Sichtbarkeit und dass Leser ausschließlich alte oder neue vollständige Versionen sehen. Parallelitätstests verwenden kontrollierte Barrieren.

Deutschsprachige Fachgrundlagen

119. Extract Cache Policy Policy / Cache-Aside

Extract Cache Policy

Ausgangsproblem

TTL, Cacheability und Schlüsselbildung sind über Controller und Adapter verteilt und widersprechen sich.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Extrahiere eine reine Cache-Policy, die Schlüssel, Cachebarkeit und Gültigkeitsdauer erklärt.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

CachePolicy.java
package com.aydinsude.workbench.catalog;
import java.time.Duration;
// Refactoring: Extract Cache Policy
// Policy Pattern: Cache-Semantik wird von Speichertechnik getrennt.
public final class CachePolicy {
  public record Decision(boolean cacheable,String key,Duration ttl) {}
  private final Duration ttl;
  public CachePolicy(Duration ttl){ if(ttl==null||ttl.isZero()||ttl.isNegative()) throw new IllegalArgumentException(); this.ttl=ttl; }
  public Decision forProduct(String productId,boolean personalized){ if(productId==null||productId.isBlank()) throw new IllegalArgumentException(); return personalized?new Decision(false,"",Duration.ZERO):new Decision(true,"product:"+productId,ttl); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.*; import org.junit.jupiter.api.Test;
class CachePolicyTest { @Test void policy_explains_cache_semantics(){ var p=new CachePolicy(Duration.ofMinutes(5)); var d=p.forProduct("P-1",false); assertTrue(d.cacheable()); assertEquals("product:P-1",d.key()); assertFalse(p.forProduct("P-1",true).cacheable()); } }
Einsatzgrenze: Die neue Abstraktion muss eine echte technische oder fachliche Grenze stabilisieren. Ein Port, eine Policy oder ein Kontext ohne konkrete Änderungsachse erhöht nur die Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Cachingentscheidungen sind als verstreute if-Abfragen, feste TTLs und direkte Map-Zugriffe in Fach- oder Adaptercode eingebettet. Schlüssel, Gültigkeit, negative Einträge und Invalidierung sind uneinheitlich.

Erkennungsmerkmale

Hardcodierte Duration-Werte; Cachezugriff in Use Cases; unterschiedliche Schlüssel für dieselben Daten; Cachefehler verändern Fachverhalten; Tests schlafen bis TTL abläuft.

Ziel und Nutzen

Eine Cache Policy macht Schlüsselbildung, TTL, Cachebarkeit, negative Ergebnisse und Invalidierungsanlässe ausdrücklich. Der Cacheadapter bleibt technisch, die Policy kann je Datentyp oder Mandant getestet werden.

Sicherer Refactoring-Ablauf

  1. Lesepfade, Aktualisierungsquellen und Konsistenzanforderungen erfassen.
  2. Cache Key und Cacheability als typisierte Regeln modellieren.
  3. TTL und negative Cachefälle begründen statt pauschal setzen.
  4. Policy vom Speicheradapter trennen und Clock injizieren.
  5. Invalidierung, Stampede-Schutz und Fallbackverhalten testen.

Wann sinnvoll - und wann nicht

Daten mit strenger Echtzeitkonsistenz oder sehr geringer Wiederverwendung sollten nicht automatisch gecacht werden. Cache darf nicht zur versteckten primären Datenquelle werden.

Risiken und typische Fehlanwendungen

Unbegrenzte oder falsche Schlüssel führen zu Speicherproblemen und Datenlecks zwischen Mandanten. Lange TTL versteckt Aktualisierungen; Cache Stampede kann Backends überlasten.

Abgrenzung

Die Policy entscheidet, was und wie gecacht wird. Cache-Aside beschreibt den Ablauf, wie Anwendung und Cache beim Lesen zusammenarbeiten.

Verwandte Refactorings und Muster

Cache-Aside, Read-Through Cache, Immutable Snapshot und Single Flight.

Enterprise-Java-Einordnung

ProductCatalogCachePolicy definiert Schlüssel aus Tenant und ProductId, kurze negative TTL und Invalidierung bei ProductChanged. Der Redis-Adapter setzt nur die technische Speicherung.

Teststrategie

Fake Clock prüft Ablauf ohne Wartezeit. Tests decken Schlüsselisolation, negative Ergebnisse, Invalidierung und Backendfehler ab.

Deutschsprachige Fachgrundlagen

120. Introduce Circuit Breaker Policy Circuit Breaker / State Machine

Introduce Circuit Breaker Policy

Ausgangsproblem

Fehlerzähler, Öffnungszeit und Wiederanlauf sind direkt in einem HTTP-Client vermischt.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Extrahiere den Zustandsautomaten als unabhängige Policy. Der Adapter meldet Erfolg oder Fehler und fragt vor dem Aufruf nach Erlaubnis.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

CircuitBreakerPolicy.java
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Introduce Circuit Breaker Policy
// Circuit Breaker Pattern: Ausfallzustand und Wiederanlauf sind explizit modelliert.
public final class CircuitBreakerPolicy {
  public enum State { CLOSED, OPEN }
  private final int threshold; private final Duration coolDown; private int failures; private Instant openedAt; private State state=State.CLOSED;
  public CircuitBreakerPolicy(int threshold,Duration coolDown){ if(threshold<1||coolDown==null||coolDown.isNegative()||coolDown.isZero()) throw new IllegalArgumentException(); this.threshold=threshold; this.coolDown=coolDown; }
  public synchronized boolean allow(Instant now){ if(state==State.CLOSED) return true; if(!now.isBefore(openedAt.plus(coolDown))){ state=State.CLOSED; failures=0; openedAt=null; return true; } return false; }
  public synchronized void success(){ failures=0; state=State.CLOSED; openedAt=null; }
  public synchronized void failure(Instant now){ if(++failures>=threshold){ state=State.OPEN; openedAt=now; } }
  public synchronized State state(){ return state; }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.*; import org.junit.jupiter.api.Test;
class CircuitBreakerPolicyTest { @Test void breaker_opens_and_recovers(){ var p=new CircuitBreakerPolicy(2,Duration.ofSeconds(30)); var now=Instant.EPOCH; p.failure(now); p.failure(now); assertEquals(CircuitBreakerPolicy.State.OPEN,p.state()); assertFalse(p.allow(now.plusSeconds(10))); assertTrue(p.allow(now.plusSeconds(30))); } }
Einsatzgrenze: Die neue Abstraktion muss eine echte technische oder fachliche Grenze stabilisieren. Ein Port, eine Policy oder ein Kontext ohne konkrete Änderungsachse erhöht nur die Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Ein Client ruft einen dauerhaft gestörten Remotedienst immer wieder auf. Jeder Aufruf wartet auf Timeout, bindet Ressourcen und verstärkt den Ausfall trotz bereits eindeutiger Fehlersignale.

Erkennungsmerkmale

Viele aufeinanderfolgende Timeouts; Retry auf jedem Request; Thread- oder Connection-Pools laufen voll; keine schnelle Ablehnung; Erholung wird nicht kontrolliert getestet.

Ziel und Nutzen

Eine Circuit Breaker Policy verfolgt relevante Fehler und wechselt zwischen geschlossen, offen und halb offen. Bei anhaltender Störung scheitern Aufrufe schnell; nach einer Wartezeit prüfen begrenzte Probeaufrufe die Erholung.

Sicherer Refactoring-Ablauf

  1. Zu schützende Remoteabhängigkeit und relevante Fehler definieren.
  2. Schwellenwert, Beobachtungsfenster und Öffnungsdauer festlegen.
  3. Policy außerhalb des Fachmodells am Clientadapter platzieren.
  4. Halboffene Probe und paralleles Verhalten ausdrücklich implementieren.
  5. Metriken, Fallback und Zusammenspiel mit Retry und Deadline testen.

Wann sinnvoll - und wann nicht

Circuit Breaker ist nicht für lokale Validierungsfehler oder einzelne fachliche Ablehnungen gedacht. Bei sehr seltenen Aufrufen liefert ein statistischer Schwellenwert möglicherweise wenig Nutzen.

Risiken und typische Fehlanwendungen

Falsch kombinierte Retries verzögern das Öffnen oder erzeugen Stürme. Ein globaler Breaker kann unabhängige Mandanten oder Endpunkte unnötig koppeln. Fallbacks dürfen keine falschen Daten versprechen.

Abgrenzung

Retry versucht einen wahrscheinlich vorübergehenden Fehler erneut. Circuit Breaker verhindert weitere Versuche, wenn Erfolg kurzfristig unwahrscheinlich ist.

Verwandte Refactorings und Muster

Retry, Timeout, Deadline, Bulkhead und Load Shedding.

Enterprise-Java-Einordnung

CreditBureauGateway besitzt einen eigenen Breaker pro Provider. Bei offenem Zustand wird sofort ManualReview ausgelöst; halb offene Probeaufrufe sind streng begrenzt.

Teststrategie

Deterministische State-Machine-Tests prüfen Übergänge, Zeitfenster und Probeverhalten. Integrationstests simulieren Timeout, Erholung und parallele Requests.

Deutschsprachige Fachgrundlagen

Kapitel 13 Impliziten Kontext durch explizite Verträge ersetzen10 Refactorings

Explizite Laufzeitkontexte, Zeitbudgets, Lastschutz, Serialisierungsgrenzen und betriebliche Ports mit kompilierbarem Java-21-Zielcode.

121. Replace Implicit Context with Explicit Context Explicit Context / Context Object

Replace Implicit Context with Explicit Context

Ausgangsproblem

Versteckte ThreadLocals und globale Requestdaten machen Tests und asynchrone Übergaben unzuverlässig.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Führe einen unveränderlichen RequestContext ein und übergib ihn explizit über Use-Case- und Adaptergrenzen.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

ExplicitRequestContext.java
package com.aydinsude.workbench.catalog;
import java.util.Locale;
// Refactoring: Replace Implicit Context with Explicit Context
// Context Object Pattern: Requestdaten werden sichtbar und testbar durchgereicht.
public record ExplicitRequestContext(String correlationId, String tenantId, Locale locale) {
  public ExplicitRequestContext {
    if (correlationId == null || correlationId.isBlank()) throw new IllegalArgumentException("correlationId");
    if (tenantId == null || tenantId.isBlank()) throw new IllegalArgumentException("tenantId");
    if (locale == null) throw new IllegalArgumentException("locale");
  }
  public String scopedKey(String businessId) {
    if (businessId == null || businessId.isBlank()) throw new IllegalArgumentException("businessId");
    return tenantId + ":" + businessId;
  }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.Locale; import org.junit.jupiter.api.Test;
class ExplicitRequestContextTest { @Test void context_is_explicit_and_validated(){ var c=new ExplicitRequestContext("corr-1","tenant-a",Locale.GERMAN); assertEquals("tenant-a:O-1",c.scopedKey("O-1")); assertThrows(IllegalArgumentException.class,()->new ExplicitRequestContext("","t",Locale.GERMAN)); } }
Einsatzgrenze: Die neue Abstraktion ist sinnvoll, wenn sie eine reale Änderungs-, Last- oder Integrationsgrenze sichtbar macht. Ohne konkrete Änderungsachse wäre sie unnötige Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Anwendungslogik liest Mandant, Benutzer, Korrelation oder Locale aus ThreadLocal, statischen Helfern oder Framework-Kontexten. Die Abhängigkeit ist in Signaturen unsichtbar und bricht bei asynchroner Verarbeitung, Tests und parallelen Requests leicht auf.

Erkennungsmerkmale

Methoden liefern unterschiedliche Ergebnisse abhängig von globalem Zustand; Tests müssen ThreadLocal setzen; Kontext geht beim Executor-Wechsel verloren; Fachcode importiert HTTP- oder Security-APIs; Logging und Berechtigungen lesen dieselben Daten auf verschiedenen Wegen.

Ziel und Nutzen

Ein unveränderlicher Context Object macht die tatsächlich benötigten Laufzeitinformationen explizit. Aufrufer sehen die Abhängigkeit, asynchrone Übergaben werden kontrollierbar und innere Schichten bleiben frei von Containerdetails.

Sicherer Refactoring-Ablauf

  1. Benötigte Kontextwerte und ihre fachliche Bedeutung inventarisieren.
  2. Einen kleinen unveränderlichen Kontexttyp mit typisierten Werten einführen.
  3. Am äußeren Adapter den Framework-Kontext genau einmal übersetzen.
  4. Kontext explizit durch Use-Case- und Port-Signaturen führen.
  5. ThreadLocal- und statische Zugriffe schrittweise entfernen und mit Paralleltests absichern.

Wann sinnvoll - und wann nicht

Sinnvoll bei Request-, Tenant-, Security- oder Correlation-Kontext, der mehrere Schichten durchläuft. Für rein lokale Hilfsdaten genügt ein normaler Parameter; ein universeller God Context wäre kontraproduktiv.

Risiken und typische Fehlanwendungen

Ein überladener Kontext wird zum versteckten Service Locator. Mutable Maps, optionale beliebige Werte oder Domänenservices im Kontext verwischen Verantwortlichkeiten. Datenschutzrelevante Daten dürfen nicht unkontrolliert geloggt werden.

Abgrenzung

Ein Context Object transportiert Laufzeitinformationen. Dependency Injection liefert langlebige Abhängigkeiten; ein Command oder Request-DTO transportiert fachliche Eingabedaten.

Verwandte Refactorings und Muster

Context Object, Parameter Object, Security Context Snapshot, Correlation Context und Explicit Dependencies.

Enterprise-Java-Einordnung

Ein Claims-Use-Case erhält RequestContext mit TenantId, PrincipalId und CorrelationId. Der REST-Adapter erstellt ihn aus Jakarta Security beziehungsweise Spring Security; Domäne und Anwendung kennen nur eigene Typen.

Teststrategie

Unit-Tests erzeugen Kontexte direkt. Parallel- und Executor-Tests prüfen Isolation und Weitergabe; Architekturtests verhindern erneute Framework-Imports in inneren Paketen.

Deutschsprachige Fachgrundlagen

122. Introduce Deadline Deadline / Time Budget

Introduce Deadline

Ausgangsproblem

Timeoutwerte werden mehrfach als Millisekunden übergeben und verlieren ihre fachliche Bedeutung.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Modelliere eine absolute Deadline mit Clock-basierter Restzeit und eindeutiger Ablaufprüfung.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

Deadline.java
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Introduce Deadline
// Value Object: Ein absolutes Zeitbudget ersetzt verstreute Timeout-Zahlen.
public record Deadline(Instant expiresAt) {
  public Deadline { if (expiresAt == null) throw new IllegalArgumentException("expiresAt"); }
  public static Deadline after(Duration duration, Clock clock){
    if(duration==null||duration.isNegative()||duration.isZero()||clock==null) throw new IllegalArgumentException();
    return new Deadline(clock.instant().plus(duration));
  }
  public boolean expired(Clock clock){ return !clock.instant().isBefore(expiresAt); }
  public Duration remaining(Clock clock){ var d=Duration.between(clock.instant(),expiresAt); return d.isNegative()?Duration.ZERO:d; }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.*; import org.junit.jupiter.api.Test;
class DeadlineTest { @Test void deadline_exposes_remaining_budget(){ var clock=Clock.fixed(Instant.EPOCH,ZoneOffset.UTC); var d=Deadline.after(Duration.ofSeconds(5),clock); assertEquals(Duration.ofSeconds(5),d.remaining(clock)); assertFalse(d.expired(clock)); assertTrue(d.expired(Clock.fixed(Instant.EPOCH.plusSeconds(5),ZoneOffset.UTC))); } }
Einsatzgrenze: Die neue Abstraktion ist sinnvoll, wenn sie eine reale Änderungs-, Last- oder Integrationsgrenze sichtbar macht. Ohne konkrete Änderungsachse wäre sie unnötige Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Mehrere entfernte Aufrufe besitzen jeweils eigene Timeouts, ohne ein gemeinsames Endzeitbudget zu berücksichtigen. Eine Anfrage kann dadurch lange weiterarbeiten, obwohl ihr fachlich zulässiges Zeitfenster bereits überschritten ist.

Erkennungsmerkmale

Jeder Adapter setzt einen festen Timeout; Retries addieren unkontrolliert Laufzeit; nachgelagerte Arbeit startet kurz vor Ablauf; Abbruchentscheidungen basieren auf verstreuten Uhrzeitvergleichen; Tests hängen von realer Zeit ab.

Ziel und Nutzen

Ein Deadline Value Object beschreibt den spätesten zulässigen Abschlusszeitpunkt. Jede Stufe kann das verbleibende Budget berechnen und entscheiden, ob ein weiterer Versuch noch sinnvoll ist.

Sicherer Refactoring-Ablauf

  1. Fachliches Gesamtzeitbudget am Eintrittspunkt bestimmen.
  2. Clock und Deadline als eigene Typen einführen.
  3. Verbleibende Dauer statt ursprünglichem Timeout an Adapter übergeben.
  4. Vor teuren Schritten und Retries Budget prüfen.
  5. Timeout-, Abbruch- und Fallbackpfade deterministisch testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei Orchestrierungen, interaktiven APIs und verteilten Workflows. Für einen einzelnen lokalen Aufruf reicht meist ein einfacher Timeout.

Risiken und typische Fehlanwendungen

Deadlines dürfen nicht mit absoluten Systemzeiten unterschiedlicher Hosts vermischt werden. Negatives Restbudget muss eindeutig behandelt werden. Zu aggressive Budgets erzeugen unnötige Abbrüche; fehlende Reserve verhindert geordnetes Aufräumen.

Abgrenzung

Ein Timeout begrenzt einen einzelnen Schritt. Eine Deadline begrenzt den gesamten Ablauf und wird durch die Kette weitergegeben.

Verwandte Refactorings und Muster

Deadline, Time Budget, Clock Port, Cancellation Token, Retry Policy und Circuit Breaker.

Enterprise-Java-Einordnung

Eine Zahlungsautorisierung erhält eine Deadline von zwei Sekunden. Fraud-Check, Provider-Call und Audit bekommen jeweils nur das verbleibende Budget; ein Retry startet nur, wenn noch genügend Reserve besteht.

Teststrategie

Fake Clock und tabellarische Szenarien prüfen Restbudget, Grenzzeitpunkt und Retry-Entscheidung. Integrationstests prüfen Abbruch und Ressourcenfreigabe.

Deutschsprachige Fachgrundlagen

123. Extract Bulkhead Policy Bulkhead / Capacity Policy

Extract Bulkhead Policy

Ausgangsproblem

Ein Adapter verwaltet Semaphore, Warteschlange und Ablehnung direkt neben dem Remote-Aufruf.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Extrahiere eine kleine Kapazitäts-Policy, die Slots reserviert und sicher freigibt.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

BulkheadPolicy.java
package com.aydinsude.workbench.catalog;
import java.util.concurrent.Semaphore;
// Refactoring: Extract Bulkhead Policy
// Bulkhead Pattern: Begrenzte Parallelität isoliert knappe Ressourcen.
public final class BulkheadPolicy {
  private final Semaphore slots;
  public BulkheadPolicy(int capacity){ if(capacity<1) throw new IllegalArgumentException("capacity"); slots=new Semaphore(capacity,true); }
  public Lease tryAcquire(){ return slots.tryAcquire()?new Lease(slots):null; }
  public int available(){ return slots.availablePermits(); }
  public static final class Lease implements AutoCloseable {
    private final Semaphore semaphore; private boolean closed;
    private Lease(Semaphore semaphore){ this.semaphore=semaphore; }
    @Override public synchronized void close(){ if(!closed){ closed=true; semaphore.release(); } }
  }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class BulkheadPolicyTest { @Test void capacity_is_released_exactly_once(){ var p=new BulkheadPolicy(1); var lease=p.tryAcquire(); assertNotNull(lease); assertNull(p.tryAcquire()); lease.close(); lease.close(); assertEquals(1,p.available()); } }
Einsatzgrenze: Die neue Abstraktion ist sinnvoll, wenn sie eine reale Änderungs-, Last- oder Integrationsgrenze sichtbar macht. Ohne konkrete Änderungsachse wäre sie unnötige Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Alle externen Aufrufe teilen denselben Thread-, Connection- oder Queue-Pool. Eine langsame oder fehlerhafte Integration verbraucht dadurch sämtliche Kapazität und blockiert fachlich unabhängige Funktionen.

Erkennungsmerkmale

Ein gemeinsamer Executor bedient alle Provider; Warteschlangen wachsen unbeschränkt; ein Ausfall macht das gesamte System langsam; Kapazitätszahlen stehen direkt im Adapter; Lasttests zeigen Kaskadeneffekte.

Ziel und Nutzen

Eine Bulkhead Policy teilt knappe Ressourcen bewusst nach Abhängigkeit oder Geschäftsfunktion. Fehler bleiben in einem abgegrenzten Bereich und wichtige Pfade behalten reservierte Kapazität.

Sicherer Refactoring-Ablauf

  1. Gemeinsam genutzte Engpassressourcen und kritische Pfade messen.
  2. Isolationsgrenzen und Kapazitätsbudgets fachlich benennen.
  3. Policy von Executor-, Pool- oder Semaphore-Implementierung trennen.
  4. Überlastreaktion und Warteschlangenlimit explizit modellieren.
  5. Last- und Ausfalltests für jede Partition durchführen.

Wann sinnvoll - und wann nicht

Sinnvoll bei externen Integrationen, Mandanten oder Funktionen mit stark unterschiedlichem Risiko. Bei sehr kleiner Last kann die zusätzliche Partitionierung unnötig Kapazität verschwenden.

Risiken und typische Fehlanwendungen

Zu viele kleine Bulkheads reduzieren Gesamtauslastung. Unbegrenzte Warteschlangen umgehen die Isolation. Statische Größen ohne Messung altern schnell; Ablehnung braucht eine fachlich verständliche Reaktion.

Abgrenzung

Bulkhead isoliert Kapazität. Rate Limiter begrenzt Zulassungsrate; Circuit Breaker stoppt aussichtslose Aufrufe; Load Shedding lehnt unter Überlast gezielt Arbeit ab.

Verwandte Refactorings und Muster

Bulkhead, Capacity Policy, Semaphore Isolation, Executor Ownership und Load Shedding.

Enterprise-Java-Einordnung

Adressprüfung und Zahlungsprovider erhalten getrennte Executor- und Connection-Budgets. Ein langsamer Adressdienst darf Checkout-Zahlungen nicht blockieren.

Teststrategie

Lasttests sättigen gezielt einen Bulkhead und prüfen, dass andere Pfade ihre Latenz halten. Unit-Tests prüfen Zulassung, Queue-Limit und Ablehnungsgrund.

Deutschsprachige Fachgrundlagen

124. Extract Rate Limiter Policy Token Bucket / Rate Limiter

Extract Rate Limiter Policy

Ausgangsproblem

Rate-Limit-Zähler sind mit Controller- und HTTP-Code vermischt und kaum deterministisch testbar.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Extrahiere eine tokenbasierte Policy mit expliziter Clock und klarer Entscheidung.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

RateLimiterPolicy.java
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Extract Rate Limiter Policy
// Token Bucket Pattern: Durchsatzregeln werden vom Transport getrennt.
public final class RateLimiterPolicy {
  private final int capacity; private final Duration refillEvery; private int tokens; private Instant lastRefill;
  public RateLimiterPolicy(int capacity, Duration refillEvery, Clock clock){
    if(capacity<1||refillEvery==null||refillEvery.isZero()||refillEvery.isNegative()||clock==null) throw new IllegalArgumentException();
    this.capacity=capacity; this.refillEvery=refillEvery; this.tokens=capacity; this.lastRefill=clock.instant();
  }
  public synchronized boolean allow(Clock clock){
    var elapsed=Duration.between(lastRefill,clock.instant()); long refill=elapsed.dividedBy(refillEvery);
    if(refill>0){ tokens=(int)Math.min(capacity,tokens+refill); lastRefill=lastRefill.plus(refillEvery.multipliedBy(refill)); }
    if(tokens==0) return false; tokens--; return true;
  }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.*; import org.junit.jupiter.api.Test;
class RateLimiterPolicyTest { @Test void tokens_refill_deterministically(){ var base=Instant.EPOCH; var p=new RateLimiterPolicy(2,Duration.ofSeconds(10),Clock.fixed(base,ZoneOffset.UTC)); var c=Clock.fixed(base,ZoneOffset.UTC); assertTrue(p.allow(c)); assertTrue(p.allow(c)); assertFalse(p.allow(c)); assertTrue(p.allow(Clock.fixed(base.plusSeconds(10),ZoneOffset.UTC))); } }
Einsatzgrenze: Die neue Abstraktion ist sinnvoll, wenn sie eine reale Änderungs-, Last- oder Integrationsgrenze sichtbar macht. Ohne konkrete Änderungsachse wäre sie unnötige Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Zugriffslimits sind als Zähler, Zeitvergleiche und magische Zahlen in Controllern oder Clients verteilt. Unterschiedliche Instanzen treffen widersprüchliche Entscheidungen und der fachliche Grund einer Ablehnung bleibt unsichtbar.

Erkennungsmerkmale

if-Blöcke prüfen Sekundenfenster; Limits sind hart codiert; Retry-After wird falsch berechnet; Tests schlafen real; Benutzer-, Mandanten- und Providerlimits sind vermischt.

Ziel und Nutzen

Eine Rate Limiter Policy kapselt Schlüssel, Kapazität, Nachfüllrate und Entscheidung. Die technische Speicherung von Token oder Zählern bleibt austauschbar und Aufrufer erhalten ein typisiertes Ergebnis.

Sicherer Refactoring-Ablauf

  1. Zu schützende Ressource und fairen Limit-Schlüssel bestimmen.
  2. Algorithmus und Zeitsemantik explizit auswählen.
  3. Zulassungsentscheidung als Value/Decision Object modellieren.
  4. Speicheradapter und verteilte Atomizität hinter einen Port legen.
  5. Grenz-, Burst- und Mehrinstanzszenarien testen.

Wann sinnvoll - und wann nicht

Sinnvoll zum Schutz knapper Dienste, fairer Mandantennutzung und externer Quoten. Kein Ersatz für fachliche Autorisierung oder Kapazitätsplanung.

Risiken und typische Fehlanwendungen

Ein falscher Schlüssel benachteiligt Nutzer. Lokale In-Memory-Zähler sind in Clustern inkonsistent. Starre Limits ohne Burst-Modell erzeugen unnötige Ablehnung; Uhrdrift und atomare Updates müssen berücksichtigt werden.

Abgrenzung

Rate Limiting steuert Häufigkeit. Throttling kann Verarbeitung verlangsamen; Admission Control entscheidet zusätzlich anhand aktueller Systemressourcen.

Verwandte Refactorings und Muster

Token Bucket, Leaky Bucket, Rate Limiter Policy, Retry-After und Admission Control.

Enterprise-Java-Einordnung

Ein Partner-API-Limit wird je PartnerId und Operation ausgewertet. Der Adapter liefert Allowed oder Rejected(retryAfter), statt HTTP-Details in die Anwendung zu ziehen.

Teststrategie

Deterministische Clock-Tests prüfen Nachfüllung, Burst und genaue Grenze. Paralleltests prüfen Atomizität; Contract-Tests sichern Retry-After.

Deutschsprachige Fachgrundlagen

125. Introduce Backpressure Policy Backpressure / Overflow Policy

Introduce Backpressure Policy

Ausgangsproblem

Produzenten schreiben unbegrenzt in Warteschlangen; Überlastung wird erst als Speicherproblem sichtbar.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Modelliere Annahme, Ablehnung und verbleibende Kapazität als explizite Policy.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

BackpressurePolicy.java
package com.aydinsude.workbench.catalog;
import java.util.concurrent.atomic.AtomicInteger;
// Refactoring: Introduce Backpressure Policy
// Backpressure Pattern: Überlastung wird als fachlich sichtbare Entscheidung behandelt.
public final class BackpressurePolicy {
  public enum Decision { ACCEPT, REJECT }
  private final int maximum; private final AtomicInteger inFlight=new AtomicInteger();
  public BackpressurePolicy(int maximum){ if(maximum<1) throw new IllegalArgumentException(); this.maximum=maximum; }
  public Decision tryEnter(){ while(true){ int current=inFlight.get(); if(current>=maximum) return Decision.REJECT; if(inFlight.compareAndSet(current,current+1)) return Decision.ACCEPT; } }
  public void leave(){ int next=inFlight.decrementAndGet(); if(next<0){ inFlight.incrementAndGet(); throw new IllegalStateException("no work in flight"); } }
  public int inFlight(){ return inFlight.get(); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class BackpressurePolicyTest { @Test void overload_is_explicit(){ var p=new BackpressurePolicy(1); assertEquals(BackpressurePolicy.Decision.ACCEPT,p.tryEnter()); assertEquals(BackpressurePolicy.Decision.REJECT,p.tryEnter()); p.leave(); assertEquals(0,p.inFlight()); } }
Einsatzgrenze: Die neue Abstraktion ist sinnvoll, wenn sie eine reale Änderungs-, Last- oder Integrationsgrenze sichtbar macht. Ohne konkrete Änderungsachse wäre sie unnötige Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Produzenten erzeugen Arbeit schneller als Konsumenten sie verarbeiten können. Warteschlangen, Speicher und Latenz wachsen unkontrolliert, weil keine explizite Regel für Überlast, Puffer und Verlust existiert.

Erkennungsmerkmale

Unbegrenzte Queues; Speicheranstieg unter Last; Timeouts weit hinter dem Eintritt; pauschales Drop oder blockierendes put; Reactive Streams ohne dokumentierte Nachfrage- und Overflow-Strategie.

Ziel und Nutzen

Eine Backpressure Policy macht Kapazität, Puffergrenze und Überlastreaktion zu einer bewussten Entscheidung: blockieren, ablehnen, zusammenfassen, älteste/neuste Elemente verwerfen oder auf persistenten Kanal ausweichen.

Sicherer Refactoring-Ablauf

  1. Produzent, Konsument und maximale sinnvolle Verzögerung messen.
  2. Puffergröße und fachlich zulässige Verluststrategie festlegen.
  3. Overflow-Entscheidung typisieren und von Framework-Operatoren trennen.
  4. Metriken, Alarmgrenzen und Recovery-Verhalten ergänzen.
  5. Dauerlast-, Burst- und langsamer-Konsument-Szenarien testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei Streams, Batch-Zufuhr und asynchronen Pipelines. Bei strikt synchroner Request-Response-Kommunikation reichen oft Deadline und Admission Control.

Risiken und typische Fehlanwendungen

Blindes Puffern verschiebt den Ausfall. Datenverlust darf nicht implizit sein. Blockierung kann Deadlocks erzeugen; pauschales Drop verletzt Fachanforderungen. Policy muss zur Datenklasse passen.

Abgrenzung

Backpressure reguliert den Fluss entlang einer Pipeline. Rate Limiting begrenzt Eintritt; Load Shedding verwirft Arbeit zum Schutz des Systems.

Verwandte Refactorings und Muster

Backpressure, Overflow Policy, Bounded Queue, Reactive Streams und Load Shedding.

Enterprise-Java-Einordnung

Telemetrieereignisse dürfen bei Überlast zusammengefasst werden, Finanzbuchungen dagegen werden persistent gepuffert und der Produzent erhält ein eindeutiges Nack.

Teststrategie

Lasttests prüfen Queuegrenze und stabile Speichernutzung. Property-Tests sichern, welche Elemente bei jeder Strategie erhalten bleiben.

Deutschsprachige Fachgrundlagen

126. Extract Serialization Boundary Serializer / Anti-Corruption Layer

Extract Serialization Boundary

Ausgangsproblem

Domainobjekte werden direkt mit Transportformaten und Feldnamen gekoppelt.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Extrahiere einen Serializer-Port und ein kanonisches Transfermodell an der Systemgrenze.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

SerializationBoundary.java
package com.aydinsude.workbench.catalog;
import java.nio.charset.StandardCharsets;
// Refactoring: Extract Serialization Boundary
// Anti-Corruption Layer: Transportformat und Domänenmodell bleiben getrennt.
public final class SerializationBoundary {
  public record OrderMessage(String orderId, long amountCents) { public OrderMessage { if(orderId==null||orderId.isBlank()||amountCents<0) throw new IllegalArgumentException(); } }
  public interface Serializer<T> { byte[] serialize(T value); }
  public static final class CanonicalOrderSerializer implements Serializer<OrderMessage> {
    public byte[] serialize(OrderMessage value){ return (value.orderId()+"|"+value.amountCents()).getBytes(StandardCharsets.UTF_8); }
  }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test;
class SerializationBoundaryTest { @Test void canonical_format_is_isolated(){ var bytes=new SerializationBoundary.CanonicalOrderSerializer().serialize(new SerializationBoundary.OrderMessage("O-1",1250)); assertEquals("O-1|1250",new String(bytes,StandardCharsets.UTF_8)); } }
Einsatzgrenze: Die neue Abstraktion ist sinnvoll, wenn sie eine reale Änderungs-, Last- oder Integrationsgrenze sichtbar macht. Ohne konkrete Änderungsachse wäre sie unnötige Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

JSON-, XML-, Avro- oder proprietäre Serialisierung ist direkt in Fachservices verteilt. Innere Modelle tragen Frameworkannotationen, Versionsfragen vermischen sich mit Fachlogik und externe Formate können die Domäne unkontrolliert verändern.

Erkennungsmerkmale

ObjectMapper-Aufrufe in Use Cases; Domänenobjekte mit Transportannotationen; Feldnamen werden in Fachcode ausgewertet; verschiedene Adapter serialisieren dieselben Typen unterschiedlich; Kompatibilitätsfehler treten spät auf.

Ziel und Nutzen

Eine Serialization Boundary kapselt Format, Schema, Version und technische Fehler. Innere Schichten arbeiten mit fachlichen Typen; Adapter übersetzen über explizite Verträge.

Sicherer Refactoring-Ablauf

  1. Transportvertrag und Domänenmodell getrennt inventarisieren.
  2. Serializer-Port mit richtungsbezogenen Methoden definieren.
  3. DTO/Envelope und Mapper an der Grenze einführen.
  4. Frameworkkonfiguration und Fehlerübersetzung in den Adapter verschieben.
  5. Golden-Master-, Round-Trip- und Kompatibilitätstests ergänzen.

Wann sinnvoll - und wann nicht

Sinnvoll sobald Formate öffentlich, versioniert oder mehrfach vorhanden sind. Für einen rein internen triviale Record kann direkte Frameworkbindung akzeptabel sein, wenn die Kopplung bewusst ist.

Risiken und typische Fehlanwendungen

Ein generischer Serializer<Object> versteckt Verträge. Automatische polymorphe Typinformationen können Sicherheitsrisiken erzeugen. Round-Trip-Gleichheit ersetzt keine Abwärtskompatibilität.

Abgrenzung

Serialisierung bildet Daten in ein Format ab. Ein Mapper trennt Modelle; ein Anti-Corruption Layer schützt zusätzlich Semantik und Verhalten.

Verwandte Refactorings und Muster

Serializer Port, Data Mapper, Event Envelope, Schema Compatibility und Anti-Corruption Layer.

Enterprise-Java-Einordnung

IntegrationEventSerializer erzeugt versionierte JSON-Envelopes aus internen Domain Events. Unbekannte Versionen werden als typisierte Incompatibility gemeldet.

Teststrategie

Golden Master und Schema-Contract-Tests prüfen Feldnamen, Versionen, unbekannte Felder und alte Fixtures; Fuzzing deckt ungültige Eingaben ab.

Deutschsprachige Fachgrundlagen

127. Replace Shared Mutable Collection with Concurrent Repository Repository / Concurrent Map

Replace Shared Mutable Collection with Concurrent Repository

Ausgangsproblem

Mehrere Threads verändern dieselbe HashMap; Synchronisation ist über Aufrufer verteilt.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Kapsle atomare Operationen hinter einem thread-sicheren Repository-Vertrag.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

ConcurrentCustomerRepository.java
package com.aydinsude.workbench.catalog;
import java.util.*; import java.util.concurrent.ConcurrentHashMap;
// Refactoring: Replace Shared Mutable Collection with Concurrent Repository
// Repository Pattern: Atomare Zugriffe liegen an einer einzigen Grenze.
public final class ConcurrentCustomerRepository {
  public record Customer(String id,String name){ public Customer { if(id==null||id.isBlank()||name==null||name.isBlank()) throw new IllegalArgumentException(); } }
  private final ConcurrentHashMap<String,Customer> customers=new ConcurrentHashMap<>();
  public boolean add(Customer customer){ return customers.putIfAbsent(customer.id(),customer)==null; }
  public Optional<Customer> find(String id){ return Optional.ofNullable(customers.get(id)); }
  public int size(){ return customers.size(); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class ConcurrentCustomerRepositoryTest { @Test void duplicate_insert_is_atomic(){ var r=new ConcurrentCustomerRepository(); var c=new ConcurrentCustomerRepository.Customer("C-1","Aydin"); assertTrue(r.add(c)); assertFalse(r.add(c)); assertEquals(1,r.size()); } }
Einsatzgrenze: Die neue Abstraktion ist sinnvoll, wenn sie eine reale Änderungs-, Last- oder Integrationsgrenze sichtbar macht. Ohne konkrete Änderungsachse wäre sie unnötige Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Eine gemeinsam veränderliche Map oder Liste wird von mehreren Threads direkt gelesen und verändert. Synchronisation ist verstreut, mehrstufige Operationen sind nicht atomar und Fachinvarianten können zwischen einzelnen Collection-Aufrufen brechen.

Erkennungsmerkmale

Synchronized-Blöcke an vielen Stellen; ConcurrentHashMap wird direkt überall benutzt; check-then-act-Sequenzen; Collection wird nach außen gegeben; Tests sind sporadisch instabil.

Ziel und Nutzen

Ein Concurrent Repository kapselt Datenstruktur, Atomizität und fachlich benannte Operationen. Aufrufer sehen keine Collection-API und können Invarianten nicht durch Teiloperationen umgehen.

Sicherer Refactoring-Ablauf

  1. Alle Zugriffs- und Änderungsmuster erfassen.
  2. Fachlich atomare Operationen benennen.
  3. Repository-Interface ohne Collection-Leak definieren.
  4. Implementierung mit compute, Locks oder immutable snapshots atomar machen.
  5. Paralleltests und linearisierbare Invarianten prüfen.

Wann sinnvoll - und wann nicht

Sinnvoll für prozesslokale gemeinsam genutzte Zustände. Bei dauerhaftem oder verteiltem Zustand ist meist ein externer Store mit passenden Konsistenzgarantien nötig.

Risiken und typische Fehlanwendungen

Concurrent Collection allein macht Sequenzen nicht atomar. Rückgabe veränderlicher Werte oder Collections öffnet die Kapselung erneut. Unbegrenztes Wachstum und fehlende Eviction bleiben Betriebsrisiken.

Abgrenzung

Ein Repository bietet fachliche Zugriffe. Eine Concurrent Map ist nur eine Datenstruktur; Unit of Work oder Datenbanktransaktionen lösen andere Konsistenzprobleme.

Verwandte Refactorings und Muster

Repository, Monitor Object, Concurrent Map, Atomic Operation und Immutable Snapshot.

Enterprise-Java-Einordnung

Eine IdempotencyRepository bietet reserve(key), complete(key,result) und release(key) als atomare Operationen statt get/put-Kombinationen.

Teststrategie

Mehrthreadtests starten konkurrierende Reservierungen und prüfen genau einen Gewinner. Invariant- und Stress-Tests laufen mit reproduzierbarem Seed.

Deutschsprachige Fachgrundlagen

128. Introduce Lease Object Lease / RAII

Introduce Lease Object

Ausgangsproblem

Reservierungen werden mit separaten acquire/release-Aufrufen verwaltet und bei Exceptions vergessen.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Führe ein AutoCloseable-Lease-Objekt ein, das Besitz und Freigabe zusammenführt.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

Lease.java
package com.aydinsude.workbench.catalog;
import java.time.*; import java.util.concurrent.atomic.AtomicBoolean;
// Refactoring: Introduce Lease Object
// Lease Pattern: Zeitlich begrenzter Besitz wird als Objekt mit Lebenszyklus modelliert.
public final class Lease implements AutoCloseable {
  private final String resourceId; private final Instant validUntil; private final Runnable release; private final AtomicBoolean closed=new AtomicBoolean();
  public Lease(String resourceId,Instant validUntil,Runnable release){ if(resourceId==null||resourceId.isBlank()||validUntil==null||release==null) throw new IllegalArgumentException(); this.resourceId=resourceId; this.validUntil=validUntil; this.release=release; }
  public String resourceId(){ return resourceId; }
  public boolean validAt(Instant now){ return !closed.get() && now.isBefore(validUntil); }
  @Override public void close(){ if(closed.compareAndSet(false,true)) release.run(); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.*; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test;
class LeaseTest { @Test void release_happens_once(){ var count=new AtomicInteger(); var lease=new Lease("R-1",Instant.EPOCH.plusSeconds(10),count::incrementAndGet); assertTrue(lease.validAt(Instant.EPOCH)); lease.close(); lease.close(); assertEquals(1,count.get()); assertFalse(lease.validAt(Instant.EPOCH)); } }
Einsatzgrenze: Die neue Abstraktion ist sinnvoll, wenn sie eine reale Änderungs-, Last- oder Integrationsgrenze sichtbar macht. Ohne konkrete Änderungsachse wäre sie unnötige Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Eine Ressource wird reserviert oder gesperrt, aber Besitz, Ablauf und Freigabe sind über mehrere primitive Werte und Methoden verteilt. Fehlerpfade vergessen Freigaben; veraltete Besitzer blockieren dauerhaft.

Erkennungsmerkmale

lock()/unlock() in verschiedenen Schichten; separate ownerId und expiresAt; Freigabe in finally fehlt; Token werden als Strings weitergereicht; Erneuerung ist unklar.

Ziel und Nutzen

Ein Lease Object bündelt Besitznachweis, Gültigkeitsdauer und kontrollierte Freigabe. Es kann AutoCloseable implementieren und verhindert, dass fremde oder abgelaufene Besitzer Änderungen ausführen.

Sicherer Refactoring-Ablauf

  1. Reservierungsprotokoll und Besitzidentität dokumentieren.
  2. LeaseId, Owner und Expiry typisieren.
  3. Acquire als Factory/Port und Release/Renew als Lease-Verhalten modellieren.
  4. try-with-resources oder expliziten Workflow einführen.
  5. Ablauf, Erneuerung, Doppel-Freigabe und Prozessabbruch testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei zeitlich begrenztem Ressourcenbesitz, verteilten Locks und Consumer-Partitionen. Für rein lexikalische lokale Ressourcen genügt oft AutoCloseable ohne Ablauf.

Risiken und typische Fehlanwendungen

Lease ist keine perfekte Exklusivitätsgarantie bei Netzwerkpartitionen. Fencing Tokens können nötig sein. Uhrabhängigkeit, zu lange Laufzeiten und stillschweigende Erneuerung erzeugen Sicherheitslücken.

Abgrenzung

Ein Lock blockiert Zugriff. Eine Lease gewährt Besitz nur für begrenzte Zeit und benötigt häufig einen Besitz- oder Fencing-Token.

Verwandte Refactorings und Muster

Lease, RAII/AutoCloseable, Distributed Lock, Fencing Token und Leader Election.

Enterprise-Java-Einordnung

Ein ExportWorker erwirbt eine PartitionLease mit monotonem FencingToken. Jede Schreiboperation prüft den Token; abgelaufene Worker können keine Ergebnisse mehr veröffentlichen.

Teststrategie

Fake-Clock-Tests prüfen Ablauf und Renew. Konkurrenztests prüfen Besitzwechsel und Fencing; Fehlerpfadtests sichern garantierte Freigabe.

Deutschsprachige Fachgrundlagen

129. Extract Feature Toggle Policy Feature Toggle / Policy

Extract Feature Toggle Policy

Ausgangsproblem

Feature-Checks sind als String-Vergleiche über Controller, Services und Adapter verteilt.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Extrahiere eine typisierte Policy, die Kontext und Rollout-Regel zentral auswertet.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

FeatureTogglePolicy.java
package com.aydinsude.workbench.catalog;
import java.util.Set;
// Refactoring: Extract Feature Toggle Policy
// Feature Toggle Pattern: Rolloutregeln werden zentral und typisiert entschieden.
public final class FeatureTogglePolicy {
  public enum Feature { NEW_PRICING, FAST_CHECKOUT }
  public record Context(String tenantId,Set<Feature> enabled){ public Context { if(tenantId==null||tenantId.isBlank()||enabled==null) throw new IllegalArgumentException(); enabled=Set.copyOf(enabled); } }
  public boolean enabled(Feature feature,Context context){ if(feature==null||context==null) throw new IllegalArgumentException(); return context.enabled().contains(feature); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.*; import org.junit.jupiter.api.Test;
class FeatureTogglePolicyTest { @Test void rollout_is_typed(){ var p=new FeatureTogglePolicy(); var c=new FeatureTogglePolicy.Context("tenant-a",Set.of(FeatureTogglePolicy.Feature.NEW_PRICING)); assertTrue(p.enabled(FeatureTogglePolicy.Feature.NEW_PRICING,c)); assertFalse(p.enabled(FeatureTogglePolicy.Feature.FAST_CHECKOUT,c)); } }
Einsatzgrenze: Die neue Abstraktion ist sinnvoll, wenn sie eine reale Änderungs-, Last- oder Integrationsgrenze sichtbar macht. Ohne konkrete Änderungsachse wäre sie unnötige Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Feature-Schalter werden als verteilte if-Abfragen mit Stringnamen gelesen. Zielgruppe, Rollout, Notfallabschaltung und Abhängigkeiten sind nicht zentral definiert; veraltete Toggles bleiben dauerhaft im Code.

Erkennungsmerkmale

config.getBoolean("new-x") in Fachklassen; unterschiedliche Defaults; Nutzerzuordnung nicht deterministisch; Toggles verschachteln sich; niemand kennt Owner oder Ablaufdatum.

Ziel und Nutzen

Eine Feature Toggle Policy kapselt fachlich benannten Schalter, Zielgruppe, stabile Kohortenzuordnung, Default, Gültigkeit und Entscheidungsevidenz. Der eigentliche Use Case erhält eine klare Entscheidung statt Konfigurationsdetails.

Sicherer Refactoring-Ablauf

  1. Toggle-Arten und Verantwortliche inventarisieren.
  2. Typisierten FeatureKey und EvaluationContext einführen.
  3. Policy und Toggle-Provider trennen.
  4. Entscheidung als Enabled/Disabled mit Grund modellieren.
  5. Ablaufdatum, Telemetrie und Cleanup-Test ergänzen.

Wann sinnvoll - und wann nicht

Sinnvoll für kontrollierte Releases, Experimente und Kill Switches. Kein Ersatz für dauerhafte fachliche Varianten oder Berechtigungen.

Risiken und typische Fehlanwendungen

Toggle-Schulden und kombinatorische Zustände wachsen schnell. Nicht deterministische Zuordnung verfälscht Tests. Sicherheitsregeln dürfen nicht allein über Release-Toggles gesteuert werden.

Abgrenzung

Feature Toggle steuert vorübergehende Aktivierung. Strategy modelliert dauerhafte Varianten; Authorization entscheidet Zugriffsrechte.

Verwandte Refactorings und Muster

Feature Toggle, Canary Cohort, Kill Switch, Branch by Abstraction und Rollout Guard.

Enterprise-Java-Einordnung

PricingV2Policy aktiviert eine neue Tarifberechnung für stabile Kundenkohorten. Beide Pfade schreiben vergleichbare Metriken; nach erfolgreichem Rollout wird der alte Pfad entfernt.

Teststrategie

Tabellentests prüfen Zielgruppen, Defaults und stabile Hash-Zuordnung. Architektur-/Cleanup-Tests markieren abgelaufene Toggles.

Deutschsprachige Fachgrundlagen

130. Introduce Health Probe Port Health Probe / Port

Introduce Health Probe Port

Ausgangsproblem

Health-Endpunkte greifen direkt auf Datenbank-Clients und technische Exceptions zu.

Sichere Transformation

  1. Bestehendes Verhalten mit einem fokussierten Characterization Test absichern.
  2. Definiere einen kleinen Probe-Port und übersetze technische Zustände in ein stabiles Ergebnis.
  3. Aufrufer schrittweise migrieren, nach jedem Schritt kompilieren und Tests ausführen.

Java-21-Zielcode

HealthProbePort.java
package com.aydinsude.workbench.catalog;
import java.time.Duration;
// Refactoring: Introduce Health Probe Port
// Ports and Adapters: Betriebszustand wird über einen stabilen Vertrag veröffentlicht.
public interface HealthProbePort {
  record Result(Status status,String detail,Duration latency){ public Result { if(status==null||detail==null||latency==null||latency.isNegative()) throw new IllegalArgumentException(); } }
  enum Status { UP, DEGRADED, DOWN }
  Result check();
  static HealthProbePort fixed(Status status,String detail,Duration latency){ return ()->new Result(status,detail,latency); }
}

JUnit-Test

JUnit-Test
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.*; import org.junit.jupiter.api.Test;
class HealthProbePortTest { @Test void probe_exposes_stable_result(){ var p=HealthProbePort.fixed(HealthProbePort.Status.UP,"database reachable",Duration.ofMillis(12)); var r=p.check(); assertEquals(HealthProbePort.Status.UP,r.status()); assertEquals(Duration.ofMillis(12),r.latency()); } }
Einsatzgrenze: Die neue Abstraktion ist sinnvoll, wenn sie eine reale Änderungs-, Last- oder Integrationsgrenze sichtbar macht. Ohne konkrete Änderungsachse wäre sie unnötige Indirektion.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Health-Endpunkte fragen Datenbank, Broker oder Fremdsysteme direkt aus Controller- oder Frameworkcode ab. Technische Details, Betriebssemantik und Fachverfügbarkeit werden vermischt; teure Checks überlasten Abhängigkeiten.

Erkennungsmerkmale

Ein /health-Handler enthält JDBC- und HTTP-Aufrufe; Liveness und Readiness sind identisch; Fehlertexte werden direkt exponiert; Tests benötigen reale Infrastruktur.

Ziel und Nutzen

Ein Health Probe Port liefert ein typisiertes, kleines Zustandsmodell. Adapter implementieren günstige technische Prüfungen; die Komposition entscheidet getrennt über Liveness, Readiness und Degraded.

Sicherer Refactoring-Ablauf

  1. Betriebsfragen Liveness, Readiness und fachliche Degradation trennen.
  2. HealthStatus und ComponentCheck typisieren.
  3. Ports für benötigte Prüfungen definieren.
  4. Framework-Health-Adapter auf das interne Modell abbilden.
  5. Timeout, Cache und sensible Detailausgabe festlegen.

Wann sinnvoll - und wann nicht

Sinnvoll für produktive Dienste und orchestrierte Laufzeiten. Ein Probe darf keine umfangreiche End-to-End-Geschäftstransaktion ausführen.

Risiken und typische Fehlanwendungen

Abhängige Dienste in Liveness einzubauen erzeugt Neustartschleifen. Teure Prüfungen können selbst Ausfälle verursachen. Interne Fehlerdetails dürfen nicht öffentlich werden.

Abgrenzung

Liveness beantwortet, ob der Prozess neu gestartet werden muss. Readiness beantwortet, ob er Verkehr annehmen kann. Observability-Metriken ersetzen keine Probe.

Verwandte Refactorings und Muster

Health Probe Port, Liveness/Readiness, Circuit Breaker State und Degradation Policy.

Enterprise-Java-Einordnung

OrderService bleibt live, wenn Kafka ausfällt, wird aber für neue Bestellungen not ready; reine Lesezugriffe können als degraded weiterlaufen.

Teststrategie

Unit-Tests prüfen Aggregationslogik. Adaptertests prüfen Timeouts; Deploymenttests validieren Orchestrator-Reaktionen.

Deutschsprachige Fachgrundlagen

Kapitel 14 Outbox, Transaktionsgrenzen und zuverlässige Zustellung10 Refactorings

Zuverlässige verteilte Verarbeitung: Outbox, Inbox, Saga, Kompensation, Event-Versionierung, Partitionierung, Dead Letters, Zustellversuche und Observability.

131. Extract Outbox Writer Transactional Outbox

Extract Outbox Writer

Ausgangsproblem

Geschäftsänderung und Event-Publikation werden getrennt gespeichert; bei Fehlern entsteht Inkonsistenz.

Sichere Transformation

Extrahiere einen OutboxWriter, der Eventdaten innerhalb derselben Transaktion als unveränderlichen Eintrag persistiert.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.time.Instant; import java.util.*;
// Refactoring: Extract Outbox Writer
// Pattern: Transactional Outbox - fachliche Änderung und Publikationsauftrag werden atomar gespeichert.
public final class OutboxWriter {
  public record Entry(UUID id,String aggregateId,String type,String payload,Instant occurredAt){
    public Entry { Objects.requireNonNull(id); if(aggregateId==null||aggregateId.isBlank()) throw new IllegalArgumentException("aggregateId"); if(type==null||type.isBlank()) throw new IllegalArgumentException("type"); Objects.requireNonNull(payload); Objects.requireNonNull(occurredAt); }
  }
  private final List<Entry> entries=new ArrayList<>();
  public Entry append(String aggregateId,String type,String payload,Instant occurredAt){ var e=new Entry(UUID.nameUUIDFromBytes((aggregateId+type+payload+occurredAt).getBytes()),aggregateId,type,payload,occurredAt); entries.add(e); return e; }
  public List<Entry> pending(){ return List.copyOf(entries); }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.Instant; import org.junit.jupiter.api.Test;
class OutboxWriterTest { @Test void appends_immutable_entry(){ var w=new OutboxWriter(); var e=w.append("O-1","OrderPlaced","{}",Instant.EPOCH); assertEquals("O-1",e.aggregateId()); assertEquals(1,w.pending().size()); assertThrows(UnsupportedOperationException.class,()->w.pending().clear()); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn die Regel an mehreren Aufrufern auftaucht oder eine eigene Fehler-, Test- oder Lebenszyklussemantik besitzt. Für einen einmaligen lokalen Ausdruck wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Anwendungsservices schreiben fachliche Daten und bauen zusätzlich Outbox-Zeilen mit SQL, JSON und Headern zusammen. Transaktionslogik, Ereignisvertrag und Persistenzdetails sind vermischt und werden je Use Case unterschiedlich umgesetzt.

Erkennungsmerkmale

Direkte INSERTs in outbox; Event-JSON wird im Service erzeugt; fehlende globale EventId; unterschiedliche Statuswerte; Tests prüfen SQL statt fachliches Verhalten.

Ziel und Nutzen

Ein Outbox Writer Port übernimmt die atomare Ablage eines typisierten Integration Events im laufenden Transaktionskontext. Ein Adapter verantwortet Envelope, Serialisierung und Persistenzschema.

Sicherer Refactoring-Ablauf

  1. Vorhandene Dual-Write- und Outboxstellen inventarisieren.
  2. Stabilen Event- und Envelope-Vertrag definieren.
  3. OutboxWriter als Port mit append(event) einführen.
  4. Persistenz und Serialisierung in einen Adapter verschieben.
  5. Use Cases innerhalb derselben Transaktion auf den Port umstellen.

Wann sinnvoll - und wann nicht

Sinnvoll wenn Datenänderung und spätere Veröffentlichung zuverlässig gekoppelt werden müssen. Bei rein lokaler Reaktion ohne externe Zustellung genügt ein internes Domain Event.

Risiken und typische Fehlanwendungen

Outbox-Schreiben außerhalb der Fachtransaktion zerstört die Garantie. Große Payloads, fehlende Retention und unklare Eventversionen machen die Tabelle unbeherrschbar.

Abgrenzung

Der Outbox Writer persistiert Ereignisse. Der Outbox Publisher liest und veröffentlicht sie später; beide Rollen müssen getrennt bleiben.

Verwandte Refactorings und Muster

Transactional Outbox, Integration Event, Event Envelope, Publisher Port und Unit of Work.

Enterprise-Java-Einordnung

Nach PolicyIssued speichert der Use Case Aggregat und PolicyIssuedIntegrationEvent über denselben Unit-of-Work-Kontext. Der Writer erzeugt EventId, Version und occurredAt.

Teststrategie

Transaktionstests prüfen atomare Speicherung. Contract-Tests sichern Envelope und Serialisierung; Fehlerfälle dürfen keine halbe Fachänderung hinterlassen.

Deutschsprachige Fachgrundlagen

132. Introduce Inbox Deduplication Inbox / Idempotent Consumer

Introduce Inbox Deduplication

Ausgangsproblem

Ein Consumer verarbeitet dasselbe Event nach Redelivery mehrfach und erzeugt doppelte Nebenwirkungen.

Sichere Transformation

Führe einen InboxStore ein, der Message-IDs atomar reserviert und Wiederholungen sichtbar ablehnt.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.util.Set; import java.util.concurrent.ConcurrentHashMap;
// Refactoring: Introduce Inbox Deduplication
// Pattern: Inbox + Idempotent Consumer - jede Message-ID wird genau einmal reserviert.
public final class InboxStore {
  public enum Reservation { FIRST_DELIVERY, DUPLICATE }
  private final Set<String> processed=ConcurrentHashMap.newKeySet();
  public Reservation reserve(String messageId){ if(messageId==null||messageId.isBlank()) throw new IllegalArgumentException("messageId"); return processed.add(messageId)?Reservation.FIRST_DELIVERY:Reservation.DUPLICATE; }
  public int size(){ return processed.size(); }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class InboxStoreTest { @Test void duplicate_is_rejected(){ var s=new InboxStore(); assertEquals(InboxStore.Reservation.FIRST_DELIVERY,s.reserve("m-1")); assertEquals(InboxStore.Reservation.DUPLICATE,s.reserve("m-1")); assertEquals(1,s.size()); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn die Regel an mehreren Aufrufern auftaucht oder eine eigene Fehler-, Test- oder Lebenszyklussemantik besitzt. Für einen einmaligen lokalen Ausdruck wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Message Consumer führen bei wiederholter Zustellung dieselbe fachliche Wirkung mehrfach aus. Einzelne Handler prüfen ad hoc IDs oder verlassen sich fälschlich auf genau-einmalige Brokerzustellung.

Erkennungsmerkmale

Doppelte Rechnungen oder E-Mails; get-then-put-Deduplizierung; MessageId fehlt; Verarbeitung und Markierung liegen in verschiedenen Transaktionen; TTL ist unklar.

Ziel und Nutzen

Eine Inbox-Deduplizierung reserviert eine stabile MessageId atomar, führt die Wirkung genau einmal pro Consumer-Kontext aus und speichert Abschlussstatus gemeinsam mit der Fachänderung.

Sicherer Refactoring-Ablauf

  1. Stabile Nachrichtenidentität und Consumer-Scope festlegen.
  2. InboxRepository mit atomarer reserve/complete-Semantik definieren.
  3. Reservierung, Fachänderung und Abschluss in eine Transaktionsgrenze bringen.
  4. Wiederholte und hängende Zustellungen explizit behandeln.
  5. Retention, Replay und Monitoring festlegen.

Wann sinnvoll - und wann nicht

Sinnvoll für at-least-once-Messaging und wiederholbare Webhooks. Nicht nötig, wenn Wirkung selbst vollständig idempotent und kostengünstig ist, bleibt aber oft als Evidenz wertvoll.

Risiken und typische Fehlanwendungen

Check-then-act ist racy. Zu frühes complete verliert Nachrichten; zu spätes Markieren kann Doppelwirkungen erzeugen. Eine globale MessageId ohne Consumer-Scope kann legitime Verbraucher blockieren.

Abgrenzung

Idempotent Consumer beschreibt die Eigenschaft. Inbox ist ein persistentes Muster, das Verarbeitung und Deduplizierung dokumentiert.

Verwandte Refactorings und Muster

Inbox Pattern, Idempotent Consumer, MessageId, Transaction Boundary und Replay.

Enterprise-Java-Einordnung

InvoiceConsumer reserviert (consumerName,eventId), bucht die Rechnung und markiert den Eintrag in derselben Datenbanktransaktion als completed.

Teststrategie

Integrationstests liefern dieselbe Nachricht parallel und wiederholt. Crash-Tests prüfen Zustände zwischen reserve und complete; Retentiontests sichern Replay-Anforderungen.

Deutschsprachige Fachgrundlagen

133. Replace Dual Write with Transactional Outbox Transactional Boundary

Replace Dual Write with Transactional Outbox

Ausgangsproblem

Servicecode schreibt Datenbank und Broker nacheinander; ein Teilausfall hinterlässt widersprüchlichen Zustand.

Sichere Transformation

Ersetze den direkten Brokeraufruf durch einen atomaren Commit von Aggregat und Outbox-Eintrag.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.time.Instant; import java.util.*;
// Refactoring: Replace Dual Write with Transactional Outbox
// Unit of Work + Outbox: Business-State und Event werden gemeinsam committed.
public final class TransactionalOrderCommit {
  public record Order(String id,String status){}
  public record Commit(Order order,OutboxWriter.Entry event){}
  private final Map<String,Order> orders=new HashMap<>(); private final OutboxWriter outbox;
  public TransactionalOrderCommit(OutboxWriter outbox){ this.outbox=Objects.requireNonNull(outbox); }
  public Commit place(String id,Instant now){ if(id==null||id.isBlank()) throw new IllegalArgumentException("id"); var order=new Order(id,"PLACED"); var event=outbox.append(id,"OrderPlaced","{\"id\":\""+id+"\"}",now); orders.put(id,order); return new Commit(order,event); }
  public Optional<Order> find(String id){ return Optional.ofNullable(orders.get(id)); }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.Instant; import org.junit.jupiter.api.Test;
class TransactionalOrderCommitTest { @Test void state_and_event_are_created_together(){ var outbox=new OutboxWriter(); var c=new TransactionalOrderCommit(outbox); c.place("O-9",Instant.EPOCH); assertTrue(c.find("O-9").isPresent()); assertEquals(1,outbox.pending().size()); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn die Regel an mehreren Aufrufern auftaucht oder eine eigene Fehler-, Test- oder Lebenszyklussemantik besitzt. Für einen einmaligen lokalen Ausdruck wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Ein Use Case speichert Geschäftsdaten und veröffentlicht anschließend direkt an einen Broker. Fällt eine Seite aus, entstehen dauerhaft inkonsistente Zustände: gespeicherte Daten ohne Ereignis oder Ereignis ohne bestätigte Daten.

Erkennungsmerkmale

Repository.save gefolgt von publisher.send; komplexe Kompensations-catches; Retry kann doppelt veröffentlichen; Transaktionen enden vor oder nach Netzwerkaufruf unklar.

Ziel und Nutzen

Die Transactional Outbox ersetzt den nicht atomaren Netzwerk-Dual-Write durch zwei zuverlässige Schritte: Fachänderung plus Outbox in einer lokalen Transaktion, danach asynchrone idempotente Veröffentlichung.

Sicherer Refactoring-Ablauf

  1. Inkonsistenzfenster und erforderliche Ereignisse identifizieren.
  2. Integration Event und Outboxschema definieren.
  3. Fachänderung und Outboxinsert atomar speichern.
  4. Separaten Publisher mit Claim/Retry/Idempotenz einführen.
  5. Direkten Brokeraufruf entfernen und Betriebsmetriken ergänzen.

Wann sinnvoll - und wann nicht

Sinnvoll wenn lokale Datenbank und externer Broker nicht in einer gemeinsamen zuverlässigen Transaktion liegen. Für synchrone Antwortdaten ist das Muster nicht automatisch passend.

Risiken und typische Fehlanwendungen

Outbox garantiert keine sofortige Zustellung und keine globale Reihenfolge. Publisher, Retention, Poison Events und Schemaevolution werden zu Betriebsverantwortung. Verbraucher müssen Duplikate tolerieren.

Abgrenzung

2PC versucht globale Atomizität. Outbox akzeptiert zeitliche Verzögerung und erreicht zuverlässige Eventual Consistency mit lokalen Transaktionen.

Verwandte Refactorings und Muster

Transactional Outbox, Inbox, Idempotent Consumer, CDC und Integration Event.

Enterprise-Java-Einordnung

Order- und Outboxzeile werden gemeinsam committed. Ein Poller oder CDC-Connector veröffentlicht später; der API-Aufruf muss nicht auf Kafka warten.

Teststrategie

Datenbanktests prüfen atomaren Commit/Rollback. Publishertests simulieren Crash nach Send vor Ack; End-to-End-Tests prüfen eventual delivery und Duplikattoleranz.

Deutschsprachige Fachgrundlagen

134. Extract Saga Coordinator Saga / Process Manager

Extract Saga Coordinator

Ausgangsproblem

Mehrstufige Geschäftsabläufe sind als verschachtelte Serviceaufrufe implementiert und verlieren Fortschritt und Fehlerzustand.

Sichere Transformation

Extrahiere einen SagaCoordinator mit expliziten Schritten, Status und deterministischem Übergang.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Saga Coordinator
// Pattern: Saga / Process Manager - verteilt ausgeführte Schritte erhalten expliziten Zustand.
public final class SagaCoordinator {
  public enum Status { NEW, PAYMENT_RESERVED, INVENTORY_RESERVED, COMPLETED, COMPENSATING, FAILED }
  public record Saga(String id,Status status,List<String> history){ public Saga { history=List.copyOf(history); } }
  public Saga start(String id){ return new Saga(id,Status.NEW,List.of("started")); }
  public Saga advance(Saga saga,String signal){ var h=new ArrayList<>(saga.history()); Status next=switch(saga.status()){ case NEW -> signal.equals("payment-ok")?Status.PAYMENT_RESERVED:Status.FAILED; case PAYMENT_RESERVED -> signal.equals("inventory-ok")?Status.INVENTORY_RESERVED:Status.COMPENSATING; case INVENTORY_RESERVED -> signal.equals("confirm")?Status.COMPLETED:Status.COMPENSATING; default -> saga.status(); }; h.add(signal+" -> "+next); return new Saga(saga.id(),next,h); }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class SagaCoordinatorTest { @Test void transitions_are_explicit(){ var c=new SagaCoordinator(); var s=c.start("S-1"); s=c.advance(s,"payment-ok"); s=c.advance(s,"inventory-ok"); s=c.advance(s,"confirm"); assertEquals(SagaCoordinator.Status.COMPLETED,s.status()); assertEquals(4,s.history().size()); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn die Regel an mehreren Aufrufern auftaucht oder eine eigene Fehler-, Test- oder Lebenszyklussemantik besitzt. Für einen einmaligen lokalen Ausdruck wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Ein Anwendungsservice ruft mehrere lokale oder entfernte Schritte nacheinander auf und enthält Zustandsvariablen, Retry- und Kompensationslogik in einer großen Methode. Wiederaufnahme nach Absturz ist nicht möglich.

Erkennungsmerkmale

Tiefe try/catch-Ketten; boolesche flags für erledigte Schritte; manuelle Rückrufe in umgekehrter Reihenfolge; Prozesszustand nur im Speicher; Timeoutlogik im Service.

Ziel und Nutzen

Ein Saga Coordinator modelliert den langlebigen Prozess als expliziten Zustand mit Schritten, Ereignissen und Kompensationen. Er entscheidet, welcher Command als Nächstes ausgeführt wird und kann nach Neustart fortsetzen.

Sicherer Refactoring-Ablauf

  1. Prozessschritte, Erfolgsergebnisse und Fehlerfälle aufzeichnen.
  2. Persistenten SagaState mit Korrelation und Version einführen.
  3. Übergänge als reine Entscheidungslogik modellieren.
  4. Befehle und Antworten über Ports anbinden.
  5. Timeout, Retry, Kompensation und Wiederaufnahme testen.

Wann sinnvoll - und wann nicht

Sinnvoll für verteilte Geschäftsprozesse ohne globale ACID-Transaktion. Für kurze lokale Operationen ist eine normale Transaktion einfacher und sicherer.

Risiken und typische Fehlanwendungen

Ein zentraler Coordinator kann zu einem fachlosen Orchestrierungsmonster werden. Kompensation ist nicht immer echte Rückabwicklung. Parallelität, Duplikate und verspätete Antworten müssen explizit behandelt werden.

Abgrenzung

Saga bezeichnet den verteilten Prozess. Process Manager/Saga Coordinator ist eine konkrete orchestrierende Umsetzung; Choreografie verteilt Übergänge auf Ereignisreaktionen.

Verwandte Refactorings und Muster

Saga, Process Manager, State Machine, Command, Compensation und Outbox/Inbox.

Enterprise-Java-Einordnung

Eine Reisebuchung koordiniert Flug, Hotel und Zahlung. Jeder bestätigte Schritt wird persistiert; bei Zahlungsfehler erzeugt der Coordinator CancelHotel und CancelFlight.

Teststrategie

State-Transition-Tests prüfen jeden Eingang. Replay- und Crash-Tests sichern Wiederaufnahme; Contract-Tests prüfen Commands und Events.

Deutschsprachige Fachgrundlagen

135. Introduce Compensation Command Command / Compensation

Introduce Compensation Command

Ausgangsproblem

Rollback-Logik ist als verstreute catch-Blöcke implementiert und kann nicht protokolliert oder wiederholt werden.

Sichere Transformation

Modelliere jede Kompensation als idempotentes Command-Objekt mit eigenem Schlüssel.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Compensation Command
// Command Pattern: Kompensationen werden explizit, protokollierbar und idempotent.
public interface CompensationCommand {
  String idempotencyKey(); void execute();
  final class Runner {
    private final Set<String> completed=new HashSet<>();
    public boolean run(CompensationCommand command){ Objects.requireNonNull(command); if(!completed.add(command.idempotencyKey())) return false; try { command.execute(); return true; } catch(RuntimeException ex){ completed.remove(command.idempotencyKey()); throw ex; } }
  }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test;
class CompensationCommandTest { @Test void command_runs_once(){ var n=new AtomicInteger(); CompensationCommand c=new CompensationCommand(){ public String idempotencyKey(){return "refund-1";} public void execute(){n.incrementAndGet();} }; var r=new CompensationCommand.Runner(); assertTrue(r.run(c)); assertFalse(r.run(c)); assertEquals(1,n.get()); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn die Regel an mehreren Aufrufern auftaucht oder eine eigene Fehler-, Test- oder Lebenszyklussemantik besitzt. Für einen einmaligen lokalen Ausdruck wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Fehlerbehandlung ruft beliebige Gegenmethoden auf, ohne die fachliche Bedeutung, Idempotenz und benötigten Daten der Kompensation zu modellieren. Nach Prozessabbruch ist nicht nachvollziehbar, was rückgängig gemacht werden soll.

Erkennungsmerkmale

catch-Blöcke mit refund(), delete() oder undo(); Parameter werden aus aktuellem Zustand rekonstruiert; doppelte Kompensation hat Nebenwirkungen; kein Audit oder Status.

Ziel und Nutzen

Ein Compensation Command beschreibt eine fachlich benannte ausgleichende Handlung mit stabiler Identität und den beim ursprünglichen Schritt gesicherten Daten. Er kann gespeichert, wiederholt und auditiert werden.

Sicherer Refactoring-Ablauf

  1. Für jeden kompensierbaren Schritt die reale fachliche Ausgleichswirkung definieren.
  2. Erforderliche Originaldaten beim Vorwärtsschritt sichern.
  3. Typisierten Command mit CompensationId einführen.
  4. Handler idempotent und statusgeführt implementieren.
  5. Saga-Übergänge und manuelle Eskalation ergänzen.

Wann sinnvoll - und wann nicht

Sinnvoll in Sagas und langlebigen Workflows. Nicht jede Handlung ist kompensierbar; dann braucht es manuelle Korrektur oder eine andere Prozessgestaltung.

Risiken und typische Fehlanwendungen

Kompensation ist häufig keine zeitliche Rückkehr zum alten Zustand. Externe Fristen, Gebühren oder bereits konsumierte Leistungen müssen berücksichtigt werden. Blinde Deletes zerstören Auditierbarkeit.

Abgrenzung

Rollback stellt eine lokale Transaktion atomar zurück. Compensation ist eine neue fachliche Transaktion, die Auswirkungen ausgleicht.

Verwandte Refactorings und Muster

Compensation Command, Saga, Command Pattern, Idempotency Key und Audit Trail.

Enterprise-Java-Einordnung

RefundPaymentCommand enthält PaymentId, ursprüngliche CaptureId, Betrag und Reason. Wiederholte Ausführung liefert denselben Refund-Beleg statt erneut zu zahlen.

Teststrategie

Tests prüfen Idempotenz, fachliche Grenzfälle und verspätete Antworten. Prozess-Tests simulieren Fehler während der Kompensation und manuelle Recovery.

Deutschsprachige Fachgrundlagen

136. Extract Event Version Translator Upcaster / Anti-Corruption Layer

Extract Event Version Translator

Ausgangsproblem

Consumercode enthält verstreute if-Abfragen für alte Eventversionen und vermischt Migration mit Fachlogik.

Sichere Transformation

Extrahiere einen Translator, der alte Eventformen in ein kanonisches aktuelles Modell überführt.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Event Version Translator
// Upcaster + Anti-Corruption Layer: alte Eventschemata werden an einer Grenze normalisiert.
public final class EventVersionTranslator {
  public record Raw(int version,Map<String,String> fields){ public Raw { fields=Map.copyOf(fields); } }
  public record CustomerChanged(String customerId,String email){}
  public CustomerChanged translate(Raw raw){ return switch(raw.version()){ case 1 -> new CustomerChanged(required(raw,"id"),required(raw,"mail")); case 2 -> new CustomerChanged(required(raw,"customerId"),required(raw,"email")); default -> throw new IllegalArgumentException("unsupported version "+raw.version()); }; }
  private static String required(Raw r,String key){ var v=r.fields().get(key); if(v==null||v.isBlank()) throw new IllegalArgumentException(key); return v; }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.Map; import org.junit.jupiter.api.Test;
class EventVersionTranslatorTest { @Test void old_and_new_versions_become_canonical(){ var t=new EventVersionTranslator(); assertEquals("a@b",t.translate(new EventVersionTranslator.Raw(1,Map.of("id","C1","mail","a@b"))).email()); assertEquals("a@b",t.translate(new EventVersionTranslator.Raw(2,Map.of("customerId","C1","email","a@b"))).email()); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn die Regel an mehreren Aufrufern auftaucht oder eine eigene Fehler-, Test- oder Lebenszyklussemantik besitzt. Für einen einmaligen lokalen Ausdruck wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Consumer enthalten verzweigte Logik für alte Eventversionen oder erwarten, dass alle Producer gleichzeitig migrieren. Historische Ereignisse können nicht mehr gelesen werden und Domänenhandler kennen technische Schemahistorie.

Erkennungsmerkmale

if(version==1) in Handlern; alte Felder gelangen in Fachcode; Migrationen verändern gespeicherte Events; unbekannte Versionen führen zu Nullwerten; jeder Consumer übersetzt anders.

Ziel und Nutzen

Ein Event Version Translator (Upcaster) übersetzt ältere Verträge schrittweise in die aktuelle interne Repräsentation. Fachhandler sehen nur eine Version und Schemaevolution bleibt an der Integrationsgrenze.

Sicherer Refactoring-Ablauf

  1. Unterstützte Versionen und semantische Unterschiede dokumentieren.
  2. Pro Versionsschritt eine kleine reine Übersetzung definieren.
  3. Translator-Kette vor Deserialisierung ins aktuelle Modell setzen.
  4. Unübersetzbare Änderungen explizit ablehnen oder migrieren.
  5. Historische Fixtures und unbekannte Versionen testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei langlebigen Events, Event Stores und unabhängigen Deployments. Bei flüchtigen internen Nachrichten kann eine koordinierte Migration ausreichend sein.

Risiken und typische Fehlanwendungen

Ein Upcaster darf fehlende fachliche Information nicht erfinden. Lange Ketten werden Wartungslast; gelegentlich ist eine Offline-Migration sinnvoller. Übersetzung muss deterministisch bleiben.

Abgrenzung

Ein Data Mapper übersetzt Modelle derselben Zeit. Ein Version Translator übersetzt historische Vertragsstände; ein Anti-Corruption Layer schützt zusätzlich fremde Semantik.

Verwandte Refactorings und Muster

Upcaster, Schema Evolution, Event Envelope, Anti-Corruption Layer und Canonical Data Model.

Enterprise-Java-Einordnung

PolicyIssuedV1 mit annualPremiumCents wird zu V2 mit Money und currency übersetzt; eine definierte historische Standardwährung wird nur verwendet, wenn sie fachlich belegt ist.

Teststrategie

Fixture-Tests für jede Version und Translator-Kombination; Golden Master sichert aktuelle Ausgabe. Unbekannte oder beschädigte Versionen müssen deterministisch fehlschlagen.

Deutschsprachige Fachgrundlagen

137. Introduce Partition Key Strategy Strategy / Ordering Boundary

Introduce Partition Key Strategy

Ausgangsproblem

Partition Keys werden ad hoc aus zufälligen Feldern gebildet; dadurch gehen Reihenfolge und Lastverteilung verloren.

Sichere Transformation

Führe eine typisierte Strategy ein, die den fachlichen Ordnungsbereich explizit bestimmt.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Partition Key Strategy
// Strategy Pattern: Reihenfolge und Verteilung werden bewusst pro Eventtyp entschieden.
public interface PartitionKeyStrategy<T> { String keyFor(T value);
  record OrderEvent(String orderId,String customerId){}
  final class ByOrder implements PartitionKeyStrategy<OrderEvent>{ public String keyFor(OrderEvent e){ if(e.orderId()==null||e.orderId().isBlank()) throw new IllegalArgumentException("orderId"); return "order:"+e.orderId(); } }
  final class ByCustomer implements PartitionKeyStrategy<OrderEvent>{ public String keyFor(OrderEvent e){ if(e.customerId()==null||e.customerId().isBlank()) throw new IllegalArgumentException("customerId"); return "customer:"+e.customerId(); } }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class PartitionKeyStrategyTest { @Test void strategy_makes_ordering_scope_explicit(){ var e=new PartitionKeyStrategy.OrderEvent("O1","C1"); assertEquals("order:O1",new PartitionKeyStrategy.ByOrder().keyFor(e)); assertEquals("customer:C1",new PartitionKeyStrategy.ByCustomer().keyFor(e)); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn die Regel an mehreren Aufrufern auftaucht oder eine eigene Fehler-, Test- oder Lebenszyklussemantik besitzt. Für einen einmaligen lokalen Ausdruck wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Der Schlüssel für Kafka-Partition, Shard oder Worker-Verteilung wird ad hoc aus verschiedenen Feldern gebildet. Reihenfolge, Lastverteilung und Datenlokalität ändern sich unbemerkt bei Schema- oder Codeänderungen.

Erkennungsmerkmale

key = customerId an einer Stelle und orderId an anderer; null-Fallbacks; zusammengesetzte Strings; Hot Partitions; fachlich zusammengehörige Events landen in unterschiedlicher Reihenfolge.

Ziel und Nutzen

Eine Partition Key Strategy macht das Ordnungs- und Verteilungsziel explizit. Sie erzeugt einen stabilen typisierten Schlüssel und kann versioniert, gemessen und getestet werden.

Sicherer Refactoring-Ablauf

  1. Erforderliche Ordnungsdomäne und Parallelitätsziel definieren.
  2. Kandidaten anhand Kardinalität und Hotspot-Risiko messen.
  3. Strategy mit stabiler kanonischer Kodierung einführen.
  4. Producer und Consumer auf denselben Vertrag umstellen.
  5. Verteilung, Reihenfolge und Migrationspfad testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei partitionierten Streams, Shards und parallelen Workern. Bei nicht geordneten kleinen Datenmengen kann Round-Robin genügen.

Risiken und typische Fehlanwendungen

Ein Schlüssel mit geringer Kardinalität erzeugt Hotspots. Änderung des Algorithmus verschiebt Zuständigkeit und kann Reihenfolge brechen. Personenbezogene Rohdaten sollten nicht unnötig als Schlüssel exponiert werden.

Abgrenzung

Partition Key entscheidet Zuordnung und lokale Reihenfolge. Shard Key betrifft dauerhafte Datenverteilung; Routing Policy kann zusätzlich Zielregion oder Dienst wählen.

Verwandte Refactorings und Muster

Partition Key Strategy, Consistent Hashing, Ordering Boundary, Shard Key und Competing Consumers.

Enterprise-Java-Einordnung

Alle Events einer Order verwenden OrderId, damit Statusübergänge geordnet bleiben. Telemetrie nutzt dagegen DeviceBucket für bessere Verteilung.

Teststrategie

Property-Tests prüfen Determinismus. Verteilungstests analysieren Kardinalität und Hotspots; Kompatibilitätstests sichern Schlüssel über Versionen.

Deutschsprachige Fachgrundlagen

138. Extract Dead Letter Policy Dead Letter Channel / Policy

Extract Dead Letter Policy

Ausgangsproblem

Fehlerhafte Nachrichten werden pauschal verworfen oder endlos wiederholt; Fehlerklassen bleiben ununterschieden.

Sichere Transformation

Extrahiere eine Policy, die Retry, Quarantäne und Verwerfen anhand von Versuchszahl und Fehlerklasse entscheidet.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
// Refactoring: Extract Dead Letter Policy
// Dead Letter Channel Pattern: unzustellbare Nachrichten erhalten eine explizite Behandlung.
public final class DeadLetterPolicy {
  public enum Action { RETRY, QUARANTINE, DISCARD }
  public Action decide(int attempt,Throwable error){ if(attempt<1||error==null) throw new IllegalArgumentException(); if(error instanceof IllegalArgumentException) return Action.QUARANTINE; if(attempt>=3) return Action.QUARANTINE; return Action.RETRY; }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class DeadLetterPolicyTest { @Test void permanent_and_exhausted_failures_are_quarantined(){ var p=new DeadLetterPolicy(); assertEquals(DeadLetterPolicy.Action.QUARANTINE,p.decide(1,new IllegalArgumentException())); assertEquals(DeadLetterPolicy.Action.RETRY,p.decide(1,new RuntimeException())); assertEquals(DeadLetterPolicy.Action.QUARANTINE,p.decide(3,new RuntimeException())); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn die Regel an mehreren Aufrufern auftaucht oder eine eigene Fehler-, Test- oder Lebenszyklussemantik besitzt. Für einen einmaligen lokalen Ausdruck wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Fehlgeschlagene Nachrichten werden pauschal unendlich wiederholt, sofort verworfen oder in eine DLQ verschoben, ohne Fehlerklasse, Evidenz, Retention und Recovery festzulegen.

Erkennungsmerkmale

catch(Exception)->sendToDlq; keine Attempt-Zahl; technische und fachliche Fehler vermischt; sensible Payload ungefiltert; kein Replayprozess; DLQ wächst unbeobachtet.

Ziel und Nutzen

Eine Dead Letter Policy entscheidet anhand typisierter Fehler, Versuchszahl und Nachrichtsklasse über Retry, Quarantäne, Verwerfen oder manuelle Behandlung. Die Entscheidung ist auditierbar und testbar.

Sicherer Refactoring-Ablauf

  1. Fehler in transient, permanent, fachlich und unbekannt klassifizieren.
  2. DeliveryAttempt und FailureEvidence modellieren.
  3. Policy mit klaren Entscheidungen definieren.
  4. DLQ/Quarantäne-Adapter, Retention und Zugriffsschutz implementieren.
  5. Replay- und manuelle Recovery-Prozesse testen.

Wann sinnvoll - und wann nicht

Sinnvoll für asynchrone Verarbeitung mit nicht automatisch lösbaren Fehlern. Eine DLQ darf kein Ersatz für saubere Fehlerklassifizierung sein.

Risiken und typische Fehlanwendungen

Unbegrenztes Retry erzeugt Stürme; sofortige DLQ bei transienten Fehlern verliert Verfügbarkeit. Payloads können Datenschutz- oder Geheimnisrisiken tragen. Replay ohne Idempotenz erzeugt Doppelwirkungen.

Abgrenzung

Dead Letter Channel ist der technische Zielkanal. Dead Letter Policy entscheidet, wann und mit welchen Metadaten eine Nachricht dorthin gelangt.

Verwandte Refactorings und Muster

Dead Letter Channel, Retry Schedule, Poison Message Quarantine, Delivery Attempt und Inbox.

Enterprise-Java-Einordnung

Unbekannte Tarifversion wird nach einem Versuch quarantined; Provider-Timeout folgt Retry Schedule. Die DLQ speichert EventId, SchemaVersion, ErrorCode und redigierte Evidenz.

Teststrategie

Decision-Table-Tests decken Fehlerklassen und Versuchszahlen ab. Integrationstests prüfen DLQ-Metadaten, Zugriffsschutz und kontrolliertes Replay.

Deutschsprachige Fachgrundlagen

139. Introduce Delivery Attempt Attempt Value Object

Introduce Delivery Attempt

Ausgangsproblem

Versuchszahl, Erstzustellung und nächster Termin werden als lose Parameter weitergereicht und inkonsistent erhöht.

Sichere Transformation

Führe ein unveränderliches DeliveryAttempt-Objekt mit validiertem Übergang und Backoff-Berechnung ein.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Introduce Delivery Attempt
// Value Object: Zustellversuche und Backoff werden als konsistenter Zustand modelliert.
public record DeliveryAttempt(int number,Instant firstSeen,Instant nextAt){
  public DeliveryAttempt { if(number<1||firstSeen==null||nextAt==null||nextAt.isBefore(firstSeen)) throw new IllegalArgumentException(); }
  public static DeliveryAttempt first(Instant now){ return new DeliveryAttempt(1,now,now); }
  public DeliveryAttempt failedAt(Instant now){ long seconds=Math.min(300L,1L<<Math.min(number,8)); return new DeliveryAttempt(number+1,firstSeen,now.plusSeconds(seconds)); }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.Instant; import org.junit.jupiter.api.Test;
class DeliveryAttemptTest { @Test void failure_creates_next_immutable_attempt(){ var a=DeliveryAttempt.first(Instant.EPOCH); var b=a.failedAt(Instant.EPOCH); assertEquals(1,a.number()); assertEquals(2,b.number()); assertTrue(b.nextAt().isAfter(Instant.EPOCH)); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn die Regel an mehreren Aufrufern auftaucht oder eine eigene Fehler-, Test- oder Lebenszyklussemantik besitzt. Für einen einmaligen lokalen Ausdruck wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Versuchszahl, Zeitpunkt, letzter Fehler und nächste Ausführung werden als lose Header oder primitive Parameter geführt. Retry-, DLQ- und Monitoringlogik interpretieren diese Informationen unterschiedlich.

Erkennungsmerkmale

Header wie x-retry-count; Integer ohne Ursprung; Zeitstempel werden überschrieben; Fehlertext statt Fehlerklasse; jeder Consumer berechnet nextAttempt neu.

Ziel und Nutzen

Ein Delivery Attempt Value Object bündelt unveränderlich den aktuellen Zustellversuch: Nummer, startedAt, vorherige Fehlerkategorie, nextEligibleAt und stabile MessageId. Policies erhalten einen klaren Vertrag.

Sicherer Refactoring-Ablauf

  1. Vorhandene Retry-Metadaten inventarisieren.
  2. Attempt-Invarianten und Zählsemantik festlegen.
  3. Value Object mit next(failure,schedule) einführen.
  4. Header-/Brokerdaten am Adapter übersetzen.
  5. Retry- und Dead-Letter-Policies auf den Typ umstellen.

Wann sinnvoll - und wann nicht

Sinnvoll sobald Zustellung mehrfach versucht wird oder verschiedene Komponenten Retry-Metadaten austauschen. Für einen einzelnen synchronen Aufruf reicht ein lokaler Versuchszähler.

Risiken und typische Fehlanwendungen

Off-by-one-Semantik führt zu falschen Limits. Mutable Attempts zerstören Auditverlauf. Fehlerdetails müssen redigiert und Größen begrenzt werden; Broker-Redelivery und fachlicher Attempt dürfen nicht doppelt zählen.

Abgrenzung

Delivery Attempt beschreibt einen konkreten Versuch. Retry Policy entscheidet, ob und wann ein weiterer Versuch erlaubt ist.

Verwandte Refactorings und Muster

Delivery Attempt, Retry Schedule, Dead Letter Policy, Event Envelope und Observability Context.

Enterprise-Java-Einordnung

PaymentWebhookAttempt trägt attemptNo=3, firstSeenAt, lastFailure=PROVIDER_TIMEOUT und nextEligibleAt. Das System kann Support und Metriken konsistent informieren.

Teststrategie

Value-Object-Tests prüfen Invarianten und next-Berechnung. Contract-Tests sichern Header-Mapping; Decision-Table-Tests kombinieren Attempts mit Policies.

Deutschsprachige Fachgrundlagen

140. Extract Observability Context Correlation Context / Decorator

Extract Observability Context

Ausgangsproblem

Trace-, Correlation- und Tenant-Informationen werden als lose Strings in Logs und Nachrichten kopiert.

Sichere Transformation

Extrahiere einen unveränderlichen ObservabilityContext und einen Decorator für konsistente Metadaten.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Observability Context
// Context Object + Decorator: technische Korrelation wird konsistent an Grenzen ergänzt.
public record ObservabilityContext(String traceId,String correlationId,String tenantId){
  public ObservabilityContext { if(traceId==null||traceId.isBlank()||correlationId==null||correlationId.isBlank()||tenantId==null||tenantId.isBlank()) throw new IllegalArgumentException(); }
  public Map<String,String> decorate(Map<String,String> metadata){ var result=new LinkedHashMap<>(metadata); result.put("traceId",traceId); result.put("correlationId",correlationId); result.put("tenantId",tenantId); return Map.copyOf(result); }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.Map; import org.junit.jupiter.api.Test;
class ObservabilityContextTest { @Test void metadata_is_decorated_without_mutating_input(){ var input=Map.of("event","OrderPlaced"); var c=new ObservabilityContext("t1","c1","tenant"); var out=c.decorate(input); assertEquals("t1",out.get("traceId")); assertEquals(1,input.size()); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn die Regel an mehreren Aufrufern auftaucht oder eine eigene Fehler-, Test- oder Lebenszyklussemantik besitzt. Für einen einmaligen lokalen Ausdruck wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Logging, Tracing und Metriken lesen Korrelation, TraceId, Tenant und fachliche Identitäten aus globalen MDCs oder bauen Labels ad hoc. Bei asynchronen Übergängen gehen Bezüge verloren und sensible Daten landen unkontrolliert in Telemetrie.

Erkennungsmerkmale

MDC.put in Fachservices; unterschiedliche Labelnamen; High-Cardinality-IDs als Metrics-Tag; Tracekontext fehlt in Commands/Events; Threadwechsel verliert Korrelation.

Ziel und Nutzen

Ein Observability Context definiert die erlaubten, typisierten Telemetriebezüge und wird an Grenzen explizit propagiert. Decorators setzen daraus Logging-, Tracing- und Metrics-Kontext, ohne Fachcode an konkrete Bibliotheken zu koppeln.

Sicherer Refactoring-Ablauf

  1. Benötigte Korrelationen und Datenschutzregeln festlegen.
  2. Kleinen unveränderlichen Kontext mit erlaubten Feldern definieren.
  3. Framework- und Brokerheader am Adapter übersetzen.
  4. Ports/Decorators für Logs, Traces und Metriken einführen.
  5. Asynchrone Propagation und Cardinality-Budgets testen.

Wann sinnvoll - und wann nicht

Sinnvoll in verteilten und asynchronen Systemen. Nicht jede fachliche ID gehört in jeden Telemetriekanal; Metriken benötigen besonders niedrige Kardinalität.

Risiken und typische Fehlanwendungen

Ein God Context und unkontrollierte Baggage erhöhen Kopplung und Kosten. Personenbezogene Daten oder Tokens dürfen nicht propagiert werden. ThreadLocal allein ist bei Virtual Threads/Executors nicht automatisch ein stabiler Vertrag.

Abgrenzung

Correlation Context verbindet Vorgänge. Observability Context begrenzt und übersetzt diese Informationen für Logs, Traces und Metriken; Request Context kann weitere fachliche Laufzeitwerte enthalten.

Verwandte Refactorings und Muster

Observability Context, Correlation Identifier, Decorator, Context Propagation und Telemetry Port.

Enterprise-Java-Einordnung

Ein IntegrationEvent enthält correlationId und causationId. Der Consumer erzeugt ObservabilityContext; ein Handler-Decorator startet Span, setzt strukturierte Logfelder und verwendet nur operationName als Metrics-Label.

Teststrategie

Tests prüfen Header-Mapping, fehlende/ungültige IDs und Redaction. Async-Tests sichern Propagation; Cardinality-Tests verhindern dynamische IDs in Metriken.

Deutschsprachige Fachgrundlagen

Kapitel 15 Nachrichten übersetzen und Integrationsgrenzen schützen10 Refactorings

Nachrichtenintegration sicher refaktorieren: Translator, kanonisches Modell, Routing, Claim Check, Aggregation, Reihenfolge, Polling, konkurrierende Consumer, Wire Tap und Request-Reply-Korrelation.

141. Extract Message Translator Message Translator / Adapter

Extract Message Translator

Ausgangsproblem

Externe Nachrichtenformate sickern als untypisierte Maps in die Fachlogik.

Sichere Transformation

Extrahiere einen Translator, der den externen Vertrag einmalig in ein typisiertes internes Kommando übersetzt.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.util.Map;
// Refactoring: Extract Message Translator
// Pattern: Message Translator / Adapter - externer Vertrag wird an der Systemgrenze normalisiert.
public final class MessageTranslator {
  public record OrderCommand(String orderId, long amountCents) {
    public OrderCommand { if(orderId==null||orderId.isBlank()) throw new IllegalArgumentException("orderId"); if(amountCents<0) throw new IllegalArgumentException("amountCents"); }
  }
  public OrderCommand translate(Map<String,String> message){
    if(message==null) throw new IllegalArgumentException("message");
    return new OrderCommand(message.get("order_id"),Long.parseLong(message.get("amount_cents")));
  }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.Map; import org.junit.jupiter.api.Test;
class MessageTranslatorTest { @Test void translates_external_contract(){ var c=new MessageTranslator().translate(Map.of("order_id","O-1","amount_cents","1250")); assertEquals("O-1",c.orderId()); assertEquals(1250,c.amountCents()); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn Integrationszustand, Reihenfolge oder Zustellung eine eigene Semantik und isolierte Tests benötigen. Für eine rein lokale, synchrone Einmaloperation wäre die zusätzliche Infrastruktur unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Ein Integrationsadapter kennt zugleich das externe Nachrichtenformat, interne Fachtypen und zahlreiche feldweise Sonderregeln. Änderungen an Partnerverträgen greifen dadurch bis in Anwendungsservices und Domänenlogik durch.

Erkennungsmerkmale

Controller oder Consumer lesen fremde Feldnamen direkt; Datums-, Währungs- und Statuskonvertierungen sind verstreut; dieselbe Transformation wird in mehreren Flows kopiert; Fachcode kennt Transport-DTOs.

Ziel und Nutzen

Ein Message Translator bildet genau eine externe Repräsentation auf ein internes, fachlich verständliches Modell ab. Formatwissen und Semantikübersetzung liegen an einer klaren Integrationsgrenze und sind separat testbar.

Sicherer Refactoring-Ablauf

  1. Eingangsformat und internes Zielmodell getrennt inventarisieren.
  2. Feldabbildungen, Normalisierungen und fachliche Defaults explizit dokumentieren.
  3. Translator als zustandslosen Adapter mit typisierter Signatur einführen.
  4. Aufrufer schrittweise vom Fremdformat auf das interne Modell umstellen.
  5. Vertrags-, Grenzwert- und Fehlerszenarien mit festen Fixtures absichern.

Wann sinnvoll - und wann nicht

Sinnvoll bei fremden oder versionierten Verträgen und mehreren Integrationspartnern. Für identische interne Typen ohne semantische Übersetzung genügt oft ein einfacher Konstruktor oder Mapper.

Risiken und typische Fehlanwendungen

Ein Translator darf fehlende Fachinformation nicht stillschweigend erfinden. Zu große universelle Mapper werden selbst zum Integrationsmonolithen; Fehler müssen mit Feldbezug und Vertragsversion nachvollziehbar bleiben.

Abgrenzung

Ein Data Mapper übersetzt häufig zwischen Domäne und Persistenz. Ein Message Translator schützt die Integrationsgrenze; ein Anti-Corruption Layer umfasst zusätzlich mehrere Übersetzer und Regeln.

Verwandte Refactorings und Muster

Adapter, Anti-Corruption Layer, Canonical Data Model, Event Version Translator und Data Mapper.

Enterprise-Java-Einordnung

Ein PartnerOrderTranslator wandelt PartnerOrderV3 in CreateOrderCommand um, normalisiert ISO-Währung, Statuscode und optionale Lieferdaten und hält das Domänenmodell frei vom Partnerschema.

Teststrategie

Contract-Tests verwenden reale anonymisierte Payloads. Tabellengetriebene Tests prüfen Codes, fehlende Pflichtwerte und Zeitzonen; Round-Trip-Tests sind nur sinnvoll, wenn die Abbildung verlustfrei sein soll.

Deutschsprachige Fachgrundlagen

142. Introduce Canonical Message Canonical Data Model

Introduce Canonical Message

Ausgangsproblem

Jeder Integrationsadapter verwendet eigene Headernamen und Payload-Metadaten.

Sichere Transformation

Führe eine unveränderliche kanonische Nachricht mit stabiler ID, Typ, Version, Payload und Metadaten ein.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.time.Instant; import java.util.*;
// Refactoring: Introduce Canonical Message
// Pattern: Canonical Data Model - Integrationen sprechen intern einen stabilen Nachrichtenvertrag.
public record CanonicalMessage(UUID id,String type,int version,String payload,Instant occurredAt,Map<String,String> metadata){
  public CanonicalMessage { Objects.requireNonNull(id); if(type==null||type.isBlank()) throw new IllegalArgumentException("type"); if(version<1) throw new IllegalArgumentException("version"); Objects.requireNonNull(payload); Objects.requireNonNull(occurredAt); metadata=Map.copyOf(metadata); }
  public CanonicalMessage withMetadata(String key,String value){ var next=new LinkedHashMap<>(metadata); next.put(key,value); return new CanonicalMessage(id,type,version,payload,occurredAt,next); }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.Instant; import java.util.*; import org.junit.jupiter.api.Test;
class CanonicalMessageTest { @Test void metadata_update_returns_new_message(){ var a=new CanonicalMessage(UUID.randomUUID(),"OrderPlaced",1,"{}",Instant.EPOCH,Map.of()); var b=a.withMetadata("tenant","t1"); assertTrue(a.metadata().isEmpty()); assertEquals("t1",b.metadata().get("tenant")); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn Integrationszustand, Reihenfolge oder Zustellung eine eigene Semantik und isolierte Tests benötigen. Für eine rein lokale, synchrone Einmaloperation wäre die zusätzliche Infrastruktur unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Bei vielen Punkt-zu-Punkt-Integrationen übersetzt jedes System jedes fremde Format direkt. Die Zahl der Mapper wächst quadratisch und dieselben Begriffe erhalten je Schnittstelle andere Bedeutungen.

Erkennungsmerkmale

Viele paarweise Translatoren; unterschiedliche Bezeichnungen für dieselbe fachliche Information; Routing hängt an Partnerfeldern; neue Teilnehmer erfordern Änderungen in zahlreichen bestehenden Flows.

Ziel und Nutzen

Eine Canonical Message stellt für einen klar begrenzten Integrationsbereich eine gemeinsame Nachrichtenform bereit. Teilnehmer übersetzen nur zwischen ihrem Vertrag und diesem kanonischen Modell.

Sicherer Refactoring-Ablauf

  1. Gemeinsamen fachlichen Kern und bewusst nicht gemeinsame Details bestimmen.
  2. Kanonisches Schema klein, versioniert und unabhängig von einzelnen Partnern entwerfen.
  3. Ein- und Ausgangstranslator je Teilnehmer einführen.
  4. Routing und Verarbeitung auf kanonische Felder umstellen.
  5. Governance, Kompatibilität und Änderungsprozess festlegen.

Wann sinnvoll - und wann nicht

Sinnvoll in stabilen Integrationsdomänen mit vielen Teilnehmern. Ein unternehmensweites Universalmodell ist meist zu groß; bei zwei einfachen Systemen erzeugt es unnötige Indirektion.

Risiken und typische Fehlanwendungen

Das Modell kann zum zentralen Engpass oder kleinsten gemeinsamen Nenner werden. Partnerbesonderheiten dürfen nicht als beliebige Extension Map hineinsickern. Versionierung und Ownership müssen eindeutig sein.

Abgrenzung

Canonical Data Model reduziert viele direkte Abbildungen. Published Language definiert einen bewusst veröffentlichten Vertrag; ein Anti-Corruption Layer schützt eine einzelne Domäne vor fremder Semantik.

Verwandte Refactorings und Muster

Canonical Data Model, Message Translator, Normalizer, Published Language und Schema Evolution.

Enterprise-Java-Einordnung

Ein kanonisches CustomerChangeEvent enthält CustomerId, ChangeType, EffectiveAt und ContactSnapshot. CRM, Billing und Support besitzen je einen Translator, aber keine gegenseitigen DTO-Abhängigkeiten.

Teststrategie

Schema-Contract-Tests prüfen Pflichtfelder und Versionen. Jeder Translator besitzt Golden-Master-Fixtures; Kompatibilitätstests sichern alte Consumer gegen additive Änderungen.

Deutschsprachige Fachgrundlagen

143. Extract Message Router Message Router / Strategy Registry

Extract Message Router

Ausgangsproblem

Routingregeln wachsen als zentrale if-else-Kette und kennen alle Zielsysteme.

Sichere Transformation

Extrahiere einen Router mit registrierten, typisierten Routen und explizitem Fehlerfall für unbekannte Typen.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.util.*; import java.util.function.Consumer;
// Refactoring: Extract Message Router
// Pattern: Message Router + Strategy Registry - Nachrichtentypen wählen registrierte Handler.
public final class MessageRouter {
  private final Map<String,Consumer<CanonicalMessage>> routes=new HashMap<>();
  public MessageRouter register(String type,Consumer<CanonicalMessage> handler){ routes.put(Objects.requireNonNull(type),Objects.requireNonNull(handler)); return this; }
  public void route(CanonicalMessage message){ var handler=routes.get(message.type()); if(handler==null) throw new IllegalStateException("No route for "+message.type()); handler.accept(message); }
  public Set<String> routeTypes(){ return Set.copyOf(routes.keySet()); }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.Instant; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test;
class MessageRouterTest { @Test void routes_by_type(){ var seen=new AtomicReference<String>(); var r=new MessageRouter().register("OrderPlaced",m->seen.set(m.type())); r.route(new CanonicalMessage(UUID.randomUUID(),"OrderPlaced",1,"{}",Instant.EPOCH,Map.of())); assertEquals("OrderPlaced",seen.get()); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn Integrationszustand, Reihenfolge oder Zustellung eine eigene Semantik und isolierte Tests benötigen. Für eine rein lokale, synchrone Einmaloperation wäre die zusätzliche Infrastruktur unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Weiterleitungsentscheidungen sind als wachsende if/switch-Ketten in Consumer- oder Controllercode eingebaut. Transport, fachliche Klassifizierung und Zielauswahl sind dadurch eng gekoppelt.

Erkennungsmerkmale

Viele Bedingungen auf Typ, Mandant oder Priorität; Zielnamen als Strings; neue Route ändert zentrale Methode; Defaultzweig verschluckt unbekannte Nachrichten; Routing ist schwer isoliert testbar.

Ziel und Nutzen

Ein Message Router kapselt Klassifizierung und Zielwahl. Er liefert eine typisierte Route oder delegiert über eine Strategy Registry an den passenden Handler.

Sicherer Refactoring-Ablauf

  1. Vorhandene Routingkriterien und Prioritäten explizit machen.
  2. Message-Metadaten in einen stabilen RoutingContext überführen.
  3. Route und unbekannte Entscheidung als Typ modellieren.
  4. Bedingungen in kleine Regeln oder registrierte Strategien verschieben.
  5. Überlappende, fehlende und neue Routen mit Entscheidungstabellen testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei mehreren dynamischen Zielen und klaren Routingkriterien. Bei einer festen linearen Pipeline genügt direkte Verdrahtung.

Risiken und typische Fehlanwendungen

Ein Router darf keine umfangreiche Fachverarbeitung übernehmen. Reihenfolge überlappender Regeln muss deterministisch sein; ein stiller Default erzeugt Datenverlust. Dynamische Registries benötigen Ownership und Sichtbarkeit.

Abgrenzung

Ein Content-Based Router entscheidet anhand des Inhalts. Ein Message Router ist der allgemeinere Baustein; ein Process Manager steuert zusätzlich mehrschrittige Abläufe und Zustand.

Verwandte Refactorings und Muster

Message Router, Content-Based Router, Strategy Registry, Recipient List und Process Manager.

Enterprise-Java-Einordnung

InboundSupportEventRouter wählt FraudHandler, BillingHandler oder GeneralSupportHandler anhand eines typisierten EventKind und TenantPolicy, nicht anhand unkontrollierter Headerstrings.

Teststrategie

Decision-Table-Tests prüfen jede Route, Priorität und Unknown-Fall. Contract-Tests stellen sicher, dass Zielnamen und Handlerregistrierung vollständig und eindeutig sind.

Deutschsprachige Fachgrundlagen

144. Introduce Claim Check Store Claim Check

Introduce Claim Check Store

Ausgangsproblem

Große Payloads werden durch jede Queue kopiert und überschreiten Transportgrenzen.

Sichere Transformation

Lagere die Payload in einen ClaimCheckStore aus und transportiere nur einen unveränderlichen Schlüssel.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.util.*; import java.util.concurrent.ConcurrentHashMap;
// Refactoring: Introduce Claim Check Store
// Pattern: Claim Check - große Payload bleibt außerhalb des Nachrichtenkanals.
public final class ClaimCheckStore {
  public record ClaimKey(UUID value){ public ClaimKey { Objects.requireNonNull(value); } }
  private final Map<ClaimKey,byte[]> payloads=new ConcurrentHashMap<>();
  public ClaimKey put(byte[] payload){ Objects.requireNonNull(payload); var key=new ClaimKey(UUID.randomUUID()); payloads.put(key,payload.clone()); return key; }
  public byte[] get(ClaimKey key){ var data=payloads.get(key); if(data==null) throw new NoSuchElementException(key.toString()); return data.clone(); }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class ClaimCheckStoreTest { @Test void stores_defensive_copies(){ var s=new ClaimCheckStore(); byte[] p={1,2}; var k=s.put(p); p[0]=9; assertArrayEquals(new byte[]{1,2},s.get(k)); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn Integrationszustand, Reihenfolge oder Zustellung eine eigene Semantik und isolierte Tests benötigen. Für eine rein lokale, synchrone Einmaloperation wäre die zusätzliche Infrastruktur unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Große Nachrichten transportieren Dokumente oder umfangreiche Payloads durch jeden Broker und Verarbeitungsschritt. Das erhöht Latenz, Kosten und Fehleranfälligkeit, obwohl viele Schritte nur Metadaten benötigen.

Erkennungsmerkmale

Brokerlimits werden erreicht; dieselben Binärdaten werden mehrfach kopiert; Retries übertragen erneut große Inhalte; Logs oder DLQs enthalten sensible Payloads; Consumer laden Daten unnötig.

Ziel und Nutzen

Ein Claim Check Store lagert den großen Inhalt in einen dafür geeigneten Speicher aus. Die Nachricht enthält nur einen unveränderlichen, autorisierten Verweis und die für den Ablauf nötigen Metadaten.

Sicherer Refactoring-Ablauf

  1. Größe, Sensitivität und Lebensdauer der Payload analysieren.
  2. Speicherport, ClaimCheck Value Object und Zugriffsschutz definieren.
  3. Payload vor dem Versand atomar oder kompensierbar ablegen.
  4. Consumer laden Inhalt nur bei Bedarf und validieren Integrität.
  5. Retention, Löschung, Wiederholung und verwaiste Claims testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei großen oder sensiblen Anhängen und mehreren Routingstufen. Für kleine Nachrichten ist der zusätzliche Speicherzugriff meist unnötig.

Risiken und typische Fehlanwendungen

Ein nackter URL-String ist kein sicherer Claim Check. Zugriffsrechte, Ablauf, Verschlüsselung und Cleanup müssen stimmen. Nichtatomare Ablage kann Nachrichten ohne Inhalt oder verwaiste Daten erzeugen.

Abgrenzung

Claim Check reduziert Nachrichtengröße. Message Store archiviert Nachrichten; Blob Storage ist nur eine Technik. Ein Content Enricher lädt zusätzliche Daten anhand eines Verweises.

Verwandte Refactorings und Muster

Claim Check, Content Enricher, Message Store, Outbox und Retention Policy.

Enterprise-Java-Einordnung

Ein DocumentSubmittedEvent enthält DocumentClaimId, SHA-256, MediaType und Size. Der Virenscanner lädt das Dokument über DocumentStorePort, andere Consumer verwenden nur Metadaten.

Teststrategie

Integrationstests prüfen Ablage, Zugriff, Hashvalidierung und Ablauf. Recovery-Tests simulieren Fehler zwischen Store und Publish; Sicherheitstests prüfen Mandantentrennung.

Deutschsprachige Fachgrundlagen

145. Extract Message Aggregator Aggregator

Extract Message Aggregator

Ausgangsproblem

Teilantworten werden mit globalen Listen und impliziter Vollständigkeitslogik gesammelt.

Sichere Transformation

Extrahiere einen Aggregator, der erwartete Teile, Duplikate und Abschlusszustand pro Correlation-ID kapselt.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Message Aggregator
// Pattern: Aggregator - Teilnachrichten werden pro Correlation-ID deterministisch zusammengeführt.
public final class MessageAggregator {
  public record Part(String correlationId,int index,String value){ public Part { if(correlationId==null||correlationId.isBlank()||index<0) throw new IllegalArgumentException(); Objects.requireNonNull(value); } }
  private final int expected; private final Map<String,Map<Integer,String>> groups=new HashMap<>();
  public MessageAggregator(int expected){ if(expected<1) throw new IllegalArgumentException("expected"); this.expected=expected; }
  public Optional<List<String>> add(Part part){ var group=groups.computeIfAbsent(part.correlationId(),k->new HashMap<>()); group.putIfAbsent(part.index(),part.value()); if(group.size()!=expected) return Optional.empty(); return Optional.of(group.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(Map.Entry::getValue).toList()); }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class MessageAggregatorTest { @Test void completes_after_all_parts(){ var a=new MessageAggregator(2); assertTrue(a.add(new MessageAggregator.Part("c",1,"B")).isEmpty()); assertEquals(java.util.List.of("A","B"),a.add(new MessageAggregator.Part("c",0,"A")).orElseThrow()); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn Integrationszustand, Reihenfolge oder Zustellung eine eigene Semantik und isolierte Tests benötigen. Für eine rein lokale, synchrone Einmaloperation wäre die zusätzliche Infrastruktur unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Zusammengehörige Teilnachrichten werden ad hoc in Maps gesammelt oder sofort weitergegeben. Es fehlt eine explizite Regel, wann eine Gruppe vollständig ist und welches Ergebnis daraus entsteht.

Erkennungsmerkmale

Globale mutable Maps; Zähler ohne erwartete Gesamtzahl; Timeouts als Scheduler-Hack; doppelte Teile verändern Ergebnisse; Speicher wird nie bereinigt; Korrelation ist uneinheitlich.

Ziel und Nutzen

Ein Message Aggregator gruppiert Nachrichten anhand eines Correlation Identifier, hält einen typisierten Aggregation State und entscheidet mit einer Completion Policy über Abschluss oder Ablauf.

Sicherer Refactoring-Ablauf

  1. Korrelationsschlüssel und erwartete Teilmenge definieren.
  2. Aggregation State unveränderlich oder atomar aktualisierbar modellieren.
  3. Completion-, Timeout- und Duplikatregeln explizit machen.
  4. State Store und Ergebnisbildung trennen.
  5. Out-of-order-, Duplicate-, Timeout- und Recovery-Szenarien testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei Splitter-Ergebnissen, Scatter-Gather und mehrteiligen Geschäftsantworten. Wenn Teile unabhängig verarbeitet werden können, ist Aggregation unnötig.

Risiken und typische Fehlanwendungen

Unbegrenzter Zustand führt zu Speicherlecks. Genau-einmal-Annahmen sind gefährlich; verspätete Teile und Wiederanlauf müssen definiert sein. Aggregation darf keine versteckte Langzeit-Saga werden.

Abgrenzung

Ein Aggregator bildet aus mehreren Nachrichten ein Ergebnis. Ein Resequencer ordnet, ohne zwingend zusammenzufassen; ein Process Manager steuert einen fachlichen Prozess.

Verwandte Refactorings und Muster

Aggregator, Correlation Identifier, Completion Policy, Splitter und Scatter-Gather.

Enterprise-Java-Einordnung

Ein PricingQuoteAggregator sammelt BasePrice, TaxQuote und ShippingQuote pro QuoteId und erzeugt erst bei vollständiger oder fachlich zulässiger Teilmenge ein QuoteResult.

Teststrategie

State-Machine-Tests prüfen Reihenfolgen und Duplikate. Fake Clock ermöglicht Timeouttests; Persistenz- und Restarttests sichern Recovery.

Deutschsprachige Fachgrundlagen

146. Introduce Message Resequencer Resequencer

Introduce Message Resequencer

Ausgangsproblem

Parallel verarbeitete Nachrichten treffen ungeordnet ein und werden zu früh weitergegeben.

Sichere Transformation

Führe einen Resequencer ein, der pro Stream nur die nächste erwartete Sequenz freigibt und Lücken puffert.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Message Resequencer
// Pattern: Resequencer - ungeordnete Nachrichten werden pro Stream in Sequenz freigegeben.
public final class MessageResequencer {
  public record Sequenced(long sequence,String payload){ public Sequenced { if(sequence<0) throw new IllegalArgumentException(); Objects.requireNonNull(payload); } }
  private long next; private final NavigableMap<Long,String> buffer=new TreeMap<>();
  public MessageResequencer(long firstSequence){ if(firstSequence<0) throw new IllegalArgumentException(); next=firstSequence; }
  public List<Sequenced> accept(Sequenced message){ buffer.putIfAbsent(message.sequence(),message.payload()); var ready=new ArrayList<Sequenced>(); while(buffer.containsKey(next)){ ready.add(new Sequenced(next,buffer.remove(next))); next++; } return List.copyOf(ready); }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class MessageResequencerTest { @Test void holds_gap_until_missing_message_arrives(){ var r=new MessageResequencer(1); assertTrue(r.accept(new MessageResequencer.Sequenced(2,"B")).isEmpty()); assertEquals(2,r.accept(new MessageResequencer.Sequenced(1,"A")).size()); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn Integrationszustand, Reihenfolge oder Zustellung eine eigene Semantik und isolierte Tests benötigen. Für eine rein lokale, synchrone Einmaloperation wäre die zusätzliche Infrastruktur unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Parallel verarbeitete oder unterschiedlich verzögerte Nachrichten treffen in falscher Reihenfolge ein, obwohl der Empfänger eine definierte Sequenz benötigt.

Erkennungsmerkmale

Versionssprünge; Consumer ignoriert ältere Events pauschal; zufällige sleep-basierte Wartezeiten; Updates überschreiben neueren Zustand; Reihenfolgeannahmen sind undokumentiert.

Ziel und Nutzen

Ein Resequencer puffert Nachrichten pro Korrelationsschlüssel, erkennt Sequenznummern und gibt nur die nächste erwartete Nachricht frei. Gap- und Timeout-Policy machen fehlende Elemente explizit.

Sicherer Refactoring-Ablauf

  1. Ordnungsdomäne und Sequenzsemantik festlegen.
  2. SequenceNumber und CorrelationId typisieren.
  3. Persistenten oder begrenzten Puffer einführen.
  4. Gap-, Duplicate- und Ablaufentscheidung definieren.
  5. Out-of-order-, Verlust- und Restart-Szenarien testen.

Wann sinnvoll - und wann nicht

Sinnvoll, wenn fachliche Korrektheit echte Reihenfolge verlangt und der Transport sie nicht garantiert. Bei kommutativen Updates sollte das Modell lieber reihenfolgeunabhängig werden.

Risiken und typische Fehlanwendungen

Unbegrenztes Warten blockiert Gruppen dauerhaft. Globale Reihenfolge skaliert schlecht; pro Aggregate ist meist sinnvoller. Sequenznummern müssen stabil erzeugt werden.

Abgrenzung

Resequencer stellt Reihenfolge her. Aggregator fasst Teile zusammen; Optimistic Versioning kann veraltete Updates ablehnen, ohne sie umzuschichten.

Verwandte Refactorings und Muster

Resequencer, Message Sequence, Consumer Offset, Aggregate Version und Gap Policy.

Enterprise-Java-Einordnung

Ein AccountProjectionResequencer verarbeitet AccountEvent(sequence=41) erst, wenn 40 vorliegt; nach definierter Frist wird eine Recovery-Anfrage statt stiller Fortschreibung erzeugt.

Teststrategie

Property-Tests permutieren Reihenfolgen. Fake Clock prüft Gap-Timeout; Restarttests sichern persistierten Puffer und nächste erwartete Nummer.

Deutschsprachige Fachgrundlagen

147. Extract Polling Consumer Polling Consumer / Port

Extract Polling Consumer

Ausgangsproblem

Polling, Cursor-Verwaltung und Verarbeitung liegen in einer Endlosschleife ohne testbare Grenze.

Sichere Transformation

Extrahiere Source- und Handler-Ports und mache einen einzelnen Poll-Zyklus deterministisch ausführbar.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Polling Consumer
// Pattern: Polling Consumer - ein kontrollierter Zyklus trennt Quelle, Verarbeitung und Cursor.
public final class PollingConsumer {
  public interface Source { List<String> poll(String cursor,int limit); }
  public interface Handler { void handle(String message); }
  private final Source source; private final Handler handler; private String cursor="0";
  public PollingConsumer(Source source,Handler handler){ this.source=Objects.requireNonNull(source); this.handler=Objects.requireNonNull(handler); }
  public int pollOnce(int limit){ var messages=source.poll(cursor,limit); messages.forEach(handler::handle); cursor=Integer.toString(Integer.parseInt(cursor)+messages.size()); return messages.size(); }
  public String cursor(){ return cursor; }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.*; import org.junit.jupiter.api.Test;
class PollingConsumerTest { @Test void advances_cursor_after_handling(){ var seen=new ArrayList<String>(); var c=new PollingConsumer((cursor,limit)->List.of("a","b"),seen::add); assertEquals(2,c.pollOnce(10)); assertEquals("2",c.cursor()); assertEquals(List.of("a","b"),seen); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn Integrationszustand, Reihenfolge oder Zustellung eine eigene Semantik und isolierte Tests benötigen. Für eine rein lokale, synchrone Einmaloperation wäre die zusätzliche Infrastruktur unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Zeitsteuerung, Abruf, Cursorverwaltung, Fehlerbehandlung und Fachverarbeitung stecken in einer Scheduler-Methode. Dadurch sind Pollingintervall, Backoff und Verarbeitung nicht getrennt testbar.

Erkennungsmerkmale

@Scheduled-Methode mit Datenbankzugriff und Businesslogik; Cursor als statisches Feld; feste Sleeps; Batchgröße und Fehlerreaktion verstreut; parallele Instanzen lesen dieselben Datensätze.

Ziel und Nutzen

Ein Polling Consumer kapselt ausschließlich den wiederholten Abruf über einen Source Port und übergibt gefundene Nachrichten an einen separaten Handler. Cursor, Lease und Polling Policy werden explizit.

Sicherer Refactoring-Ablauf

  1. Quelle, Cursor und Sichtbarkeitssemantik definieren.
  2. SourcePort und PollBatch Result Type einführen.
  3. Polling Policy für Intervall, leere Ergebnisse und Fehler extrahieren.
  4. Fachverarbeitung in idempotenten Handler verschieben.
  5. Mehrinstanz-, Restart- und Empty-Poll-Szenarien testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei Quellen ohne Pushmechanismus, Tabellen-Outbox oder Legacy-APIs. Bei nativer Eventzustellung ist Polling oft nur zusätzlicher Ballast.

Risiken und typische Fehlanwendungen

Zu aggressives Polling belastet Quelle; zu langsames erhöht Latenz. Cursor darf erst nach sicherer Verarbeitung fortgeschrieben werden. Mehrere Instanzen benötigen Claim/Lease oder partitionierte Zuständigkeit.

Abgrenzung

Polling Consumer zieht aktiv Daten. Event-Driven Consumer reagiert auf Push; Outbox Poller ist eine konkrete Anwendung mit Transaktionsanforderungen.

Verwandte Refactorings und Muster

Polling Consumer, Outbox Poller, Consumer Offset, Lease und Idempotent Receiver.

Enterprise-Java-Einordnung

OutboxPollingConsumer liest maximal 100 PendingOutboxRecord über OutboxSourcePort, publiziert über EventPublisher und bestätigt erst nach erfolgreicher Zustellung.

Teststrategie

Fake Scheduler und Source Fake prüfen leere, teilweise und fehlerhafte Batches. Restarttests sichern Cursor; Paralleltests prüfen Lease und Doppelverarbeitung.

Deutschsprachige Fachgrundlagen

148. Introduce Competing Consumer Lease Competing Consumers / Lease

Introduce Competing Consumer Lease

Ausgangsproblem

Mehrere Worker verarbeiten dieselbe Aufgabe, weil Besitz und Ablaufzeit nur implizit sind.

Sichere Transformation

Führe eine zeitlich begrenzte Lease ein, die Besitzer, Ablauf und Erneuerung explizit modelliert.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.time.*; import java.util.Objects;
// Refactoring: Introduce Competing Consumer Lease
// Pattern: Competing Consumers + Lease - exklusiver Besitz besitzt eine klare Ablaufzeit.
public record CompetingConsumerLease(String taskId,String owner,Instant expiresAt){
  public CompetingConsumerLease { if(taskId==null||taskId.isBlank()||owner==null||owner.isBlank()) throw new IllegalArgumentException(); Objects.requireNonNull(expiresAt); }
  public boolean activeAt(Instant now){ return now.isBefore(expiresAt); }
  public CompetingConsumerLease renew(String requestingOwner,Instant now,Duration duration){ if(!owner.equals(requestingOwner)||!activeAt(now)) throw new IllegalStateException("lease unavailable"); return new CompetingConsumerLease(taskId,owner,now.plus(duration)); }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.*; import org.junit.jupiter.api.Test;
class CompetingConsumerLeaseTest { @Test void only_owner_can_renew_active_lease(){ var l=new CompetingConsumerLease("T-1","w1",Instant.EPOCH.plusSeconds(10)); assertTrue(l.renew("w1",Instant.EPOCH,Duration.ofSeconds(20)).activeAt(Instant.EPOCH.plusSeconds(19))); assertThrows(IllegalStateException.class,()->l.renew("w2",Instant.EPOCH,Duration.ofSeconds(1))); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn Integrationszustand, Reihenfolge oder Zustellung eine eigene Semantik und isolierte Tests benötigen. Für eine rein lokale, synchrone Einmaloperation wäre die zusätzliche Infrastruktur unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Mehrere Worker sollen dieselbe Queue oder Tabelle parallel bearbeiten, aber Zuständigkeit und Wiederfreigabe sind implizit. Doppelverarbeitung oder dauerhaft blockierte Arbeit entsteht bei Abstürzen.

Erkennungsmerkmale

Statusfeld PROCESSING ohne Ablaufzeit; Select-then-update-Rennen; WorkerId als String; keine Heartbeats; verwaiste Jobs benötigen manuelle Korrektur.

Ziel und Nutzen

Ein Lease Object beschreibt zeitlich begrenzten exklusiven Besitz. Atomarer Claim, Ablauf und optionale Verlängerung erlauben parallele Consumer mit kontrollierter Wiederübernahme.

Sicherer Refactoring-Ablauf

  1. Arbeitseinheit und gewünschte Exklusivität bestimmen.
  2. LeaseId, Owner, acquiredAt und expiresAt typisieren.
  3. Atomaren Claim im Repository implementieren.
  4. Renew-, Complete- und Expire-Regeln definieren.
  5. Crash-, Clock- und Mehrworker-Szenarien testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei tabellenbasierten Work Queues oder Ressourcen ohne Broker-Consumer-Gruppen. Wenn der Broker Exklusivität zuverlässig liefert, ist ein zusätzliches Lease oft unnötig.

Risiken und typische Fehlanwendungen

Lease ist keine Garantie gegen Seiteneffekte nach Ablauf; Handler müssen idempotent bleiben. Lange Leases verzögern Recovery, kurze erzeugen unnötige Renewals. Uhr- und Transaktionssemantik sind kritisch.

Abgrenzung

Competing Consumers verteilen Last. Lease regelt zeitlichen Besitz; Distributed Lock schützt meist eine Ressource statt einzelner Arbeitseinheiten.

Verwandte Refactorings und Muster

Competing Consumers, Lease, Claim Check, Inbox, Idempotent Receiver und Work Queue.

Enterprise-Java-Einordnung

MigrationWorkerRepository.claimNext(workerId, now, ttl) liefert einen MigrationLease. Nach erfolgreichem Schritt wird complete(leaseId) atomar bestätigt.

Teststrategie

Paralleltests prüfen eindeutigen Claim. Fake Clock simuliert Ablauf und Erneuerung; Crash-Recovery-Tests prüfen erneute Zustellung ohne doppelte Wirkung.

Deutschsprachige Fachgrundlagen

149. Extract Wire Tap Wire Tap / Decorator

Extract Wire Tap

Ausgangsproblem

Diagnosekopien werden ad hoc in Geschäftslogik eingefügt und können die Hauptverarbeitung blockieren.

Sichere Transformation

Extrahiere einen Wire-Tap-Decorator, der Nachrichten unverändert weiterleitet und eine getrennte Beobachtungskopie erzeugt.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.util.Objects; import java.util.function.Consumer;
// Refactoring: Extract Wire Tap
// Pattern: Wire Tap + Decorator - Beobachtung wird ohne Änderung der Hauptnachricht ergänzt.
public final class WireTap implements Consumer<CanonicalMessage> {
  private final Consumer<CanonicalMessage> downstream; private final Consumer<CanonicalMessage> tap;
  public WireTap(Consumer<CanonicalMessage> downstream,Consumer<CanonicalMessage> tap){ this.downstream=Objects.requireNonNull(downstream); this.tap=Objects.requireNonNull(tap); }
  @Override public void accept(CanonicalMessage message){ tap.accept(message); downstream.accept(message); }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.Instant; import java.util.*; import org.junit.jupiter.api.Test;
class WireTapTest { @Test void observes_and_forwards_same_message(){ var calls=new ArrayList<String>(); var w=new WireTap(m->calls.add("down:"+m.type()),m->calls.add("tap:"+m.type())); w.accept(new CanonicalMessage(UUID.randomUUID(),"X",1,"{}",Instant.EPOCH,Map.of())); assertEquals(List.of("tap:X","down:X"),calls); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn Integrationszustand, Reihenfolge oder Zustellung eine eigene Semantik und isolierte Tests benötigen. Für eine rein lokale, synchrone Einmaloperation wäre die zusätzliche Infrastruktur unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Logging, Audit oder Diagnose wird direkt in die Hauptverarbeitung eingebaut und verändert deren Fehler- und Latenzverhalten. Beobachtung ist dadurch mit Fachlogik gekoppelt.

Erkennungsmerkmale

Handler ruft zusätzlich AuditService; Diagnosefehler brechen Businessflow; komplette Payload wird geloggt; Beobachtung ist in vielen Handlern kopiert; Ein-/Ausschalten erfordert Fachcodeänderung.

Ziel und Nutzen

Ein Wire Tap erzeugt an einer klaren Kanalgrenze eine kontrollierte Kopie ausgewählter Nachrichtendaten für Beobachtung, Audit oder Analyse, ohne den Hauptfluss fachlich zu verändern.

Sicherer Refactoring-Ablauf

  1. Beobachtungszweck und erlaubte Daten festlegen.
  2. Redaktion und Metadatenprojektion vor der Kopie definieren.
  3. Tap als Decorator oder Kanalinterceptor einführen.
  4. Fehler- und Backpressure-Verhalten unabhängig vom Hauptfluss bestimmen.
  5. Datenschutz-, Ausfall- und Lastszenarien testen.

Wann sinnvoll - und wann nicht

Sinnvoll für Diagnose, Audit und nichtkritische Analyse. Kritische fachliche Zustellung darf nicht als unverbindlicher Wire Tap modelliert werden.

Risiken und typische Fehlanwendungen

Unkontrollierte Payloadkopien verletzen Datenschutz und erhöhen Kosten. Ein synchroner Tap kann Hauptlatenz verschlechtern; ein still fehlernder Tap kann Auditversprechen brechen. Zweck und Garantie müssen klar sein.

Abgrenzung

Wire Tap kopiert Beobachtungsdaten. Decorator erweitert einen Aufruf; Domain Event löst fachlich relevante Folgewirkung aus.

Verwandte Refactorings und Muster

Wire Tap, Decorator, Audit Port, Observability Context und Data Masking Policy.

Enterprise-Java-Einordnung

Ein PaymentEventChannelDecorator sendet redigierte Metadaten an SecurityAnalytics, während die originale Nachricht unverändert an den PaymentHandler geht.

Teststrategie

Tests prüfen unveränderte Hauptnachricht, Redaction und Ausfallisolation. Lasttests messen Kopierkosten; Securitytests verhindern sensible Felder.

Deutschsprachige Fachgrundlagen

150. Introduce Request-Reply Correlation Request-Reply / Correlation Identifier

Introduce Request-Reply Correlation

Ausgangsproblem

Antworten werden anhand von Thread, Reihenfolge oder globalem Zustand einem Request zugeordnet.

Sichere Transformation

Führe einen expliziten Correlation-Store ein, der Request-ID und erwarteten Antworttyp sicher zusammenführt.

Java-21-Zielcode

package com.aydinsude.workbench.catalog;
import java.util.*; import java.util.concurrent.ConcurrentHashMap;
// Refactoring: Introduce Request-Reply Correlation
// Pattern: Request-Reply + Correlation Identifier - Antworten werden explizit dem ursprünglichen Request zugeordnet.
public final class RequestReplyCorrelation {
  public record Pending(UUID correlationId,String expectedType){ public Pending { Objects.requireNonNull(correlationId); if(expectedType==null||expectedType.isBlank()) throw new IllegalArgumentException(); } }
  private final Map<UUID,Pending> pending=new ConcurrentHashMap<>();
  public Pending register(String expectedType){ var p=new Pending(UUID.randomUUID(),expectedType); pending.put(p.correlationId(),p); return p; }
  public boolean complete(UUID correlationId,String responseType){ var p=pending.get(correlationId); if(p==null||!p.expectedType().equals(responseType)) return false; return pending.remove(correlationId,p); }
  public int pendingCount(){ return pending.size(); }
}

JUnit-Test

package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class RequestReplyCorrelationTest { @Test void completes_only_matching_reply(){ var c=new RequestReplyCorrelation(); var p=c.register("PriceReply"); assertFalse(c.complete(p.correlationId(),"Other")); assertTrue(c.complete(p.correlationId(),"PriceReply")); assertEquals(0,c.pendingCount()); } }

Einsatzgrenze: Das Refactoring ist sinnvoll, wenn Integrationszustand, Reihenfolge oder Zustellung eine eigene Semantik und isolierte Tests benötigen. Für eine rein lokale, synchrone Einmaloperation wäre die zusätzliche Infrastruktur unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Asynchrone Antworten werden anhand von Threadzustand, Reihenfolge oder frei gebildeten Strings einem Request zugeordnet. Bei Parallelität und Wiederholung entstehen falsche Zuordnungen.

Erkennungsmerkmale

pending map mit String-Keys; Antwort ohne CorrelationId; Timeout entfernt falschen Eintrag; gleiche BusinessId für mehrere Versuche; Restart verliert wartende Zuordnung.

Ziel und Nutzen

Ein Correlation Identifier Value Object verbindet genau einen Request mit seinen Antworten. Ein Request Registry oder PendingRequest Store verwaltet Lebenszyklus, Timeout und Mehrfachantworten.

Sicherer Refactoring-Ablauf

  1. Request-Identität und Antwortkardinalität definieren.
  2. CorrelationId unabhängig von fachlichen IDs erzeugen.
  3. Identifier in Envelope und Antwortvertrag aufnehmen.
  4. PendingRequestStore mit Ablauf und atomarer Vervollständigung einführen.
  5. Parallel-, Duplicate-, Timeout- und Restart-Szenarien testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei asynchronem Request-Reply und Scatter-Gather. Bei synchronem Methodenaufruf liefert der Call Stack bereits Korrelation.

Risiken und typische Fehlanwendungen

CorrelationId darf nicht mit Authentisierung oder IdempotencyKey verwechselt werden. Unbegrenzte Pending Maps lecken Speicher; verspätete Antworten brauchen eine definierte Behandlung.

Abgrenzung

Correlation Identifier verbindet Request und Reply. CausationId beschreibt Ursache, IdempotencyKey schützt Wirkung, BusinessId identifiziert Fachobjekt.

Verwandte Refactorings und Muster

Request-Reply, Correlation Identifier, Pending Request Store, Timeout und Aggregator.

Enterprise-Java-Einordnung

CreditCheckRequest erhält CorrelationId; CreditCheckReply trägt denselben Wert. PendingCreditChecks vervollständigt genau einmal und markiert späte Antworten als LateReply.

Teststrategie

Paralleltests schicken Antworten in anderer Reihenfolge. Fake Clock prüft Timeout; Duplicate- und Restarttests sichern atomare Vervollständigung.

Deutschsprachige Fachgrundlagen

Kapitel 16 Idempotente Empfänger und Inbox-Verarbeitung10 Refactorings

Refactorings 151–160 machen Zustellsemantik, Offset, Subscription, Ablauf, Retry, Quarantäne, Schema-Kompatibilität und externe Event-Verträge explizit.

Refactoring-Katalog · Abschnitt 1

Integration Reliability Refactorings

Refactorings 151–160 machen Zustellsemantik, Offset, Subscription, Ablauf, Retry, Quarantäne, Schema-Kompatibilität und externe Event-Verträge explizit.

160 von 300 Refactorings abgeschlossen

140 Refactorings offen.

151. Extract Idempotent Receiver Idempotent Receiver / Inbox

Ausgangsproblem

Wiederholte Zustellungen lösen dieselbe Fachaktion mehrfach aus.

Sichere Transformation

Kapsle die Deduplizierung in einem Receiver, der eine Message-ID atomar reserviert und nur neue Nachrichten verarbeitet.

Diagramm zu Extract Idempotent Receiver

Java-21-Zielcode

Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer;
// Refactoring: Extract Idempotent Receiver
// Pattern: Idempotent Receiver / Inbox - atomare Reservierung verhindert Doppelverarbeitung.
public final class IdempotentReceiver {
  private final Set<String> processed=ConcurrentHashMap.newKeySet();
  public boolean receive(String messageId,Runnable action){
    if(messageId==null||messageId.isBlank()) throw new IllegalArgumentException("messageId");
    if(!processed.add(messageId)) return false;
    try { action.run(); return true; } catch(RuntimeException ex){ processed.remove(messageId); throw ex; }
  }
  public boolean wasProcessed(String messageId){ return processed.contains(messageId); }
}

JUnit-Vertrag

JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test;
class IdempotentReceiverTest { @Test void processes_only_once(){ var c=new AtomicInteger(); var r=new IdempotentReceiver(); assertTrue(r.receive("m1",c::incrementAndGet)); assertFalse(r.receive("m1",c::incrementAndGet)); assertEquals(1,c.get()); } }

Einsatzgrenze: Das Refactoring lohnt sich, wenn die jeweilige Integrationsregel mehrfach vorkommt, fachliche Risiken trägt oder separat getestet werden muss. Für einen einmaligen, trivialen Datenfluss wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Ein Consumer führt Seiteneffekte bei jeder Zustellung erneut aus, obwohl Broker und Netzwerk Nachrichten mehrfach liefern können.

Erkennungsmerkmale

Doppelte Buchung oder E-Mail; Consumer vertraut exactly once; MessageId wird nur geloggt; dedup check und Side Effect sind nicht atomar; Retries wiederholen teilweise abgeschlossene Arbeit.

Ziel und Nutzen

Ein Idempotent Receiver prüft einen stabilen Message Identifier in einer Inbox und koppelt Deduplizierung mit der fachlichen Transaktion oder einem wiederholbaren Ergebnis.

Sicherer Refactoring-Ablauf

  1. Stabile Nachrichtenidentität und gewünschte Idempotenzdomäne bestimmen.
  2. InboxPort mit atomarem reserve/complete oder transaktionalem insert einführen.
  3. Handlerresultat und Side Effect in dieselbe Konsistenzgrenze bringen.
  4. Duplicate-Entscheidung explizit zurückgeben.
  5. Crashpunkte, Parallelzustellung und Retention testen.

Wann sinnvoll - und wann nicht

Sinnvoll für mindestens-einmal-Zustellung und externe Webhooks. Wenn eine Operation bereits mathematisch idempotent ist, kann zusätzliche Inbox unnötig sein.

Risiken und typische Fehlanwendungen

Check-then-act ohne Transaktion ist racy. Zu kurze Retention lässt alte Duplikate erneut wirken. MessageId eines Providers kann nur innerhalb eines Scopes eindeutig sein.

Abgrenzung

Idempotent Receiver schützt die Verarbeitung. Idempotency Key schützt meist einen API-Befehl; Inbox ist eine typische Persistenztechnik.

Verwandte Refactorings und Muster

Idempotent Receiver, Inbox Pattern, Transaction Boundary, Delivery Attempt und Idempotency Key.

Enterprise-Java-Einordnung

PaymentWebhookReceiver reserviert ProviderEventId innerhalb ProviderAccountId, verbucht die Zahlung und markiert den InboxRecord atomar abgeschlossen.

Teststrategie

Paralleltests prüfen eine Wirkung bei vielen gleichen Zustellungen. Crash-Injection testet Reserve, Side Effect und Complete; Retentiontests prüfen definierte Wiederaufnahme.

Deutschsprachige Fachgrundlagen

152. Introduce Event Envelope Event Envelope

Ausgangsproblem

Payload, Typ, Version, Korrelation und Zeitpunkt werden als lose Header weitergereicht.

Sichere Transformation

Führe einen unveränderlichen Envelope ein, der technische Metadaten und fachliche Payload sauber trennt.

Diagramm zu Introduce Event Envelope

Java-21-Zielcode

Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant; import java.util.*;
// Refactoring: Introduce Event Envelope
// Pattern: Event Envelope - technische Metadaten werden vom fachlichen Payload getrennt.
public record EventEnvelope<T>(UUID eventId,String type,int version,String correlationId,Instant occurredAt,T payload,Map<String,String> headers){
  public EventEnvelope { Objects.requireNonNull(eventId); if(type==null||type.isBlank()) throw new IllegalArgumentException("type"); if(version<1) throw new IllegalArgumentException("version"); Objects.requireNonNull(occurredAt); Objects.requireNonNull(payload); headers=Map.copyOf(headers); }
  public EventEnvelope<T> withHeader(String key,String value){ var h=new LinkedHashMap<>(headers); h.put(key,value); return new EventEnvelope<>(eventId,type,version,correlationId,occurredAt,payload,h); }
}

JUnit-Vertrag

JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.Instant; import java.util.*; import org.junit.jupiter.api.Test;
class EventEnvelopeTest { @Test void header_update_is_immutable(){ var a=new EventEnvelope<>(UUID.randomUUID(),"OrderPlaced",1,"c1",Instant.EPOCH,"payload",Map.of()); var b=a.withHeader("tenant","t1"); assertTrue(a.headers().isEmpty()); assertEquals("t1",b.headers().get("tenant")); } }

Einsatzgrenze: Das Refactoring lohnt sich, wenn die jeweilige Integrationsregel mehrfach vorkommt, fachliche Risiken trägt oder separat getestet werden muss. Für einen einmaligen, trivialen Datenfluss wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Fachdaten, Transportheader und Betriebsmetadaten werden als lose Parameter oder untypisierte Map weitergegeben. Jeder Consumer interpretiert Version, Zeit und Korrelation anders.

Erkennungsmerkmale

Map<String,Object> headers; payload ohne EventType; unterschiedliche Timestampfelder; Trace- und Tenantdaten in Fachpayload; keine klare SchemaVersion.

Ziel und Nutzen

Ein Event Envelope trennt unveränderlich Payload von standardisierten Metadaten wie EventId, Type, SchemaVersion, occurredAt, correlationId und causationId.

Sicherer Refactoring-Ablauf

  1. Benötigte gemeinsame Metadaten und Datenschutzgrenzen festlegen.
  2. Typisierten generischen oder domänenspezifischen Envelope entwerfen.
  3. Brokerheader am Adapter in den Envelope übersetzen.
  4. Consumer auf Envelope statt Rohmap umstellen.
  5. Versionierungs-, Serialisierungs- und Kompatibilitätstests ergänzen.

Wann sinnvoll - und wann nicht

Sinnvoll bei mehreren Eventtypen und Integrationskanälen. Für einen lokalen Methodenaufruf ist ein Envelope meist unnötig.

Risiken und typische Fehlanwendungen

Ein überladener Envelope wird zum globalen Kontextcontainer. Fachliche Information gehört in die Payload; sensible Betriebsdaten dürfen nicht unkontrolliert propagiert werden.

Abgrenzung

Envelope Wrapper ergänzt Transportmetadaten. Domain Event beschreibt fachliche Tatsache; Integration Event ist veröffentlichter Vertrag.

Verwandte Refactorings und Muster

Event Envelope, Envelope Wrapper, Correlation Identifier, Schema Version und Integration Event.

Enterprise-Java-Einordnung

EventEnvelope<OrderShippedV2> trägt EventId, schemaVersion=2, occurredAt und correlationId; der Handler erhält eine klar typisierte Payload.

Teststrategie

Round-Trip-Tests prüfen Serialisierung. Contract-Tests sichern Pflichtmetadaten und alte Versionen; Securitytests prüfen erlaubte Header.

Deutschsprachige Fachgrundlagen

153. Extract Delivery Guarantee Policy Policy / Delivery Semantics

Ausgangsproblem

At-most-once, at-least-once und Retry-Verhalten sind als verstreute Booleans implementiert.

Sichere Transformation

Kapsle Zustellsemantik und zulässige Wiederholungen in einer typisierten Policy.

Diagramm zu Extract Delivery Guarantee Policy

Java-21-Zielcode

Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Delivery Guarantee Policy
// Pattern: Policy - Zustellsemantik wird explizit und testbar.
public record DeliveryGuaranteePolicy(Mode mode,int maxAttempts){
  public enum Mode { AT_MOST_ONCE, AT_LEAST_ONCE }
  public DeliveryGuaranteePolicy { if(mode==null) throw new IllegalArgumentException("mode"); if(maxAttempts<1) throw new IllegalArgumentException("maxAttempts"); if(mode==Mode.AT_MOST_ONCE && maxAttempts!=1) throw new IllegalArgumentException("at-most-once requires one attempt"); }
  public boolean mayRetry(int completedAttempts){ return mode==Mode.AT_LEAST_ONCE && completedAttempts<maxAttempts; }
}

JUnit-Vertrag

JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class DeliveryGuaranteePolicyTest { @Test void retries_only_for_at_least_once(){ var p=new DeliveryGuaranteePolicy(DeliveryGuaranteePolicy.Mode.AT_LEAST_ONCE,3); assertTrue(p.mayRetry(1)); assertFalse(p.mayRetry(3)); } }

Einsatzgrenze: Das Refactoring lohnt sich, wenn die jeweilige Integrationsregel mehrfach vorkommt, fachliche Risiken trägt oder separat getestet werden muss. Für einen einmaligen, trivialen Datenfluss wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Zustellgarantien entstehen zufällig aus Brokerkonfiguration, Ack-Zeitpunkt und Retrycode. Teams sprechen von exactly once, obwohl End-to-End-Verhalten nicht definiert ist.

Erkennungsmerkmale

Ack vor oder nach Side Effect ohne Dokumentation; automatische Retries; gemischte at-most/at-least-once-Pfade; keine Idempotenz; DLQ und Timeout widersprechen sich.

Ziel und Nutzen

Eine Delivery Guarantee Policy beschreibt pro Nachrichtenklasse bewusst Ack-Zeitpunkt, Wiederholung, Deduplizierung, Persistenz und zulässigen Verlust.

Sicherer Refactoring-Ablauf

  1. Geschäftliche Folgen von Verlust und Duplikat bestimmen.
  2. Garantie als explizite Policy und Decision Record dokumentieren.
  3. Ack, Inbox/Outbox und Retry entsprechend ausrichten.
  4. Adapterkonfiguration aus der Policy ableiten.
  5. Fehler- und Crashpunkte End-to-End testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei unterschiedlichen Kritikalitäten und mehreren Brokertechnologien. Eine Policy kann physikalische Garantien nicht herbeizaubern; sie muss reale Grenzen benennen.

Risiken und typische Fehlanwendungen

Exactly once wird oft fälschlich versprochen. Deduplizierung hat Retentiongrenzen; externe Side Effects können zusätzliche Idempotenz benötigen. Höhere Garantie erhöht Latenz und Kosten.

Abgrenzung

At-most-once, at-least-once und effectively-once beschreiben Wirkung. Delivery Policy bündelt die dafür nötigen technischen und fachlichen Entscheidungen.

Verwandte Refactorings und Muster

Delivery Semantics, Inbox, Outbox, Idempotent Receiver, Ack Policy und Dead Letter Policy.

Enterprise-Java-Einordnung

LedgerPostingEvent nutzt at-least-once plus Inbox und transaktionale Buchung; TelemetrySample darf nach definiertem Budget at-most-once verarbeitet werden.

Teststrategie

Failure-Injection-Tests unterbrechen vor/nach Ack und Side Effect. Contract-Tests prüfen Adapterkonfiguration; Recoverytests zählen Verlust und Duplikate.

Deutschsprachige Fachgrundlagen

154. Introduce Consumer Offset Checkpoint / Offset Value Object

Ausgangsproblem

Partition und Offset werden als primitive Zahlen verteilt und versehentlich vertauscht.

Sichere Transformation

Führe ein Value Object für Partition und Offset ein und mache Fortschritt nur monoton fortschreibbar.

Diagramm zu Introduce Consumer Offset

Java-21-Zielcode

Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Consumer Offset
// Pattern: Checkpoint Value Object - Fortschritt wird typisiert und monoton modelliert.
public record ConsumerOffset(int partition,long value){
  public ConsumerOffset { if(partition<0||value<0) throw new IllegalArgumentException(); }
  public ConsumerOffset advanceTo(long next){ if(next<value) throw new IllegalArgumentException("offset regression"); return new ConsumerOffset(partition,next); }
}

JUnit-Vertrag

JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class ConsumerOffsetTest { @Test void offset_cannot_move_backwards(){ var o=new ConsumerOffset(2,10); assertEquals(12,o.advanceTo(12).value()); assertThrows(IllegalArgumentException.class,()->o.advanceTo(9)); } }

Einsatzgrenze: Das Refactoring lohnt sich, wenn die jeweilige Integrationsregel mehrfach vorkommt, fachliche Risiken trägt oder separat getestet werden muss. Für einen einmaligen, trivialen Datenfluss wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Fortschritt eines Consumers wird als primitive Zahl, statisches Feld oder impliziter Brokerzustand behandelt. Replay, Neustart und Partitionierung sind dadurch schwer kontrollierbar.

Erkennungsmerkmale

long lastId in Service; Offset wird vor Verarbeitung gespeichert; Partition fehlt; Reset erfolgt manuell; unterschiedliche Consumer teilen unbeabsichtigt denselben Fortschritt.

Ziel und Nutzen

Ein Consumer Offset Value Object identifiziert Stream, Partition, Consumergruppe und Position. Ein Offset Store legt Fortschritt erst gemäß definierter Commit Policy fest.

Sicherer Refactoring-Ablauf

  1. Fortschrittsdomäne und Partitionen explizit bestimmen.
  2. Offset als typisierten unveränderlichen Wert modellieren.
  3. OffsetStorePort mit compare-and-set oder transaktionalem Commit einführen.
  4. Verarbeitungs- und Commitreihenfolge definieren.
  5. Replay-, Gap-, Restart- und Paralleltests durchführen.

Wann sinnvoll - und wann nicht

Sinnvoll bei Streams, Pollingquellen und Projection Rebuilds. Bei brokerverwaltetem einfachen Consumer reicht die native Verwaltung, sofern sie als Infrastrukturdetail gekapselt bleibt.

Risiken und typische Fehlanwendungen

Commit vor Wirkung erzeugt Verlust, Commit danach Duplikate. Offset allein garantiert keine fachliche Idempotenz. Reset und Retention können nicht verfügbare Daten adressieren.

Abgrenzung

Offset misst Konsumentenfortschritt. SequenceNumber ordnet Nachrichten; AggregateVersion schützt fachlichen Zustand.

Verwandte Refactorings und Muster

Consumer Offset, Checkpoint, Projection, Idempotent Receiver und Partition Key.

Enterprise-Java-Einordnung

OrderProjectionOffset(StreamId, PartitionId, Position) wird nach erfolgreicher, idempotenter Projektion per compareAndSet gespeichert.

Teststrategie

Restarttests prüfen Wiederaufnahme. Paralleltests sichern CAS; Replaytests prüfen Reset und idempotente Neuberechnung.

Deutschsprachige Fachgrundlagen

155. Extract Subscription Registry Registry / Observer

Ausgangsproblem

Subscriber werden in statischen Listen verwaltet und kennen Transportdetails.

Sichere Transformation

Extrahiere eine Registry mit typisierten Topics, idempotenter Anmeldung und expliziter Veröffentlichung.

Diagramm zu Extract Subscription Registry

Java-21-Zielcode

Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer;
// Refactoring: Extract Subscription Registry
// Pattern: Registry + Observer - Subscriber werden typisiert und entkoppelt verwaltet.
public final class SubscriptionRegistry {
  private final Map<String,Map<String,Consumer<EventEnvelope<?>>>> subscribers=new ConcurrentHashMap<>();
  public void subscribe(String topic,String subscriberId,Consumer<EventEnvelope<?>> handler){ subscribers.computeIfAbsent(topic,k->new ConcurrentHashMap<>()).put(subscriberId,handler); }
  public int publish(String topic,EventEnvelope<?> event){ var map=subscribers.getOrDefault(topic,Map.of()); map.values().forEach(h->h.accept(event)); return map.size(); }
  public void unsubscribe(String topic,String subscriberId){ var map=subscribers.get(topic); if(map!=null) map.remove(subscriberId); }
}

JUnit-Vertrag

JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.Instant; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test;
class SubscriptionRegistryTest { @Test void publishes_to_registered_subscribers(){ var count=new AtomicInteger(); var r=new SubscriptionRegistry(); r.subscribe("orders","s1",e->count.incrementAndGet()); var e=new EventEnvelope<>(UUID.randomUUID(),"OrderPlaced",1,"c",Instant.EPOCH,"x",Map.of()); assertEquals(1,r.publish("orders",e)); assertEquals(1,count.get()); } }

Einsatzgrenze: Das Refactoring lohnt sich, wenn die jeweilige Integrationsregel mehrfach vorkommt, fachliche Risiken trägt oder separat getestet werden muss. Für einen einmaligen, trivialen Datenfluss wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Abonnenten und Eventtypen sind über if-Ketten, Frameworkannotation und Stringnamen verteilt. Vollständigkeit, Mehrfachregistrierung und Lebenszyklus sind nicht sichtbar.

Erkennungsmerkmale

handler map wird an mehreren Stellen verändert; Klassenname als Eventtyp; unbekannte Events werden ignoriert; Tests booten ganzen Container; dynamisches Entfernen ist unsicher.

Ziel und Nutzen

Eine Subscription Registry verwaltet typisierte Zuordnung zwischen EventType und Handlern sowie Regeln für Eindeutigkeit, Priorität und Lebenszyklus.

Sicherer Refactoring-Ablauf

  1. Eventtypen und Handlervertrag typisieren.
  2. Registrierungsregeln und Duplicate-Verhalten festlegen.
  3. Registry im Composition Root aufbauen.
  4. Dispatcher nur gegen die Registry arbeiten lassen.
  5. Vollständigkeit, Reihenfolge und unbekannte Typen testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei erweiterbaren Handlerlandschaften oder Plugins. Bei wenigen statischen Abhängigkeiten ist direkte Konstruktorverdrahtung einfacher.

Risiken und typische Fehlanwendungen

Dynamische Registrierung zur Laufzeit kann Race Conditions und schwer reproduzierbare Systeme erzeugen. Eine globale Registry wird leicht zum Service Locator.

Abgrenzung

Observer beschreibt Benachrichtigung. Registry verwaltet Zuordnung; Dependency Injection erstellt und verdrahtet Handler.

Verwandte Refactorings und Muster

Subscription Registry, Observer, Dispatcher, Strategy Registry und Composition Root.

Enterprise-Java-Einordnung

IntegrationEventSubscriptionRegistry ordnet EventType<OrderShippedV2> einer geordneten Liste von Handlern zu und wird beim Start unveränderlich abgeschlossen.

Teststrategie

Bootstrapping-Tests prüfen doppelte und fehlende Registrierung. Dispatcher-Tests prüfen Reihenfolge, Unknown-Fall und Fehlerisolation.

Deutschsprachige Fachgrundlagen

156. Introduce Message Expiration Expiration Policy / TTL

Ausgangsproblem

Veraltete Nachrichten werden trotz überschrittener Fachfrist verarbeitet.

Sichere Transformation

Kapsle TTL und Ablaufentscheidung mit injizierbarer Clock-Zeit in einem unveränderlichen Objekt.

Diagramm zu Introduce Message Expiration

Java-21-Zielcode

Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*; import java.util.Objects;
// Refactoring: Introduce Message Expiration
// Pattern: Expiration Policy - fachliche Gültigkeit wird explizit geprüft.
public record MessageExpiration(Instant createdAt,Duration ttl){
  public MessageExpiration { Objects.requireNonNull(createdAt); Objects.requireNonNull(ttl); if(ttl.isNegative()||ttl.isZero()) throw new IllegalArgumentException("ttl"); }
  public Instant expiresAt(){ return createdAt.plus(ttl); }
  public boolean isExpired(Instant now){ return !now.isBefore(expiresAt()); }
}

JUnit-Vertrag

JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.*; import org.junit.jupiter.api.Test;
class MessageExpirationTest { @Test void expires_at_boundary(){ var e=new MessageExpiration(Instant.EPOCH,Duration.ofSeconds(10)); assertFalse(e.isExpired(Instant.ofEpochSecond(9))); assertTrue(e.isExpired(Instant.ofEpochSecond(10))); } }

Einsatzgrenze: Das Refactoring lohnt sich, wenn die jeweilige Integrationsregel mehrfach vorkommt, fachliche Risiken trägt oder separat getestet werden muss. Für einen einmaligen, trivialen Datenfluss wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Veraltete Nachrichten werden noch verarbeitet, obwohl ihr fachlicher Wert abgelaufen ist. Broker-TTL, fachliche Gültigkeit und Verarbeitungsdeadline sind nicht unterschieden.

Erkennungsmerkmale

PromotionEvent nach Kampagnenende; Retry läuft stundenlang; TTL als magische Brokerzahl; Consumer kennt occurredAt, aber keine Gültigkeitsregel; abgelaufene Nachricht erzeugt Side Effect.

Ziel und Nutzen

Eine Message Expiration Policy oder ExpiresAt im Envelope macht fachliche Lebensdauer explizit. Consumer trifft vor Verarbeitung eine typisierte Entscheidung über Process, Discard oder Compensate.

Sicherer Refactoring-Ablauf

  1. Fachliche Gültigkeitsdauer je Nachrichtentyp bestimmen.
  2. ExpiresAt oder ExpirationPolicy mit Clock einführen.
  3. Broker-TTL nur als technische Optimierung aus der Policy ableiten.
  4. Consumer vor Side Effects prüfen lassen.
  5. Grenzzeit, Retry und verspätete Zustellung testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei zeitkritischen Befehlen, Angeboten und temporären Daten. Historische Events dürfen oft nicht verfallen, auch wenn ihre Verarbeitung verspätet ist.

Risiken und typische Fehlanwendungen

Broker-TTL kann Nachrichten still entfernen und Audit erschweren. Clockskew und Zeitzonen müssen klar sein. Verwerfen ohne Evidenz kann fachliche Pflichten verletzen.

Abgrenzung

Message Expiration betrifft Lebensdauer einer Nachricht. Deadline begrenzt einen Ablauf; Retention bestimmt Speicherdauer.

Verwandte Refactorings und Muster

Message Expiration, Deadline, Retention Policy, Delivery Attempt und Dead Letter Policy.

Enterprise-Java-Einordnung

ReserveInventoryCommand trägt expiresAt. Nach Ablauf erzeugt der Consumer ExpiredCommandEvidence statt eine späte Reservierung auszuführen.

Teststrategie

Fake-Clock-Tests prüfen exakt vor, bei und nach Ablauf. Integrationstests vergleichen Broker-TTL und fachliche Entscheidung; Audittests sichern Evidenz.

Deutschsprachige Fachgrundlagen

157. Extract Retry Schedule Retry Policy / Backoff Strategy

Ausgangsproblem

Wartezeiten werden als magische Zahlen in Schleifen berechnet.

Sichere Transformation

Extrahiere eine Backoff-Strategie mit deterministischen Verzögerungen und expliziter Obergrenze.

Diagramm zu Extract Retry Schedule

Java-21-Zielcode

Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Duration;
// Refactoring: Extract Retry Schedule
// Pattern: Retry Policy / Strategy - Backoff wird separat berechnet und getestet.
public record RetrySchedule(Duration initial,double multiplier,Duration maximum){
  public RetrySchedule { if(initial==null||maximum==null||initial.isNegative()||initial.isZero()||maximum.compareTo(initial)<0||multiplier<1.0) throw new IllegalArgumentException(); }
  public Duration delayForAttempt(int attempt){ if(attempt<1) throw new IllegalArgumentException("attempt"); double factor=Math.pow(multiplier,attempt-1); long millis=Math.min(maximum.toMillis(),Math.round(initial.toMillis()*factor)); return Duration.ofMillis(millis); }
}

JUnit-Vertrag

JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.Duration; import org.junit.jupiter.api.Test;
class RetryScheduleTest { @Test void caps_exponential_backoff(){ var r=new RetrySchedule(Duration.ofMillis(100),2.0,Duration.ofMillis(250)); assertEquals(100,r.delayForAttempt(1).toMillis()); assertEquals(200,r.delayForAttempt(2).toMillis()); assertEquals(250,r.delayForAttempt(3).toMillis()); } }

Einsatzgrenze: Das Refactoring lohnt sich, wenn die jeweilige Integrationsregel mehrfach vorkommt, fachliche Risiken trägt oder separat getestet werden muss. Für einen einmaligen, trivialen Datenfluss wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Wiederholungsabstände werden in Schleifen, Schedulerjobs oder Brokerheadern ad hoc berechnet. Unterschiedliche Consumer verwenden widersprüchliche Backoff- und Jitterregeln.

Erkennungsmerkmale

Thread.sleep; attempt*1000; keine Obergrenze; alle Instanzen retryen gleichzeitig; Retryzeit wird bei Neustart neu berechnet; Fehlerklasse wird ignoriert.

Ziel und Nutzen

Ein Retry Schedule berechnet aus Versuch, Fehlerklasse und Policy den nächsten zulässigen Zeitpunkt. Backoff, Jitter, Maximum und Stop-Entscheidung werden deterministisch modelliert.

Sicherer Refactoring-Ablauf

  1. Retryfähige Fehler und maximale Versuche definieren.
  2. Backoff-Algorithmus und Jitterquelle abstrahieren.
  3. RetryDecision mit nextEligibleAt oder Stop einführen.
  4. Scheduler/Brokeradapter nur noch die Entscheidung ausführen lassen.
  5. Deterministische Sequenzen und Lastverhalten testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei asynchronen Retries und persistenter Wiederaufnahme. Für einen einzelnen kurzen synchronen Retry genügt eine kleine lokale Policy.

Risiken und typische Fehlanwendungen

Unbegrenzte Retries verstecken dauerhafte Fehler. Zufallsjitter ohne injizierbare Quelle macht Tests instabil. Backoff darf die fachliche Deadline nicht überschreiten.

Abgrenzung

Retry Policy entscheidet ob; Retry Schedule entscheidet wann. Circuit Breaker stoppt Aufrufe anhand gemeinsamer Fehlerlage.

Verwandte Refactorings und Muster

Retry Schedule, Backoff Strategy, Delivery Attempt, Deadline und Circuit Breaker.

Enterprise-Java-Einordnung

ProviderDeliveryRetrySchedule liefert für Attempt 3 bei Timeout einen Zeitpunkt mit exponentiellem Backoff und deterministischem Jitter; bei ValidationError liefert es Stop.

Teststrategie

Fake Clock und feste JitterSource sichern exakte Zeitpunkte. Tabellen prüfen Fehlerklassen, Maximum und Deadline; Lasttests prüfen Entsynchronisierung.

Deutschsprachige Fachgrundlagen

158. Introduce Poison Message Quarantine Dead Letter / Quarantine

Ausgangsproblem

Dauerhaft fehlerhafte Nachrichten blockieren Partitionen oder verschwinden nur im Log.

Sichere Transformation

Führe eine Quarantäne mit Grund, Versuchszahl und Zeitstempel ein.

Diagramm zu Introduce Poison Message Quarantine

Java-21-Zielcode

Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant; import java.util.*;
// Refactoring: Introduce Poison Message Quarantine
// Pattern: Dead Letter Channel / Quarantine - dauerhaft fehlerhafte Nachrichten werden nachvollziehbar isoliert.
public final class PoisonMessageQuarantine {
  public record Entry(String messageId,String reason,int attempts,Instant quarantinedAt){ public Entry { if(messageId==null||messageId.isBlank()||reason==null||reason.isBlank()||attempts<1||quarantinedAt==null) throw new IllegalArgumentException(); } }
  private final List<Entry> entries=new ArrayList<>();
  public void add(Entry entry){ entries.add(entry); }
  public List<Entry> entries(){ return List.copyOf(entries); }
}

JUnit-Vertrag

JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.time.Instant; import org.junit.jupiter.api.Test;
class PoisonMessageQuarantineTest { @Test void stores_auditable_entry(){ var q=new PoisonMessageQuarantine(); q.add(new PoisonMessageQuarantine.Entry("m1","invalid schema",3,Instant.EPOCH)); assertEquals("invalid schema",q.entries().getFirst().reason()); } }

Einsatzgrenze: Das Refactoring lohnt sich, wenn die jeweilige Integrationsregel mehrfach vorkommt, fachliche Risiken trägt oder separat getestet werden muss. Für einen einmaligen, trivialen Datenfluss wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Eine dauerhaft nicht verarbeitbare Nachricht blockiert Partition oder Queue oder wird ohne ausreichende Evidenz verworfen.

Erkennungsmerkmale

Immer dieselbe Nachricht schlägt fehl; Consumer restartet endlos; DLQ enthält nur Rohpayload; keine Klassifizierung; Support kann Nachricht weder sicher untersuchen noch kontrolliert freigeben.

Ziel und Nutzen

Eine Quarantäne trennt Poison Messages vom normalen Fluss, speichert redigierte Evidenz, Fehlerklasse und Wiederaufnahmezustand und erzwingt einen kontrollierten Freigabeprozess.

Sicherer Refactoring-Ablauf

  1. Poison-Kriterien und maximale Versuche definieren.
  2. QuarantineRecord mit MessageId, Schema, Fehler und Redaction modellieren.
  3. Atomaren Übergang aus normalem Kanal in Quarantäne implementieren.
  4. Analyse-, Korrektur- und Replayworkflow festlegen.
  5. Zugriff, Retention, Replay und Audit testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei dauerhaft fehlerhaften oder sicherheitsrelevanten Nachrichten. Transiente Fehler gehören zunächst in Retry, nicht sofort in Quarantäne.

Risiken und typische Fehlanwendungen

Quarantäne darf kein unbeobachteter Friedhof werden. Rohpayload kann sensible Daten enthalten. Replay ohne Idempotenz oder korrigierte Ursache erzeugt erneute Schäden.

Abgrenzung

Dead Letter Channel ist ein Zielkanal. Quarantäne umfasst zusätzlich Evidenz, Zugriffsschutz und kontrollierten Recoveryprozess.

Verwandte Refactorings und Muster

Poison Message, Dead Letter Policy, Quarantine, Retry Schedule und Idempotent Receiver.

Enterprise-Java-Einordnung

UnknownSchemaEvent wird nach klassifiziertem PermanentFailure als QuarantineRecord gespeichert; ein Operator kann nach Schemafix einen signierten ReplayCommand auslösen.

Teststrategie

Integrationstests prüfen atomaren Transfer und Redaction. Authorization-Tests sichern Zugriff; Replaytests prüfen Idempotenz und vollständigen Audittrail.

Deutschsprachige Fachgrundlagen

159. Extract Schema Compatibility Checker Compatibility Policy

Ausgangsproblem

Schemaänderungen werden erst beim Consumer-Laufzeitfehler entdeckt.

Sichere Transformation

Extrahiere eine Policy, die additive und brechende Änderungen vor Veröffentlichung klassifiziert.

Diagramm zu Extract Schema Compatibility Checker

Java-21-Zielcode

Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Set;
// Refactoring: Extract Schema Compatibility Checker
// Pattern: Compatibility Policy - Vertragsänderungen werden vor Auslieferung bewertet.
public final class SchemaCompatibilityChecker {
  public enum Result { COMPATIBLE, BREAKING }
  public Result backwardCompatible(Set<String> previousRequired,Set<String> nextRequired){
    if(previousRequired==null||nextRequired==null) throw new IllegalArgumentException();
    return nextRequired.equals(previousRequired) || previousRequired.containsAll(nextRequired) ? Result.COMPATIBLE : Result.BREAKING;
  }
}

JUnit-Vertrag

JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import java.util.Set; import org.junit.jupiter.api.Test;
class SchemaCompatibilityCheckerTest { @Test void new_required_field_is_breaking(){ var c=new SchemaCompatibilityChecker(); assertEquals(SchemaCompatibilityChecker.Result.BREAKING,c.backwardCompatible(Set.of("id"),Set.of("id","tenant"))); assertEquals(SchemaCompatibilityChecker.Result.COMPATIBLE,c.backwardCompatible(Set.of("id","tenant"),Set.of("id"))); } }

Einsatzgrenze: Das Refactoring lohnt sich, wenn die jeweilige Integrationsregel mehrfach vorkommt, fachliche Risiken trägt oder separat getestet werden muss. Für einen einmaligen, trivialen Datenfluss wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Schemaänderungen werden anhand von Entwicklerintuition oder erst durch fehlschlagende Consumer erkannt. Kompatibilitätsregeln sind nicht automatisiert.

Erkennungsmerkmale

Feld wird umbenannt oder Pflichtfeld hinzugefügt; alte Fixtures fehlen; Registry prüft nur Syntax; Producer und Consumer deployen unabhängig; Breaking Change fällt in Produktion auf.

Ziel und Nutzen

Ein Schema Compatibility Checker bewertet alte und neue Vertragsversion nach expliziten Regeln wie backward, forward oder full compatibility und liefert nachvollziehbare Verstöße.

Sicherer Refactoring-Ablauf

  1. Unterstützte Kompatibilitätsrichtung je Vertrag definieren.
  2. Schema- und Semantikregeln trennen.
  3. CheckerPort und CompatibilityReport einführen.
  4. CI/Registry-Gate vor Veröffentlichung integrieren.
  5. Historische Versionen und bekannte Breaking Changes testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei versionierten Events und unabhängigen Deployments. Bei gemeinsam veröffentlichtem internem Modul kann Compile-Time-Kompatibilität ausreichen.

Risiken und typische Fehlanwendungen

Reine Schemakompatibilität erkennt keine semantische Bedeutungsänderung. Defaultwerte können fachlich falsch sein. Zu viele historische Versionen erhöhen Prüfkosten.

Abgrenzung

Schema Checker prüft strukturelle Verträge. Event Version Translator ermöglicht Verarbeitung alter Versionen; Consumer-Driven Contract prüft konkrete Erwartungen.

Verwandte Refactorings und Muster

Schema Compatibility, Event Versioning, Contract Test, Upcaster und Published Language.

Enterprise-Java-Einordnung

OrderEventCompatibilityChecker meldet, dass removal von currency backward-incompatible ist und dokumentiert betroffene ConsumerCapabilities.

Teststrategie

Fixturetests enthalten erlaubte und verbotene Evolutionen. CI-Tests prüfen alle veröffentlichten Versionen; semantische Contract-Tests ergänzen Strukturprüfung.

Deutschsprachige Fachgrundlagen

160. Introduce Integration Event Mapper Domain Event to Integration Event Mapper

Ausgangsproblem

Domänenereignisse werden direkt serialisiert und veröffentlichen interne Modellstrukturen.

Sichere Transformation

Führe einen Mapper ein, der interne Domain Events in versionierte externe Verträge übersetzt.

Diagramm zu Introduce Integration Event Mapper

Java-21-Zielcode

Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant; import java.util.*;
// Refactoring: Introduce Integration Event Mapper
// Pattern: Domain Event to Integration Event Mapper / Anti-Corruption Layer.
public final class IntegrationEventMapper {
  public record OrderPlaced(String orderId,long totalCents) { public OrderPlaced { if(orderId==null||orderId.isBlank()||totalCents<0) throw new IllegalArgumentException(); } }
  public EventEnvelope<Map<String,Object>> map(OrderPlaced event,String correlationId){
    var payload=Map.<String,Object>of("orderId",event.orderId(),"totalCents",event.totalCents());
    return new EventEnvelope<>(UUID.randomUUID(),"order.placed",1,correlationId,Instant.now(),payload,Map.of("source","orders"));
  }
}

JUnit-Vertrag

JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
class IntegrationEventMapperTest { @Test void hides_internal_type_behind_versioned_contract(){ var e=new IntegrationEventMapper().map(new IntegrationEventMapper.OrderPlaced("O-1",990),"c1"); assertEquals("order.placed",e.type()); assertEquals(1,e.version()); assertEquals("O-1",e.payload().get("orderId")); } }

Einsatzgrenze: Das Refactoring lohnt sich, wenn die jeweilige Integrationsregel mehrfach vorkommt, fachliche Risiken trägt oder separat getestet werden muss. Für einen einmaligen, trivialen Datenfluss wäre die zusätzliche Abstraktion unnötig.

Ausführliche fachliche Vertiefung

Fachliches Ausgangsproblem

Domain Events werden unverändert nach außen veröffentlicht oder Integrationsverträge direkt im Aggregat erzeugt. Interne Modelländerungen brechen dadurch externe Consumer.

Erkennungsmerkmale

Aggregate serialisiert sich selbst; interne Entityreferenzen im Event; vertrauliche Felder werden veröffentlicht; externe Schemanamen stehen im Domainmodul; jede Änderung zwingt Consumerupdate.

Ziel und Nutzen

Ein Integration Event Mapper übersetzt interne Domain Events in bewusst veröffentlichte, versionierte Integration Events. Er filtert, normalisiert und ergänzt nur erlaubte Kontextdaten.

Sicherer Refactoring-Ablauf

  1. Interne Tatsache und extern benötigten Vertrag getrennt beschreiben.
  2. Published Integration Event mit Ownership und Version definieren.
  3. Mapper an Application-/Outbox-Grenze einführen.
  4. Datenschutz, Normalisierung und IDs explizit behandeln.
  5. Domain- und Integrationsevolution unabhängig testen.

Wann sinnvoll - und wann nicht

Sinnvoll bei externen oder modulübergreifenden Consumerverträgen. Innerhalb eines kleinen Moduls kann das Domain Event direkt genügen.

Risiken und typische Fehlanwendungen

Der Mapper darf keine neue Fachentscheidung treffen oder Daten nachladen, die den Eventzeitpunkt verfälschen. Ein universeller Mapper koppelt alle Domänen erneut zentral.

Abgrenzung

Domain Event ist internes Fachsignal. Integration Event ist veröffentlichter Vertrag; Message Translator übersetzt meist fremde Formate.

Verwandte Refactorings und Muster

Domain Event, Integration Event, Outbox, Mapper, Published Language und Anti-Corruption Layer.

Enterprise-Java-Einordnung

PolicyIssuedDomainEvent wird zu PolicyIssuedV2 mit PolicyId, productCode, premium und occurredAt; interne UnderwritingDetails bleiben unveröffentlicht.

Teststrategie

Mappingtests prüfen erlaubte Felder, Version und deterministische Ausgabe. Contract-Tests sichern Consumervertrag; Datenschutztests verhindern interne Datenlecks.

Deutschsprachige Fachgrundlagen

Kapitel 17 Event Store, Historie und Event-Sourcing-Grenzen10 Refactorings

Refactorings 161–170 machen Event-Persistenz, Versionierung, Snapshots, Projektionen, Konflikte, CDC, Replikation und Löschsignale explizit.

Refactoring-Katalog · Abschnitt 2

Event-Sourcing- und Replikations-Refactorings

Refactorings 161–170 machen Event-Persistenz, Versionierung, Snapshots, Projektionen, Konflikte, CDC, Replikation und Löschsignale explizit.

170 von 300 Refactorings abgeschlossen

130 Refactorings offen.

161. Extract Event Store

Diagramm zu Extract Event Store

Fachliche Einordnung

Ereignisse werden in beliebigen Tabellen, Logdateien oder technischen Queues abgelegt. Schreiben, Stream-Lesen, Versionierung und Konfliktprüfung sind über Services verteilt. Ein expliziter Event Store bildet Append-only-Schreiben, geordnetes Lesen und erwartete Version als eine konsistente Persistenzgrenze ab.

Erkennungsmerkmale

  • mehrere Persistenzpfade für dieselbe Ereignisfolge
  • Aggregate werden aus ungeordneten Datensätzen rekonstruiert
  • Versionskonflikte werden erst durch Datenfehler sichtbar

Sicherer Ablauf

  1. bestehende Ereignisreihenfolge und Identität durch Charakterisierungstests sichern
  2. Event Envelope mit Aggregate-ID, Version, Typ und Nutzdaten definieren
  3. append(streamId, expectedVersion, events) als atomaren Vertrag einführen
  4. Lesen ausschließlich über geordnete Streams führen
  5. Konflikt-, Replay- und Wiederanlauftests ergänzen

Wann sinnvoll - und wann nicht?

Sinnvoll, wenn Ereignisse die maßgebliche Historie eines Aggregats bilden und Auditierbarkeit oder Replay benötigt werden. Für gewöhnliches CRUD ohne fachliche Historie wäre der Aufwand meist überzogen.

Risiken und Fehlanwendungen

Ein Event Store ist keine allgemeine Logablage. Unveränderlichkeit, Reihenfolge, Schemaentwicklung und Zugriffsschutz müssen verbindlich sein; spätere Korrekturen erfolgen durch neue Ereignisse, nicht durch stilles Überschreiben.

Enterprise-Java-Beispiel

Ein Order-Aggregat wird aus OrderCreated, ItemAdded und OrderConfirmed rekonstruiert. Der erwartete Versionswert verhindert, dass zwei parallele Bestätigungen denselben Stand überschreiben.

Abgrenzung und verwandte Muster

Event Sourcing, Repository, Event Envelope, Aggregate Version, Snapshot Store.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Event Store
// Pattern: Event Store / Repository - Append und Stream-Lesen bilden eine klare Persistenzgrenze.
public final class EventStore {
  public record StoredEvent(String aggregateId,long version,String type,String payload){ public StoredEvent { if(aggregateId==null||aggregateId.isBlank()||version<1||type==null||type.isBlank()) throw new IllegalArgumentException(); } }
  private final Map<String,List<StoredEvent>> streams=new HashMap<>();
  public synchronized void append(String id,long expectedVersion,List<StoredEvent> events){ var current=streams.computeIfAbsent(id,k->new ArrayList<>()); if(current.size()!=expectedVersion) throw new IllegalStateException("version conflict"); long v=expectedVersion; for(var e:events) current.add(new StoredEvent(id,++v,e.type(),e.payload())); }
  public synchronized List<StoredEvent> load(String id){ return List.copyOf(streams.getOrDefault(id,List.of())); }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class EventStoreTest { @Test void contract(){ var s=new EventStore(); s.append("o1",0,java.util.List.of(new EventStore.StoredEvent("o1",1,"CREATED","{}"))); org.junit.jupiter.api.Assertions.assertEquals(1,s.load("o1").size()); org.junit.jupiter.api.Assertions.assertThrows(IllegalStateException.class,()->s.append("o1",0,java.util.List.of())); } }
Teststrategie: Vertragstests prüfen atomisches Append, Versionskonflikt, stabile Reihenfolge, unveränderte Historie und deterministisches Replay.

Deutschsprachige Fachgrundlagen

162. Introduce Aggregate Version

Diagramm zu Introduce Aggregate Version

Fachliche Einordnung

Parallele Befehle arbeiten auf demselben Aggregatzustand, ohne dass der veraltete Stand beim Speichern erkennbar ist. Eine explizite Aggregate-Version wird Bestandteil des Lade- und Speichervertrags und dient als Optimistic-Concurrency-Token.

Erkennungsmerkmale

  • Last-write-wins überschreibt fachliche Änderungen
  • Konflikte treten nur unter Last auf
  • Versionswerte sind technische Zufallsfelder ohne Domänenvertrag

Sicherer Ablauf

  1. Version beim Laden gemeinsam mit dem Aggregat zurückgeben
  2. Befehl gegen genau diese Version ausführen
  3. beim Speichern expectedVersion vergleichen
  4. bei Konflikt einen typisierten Fehler statt stiller Wiederholung liefern
  5. fachlich entscheiden, ob neu laden, zusammenführen oder ablehnen

Wann sinnvoll - und wann nicht?

Sinnvoll bei konkurrierenden Änderungen mit niedriger bis mittlerer Konfliktrate. Bei exklusiver Bearbeitung oder sehr hoher Konfliktdichte kann ein anderer Koordinationsmechanismus geeigneter sein.

Risiken und Fehlanwendungen

Automatisches Retry kann eine fachlich nicht mehr gültige Entscheidung erneut ausführen. Die Version schützt nur den technischen Stand; Konfliktauflösung bleibt eine Fachentscheidung.

Enterprise-Java-Beispiel

Zwei Sachbearbeiter ändern denselben Schadenfall. Die zweite Speicherung erkennt, dass die Freigabe inzwischen erfolgt ist, und verlangt eine neue Bewertung.

Abgrenzung und verwandte Muster

Optimistic Locking, Event Store, Conflict Resolver, Unit of Work.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Aggregate Version
// Pattern: Optimistic Concurrency Token - konkurrierende Commits werden sichtbar.
public record AggregateVersion(long value){
  public AggregateVersion { if(value<0) throw new IllegalArgumentException("value"); }
  public AggregateVersion next(){ return new AggregateVersion(value+1); }
  public void requireExpected(AggregateVersion expected){ if(!equals(expected)) throw new IllegalStateException("stale aggregate"); }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class AggregateVersionTest { @Test void contract(){ var v=new AggregateVersion(2); org.junit.jupiter.api.Assertions.assertEquals(3,v.next().value()); org.junit.jupiter.api.Assertions.assertThrows(IllegalStateException.class,()->v.requireExpected(new AggregateVersion(1))); } }
Teststrategie: Paralleltests erzeugen zwei Befehle auf derselben Version und erwarten genau einen Erfolg sowie einen klaren Konflikt.

Deutschsprachige Fachgrundlagen

163. Extract Snapshot Store

Diagramm zu Extract Snapshot Store

Fachliche Einordnung

Das Wiederherstellen eines Aggregats erfordert das Abspielen einer sehr langen Ereignishistorie und belastet Latenz sowie Infrastruktur. Ein getrenntes Snapshot Store speichert einen überprüfbaren Zustandsstand samt Event-Version; danach werden nur neuere Ereignisse nachgespielt.

Erkennungsmerkmale

  • Replay-Zeit wächst kontinuierlich
  • häufig gelesene Aggregate besitzen tausende Ereignisse
  • Snapshot-Logik liegt verstreut in Repositories

Sicherer Ablauf

  1. Snapshot als abgeleitete Optimierung definieren, nicht als neue Wahrheit
  2. Schema, Aggregate-ID und abgedeckte Version speichern
  3. beim Laden jüngsten kompatiblen Snapshot wählen
  4. Ereignisse nach Snapshot-Version nachspielen
  5. Snapshots jederzeit lösch- und reproduzierbar halten

Wann sinnvoll - und wann nicht?

Sinnvoll bei langen Streams und messbarer Replay-Latenz. Bei kurzen Historien erhöht Snapshotting nur Komplexität.

Risiken und Fehlanwendungen

Veraltete oder inkompatible Snapshots dürfen den Event Stream nicht verfälschen. Snapshot-Migration muss entweder explizit erfolgen oder durch Neuaufbau ersetzt werden.

Enterprise-Java-Beispiel

Ein Vertragskonto mit mehrjähriger Historie wird aus einem monatlichen Snapshot plus den Ereignissen des laufenden Monats rekonstruiert.

Abgrenzung und verwandte Muster

Event Sourcing, Snapshot, Schema Compatibility, Materialized View.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Snapshot Store
// Pattern: Snapshot / Repository - Event-Replay wird durch versionierte Snapshots begrenzt.
public final class SnapshotStore {
  public record Snapshot(String aggregateId,long version,Map<String,String> state){ public Snapshot { state=Map.copyOf(state); } }
  private final Map<String,Snapshot> data=new HashMap<>();
  public synchronized void save(Snapshot snapshot){ var old=data.get(snapshot.aggregateId()); if(old!=null&&old.version()>snapshot.version()) throw new IllegalStateException("older snapshot"); data.put(snapshot.aggregateId(),snapshot); }
  public synchronized Optional<Snapshot> find(String id){ return Optional.ofNullable(data.get(id)); }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class SnapshotStoreTest { @Test void contract(){ var s=new SnapshotStore(); s.save(new SnapshotStore.Snapshot("a",3,java.util.Map.of("status","OPEN"))); org.junit.jupiter.api.Assertions.assertEquals(3,s.find("a").orElseThrow().version()); } }
Teststrategie: Tests vergleichen Zustand aus vollständigem Replay mit Zustand aus Snapshot plus Restereignissen und prüfen beschädigte oder alte Snapshot-Versionen.

Deutschsprachige Fachgrundlagen

164. Introduce Projection

Diagramm zu Introduce Projection

Fachliche Einordnung

Fachereignisse werden direkt in Controller- oder Reportcode ausgewertet; jede Abfrage rekonstruiert erneut denselben Lesestand. Eine Projection übersetzt Ereignisse deterministisch in ein für einen konkreten Lesezweck optimiertes Modell.

Erkennungsmerkmale

  • UI-Abfragen spielen Events zur Laufzeit nach
  • mehrere Leser duplizieren Transformationslogik
  • Lesemodelle sind eng an Schreibaggregate gekoppelt

Sicherer Ablauf

  1. eine konkrete Abfrage und ihr benötigtes Schema festlegen
  2. Projektor als reine oder idempotente Ereignisverarbeitung extrahieren
  3. Checkpoint beziehungsweise Offset speichern
  4. Rebuild-Pfad aus vollständiger Historie vorsehen
  5. Projektionsverzögerung sichtbar messen

Wann sinnvoll - und wann nicht?

Sinnvoll bei eventbasierten Schreibmodellen oder teuren zusammengesetzten Abfragen. Für einfache transaktionale Tabellen ist eine zusätzliche Projektion oft unnötig.

Risiken und Fehlanwendungen

Projektionen sind abgeleitete Daten und dürfen nicht zur konkurrierenden Schreibwahrheit werden. Reihenfolge, Duplikate und Schemaänderungen müssen behandelt werden.

Enterprise-Java-Beispiel

Aus Bestellereignissen entsteht ein OrderOverview-Lesemodell mit Betrag, Status und Lieferadresse für die Kundenansicht.

Abgrenzung und verwandte Muster

Materialized View, CQRS, Read Model, Consumer Offset.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Projection
// Pattern: Projection / Materialized View - Ereignisse erzeugen ein dediziertes Lesemodell.
public final class OrderProjection {
  private final Map<String,String> status=new HashMap<>(); private final Set<String> applied=new HashSet<>();
  public synchronized boolean apply(String eventId,String orderId,String newStatus){ if(!applied.add(eventId)) return false; status.put(orderId,newStatus); return true; }
  public Optional<String> statusOf(String orderId){ return Optional.ofNullable(status.get(orderId)); }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class OrderProjectionTest { @Test void contract(){ var p=new OrderProjection(); org.junit.jupiter.api.Assertions.assertTrue(p.apply("e1","o1","PAID")); org.junit.jupiter.api.Assertions.assertFalse(p.apply("e1","o1","CANCELLED")); org.junit.jupiter.api.Assertions.assertEquals("PAID",p.statusOf("o1").orElseThrow()); } }
Teststrategie: Replay-, Duplicate- und Out-of-order-Tests prüfen deterministische Ergebnisse und einen vollständigen Rebuild.

Deutschsprachige Fachgrundlagen

165. Extract Read Model

Diagramm zu Extract Read Model

Fachliche Einordnung

Dasselbe Objektmodell soll gleichzeitig komplexe Geschäftsregeln schützen und flexible, schnelle Abfragen bedienen. Ein separates Read Model wird auf die Bedürfnisse einer Abfrage zugeschnitten und unabhängig vom fachlichen Schreibmodell gepflegt.

Erkennungsmerkmale

  • DTOs greifen auf interne Aggregatstrukturen zu
  • Lesen erfordert zahlreiche Joins oder Lazy Loads
  • Schreibmodell wird für Reporting mit technischen Feldern aufgebläht

Sicherer Ablauf

  1. Query-Anwendungsfälle und erforderliche Felder erfassen
  2. eigenes unveränderliches Leseschema definieren
  3. Aktualisierung über Projektion oder kontrollierte Synchronisation einführen
  4. Query-Port vom Schreibrepository trennen
  5. Staleness und Aktualisierungszeitpunkt dokumentieren

Wann sinnvoll - und wann nicht?

Sinnvoll, wenn Lese- und Schreibanforderungen deutlich auseinandergehen. Bei kleinen CRUD-Modellen kann die Trennung unnötige Synchronisationsarbeit erzeugen.

Risiken und Fehlanwendungen

Ein Read Model kann zeitverzögert sein. Benutzeroberflächen und Fachprozesse müssen wissen, wann starke Aktualität erforderlich ist.

Enterprise-Java-Beispiel

Das Zahlungsaggregat schützt Autorisierung und Capture; ein PaymentSearchView liefert dagegen flache Suchdaten für den Support.

Abgrenzung und verwandte Muster

CQRS, Projection, Materialized View, Query Object.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.math.BigDecimal;
// Refactoring: Extract Read Model
// Pattern: CQRS Read Model - Abfragen verwenden ein stabiles, flaches Modell.
public record OrderReadModel(String orderId,String customerName,String status,BigDecimal total){
  public OrderReadModel { if(orderId==null||orderId.isBlank()||customerName==null||status==null||total==null) throw new IllegalArgumentException(); }
  public String summary(){ return orderId+" | "+customerName+" | "+status+" | "+total; }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class OrderReadModelTest { @Test void contract(){ var r=new OrderReadModel("o1","Aydin","PAID",new java.math.BigDecimal("10.00")); org.junit.jupiter.api.Assertions.assertTrue(r.summary().contains("PAID")); } }
Teststrategie: Query-Verträge, Projektions-Replay und definierte Staleness-Szenarien werden unabhängig vom Schreibmodell geprüft.

Deutschsprachige Fachgrundlagen

166. Introduce Conflict Resolver

Diagramm zu Introduce Conflict Resolver

Fachliche Einordnung

Bei parallelen oder replizierten Änderungen wird an vielen Stellen ad hoc entschieden, welcher Wert gewinnt. Ein typisierter Conflict Resolver kapselt Vergleich, Merge oder Eskalation als explizite fachliche Strategie.

Erkennungsmerkmale

  • Last-write-wins ist implizit verteilt
  • Konfliktfälle werden manuell in Datenbankskripten bereinigt
  • verschiedene Felder benötigen unterschiedliche Merge-Regeln

Sicherer Ablauf

  1. Konfliktarten und unverlierbare Informationen klassifizieren
  2. Versionen und Metadaten vollständig an den Resolver übergeben
  3. deterministische Merge-Regeln pro Datentyp definieren
  4. nicht automatisch lösbare Fälle als Decision/Escalation zurückgeben
  5. jede Entscheidung auditierbar protokollieren

Wann sinnvoll - und wann nicht?

Sinnvoll bei Multi-Writer-Replikation, Offline-Änderungen oder parallelen Fachentscheidungen. Vermeidbar, wenn ein klarer Single Writer garantiert ist.

Risiken und Fehlanwendungen

Technische Zeitstempel allein bilden selten fachliche Priorität ab. Ein falscher automatischer Merge kann Daten still verlieren.

Enterprise-Java-Beispiel

Zwei Regionen ändern Kontakt- und Risikodaten eines Kunden. Kontaktfelder lassen sich zusammenführen, eine widersprüchliche Risikoklasse wird zur Prüfung eskaliert.

Abgrenzung und verwandte Muster

Vector Clock, Conflict-Free Merge, Policy, Decision Object.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
// Refactoring: Introduce Conflict Resolver
// Pattern: Strategy - fachliche Konfliktauflösung ist austauschbar und testbar.
public interface ConflictResolver {
  record Candidate(String value,int priority,Instant changedAt){ }
  Candidate resolve(Candidate local,Candidate remote);
  static ConflictResolver priorityThenLatest(){ return (a,b)->a.priority()!=b.priority()?(a.priority()>b.priority()?a:b):(a.changedAt().isAfter(b.changedAt())?a:b); }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ConflictResolverTest { @Test void contract(){ var now=java.time.Instant.parse("2026-01-01T00:00:00Z"); var r=ConflictResolver.priorityThenLatest(); var x=r.resolve(new ConflictResolver.Candidate("A",2,now),new ConflictResolver.Candidate("B",1,now.plusSeconds(5))); org.junit.jupiter.api.Assertions.assertEquals("A",x.value()); } }
Teststrategie: Property- und Reihenfolgetests prüfen Determinismus, Kommutativität soweit gefordert und verlustfreie Eskalation.

Deutschsprachige Fachgrundlagen

167. Extract Consistency Boundary

Diagramm zu Extract Consistency Boundary

Fachliche Einordnung

Unklar ist, welche Änderungen gemeinsam atomar sein müssen und welche nur irgendwann konsistent werden dürfen. Eine benannte Consistency Boundary macht fachliche Invarianten, atomaren Umfang und asynchrone Folgeaktionen explizit.

Erkennungsmerkmale

  • große Transaktionen über mehrere Dienste
  • Teilupdates ohne dokumentierte Invariante
  • Anwendungsservice mischt lokale Transaktion und externe Aufrufe

Sicherer Ablauf

  1. fachliche Invarianten und betroffene Daten identifizieren
  2. kleinste notwendige atomare Grenze bestimmen
  3. externe Systeme aus der lokalen Transaktion herauslösen
  4. Folgeaktionen über Ereignis/Outbox modellieren
  5. Kompensation und sichtbaren Zwischenzustand definieren

Wann sinnvoll - und wann nicht?

Sinnvoll, sobald ein Vorgang mehrere Aggregate, Datenbanken oder Dienste berührt. Innerhalb eines einfachen Aggregats genügt häufig die bestehende Transaktionsgrenze.

Risiken und Fehlanwendungen

Eine zu große Grenze senkt Verfügbarkeit und Skalierbarkeit; eine zu kleine verletzt Invarianten. Eventual Consistency darf nicht als fehlende Fehlerbehandlung missverstanden werden.

Enterprise-Java-Beispiel

Die Reservierung einer Bestellung ist lokal atomar. Versand und Benachrichtigung folgen zuverlässig über Outbox-Ereignisse.

Abgrenzung und verwandte Muster

Aggregate, Transaction Boundary, Outbox, Saga, Unit of Work.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Consistency Boundary
// Pattern: Unit of Work - nur explizit registrierte Änderungen werden gemeinsam committed.
public final class ConsistencyBoundary {
  private final List<Runnable> changes=new ArrayList<>(); private boolean committed;
  public void register(Runnable change){ if(committed) throw new IllegalStateException("already committed"); changes.add(Objects.requireNonNull(change)); }
  public void commit(){ if(committed) throw new IllegalStateException("already committed"); changes.forEach(Runnable::run); committed=true; }
  public int pending(){ return committed?0:changes.size(); }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ConsistencyBoundaryTest { @Test void contract(){ var x=new java.util.concurrent.atomic.AtomicInteger(); var b=new ConsistencyBoundary(); b.register(x::incrementAndGet); org.junit.jupiter.api.Assertions.assertEquals(1,b.pending()); b.commit(); org.junit.jupiter.api.Assertions.assertEquals(1,x.get()); } }
Teststrategie: Invarianten-, Crash- und Wiederanlauftests prüfen, dass lokale Zustände atomar und externe Folgen wiederholbar sind.

Deutschsprachige Fachgrundlagen

168. Introduce Change Data Capture Adapter

Diagramm zu Introduce Change Data Capture Adapter

Fachliche Einordnung

Nachgelagerte Systeme lesen Tabellen direkt oder periodisch vollständig aus, um Änderungen zu erkennen. Ein CDC-Adapter übersetzt das technische Änderungsprotokoll der Datenbank in stabile, typisierte Integrationsereignisse.

Erkennungsmerkmale

  • Polling mit Zeitstempeln übersieht oder dupliziert Änderungen
  • Reporting kennt interne Tabellenschemata
  • Datenbanktrigger enthalten Integrationslogik

Sicherer Ablauf

  1. Quelle und garantierte Reihenfolge des Änderungslogs bestimmen
  2. technisches CDC-Format hinter einem Adapter kapseln
  3. Schema- und Operationsarten in interne Change-Objekte übersetzen
  4. Checkpoint und Wiederaufnahme definieren
  5. fachliche Events nur dann ableiten, wenn Semantik eindeutig ist

Wann sinnvoll - und wann nicht?

Sinnvoll bei Legacy-Datenbanken oder Replikations-/Analysepfaden ohne direkte Ereignispublikation. Für neue Fachprozesse ist eine explizite Outbox oft klarer.

Risiken und Fehlanwendungen

Eine Tabellenänderung ist nicht automatisch ein Domain Event. CDC koppelt an physische Datenmodelle und benötigt Schemaevolution, Datenschutz und Rebuild-Strategie.

Enterprise-Java-Beispiel

Änderungen an einer alten Vertragsdatenbank werden durch den Adapter in ContractChanged-Integrationsnachrichten für das Data Warehouse übersetzt.

Abgrenzung und verwandte Muster

CDC, Adapter, Transactional Outbox, Integration Event Mapper.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Map;
// Refactoring: Introduce Change Data Capture Adapter
// Pattern: Adapter / Anti-Corruption Layer - technische Change-Records werden in Fachänderungen übersetzt.
public final class ChangeDataCaptureAdapter {
  public record Change(String aggregateId,String operation,Map<String,String> fields){ public Change { fields=Map.copyOf(fields); } }
  public Change translate(Map<String,Object> record){ String id=String.valueOf(record.get("id")); String op=String.valueOf(record.get("op")); @SuppressWarnings("unchecked") var fields=(Map<String,String>)record.getOrDefault("after",Map.of()); return new Change(id,op,fields); }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ChangeDataCaptureAdapterTest { @Test void contract(){ var a=new ChangeDataCaptureAdapter(); var c=a.translate(java.util.Map.of("id","o1","op","u","after",java.util.Map.of("status","PAID"))); org.junit.jupiter.api.Assertions.assertEquals("PAID",c.fields().get("status")); } }
Teststrategie: Fixtures verschiedener CDC-Versionen, Checkpoint-Recovery, Delete/Tombstone und Schemaänderungen werden geprüft.

Deutschsprachige Fachgrundlagen

169. Extract Replication Policy

Diagramm zu Extract Replication Policy

Fachliche Einordnung

Replikationsfaktor, Synchronität und Zielknoten werden als verstreute Infrastrukturparameter ohne fachlichen Kontext konfiguriert. Eine Replication Policy beschreibt pro Datenklasse Replikationsfaktor, Bestätigungsanforderung, Regionen und zulässige Verzögerung.

Erkennungsmerkmale

  • jede Komponente nutzt andere Replikationsannahmen
  • kritische und unkritische Daten werden gleich behandelt
  • Failover-Verhalten ist nicht testbar

Sicherer Ablauf

  1. Datenklassen nach Verlust- und Verfügbarkeitsrisiko ordnen
  2. Replikationsziele und Bestätigungsregeln explizit modellieren
  3. Policy vom konkreten Datenbanktreiber trennen
  4. Entscheidung und aktive Konfiguration beobachtbar machen
  5. Fehler- und Degradationsmodi testen

Wann sinnvoll - und wann nicht?

Sinnvoll, wenn mehrere Speicherorte oder Datenklassen unterschiedliche Ziele besitzen. Bei einer einzigen verwalteten Datenbank genügt oft die Plattformkonfiguration.

Risiken und Fehlanwendungen

Eine Policy kann physikalische Grenzen nicht wegabstrahieren. Höhere Replikation erhöht Kosten und kann Schreiblatenz beziehungsweise Konfliktrisiko steigern.

Enterprise-Java-Beispiel

Zahlungsbuchungen benötigen synchrone lokale Mehrfachbestätigung; Produktbilder dürfen asynchron in entfernte Regionen repliziert werden.

Abgrenzung und verwandte Muster

Quorum Policy, Consistency Level, Geo Replication, Data Sovereignty.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Replication Policy
// Pattern: Policy / Strategy - Zielauswahl und Quorum sind unabhängig von Transportcode.
public record ReplicationPolicy(List<String> orderedTargets,int quorum){
  public ReplicationPolicy { orderedTargets=List.copyOf(orderedTargets); if(orderedTargets.isEmpty()||quorum<1||quorum>orderedTargets.size()) throw new IllegalArgumentException(); }
  public boolean successful(Set<String> acknowledged){ return orderedTargets.stream().filter(acknowledged::contains).count()>=quorum; }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ReplicationPolicyTest { @Test void contract(){ var p=new ReplicationPolicy(java.util.List.of("a","b","c"),2); org.junit.jupiter.api.Assertions.assertTrue(p.successful(java.util.Set.of("a","c"))); org.junit.jupiter.api.Assertions.assertFalse(p.successful(java.util.Set.of("a"))); } }
Teststrategie: Entscheidungstabellen prüfen Datenklassen, Regionen, Knotenausfälle und Degradationsreaktionen.

Deutschsprachige Fachgrundlagen

170. Introduce Tombstone

Diagramm zu Introduce Tombstone

Fachliche Einordnung

Gelöschte Datensätze verschwinden lokal, werden aber bei Replikation oder Offline-Synchronisation aus älteren Kopien wiederhergestellt. Ein Tombstone ist ein versionierter Löschmarker, der die Löschentscheidung replizierbar und zeitlich geordnet macht.

Erkennungsmerkmale

  • Hard Delete ohne verteilte Löschinformation
  • gelöschte Einträge tauchen nach Recovery erneut auf
  • Caches und Projektionen kennen keine Löschung

Sicherer Ablauf

  1. Löschung als eigenes Ereignis beziehungsweise Marker modellieren
  2. Identität, Version, Zeitpunkt und Grund aufnehmen
  3. Leser so anpassen, dass Tombstones fachlich unsichtbar sind
  4. Weitergabe an Replikate und Projektionen sicherstellen
  5. Garbage-Collection erst nach bestätigter Verteilung ausführen

Wann sinnvoll - und wann nicht?

Sinnvoll bei replizierten, eventbasierten oder offlinefähigen Datensystemen. In einer lokalen, streng transaktionalen Tabelle kann Soft Delete genügen oder unnötig sein.

Risiken und Fehlanwendungen

Tombstones wachsen ohne Retention unbegrenzt. Zu frühes Entfernen kann gelöschte Daten wiederbeleben; Datenschutzanforderungen müssen mit technischer Replikation abgestimmt werden.

Enterprise-Java-Beispiel

Ein widerrufener API-Schlüssel bleibt als Tombstone erhalten, bis alle Regionen und Caches die Sperre verarbeitet haben.

Abgrenzung und verwandte Muster

Soft Delete, Event Sourcing, Replication, Retention Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
// Refactoring: Introduce Tombstone
// Pattern: Tombstone / Soft Delete Event - Löschung wird replizierbar und versioniert.
public record Tombstone(String aggregateId,long version,Instant deletedAt,String reason){
  public Tombstone { if(aggregateId==null||aggregateId.isBlank()||version<1||deletedAt==null||reason==null||reason.isBlank()) throw new IllegalArgumentException(); }
  public boolean supersedes(long knownVersion){ return version>knownVersion; }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class TombstoneTest { @Test void contract(){ var t=new Tombstone("o1",4,java.time.Instant.parse("2026-01-01T00:00:00Z"),"gdpr"); org.junit.jupiter.api.Assertions.assertTrue(t.supersedes(3)); org.junit.jupiter.api.Assertions.assertFalse(t.supersedes(4)); } }
Teststrategie: Replikations-, verspätete Update-, Restore- und Garbage-Collection-Szenarien sichern, dass gelöschte Werte nicht zurückkehren.

Deutschsprachige Fachgrundlagen

Kapitel 18 Shard Keys und verteilte Datenpartitionierung10 Refactorings

Refactorings 171–180 machen Partitionierung, Quorum, Kausalität, Konfliktauflösung, Failover, Führung, Datenresidenz und Konsistenzlevel explizit.

Refactoring-Katalog · Abschnitt 3

Verteilte Daten- und Konsistenz-Refactorings

Refactorings 171–180 machen Partitionierung, Quorum, Kausalität, Konfliktauflösung, Failover, Führung, Datenresidenz und Konsistenzlevel explizit.

180 von 300 Refactorings abgeschlossen

120 Refactorings offen.

171. Extract Shard Key Strategy

Diagramm zu Extract Shard Key Strategy

Fachliche Einordnung

Die Verteilung von Daten erfolgt durch spontane Hashes oder technische IDs, obwohl Zugriffsmuster und Hotspots fachlich variieren. Eine Shard Key Strategy kapselt die nachvollziehbare Zuordnung eines Datensatzes zu einer Partition und macht ihre Versionierung explizit.

Erkennungsmerkmale

  • einzelne Shards werden dauerhaft überlastet
  • zusammengehörige Daten liegen weit verteilt
  • Shard-Berechnung wird in vielen Repositories dupliziert

Sicherer Ablauf

  1. Zugriffs-, Schreib- und Wachstumsprofile messen
  2. Kandidaten nach Kardinalität, Stabilität und Lokalität bewerten
  3. Strategie als reinen deterministischen Vertrag extrahieren
  4. Version und Migrationspfad für Schlüsseländerungen vorsehen
  5. Hot-Partition- und Rebalancing-Szenarien testen

Wann sinnvoll - und wann nicht?

Sinnvoll bei horizontal partitionierten Datenbeständen. Ohne echte Skalierungs- oder Isolationsanforderung ist Sharding eine unnötige Betriebsbelastung.

Risiken und Fehlanwendungen

Ein falscher Schlüssel lässt sich später nur teuer ändern. Monotone Schlüssel, große Mandanten und globale Abfragen erzeugen Hotspots oder Scatter-Gather.

Enterprise-Java-Beispiel

Bestellungen werden nach TenantId und einem stabilen Bucket verteilt, damit sehr große Mandanten nicht eine einzelne Partition blockieren.

Abgrenzung und verwandte Muster

Sharding, Data Locality Policy, Partition Key Strategy, Rebalancing.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.nio.charset.StandardCharsets;
// Refactoring: Extract Shard Key Strategy
// Pattern: Strategy - Partitionierung wird deterministisch, testbar und austauschbar.
public final class ShardKeyStrategy {
  private final int shardCount;
  public ShardKeyStrategy(int shardCount){ if(shardCount < 1) throw new IllegalArgumentException("shardCount"); this.shardCount=shardCount; }
  public int shardFor(String tenantId,String aggregateId){
    if(tenantId==null||tenantId.isBlank()||aggregateId==null||aggregateId.isBlank()) throw new IllegalArgumentException("key");
    int hash=java.util.Arrays.hashCode((tenantId+":"+aggregateId).getBytes(StandardCharsets.UTF_8));
    return Math.floorMod(hash,shardCount);
  }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ShardKeyStrategyTest { @Test void contract(){ var s=new ShardKeyStrategy(8); int a=s.shardFor("t1","o1"); org.junit.jupiter.api.Assertions.assertEquals(a,s.shardFor("t1","o1")); org.junit.jupiter.api.Assertions.assertTrue(a>=0&&a<8); } }
Teststrategie: Verteilungstests prüfen Determinismus, Balance, Datenlokalität und Verhalten bei neuen Strategieversionen.

Deutschsprachige Fachgrundlagen

172. Introduce Read Repair

Diagramm zu Introduce Read Repair

Fachliche Einordnung

Lesen aus mehreren Replikaten erkennt unterschiedliche Versionen, korrigiert veraltete Kopien jedoch nicht systematisch. Read Repair nutzt einen ohnehin erfolgenden Lesezugriff, um veraltete Replikate kontrolliert auf den gewählten Stand zu bringen.

Erkennungsmerkmale

  • stale reads bleiben dauerhaft bestehen
  • Reparaturjobs müssen komplette Bestände scannen
  • Leselogik wählt zwar neueste Version, verwirft aber Konfliktwissen

Sicherer Ablauf

  1. Versionen oder kausale Metadaten jeder Antwort vergleichen
  2. Gewinner beziehungsweise Merge-Ergebnis deterministisch bestimmen
  3. Antwort an den Client von der Reparatur entkoppeln
  4. Reparatur idempotent und begrenzt ausführen
  5. Fehler, Latenz und Reparaturquote messen

Wann sinnvoll - und wann nicht?

Sinnvoll in eventually-consistent replizierten Speichern mit Mehrfachlesen. Bei stark konsistenten Datenbanken ist es Aufgabe der Plattform.

Risiken und Fehlanwendungen

Read Repair erhöht Lesekosten und darf keine ungelösten Konflikte still überschreiben. Selten gelesene Daten benötigen zusätzlich Anti-Entropy-Prozesse.

Enterprise-Java-Beispiel

Ein Kundenprofil wird von drei Replikaten gelesen; zwei liefern Version 12, eines Version 10. Version 12 wird zurückgegeben und die alte Kopie asynchron aktualisiert.

Abgrenzung und verwandte Muster

Quorum Read, Vector Clock, Conflict Resolver, Anti-Entropy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Read Repair
// Pattern: Read Repair - Lesen erkennt und repariert veraltete Replikate.
public final class ReadRepairService {
  public record ReplicaValue(String replica,long version,String value){ public ReplicaValue { if(replica==null||replica.isBlank()||version<0) throw new IllegalArgumentException(); } }
  public record RepairPlan(ReplicaValue winner,List<String> staleReplicas){ public RepairPlan { staleReplicas=List.copyOf(staleReplicas); } }
  public RepairPlan plan(List<ReplicaValue> values){
    if(values==null||values.isEmpty()) throw new IllegalArgumentException("values");
    var winner=values.stream().max(Comparator.comparingLong(ReplicaValue::version)).orElseThrow();
    var stale=values.stream().filter(v->v.version()<winner.version()).map(ReplicaValue::replica).sorted().toList();
    return new RepairPlan(winner,stale);
  }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ReadRepairServiceTest { @Test void contract(){ var p=new ReadRepairService().plan(java.util.List.of(new ReadRepairService.ReplicaValue("a",1,"old"),new ReadRepairService.ReplicaValue("b",2,"new"))); org.junit.jupiter.api.Assertions.assertEquals("new",p.winner().value()); org.junit.jupiter.api.Assertions.assertEquals(java.util.List.of("a"),p.staleReplicas()); } }
Teststrategie: Tests simulieren veraltete, konkurrierende und nicht erreichbare Replikate sowie idempotente Wiederholung.

Deutschsprachige Fachgrundlagen

173. Extract Quorum Policy

Diagramm zu Extract Quorum Policy

Fachliche Einordnung

Wie viele Replikate Lesen oder Schreiben bestätigen müssen, ist als magische Zahl in Clients und Treibern verstreut. Eine Quorum Policy macht Replikationsfaktor, Lese- und Schreibquorum sowie Degradationsregeln als testbare Entscheidung sichtbar.

Erkennungsmerkmale

  • unterschiedliche R/W-Werte ohne Begründung
  • Verfügbarkeit und Konsistenz ändern sich unbemerkt
  • Timeouts werden mit fachlicher Ablehnung verwechselt

Sicherer Ablauf

  1. Replikationsfaktor und zulässige Ausfälle erfassen
  2. Konsistenzanforderung pro Operation klassifizieren
  3. R- und W-Werte samt Bedingung R+W>N festlegen, wenn aktuelle Überschneidung benötigt wird
  4. Timeout und Teilantwort als eigene Ergebnisse modellieren
  5. Policy in Last- und Partitionstests validieren

Wann sinnvoll - und wann nicht?

Sinnvoll bei Systemen, in denen die Anwendung Quorumparameter beeinflusst. Bei vollständig verwalteter starker Konsistenz bleibt dies Plattformverantwortung.

Risiken und Fehlanwendungen

Quorum garantiert nicht automatisch semantische Konfliktfreiheit. Hohe Quoren erhöhen Latenz und senken Verfügbarkeit während Partitionen.

Enterprise-Java-Beispiel

Kontostandsbuchungen verlangen W=2 von N=3; Produktkatalog-Lesen darf R=1 nutzen und akzeptiert kurze Verzögerung.

Abgrenzung und verwandte Muster

Replication Policy, Consistency Level, Read Repair, CAP.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Quorum Policy
// Pattern: Policy - Konsistenzanforderungen werden explizit und prüfbar.
public record QuorumPolicy(int replicas,int readQuorum,int writeQuorum){
  public QuorumPolicy { if(replicas<1||readQuorum<1||writeQuorum<1||readQuorum>replicas||writeQuorum>replicas) throw new IllegalArgumentException(); }
  public boolean hasStrongOverlap(){ return readQuorum+writeQuorum>replicas; }
  public boolean readSatisfied(int acknowledgements){ return acknowledgements>=readQuorum; }
  public boolean writeSatisfied(int acknowledgements){ return acknowledgements>=writeQuorum; }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class QuorumPolicyTest { @Test void contract(){ var q=new QuorumPolicy(3,2,2); org.junit.jupiter.api.Assertions.assertTrue(q.hasStrongOverlap()); org.junit.jupiter.api.Assertions.assertTrue(q.writeSatisfied(2)); org.junit.jupiter.api.Assertions.assertFalse(q.readSatisfied(1)); } }
Teststrategie: Entscheidungstabellen prüfen N/R/W, Knotenausfälle, Partitionen, Zeitüberschreitungen und aktuelle Versionsermittlung.

Deutschsprachige Fachgrundlagen

174. Introduce Vector Clock

Diagramm zu Introduce Vector Clock

Fachliche Einordnung

Einfache Versionsnummern oder Zeitstempel können nicht unterscheiden, ob zwei Änderungen kausal aufeinander folgen oder unabhängig entstanden sind. Eine Vector Clock hält pro beteiligtem Ursprung einen logischen Zähler und ermöglicht die Erkennung von Vorgänger-, Nachfolger- und nebenläufigen Versionen.

Erkennungsmerkmale

  • Multi-Writer-System mit Last-write-wins
  • Uhrzeit entscheidet über Datenverlust
  • Konflikte erscheinen trotz gleicher Versionsnummer

Sicherer Ablauf

  1. stabile Knoten- oder Writer-Identitäten definieren
  2. Zähler beim lokalen Schreiben erhöhen
  3. beim Empfang komponentenweise Maximum bilden
  4. partielle Ordnung und Concurrent-Fall als API modellieren
  5. Wachstum, Writer-Austritt und Kompaktierung regeln

Wann sinnvoll - und wann nicht?

Sinnvoll bei dezentralen Multi-Writer-Daten mit relevanter Kausalität. Für Single Writer oder zentrale Sequenzierung ist eine einfache Version leichter.

Risiken und Fehlanwendungen

Vektoren wachsen mit Writern und lösen Konflikte nicht selbst; sie erkennen sie nur. Dynamische Identitäten und Datenschutz können die Metadaten erschweren.

Enterprise-Java-Beispiel

Zwei offline arbeitende Außendienstgeräte ändern dieselbe Kundenakte. Die Vector Clock erkennt echte Nebenläufigkeit und übergibt beide Versionen an den Resolver.

Abgrenzung und verwandte Muster

Logical Clock, Conflict Resolver, Multi-Writer Replication, CRDT.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Vector Clock
// Pattern: Version Vector - Kausalität und konkurrierende Änderungen werden explizit.
public final class VectorClock {
  public enum Relation { BEFORE, AFTER, EQUAL, CONCURRENT }
  private final Map<String,Long> counters;
  public VectorClock(Map<String,Long> counters){ this.counters=Map.copyOf(counters); if(this.counters.values().stream().anyMatch(v->v<0)) throw new IllegalArgumentException(); }
  public VectorClock tick(String node){ var copy=new HashMap<>(counters); copy.merge(node,1L,Long::sum); return new VectorClock(copy); }
  public Relation compare(VectorClock other){
    boolean less=false,greater=false; var nodes=new HashSet<>(counters.keySet()); nodes.addAll(other.counters.keySet());
    for(var n:nodes){ long a=counters.getOrDefault(n,0L),b=other.counters.getOrDefault(n,0L); less|=a<b; greater|=a>b; }
    if(less&&greater) return Relation.CONCURRENT; if(less) return Relation.BEFORE; if(greater) return Relation.AFTER; return Relation.EQUAL;
  }
  public Map<String,Long> counters(){ return counters; }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class VectorClockTest { @Test void contract(){ var base=new VectorClock(java.util.Map.of()); var a=base.tick("a"); var b=base.tick("b"); org.junit.jupiter.api.Assertions.assertEquals(VectorClock.Relation.CONCURRENT,a.compare(b)); org.junit.jupiter.api.Assertions.assertEquals(VectorClock.Relation.AFTER,a.tick("a").compare(a)); } }
Teststrategie: Ordnungsrelationen, Merge, Nebenläufigkeit und Replays werden mit bekannten Vektorpaaren geprüft.

Deutschsprachige Fachgrundlagen

175. Extract Conflict-Free Merge

Diagramm zu Extract Conflict-Free Merge

Fachliche Einordnung

Zusammenführung replizierter Daten basiert auf verzweigten Sonderfällen und ist nicht deterministisch oder reihenfolgeunabhängig. Eine klar definierte Merge-Strategie nutzt algebraische Eigenschaften wie Idempotenz, Kommutativität und Assoziativität, um konfliktfreie Zusammenführung zu ermöglichen.

Erkennungsmerkmale

  • Merge-Ergebnis hängt von Ankunftsreihenfolge ab
  • gleiches Ereignisset erzeugt verschiedene Zustände
  • manuelle Konfliktbereinigung bei additiven Daten

Sicherer Ablauf

  1. Datentyp und erlaubte Fachoperationen analysieren
  2. Merge-Invarianten ausdrücklich formulieren
  3. geeignete monotone Repräsentation wählen
  4. Merge als reine Funktion implementieren
  5. Löschung und nicht monotone Regeln separat behandeln

Wann sinnvoll - und wann nicht?

Sinnvoll für Mengen, Zähler, Versionen oder andere Daten mit geeigneter Merge-Semantik. Nicht jede Fachentscheidung ist konfliktfrei automatisierbar.

Risiken und Fehlanwendungen

Ein technisch konfliktfreier Merge kann fachlich falsch sein. CRDT-artige Modelle erhöhen Metadaten und verlangen klare Lösch- beziehungsweise Kompaktierungsregeln.

Enterprise-Java-Beispiel

Tags eines Dokuments werden als add-wins Set zusammengeführt; eine Kreditentscheidung bleibt dagegen eskalationspflichtig.

Abgrenzung und verwandte Muster

CRDT, Conflict Resolver, Vector Clock, Eventual Consistency.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Conflict-Free Merge
// Pattern: Grow-only CRDT - parallele Ergänzungen werden verlustfrei zusammengeführt.
public final class ConflictFreeTagSet {
  private final Set<String> tags;
  public ConflictFreeTagSet(Set<String> tags){ this.tags=Set.copyOf(tags); }
  public ConflictFreeTagSet add(String tag){ if(tag==null||tag.isBlank()) throw new IllegalArgumentException("tag"); var copy=new HashSet<>(tags); copy.add(tag); return new ConflictFreeTagSet(copy); }
  public ConflictFreeTagSet merge(ConflictFreeTagSet other){ var copy=new HashSet<>(tags); copy.addAll(other.tags); return new ConflictFreeTagSet(copy); }
  public Set<String> values(){ return tags; }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ConflictFreeTagSetTest { @Test void contract(){ var a=new ConflictFreeTagSet(java.util.Set.of()).add("vip"); var b=new ConflictFreeTagSet(java.util.Set.of()).add("risk"); var merged=a.merge(b); org.junit.jupiter.api.Assertions.assertEquals(java.util.Set.of("vip","risk"),merged.values()); org.junit.jupiter.api.Assertions.assertEquals(merged.values(),b.merge(a).values()); } }
Teststrategie: Property-Based Tests prüfen Idempotenz, Kommutativität, Assoziativität und Erhaltung fachlicher Invarianten.

Deutschsprachige Fachgrundlagen

176. Introduce Replication Lag Budget

Diagramm zu Introduce Replication Lag Budget

Fachliche Einordnung

Replikationsverzögerung wird nur als technische Metrik betrachtet, obwohl unterschiedliche Fachprozesse verschiedene Aktualitätsgrenzen besitzen. Ein Replication Lag Budget definiert pro Anwendungsfall maximal akzeptable Verzögerung und die Reaktion bei Überschreitung.

Erkennungsmerkmale

  • stale reads verursachen sporadische Fachfehler
  • alle Abfragen nutzen beliebige Replikate
  • kein klarer Schwellenwert für Routing oder Alarmierung

Sicherer Ablauf

  1. fachliche Aktualitätsanforderung pro Query bestimmen
  2. messbare Lag-Metrik und Zeitbezug festlegen
  3. Budget als Value Object oder Policy modellieren
  4. bei Überschreitung auf Primary routen, warten oder ablehnen
  5. Budgetverletzungen getrennt überwachen

Wann sinnvoll - und wann nicht?

Sinnvoll bei asynchroner Replikation und read-after-write-Anforderungen. Bei starker Konsistenz ohne Replikatwahl ist es nicht erforderlich.

Risiken und Fehlanwendungen

Lag-Metriken können ungenau sein. Blindes Fallback auf Primary kann einen Ausfall verstärken; die Reaktion braucht Kapazitäts- und Fehlergrenzen.

Enterprise-Java-Beispiel

Die Auftragsübersicht akzeptiert 30 Sekunden Verzögerung; direkt nach einer Zahlung verlangt die Bestätigungsseite maximal zwei Sekunden und routet sonst zum Primary.

Abgrenzung und verwandte Muster

Read Preference, Consistency Level, SLO, Routing Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Duration;
// Refactoring: Introduce Replication Lag Budget
// Pattern: Service Level Policy - Replikationsverzögerung erhält explizite Betriebsgrenzen.
public record ReplicationLagBudget(Duration warning,Duration critical){
  public enum Status { HEALTHY, DEGRADED, VIOLATED }
  public ReplicationLagBudget { if(warning==null||critical==null||warning.isNegative()||critical.compareTo(warning)<0) throw new IllegalArgumentException(); }
  public Status evaluate(Duration lag){ if(lag==null||lag.isNegative()) throw new IllegalArgumentException("lag"); if(lag.compareTo(critical)>0) return Status.VIOLATED; if(lag.compareTo(warning)>0) return Status.DEGRADED; return Status.HEALTHY; }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ReplicationLagBudgetTest { @Test void contract(){ var b=new ReplicationLagBudget(java.time.Duration.ofSeconds(2),java.time.Duration.ofSeconds(5)); org.junit.jupiter.api.Assertions.assertEquals(ReplicationLagBudget.Status.HEALTHY,b.evaluate(java.time.Duration.ofSeconds(1))); org.junit.jupiter.api.Assertions.assertEquals(ReplicationLagBudget.Status.VIOLATED,b.evaluate(java.time.Duration.ofSeconds(6))); } }
Teststrategie: Fake Metrics prüfen Grenzwerte, Fallback, Degradation und fehlende Messwerte.

Deutschsprachige Fachgrundlagen

177. Extract Failover Policy

Diagramm zu Extract Failover Policy

Fachliche Einordnung

Bei Ausfall einer Instanz oder Region entscheiden Infrastrukturcode und Anwendung uneinheitlich über Umschaltung und Rückfall. Eine Failover Policy bewertet Gesundheit, Datenaktualität, Kapazität und Sperrzeiten und liefert eine explizite Umschaltentscheidung.

Erkennungsmerkmale

  • automatische Umschaltung ohne Datenstandprüfung
  • manuelle Runbooks widersprechen dem Code
  • Failover und Retry verstärken sich gegenseitig

Sicherer Ablauf

  1. zulässige Ziele und Voraussetzungen definieren
  2. Health-, Lag- und Quorumdaten in ein Decision-Objekt bündeln
  3. Umschaltung, Abbruch und Degradation unterscheiden
  4. Cooldown und Flapping-Schutz ergänzen
  5. Failover und späteres Failback getrennt modellieren

Wann sinnvoll - und wann nicht?

Sinnvoll bei mehreren aktiven oder bereiten Standorten. Bei Single-Instance-Systemen ist zunächst Redundanz statt Policy nötig.

Risiken und Fehlanwendungen

Automatisches Failover kann Split Brain oder Datenverlust auslösen. Gesundheit allein reicht nicht; Konsistenz und Kapazität müssen mitbewertet werden.

Enterprise-Java-Beispiel

Der Zahlungslesedienst wechselt nur auf Region B, wenn Health grün, Replikationslag unter fünf Sekunden und ausreichende Kapazität vorhanden sind.

Abgrenzung und verwandte Muster

Region Health Model, Leader Election, Failback Policy, Circuit Breaker.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Extract Failover Policy
// Pattern: Policy - Failover-Entscheidungen werden deterministisch und zentral.
public record FailoverPolicy(Duration failureThreshold,Duration cooldown){
  public FailoverPolicy { if(failureThreshold==null||cooldown==null||failureThreshold.isNegative()||cooldown.isNegative()) throw new IllegalArgumentException(); }
  public boolean shouldFailover(Instant failedSince,boolean targetHealthy,Instant lastFailover,Instant now){
    if(failedSince==null||now==null) return false;
    boolean failedLongEnough=!now.isBefore(failedSince.plus(failureThreshold));
    boolean cooledDown=lastFailover==null||!now.isBefore(lastFailover.plus(cooldown));
    return failedLongEnough&&targetHealthy&&cooledDown;
  }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class FailoverPolicyTest { @Test void contract(){ var p=new FailoverPolicy(java.time.Duration.ofSeconds(10),java.time.Duration.ofMinutes(1)); var now=java.time.Instant.parse("2026-01-01T00:02:00Z"); org.junit.jupiter.api.Assertions.assertTrue(p.shouldFailover(now.minusSeconds(20),true,now.minusSeconds(70),now)); org.junit.jupiter.api.Assertions.assertFalse(p.shouldFailover(now.minusSeconds(5),true,null,now)); } }
Teststrategie: Game-Day-Szenarien prüfen Ausfall, Teilstörung, Flapping, veraltete Replikate und Rückkehr des Primärstandorts.

Deutschsprachige Fachgrundlagen

178. Introduce Leader Lease

Diagramm zu Introduce Leader Lease

Fachliche Einordnung

Mehrere Knoten führen gleichzeitig eine Aufgabe aus oder ein alter Leader arbeitet nach Netzwerkverlust weiter. Ein Leader Lease gewährt Führung nur bis zu einem klaren Ablaufzeitpunkt und verlangt regelmäßige, atomare Erneuerung.

Erkennungsmerkmale

  • doppelte Scheduler-Ausführung
  • Lock ohne Ablauf blockiert dauerhaft
  • Leader besitzt keinen nachweisbaren Gültigkeitszeitraum

Sicherer Ablauf

  1. Lease-ID, Besitzer, Gültigkeitsende und Fencing Token definieren
  2. Acquire und Renew atomar umsetzen
  3. jede geschützte Schreiboperation mit Fencing Token absichern
  4. bei abgelaufenem Lease sofort stoppen
  5. Uhrdrift und Netzwerkpartitionen testen

Wann sinnvoll - und wann nicht?

Sinnvoll für genau-einen aktiven Koordinator, Scheduler oder Partitionseigentümer. Bei idempotenten parallelisierbaren Arbeiten sind Competing Consumers einfacher.

Risiken und Fehlanwendungen

Ein Lease ohne Fencing verhindert alte Leader nicht sicher. Lokale Uhren, lange Pausen und verzögerte Nachrichten müssen berücksichtigt werden.

Enterprise-Java-Beispiel

Nur ein Knoten erzeugt Abrechnungsbatches. Jeder Schreibzugriff trägt ein monoton steigendes Fencing Token, sodass ein früherer Leader abgewiesen wird.

Abgrenzung und verwandte Muster

Leader Election, Distributed Lock, Lease, Fencing Token.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
// Refactoring: Introduce Leader Lease
// Pattern: Lease - Führungsrecht ist zeitlich begrenzt und gegen alte Epochen geschützt.
public record LeaderLease(String owner,long epoch,Instant validUntil){
  public LeaderLease { if(owner==null||owner.isBlank()||epoch<1||validUntil==null) throw new IllegalArgumentException(); }
  public boolean permits(String node,long observedEpoch,Instant now){ return owner.equals(node)&&epoch==observedEpoch&&now.isBefore(validUntil); }
  public LeaderLease renew(String node,Instant newUntil){ if(!owner.equals(node)||!newUntil.isAfter(validUntil)) throw new IllegalStateException("invalid renewal"); return new LeaderLease(owner,epoch,newUntil); }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class LeaderLeaseTest { @Test void contract(){ var l=new LeaderLease("n1",4,java.time.Instant.parse("2026-01-01T00:01:00Z")); org.junit.jupiter.api.Assertions.assertTrue(l.permits("n1",4,java.time.Instant.parse("2026-01-01T00:00:30Z"))); org.junit.jupiter.api.Assertions.assertFalse(l.permits("n1",3,java.time.Instant.parse("2026-01-01T00:00:30Z"))); } }
Teststrategie: Tests simulieren Ablauf, verspätete Erneuerung, zwei Bewerber, Uhrsprung und alten Fencing Token.

Deutschsprachige Fachgrundlagen

179. Extract Data Locality Policy

Diagramm zu Extract Data Locality Policy

Fachliche Einordnung

Daten und Verarbeitung werden ohne Rücksicht auf Latenz, Mandantenzuordnung, Rechtsraum oder Kosten verteilt. Eine Data Locality Policy entscheidet nachvollziehbar, wo Daten gespeichert, gelesen und verarbeitet werden dürfen beziehungsweise sollen.

Erkennungsmerkmale

  • häufige Cross-Region-Abfragen
  • personenbezogene Daten verlassen ungewollt eine Region
  • Rechenjobs laufen weit entfernt von ihren Daten

Sicherer Ablauf

  1. Datenklassifikation, Mandant, Rechtsraum und Zugriffsmuster erfassen
  2. harte Verbote von Optimierungspräferenzen trennen
  3. zulässige Regionen und bevorzugte Verarbeitung modellieren
  4. Routing und Platzierung hinter Ports anwenden
  5. Verstöße und Ausnahmen auditieren

Wann sinnvoll - und wann nicht?

Sinnvoll bei Multi-Region-, Multi-Tenant- oder regulierten Systemen. In einer einzigen Region kann die Policy zunächst einfach bleiben.

Risiken und Fehlanwendungen

Lokalität kann Verfügbarkeit und globale Performance einschränken. Caches, Backups, Logs und Supportzugriffe müssen ebenfalls erfasst werden.

Enterprise-Java-Beispiel

Gesundheitsdaten eines österreichischen Mandanten bleiben in einer EU-Region; anonymisierte Aggregationen dürfen zentral verarbeitet werden.

Abgrenzung und verwandte Muster

Data Sovereignty, Shard Key Strategy, Geo Replication, Routing Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Data Locality Policy
// Pattern: Policy - Residenz- und Verarbeitungsregeln werden als Fachgrenze modelliert.
public final class DataLocalityPolicy {
  private final Map<String,Set<String>> allowedRegionsByClassification;
  public DataLocalityPolicy(Map<String,Set<String>> rules){ var copy=new HashMap<String,Set<String>>(); rules.forEach((k,v)->copy.put(k,Set.copyOf(v))); this.allowedRegionsByClassification=Map.copyOf(copy); }
  public boolean permits(String classification,String storageRegion,String processingRegion){ var allowed=allowedRegionsByClassification.getOrDefault(classification,Set.of()); return allowed.contains(storageRegion)&&allowed.contains(processingRegion); }
  public void requireAllowed(String classification,String storageRegion,String processingRegion){ if(!permits(classification,storageRegion,processingRegion)) throw new SecurityException("data locality violation"); }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class DataLocalityPolicyTest { @Test void contract(){ var p=new DataLocalityPolicy(java.util.Map.of("PERSONAL",java.util.Set.of("eu-central","eu-west"))); org.junit.jupiter.api.Assertions.assertTrue(p.permits("PERSONAL","eu-central","eu-west")); org.junit.jupiter.api.Assertions.assertThrows(SecurityException.class,()->p.requireAllowed("PERSONAL","eu-central","us-east")); } }
Teststrategie: Entscheidungstabellen prüfen Datenklasse, Mandant, Region, Notfallausnahme und unzulässige Ziele.

Deutschsprachige Fachgrundlagen

180. Introduce Consistency Level

Diagramm zu Introduce Consistency Level

Fachliche Einordnung

Clients erwarten implizit „aktuelle“ Daten, obwohl der Speicher verschiedene Konsistenzmodelle und Kosten anbietet. Ein typisiertes Consistency Level macht die gewünschte Sichtbarkeit und Reihenfolge pro Anwendungsfall explizit und vom Datenbanktreiber unabhängig.

Erkennungsmerkmale

  • read-after-write funktioniert nur manchmal
  • jede Abfrage nutzt dieselbe teure Konsistenz
  • Begriffe wie strong oder eventual bleiben Treiberstrings

Sicherer Ablauf

  1. fachliche Lesegarantie pro Use Case bestimmen
  2. unterstützte Stufen als Domänentyp modellieren
  3. Mapping zum konkreten Datenspeicher in Adapter verschieben
  4. nicht erfüllbare Anforderungen explizit ablehnen
  5. Latenz, Verfügbarkeit und Kosten je Stufe messen

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Datenspeichern mit wählbarer Konsistenz. In einer lokalen ACID-Datenbank darf kein künstlicher Abstraktionsapparat entstehen.

Risiken und Fehlanwendungen

Bezeichnungen unterscheiden sich zwischen Produkten. Stärkere Konsistenz kann Latenz und Verfügbarkeit reduzieren; schwächere verlangt sichtbare Fachsemantik.

Enterprise-Java-Beispiel

Produktlisten verwenden eventual consistency, der eigene Warenkorb session consistency und eine unmittelbar folgende Zahlungsprüfung eine starke beziehungsweise primäre Lesegarantie.

Abgrenzung und verwandte Muster

CAP, Replication Lag Budget, Read Preference, Quorum Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Consistency Level
// Pattern: Explicit Policy Value - Konsistenz wird Teil des Aufrufvertrags.
public enum ConsistencyLevel {
  ONE, QUORUM, ALL;
  public int requiredAcknowledgements(int replicas){
    if(replicas<1) throw new IllegalArgumentException("replicas");
    return switch(this){ case ONE -> 1; case QUORUM -> replicas/2+1; case ALL -> replicas; };
  }
  public boolean satisfied(int acknowledgements,int replicas){ return acknowledgements>=requiredAcknowledgements(replicas); }
}

Vorhandener JUnit-Vertrag

Vorhandener JUnit-Vertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ConsistencyLevelTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertEquals(2,ConsistencyLevel.QUORUM.requiredAcknowledgements(3)); org.junit.jupiter.api.Assertions.assertTrue(ConsistencyLevel.ALL.satisfied(3,3)); org.junit.jupiter.api.Assertions.assertFalse(ConsistencyLevel.ALL.satisfied(2,3)); } }
Teststrategie: Adapter-Vertragstests prüfen korrektes Mapping, unsupported levels, read-after-write und Degradationsverhalten.

Deutschsprachige Fachgrundlagen

Kapitel 19 Geo-Replikation und Multi-Region-Konsistenz10 Refactorings

Refactorings 181–190 machen regionale Replikation, Routing, Failback, Datenresidenz, Backup und Wiederherstellbarkeit explizit.

Refactoring-Katalog · Abschnitt 4

Multi-Region- und Disaster-Recovery-Refactorings

Refactorings 181–190 machen regionale Replikation, Routing, Failback, Datenresidenz, Backup und Wiederherstellbarkeit explizit.

190 von 300 Refactorings abgeschlossen

110 Refactorings offen.

181. Extract Geo Replication Policy

Diagramm zu Extract Geo Replication Policy

Fachliche Einordnung

Replikationsziele, Mindestkopien und Schreibpräferenzen sind über Infrastrukturdateien, Datenbankadapter und Betriebswissen verteilt. Dadurch bleibt unklar, welche Daten in welcher Region liegen müssen und welche Ausfallannahmen tatsächlich gelten.

Erkennungsmerkmale

  • Regionen werden als freie Strings in mehreren Komponenten verwendet
  • kritische und unkritische Daten teilen dieselben Replikationsregeln
  • Failover- und Datenresidenzannahmen sind nur in Runbooks dokumentiert

Sicherer Ablauf

  1. Datenklassen, Schutzbedarf und zulässige Regionen erfassen
  2. harte Residenzregeln von Verfügbarkeitspräferenzen trennen
  3. Replikationsziele und Mindestbestätigungen als unveränderliche Policy modellieren
  4. Policy hinter einem Port mit der Plattformkonfiguration verbinden
  5. Regionsausfall, verzögerte Replikation und unzulässige Zielregionen testen

Wann sinnvoll - und wann nicht?

Sinnvoll bei Multi-Region-Betrieb, regulierten Daten und expliziten Wiederanlaufzielen. Für eine kleine Einzelregionsanwendung genügt meist eine klar dokumentierte Plattformkonfiguration.

Risiken und Fehlanwendungen

Eine Policy erzeugt keine physische Replikation. Sie muss mit tatsächlichen Plattformfähigkeiten, Verschlüsselung, Backups und Betriebsprozessen übereinstimmen.

Enterprise-Java-Beispiel

Ein Zahlungsjournal verlangt zwei bestätigte Kopien innerhalb der EU; Produktbilder dürfen asynchron in weitere Regionen repliziert werden.

Abgrenzung und verwandte Muster

Data Sovereignty Policy, Write Routing Policy, Consistency Level, Disaster Recovery Plan.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Geo Replication Policy
// Pattern: Policy - regionale Replikationsregeln werden explizit und testbar.
public record GeoReplicationPolicy(Set<String> regions,int minimumCopies,boolean preferLocalWrite){
  public GeoReplicationPolicy { regions=Set.copyOf(regions); if(regions.isEmpty()||minimumCopies<1||minimumCopies>regions.size()) throw new IllegalArgumentException(); }
  public boolean durable(Set<String> acknowledgedRegions){ return acknowledgedRegions!=null && regions.containsAll(acknowledgedRegions) && acknowledgedRegions.size()>=minimumCopies; }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

182. Introduce Read Preference

Diagramm zu Introduce Read Preference

Fachliche Einordnung

Lesezugriffe wählen Primär- oder Replikaknoten implizit. Fachlich unterschiedliche Anforderungen an Aktualität, Latenz und Ausfallsicherheit werden dadurch vermischt.

Erkennungsmerkmale

  • jeder Adapter entscheidet selbst zwischen Primary und Replica
  • stale reads werden erst im Produktionsbetrieb bemerkt
  • Read-after-write-Anforderungen sind nicht explizit testbar

Sicherer Ablauf

  1. Leseanwendungsfälle nach Aktualitätsanforderung klassifizieren
  2. starke, gebundene und eventual konsistente Reads benennen
  3. Read Preference als typisierten Vertrag einführen
  4. Routingadapter auf diese Präferenz abbilden
  5. Replikationsverzug und Fallback-Verhalten in Vertragstests simulieren

Wann sinnvoll - und wann nicht?

Sinnvoll, wenn mehrere Replikate existieren und nicht jeder Lesevorgang dieselbe Aktualität benötigt. Ohne Replikation wäre die zusätzliche Abstraktion nur Ballast.

Risiken und Fehlanwendungen

Eine zu aggressive Replica-Präferenz kann fachlich falsche Entscheidungen erzeugen. Für schreibnahe Prozesse müssen Session- oder Read-after-write-Garantien ausdrücklich berücksichtigt werden.

Enterprise-Java-Beispiel

Die Produktübersicht liest aus der nächstgelegenen Replica; die Bestellbestätigung liest nach dem Schreiben vom Primary.

Abgrenzung und verwandte Muster

Consistency Level, Replication Lag Budget, Query Port, Data Locality Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Read Preference
// Pattern: Strategy - die Wahl eines Lesereplikats wird austauschbar.
public final class ReadPreference {
  public enum Mode { LOCAL, FRESHEST, ANY }
  public record Replica(String id,String region,long version,boolean healthy){}
  private final Mode mode; private final String localRegion;
  public ReadPreference(Mode mode,String localRegion){ this.mode=Objects.requireNonNull(mode); this.localRegion=localRegion; }
  public Replica choose(List<Replica> replicas){ var healthy=replicas.stream().filter(Replica::healthy); return switch(mode){ case LOCAL -> healthy.filter(r->Objects.equals(r.region(),localRegion)).max(Comparator.comparingLong(Replica::version)).orElseThrow(); case FRESHEST -> healthy.max(Comparator.comparingLong(Replica::version)).orElseThrow(); case ANY -> healthy.findFirst().orElseThrow(); }; }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

183. Extract Write Routing Policy

Diagramm zu Extract Write Routing Policy

Fachliche Einordnung

Schreibziele werden durch technische Standortabfragen oder hart codierte Regionstabellen bestimmt. Damit sind Ownership, Datenresidenz und Failover nicht als fachliche Entscheidung sichtbar.

Erkennungsmerkmale

  • mehrere Services enthalten eigene Region-if-Ketten
  • Writes werden bei Störungen manuell umgeleitet
  • Mandanten- oder Datenklassifikation beeinflusst Routing ohne zentralen Vertrag

Sicherer Ablauf

  1. Schreib-Ownership und zulässige Zielregionen erheben
  2. Normalbetrieb und Notfallrouting getrennt modellieren
  3. deterministische Routingentscheidung als Policy extrahieren
  4. Fencing- und Idempotenzanforderungen ergänzen
  5. Split-Brain-, Regionsausfall- und Failback-Szenarien testen

Wann sinnvoll - und wann nicht?

Sinnvoll bei aktiv-aktiv, aktiv-passiv oder mandantenbezogener Regionalisierung. Bei genau einem Schreibstandort reicht ein einfacher Adapter.

Risiken und Fehlanwendungen

Schreibumleitung ohne Fencing kann Doppelverarbeitung und widersprüchliche Daten erzeugen. Routing darf nicht allein auf Netzwerk-Erreichbarkeit beruhen.

Enterprise-Java-Beispiel

Vertragsänderungen werden in der Heimatregion des Mandanten geschrieben; bei freigegebenem Notfallmodus übernimmt eine vorbereitete Sekundärregion.

Abgrenzung und verwandte Muster

Leader Lease, Idempotency Key, Failover Policy, Data Sovereignty Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Write Routing Policy
// Pattern: Policy - Schreibautorität und Fallback werden explizit.
public record WriteRoutingPolicy(Map<String,String> tenantPrimaryRegion,List<String> fallbackOrder){
  public WriteRoutingPolicy { tenantPrimaryRegion=Map.copyOf(tenantPrimaryRegion); fallbackOrder=List.copyOf(fallbackOrder); }
  public String route(String tenant,Set<String> healthy){ String primary=tenantPrimaryRegion.get(tenant); if(primary!=null&&healthy.contains(primary)) return primary; return fallbackOrder.stream().filter(healthy::contains).findFirst().orElseThrow(); }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

184. Introduce Region Health Model

Diagramm zu Introduce Region Health Model

Fachliche Einordnung

Regionen gelten nur als erreichbar oder nicht erreichbar. Degradierte Datenbanken, hohe Replikationsverzögerung oder teilweise abhängige Dienste werden dadurch nicht angemessen bewertet.

Erkennungsmerkmale

  • Healthchecks liefern nur boolean
  • Failover wird wegen einzelner transienter Fehler ausgelöst
  • Anwendungs- und Datenebene melden widersprüchliche Zustände

Sicherer Ablauf

  1. relevante Gesundheitsdimensionen definieren
  2. Messwerte in Healthy, Degraded, Unavailable und Unknown übersetzen
  3. Zeitfenster und Mindestbeobachtungen modellieren
  4. Entscheidungen von einzelnen Rohmetriken entkoppeln
  5. Flapping, Teilstörung und veraltete Messwerte testen

Wann sinnvoll - und wann nicht?

Sinnvoll, wenn Routing oder Failover von mehreren Signalen abhängt. Für rein lokale Prozesse genügt ein einfacher Readiness-Check.

Risiken und Fehlanwendungen

Ein zu komplexes Modell kann Störungen verschleiern. Rohsignale, Alter der Messung und Entscheidungsgrund müssen beobachtbar bleiben.

Enterprise-Java-Beispiel

Eine Region ist erreichbar, aber wegen hoher Datenbanklatenz nur für statische Lesezugriffe freigegeben.

Abgrenzung und verwandte Muster

Health Probe Port, Consistency Monitor, Failover Policy, SLO Evaluation.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Duration;
// Refactoring: Introduce Region Health Model
// Pattern: Decision Object - mehrere Signale ergeben einen nachvollziehbaren Zustand.
public record RegionHealthModel(Duration latency,double errorRate,Duration replicationLag){
  public enum Status { HEALTHY, DEGRADED, UNAVAILABLE }
  public RegionHealthModel { if(latency.isNegative()||replicationLag.isNegative()||errorRate<0||errorRate>1) throw new IllegalArgumentException(); }
  public Status status(){ if(errorRate>=0.5||latency.compareTo(Duration.ofSeconds(5))>0) return Status.UNAVAILABLE; if(errorRate>=0.1||latency.compareTo(Duration.ofSeconds(1))>0||replicationLag.compareTo(Duration.ofSeconds(3))>0) return Status.DEGRADED; return Status.HEALTHY; }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

185. Extract Failback Policy

Diagramm zu Extract Failback Policy

Fachliche Einordnung

Nach einem Failover wird die Rückkehr zur Primärregion ad hoc durchgeführt. Datenabgleich, Mindeststabilität und Verkehrsverlagerung sind nicht als überprüfbarer Ablauf modelliert.

Erkennungsmerkmale

  • Failback erfolgt per manueller Checkliste ohne Entscheidungskriterien
  • Verkehr wird sofort vollständig zurückgeschaltet
  • Datenkonflikte werden erst nach der Rückkehr sichtbar

Sicherer Ablauf

  1. Vorbedingungen für Datenabgleich und Stabilität definieren
  2. Mindestbeobachtungszeit und Freigaben modellieren
  3. Traffic schrittweise statt abrupt zurückführen
  4. Rollback des Failbacks vorsehen
  5. Störung während Synchronisation und Teilrückkehr testen

Wann sinnvoll - und wann nicht?

Sinnvoll bei vorbereitetem Multi-Region-Failover. Ohne sekundären Betriebsstandort existiert kein eigenständiger Failback-Prozess.

Risiken und Fehlanwendungen

Zu früher Failback erzeugt einen zweiten Ausfall; zu später Failback verlängert teuren oder eingeschränkten Notbetrieb.

Enterprise-Java-Beispiel

Nach 30 Minuten stabiler Primärregion und bestätigtem Replikationsgleichstand werden zunächst interne Nutzer und danach Kundentraffic zurückgeführt.

Abgrenzung und verwandte Muster

Disaster Recovery Plan, Traffic Shaping Policy, Consistency Monitor, Rollback Command.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Extract Failback Policy
// Pattern: Policy - Rückschaltung wird stabilitäts- und zeitgesteuert.
public record FailbackPolicy(Duration stableFor,Duration cooldown){
  public FailbackPolicy { if(stableFor.isNegative()||cooldown.isNegative()) throw new IllegalArgumentException(); }
  public boolean allowed(Instant now,Instant healthySince,Instant lastSwitch,boolean approved){ return approved && !healthySince.plus(stableFor).isAfter(now) && !lastSwitch.plus(cooldown).isAfter(now); }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

186. Introduce Disaster Recovery Plan

Diagramm zu Introduce Disaster Recovery Plan

Fachliche Einordnung

Wiederanlaufwissen liegt in verstreuten Runbooks, Tickets und Köpfen. Abhängigkeiten, Reihenfolge, Verantwortlichkeiten sowie RTO und RPO sind nicht als ausführbarer Plan sichtbar.

Erkennungsmerkmale

  • Notfallübungen entdecken immer neue manuelle Schritte
  • RTO/RPO werden genannt, aber nicht gemessen
  • Wiederherstellung von Daten und Anwendung wird getrennt geplant

Sicherer Ablauf

  1. kritische Geschäftsfähigkeiten und Abhängigkeiten erfassen
  2. RTO, RPO und zulässige Degradationen pro Fähigkeit definieren
  3. Wiederanlaufschritte als Zustände und Commands modellieren
  4. manuelle Freigaben und Automatisierung klar trennen
  5. regelmäßige Übungen mit gemessenen Zeiten durchführen

Wann sinnvoll - und wann nicht?

Sinnvoll für geschäftskritische Systeme und regulatorisch verlangte Wiederanlaufverfahren. Der Plan muss zur tatsächlichen Kritikalität passen.

Risiken und Fehlanwendungen

Ein Dokument ohne Übungen ist keine belastbare Wiederherstellungsfähigkeit. Abhängigkeiten wie Identität, DNS, Schlüssel und externe Partner dürfen nicht fehlen.

Enterprise-Java-Beispiel

Der Plan stellt zuerst Identität und Schlüsselzugriff, danach Datenbank, Messaging, Kern-API und zuletzt nichtkritische Reports wieder her.

Abgrenzung und verwandte Muster

Restore Point, Backup Retention Policy, Region Health Model, Recovery Scenario Harness.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*; import java.util.*;
// Refactoring: Introduce Disaster Recovery Plan
// Pattern: Plan Object - RTO, RPO und Schritte bilden einen prüfbaren Vertrag.
public record DisasterRecoveryPlan(Duration rto,Duration rpo,List<String> recoverySteps){
  public DisasterRecoveryPlan { recoverySteps=List.copyOf(recoverySteps); if(rto.isNegative()||rpo.isNegative()||recoverySteps.isEmpty()) throw new IllegalArgumentException(); }
  public boolean meets(Duration outage,Duration dataLoss){ return outage.compareTo(rto)<=0&&dataLoss.compareTo(rpo)<=0; }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

187. Extract Data Sovereignty Policy

Diagramm zu Extract Data Sovereignty Policy

Fachliche Einordnung

Datenresidenz, Zugriffsort und zulässige Verarbeitung werden als technische Regionseinstellungen behandelt. Fachliche Klassifikation und Rechtsraum bleiben unsichtbar.

Erkennungsmerkmale

  • personenbezogene Daten werden in generische globale Speicher geschrieben
  • Backups und Logs folgen anderen Regionen als Primärdaten
  • Ausnahmen werden informell genehmigt

Sicherer Ablauf

  1. Datenklassen und Rechtsräume explizit erfassen
  2. Speicherung, Verarbeitung, Supportzugriff und Backup getrennt bewerten
  3. harte Verbote als Policy modellieren
  4. Entscheidung in Routing- und Speicherports erzwingen
  5. Ausnahmen zeitlich begrenzen und auditieren

Wann sinnvoll - und wann nicht?

Sinnvoll bei personenbezogenen, behördlichen oder vertraglich regional gebundenen Daten. Für nicht regulierte öffentliche Daten kann eine einfache Lokalitätspräferenz reichen.

Risiken und Fehlanwendungen

Nur den Primärspeicher zu prüfen ist unzureichend. Caches, Telemetrie, Suchindizes, Backups und Supportwerkzeuge müssen dieselbe Policy beachten.

Enterprise-Java-Beispiel

Schadendokumente österreichischer Kunden bleiben in EU-Regionen; anonymisierte Kennzahlen dürfen zentral aggregiert werden.

Abgrenzung und verwandte Muster

Data Locality Policy, Geo Replication Policy, Privacy Classification Policy, Audit Trail Port.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Data Sovereignty Policy
// Pattern: Policy - Datenresidenz wird als fachlich-rechtliche Regel modelliert.
public final class DataSovereigntyPolicy {
  public enum Classification { PUBLIC, INTERNAL, PERSONAL, RESTRICTED }
  private final Map<Classification,Set<String>> allowed;
  public DataSovereigntyPolicy(Map<Classification,Set<String>> allowed){ var copy=new EnumMap<Classification,Set<String>>(Classification.class); allowed.forEach((k,v)->copy.put(k,Set.copyOf(v))); this.allowed=Map.copyOf(copy); }
  public boolean permits(Classification c,String region,boolean legalBasis){ return allowed.getOrDefault(c,Set.of()).contains(region) && (c.ordinal()<Classification.PERSONAL.ordinal()||legalBasis); }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

188. Introduce Backup Retention Policy

Diagramm zu Introduce Backup Retention Policy

Fachliche Einordnung

Aufbewahrungsfristen und Backupklassen sind als Zahlen in Skripten verteilt. Fachliche Wiederherstellungsziele, rechtliche Pflichten und Löschanforderungen werden vermischt.

Erkennungsmerkmale

  • alle Daten erhalten dieselbe Retention
  • Backups überleben Lösch- oder Mandantenregeln unbegrenzt
  • niemand kann erklären, welche Sicherung für welches Szenario gedacht ist

Sicherer Ablauf

  1. Backupklassen nach Wiederherstellungszweck definieren
  2. Fristen, Unveränderlichkeit und geografische Ablage modellieren
  3. Retention von operativer Datenhaltung trennen
  4. automatische Ablauf- und Löschentscheidungen einführen
  5. Wiederherstellung aus jeder relevanten Backupklasse testen

Wann sinnvoll - und wann nicht?

Sinnvoll, sobald mehrere Datenklassen oder Wiederherstellungspunkte existieren. Ein einfaches Entwicklungssystem benötigt keine umfangreiche Policy.

Risiken und Fehlanwendungen

Lange Retention erhöht Kosten und Datenschutzrisiken; kurze Retention kann regulatorische und betriebliche Anforderungen verletzen.

Enterprise-Java-Beispiel

Transaktionsjournale werden sieben Jahre unveränderlich aufbewahrt, temporäre Importdateien nur 30 Tage.

Abgrenzung und verwandte Muster

Restore Point, Data Retention Decision, Legal Hold, Disaster Recovery Plan.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Introduce Backup Retention Policy
// Pattern: Policy - Aufbewahrung und Löschschutz werden deterministisch.
public record BackupRetentionPolicy(int dailyDays,int monthlyMonths){
  public BackupRetentionPolicy { if(dailyDays<1||monthlyMonths<1) throw new IllegalArgumentException(); }
  public boolean retain(LocalDate backupDate,LocalDate today,boolean monthly,boolean legalHold){ if(legalHold) return true; return monthly ? !backupDate.plusMonths(monthlyMonths).isBefore(today) : !backupDate.plusDays(dailyDays).isBefore(today); }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

189. Introduce Restore Point

Diagramm zu Introduce Restore Point

Fachliche Einordnung

Wiederherstellung wird über Dateinamen, Zeitstempel und Provider-IDs gesteuert. Gültigkeit, Datenstand und Abhängigkeiten eines Wiederherstellungspunkts sind nicht typisiert.

Erkennungsmerkmale

  • Restore-Skripte akzeptieren beliebige Strings
  • Anwendungs- und Datenbanksnapshots passen zeitlich nicht zusammen
  • RPO wird nur geschätzt

Sicherer Ablauf

  1. Identität, Zeitpunkt, Datenquellen und Vollständigkeit modellieren
  2. Restore Point als unveränderliches Value Object einführen
  3. Kompatibilität abhängiger Komponenten prüfen
  4. Auswahl und Freigabe protokollieren
  5. Restore in isolierter Umgebung regelmäßig verifizieren

Wann sinnvoll - und wann nicht?

Sinnvoll bei zeitpunktbezogenen Backups, Snapshots oder Event-Store-Wiederherstellung. Ohne mehrere Wiederherstellungsstände genügt ein einfacher Provider-Aufruf.

Risiken und Fehlanwendungen

Ein vorhandener Snapshot ist nicht automatisch wiederherstellbar. Schlüssel, Schema, externe Dateien und Event-Offsets müssen zusammenpassen.

Enterprise-Java-Beispiel

Ein Restore Point bündelt Datenbank-LSN, Object-Storage-Snapshot, Event-Offset und Schema-Version für einen konsistenten Wiederanlauf.

Abgrenzung und verwandte Muster

Backup Retention Policy, Disaster Recovery Plan, Snapshot Store, Recovery Scenario Harness.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*; import java.util.*;
// Refactoring: Introduce Restore Point
// Pattern: Value Object - ein Wiederherstellungspunkt wird unveränderlich und eindeutig.
public record RestorePoint(String snapshotId,long logPosition,Instant createdAt){
  public RestorePoint { if(snapshotId==null||snapshotId.isBlank()||logPosition<0||createdAt==null) throw new IllegalArgumentException(); }
  public boolean before(RestorePoint other){ return logPosition<other.logPosition; }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

190. Extract Consistency Monitor

Diagramm zu Extract Consistency Monitor

Fachliche Einordnung

Replikationsverzug und Datenabweichungen werden nur durch Infrastrukturmetriken beobachtet. Fachliche Konsistenzverletzungen bleiben bis zu Kundenmeldungen unentdeckt.

Erkennungsmerkmale

  • nur technische Lag-Metriken vorhanden
  • Checksummen oder fachliche Summen unterscheiden sich unbemerkt
  • Alarmierung kennt keine Kritikalität der betroffenen Daten

Sicherer Ablauf

  1. fachlich relevante Konsistenzinvarianten definieren
  2. Messung von Bewertung und Alarmierung trennen
  3. Stichproben, Checksummen und Versionsstände kombinieren
  4. Toleranzfenster je Datenklasse modellieren
  5. Fehlalarm, echte Divergenz und Reconciliation testen

Wann sinnvoll - und wann nicht?

Sinnvoll bei asynchroner Replikation, CDC und Materialized Views. Bei synchroner Einzelinstanz ist ein separater Monitor meist unnötig.

Risiken und Fehlanwendungen

Vollständige Vergleiche können teuer sein. Stichproben und Hashes müssen so gewählt werden, dass kritische Abweichungen nicht systematisch verborgen bleiben.

Enterprise-Java-Beispiel

Der Monitor vergleicht Anzahl und Gesamtsumme gebuchter Zahlungen pro Zeitraum zwischen Primary und Reporting-Region.

Abgrenzung und verwandte Muster

Replication Lag Budget, Conflict Resolver, Read Repair, Configuration Drift Detector.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Consistency Monitor
// Pattern: Monitor - Replikatabweichungen werden als expliziter Bericht sichtbar.
public final class ConsistencyMonitor {
  public record Sample(String region,long version,String checksum){}
  public record Report(long highestVersion,Set<String> staleRegions,boolean checksumConflict){ public Report { staleRegions=Set.copyOf(staleRegions); } }
  public Report inspect(List<Sample> samples){ if(samples==null||samples.isEmpty()) throw new IllegalArgumentException(); long max=samples.stream().mapToLong(Sample::version).max().orElseThrow(); var stale=samples.stream().filter(s->s.version()<max).map(Sample::region).collect(java.util.stream.Collectors.toSet()); boolean conflict=samples.stream().filter(s->s.version()==max).map(Sample::checksum).distinct().count()>1; return new Report(max,stale,conflict); }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

Kapitel 20 Traffic Shaping, Rollout und Laststeuerung10 Refactorings

Refactorings 191–200 machen Traffic-Verteilung, Canary-Rollouts, SLOs, Kapazität, Überlastschutz, Kosten und Laufzeitkonfiguration explizit.

Refactoring-Katalog · Abschnitt 5

SRE-, Traffic- und Rollout-Refactorings

Refactorings 191–200 machen Traffic-Verteilung, Canary-Rollouts, SLOs, Kapazität, Überlastschutz, Kosten und Laufzeitkonfiguration explizit.

200 von 300 Refactorings abgeschlossen

100 Refactorings offen.

191. Extract Traffic Shaping Policy

Diagramm zu Extract Traffic Shaping Policy

Fachliche Einordnung

Routinggewichte, Wartungszustände und Zielgruppen werden direkt in Gatewayregeln verändert. Die geschäftliche Absicht einer Verkehrsverlagerung ist nicht testbar.

Erkennungsmerkmale

  • Gewichte werden manuell ohne Versionierung geändert
  • interne, Beta- und Standardnutzer teilen dieselbe Routinglogik
  • Notfallrouting ist mit Rolloutlogik vermischt

Sicherer Ablauf

  1. Zielgruppen, Ziele und Gewichte als Domainmodell erfassen
  2. Normalbetrieb, Wartung und Notfall getrennt modellieren
  3. deterministische Policy extrahieren
  4. Änderungen versionieren und auditieren
  5. Gewichtsgrenzen, leere Ziele und Rollback testen

Wann sinnvoll - und wann nicht?

Sinnvoll bei mehreren aktiven Versionen, Regionen oder Kapazitätspools. Bei einem einzigen Ziel ist keine Shaping-Policy erforderlich.

Risiken und Fehlanwendungen

Fehlerhafte Gewichte können Überlast oder vollständigen Ausfall erzeugen. Die Summe, Mindestkapazität und Health-Signale müssen validiert werden.

Enterprise-Java-Beispiel

Zahlungsverkehr wird zu 90 Prozent auf Version A und zu 10 Prozent auf eine geprüfte Version B verteilt.

Abgrenzung und verwandte Muster

Canary Cohort, Rollout Guard, Load Shedding Policy, Failback Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Traffic Shaping Policy
// Pattern: Policy - Routinggewichte und Wartungsregeln werden explizit und testbar.
public record TrafficShapingPolicy(Map<String,Integer> weights, Set<String> disabledTargets) {
  public TrafficShapingPolicy { weights=Map.copyOf(weights); disabledTargets=Set.copyOf(disabledTargets); if(weights.values().stream().anyMatch(v->v<0)) throw new IllegalArgumentException(); }
  public String route(int bucket){ var active=weights.entrySet().stream().filter(e->!disabledTargets.contains(e.getKey())&&e.getValue()>0).sorted(Map.Entry.comparingByKey()).toList(); int total=active.stream().mapToInt(Map.Entry::getValue).sum(); if(total==0) throw new IllegalStateException("no target"); int point=Math.floorMod(bucket,total); int sum=0; for(var e:active){ sum+=e.getValue(); if(point<sum) return e.getKey(); } throw new IllegalStateException(); }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

192. Introduce Canary Cohort

Diagramm zu Introduce Canary Cohort

Fachliche Einordnung

Canary-Nutzer werden durch zufällige Prozentwerte oder uneinheitliche Header ausgewählt. Dieselben Nutzer landen dadurch nicht stabil auf derselben Version.

Erkennungsmerkmale

  • Fehler lassen sich nicht einer stabilen Kohorte zuordnen
  • Nutzer wechseln zwischen alter und neuer Version
  • interne Tester und echte Kunden sind nicht getrennt

Sicherer Ablauf

  1. Kohortenkriterien und Ausschlüsse definieren
  2. stabile Hash- oder Attributzuordnung einführen
  3. Kohortenversion und Zeitraum modellieren
  4. Beobachtbarkeit nach Kohorte ergänzen
  5. Stabilität, Datenschutz und Rücknahme testen

Wann sinnvoll - und wann nicht?

Sinnvoll für kontrollierte progressive Releases. Bei rein internen Änderungen ohne Nutzerwirkung reicht eine technische Staging-Umgebung.

Risiken und Fehlanwendungen

Eine nicht repräsentative Kohorte liefert falsche Sicherheit. Sensible Attribute dürfen nicht unnötig für Routing verwendet werden.

Enterprise-Java-Beispiel

Zunächst interne Konten, danach ein stabil gehashter Prozentanteil geeigneter Geschäftskunden erhalten die neue Preisberechnung.

Abgrenzung und verwandte Muster

Traffic Shaping Policy, Feature Toggle Policy, Rollout Guard, Experiment Cohort.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Objects;
// Refactoring: Introduce Canary Cohort
// Pattern: Value Object - eine stabile Rollout-Kohorte ersetzt flüchtige Zufallsentscheidungen.
public record CanaryCohort(int percentage, String salt) {
  public CanaryCohort { if(percentage<0||percentage>100) throw new IllegalArgumentException(); Objects.requireNonNull(salt); }
  public boolean contains(String subject){ Objects.requireNonNull(subject); return Math.floorMod(Objects.hash(salt,subject),100)<percentage; }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

193. Extract Rollout Guard

Diagramm zu Extract Rollout Guard

Fachliche Einordnung

Rollouts werden anhand einzelner Dashboards oder manueller Einschätzung fortgesetzt. Abbruchkriterien und Mindestdaten sind nicht automatisiert.

Erkennungsmerkmale

  • jedes Team nutzt andere Grenzwerte
  • Rollout wird trotz fehlender Messdaten fortgesetzt
  • Rollback erfolgt erst nach deutlicher Kundenauswirkung

Sicherer Ablauf

  1. erforderliche Signale und Mindeststichprobe festlegen
  2. harte Stopkriterien von Warnungen trennen
  3. Guard als reinen Decision Service extrahieren
  4. Entscheidungsbegründung protokollieren
  5. fehlende Daten, Grenzwertnähe und Rücknahme testen

Wann sinnvoll - und wann nicht?

Sinnvoll bei gestuften Releases mit messbaren Qualitäts- oder Geschäftsindikatoren. Für kleine, sofort reversible Änderungen kann eine einfache Checkliste genügen.

Risiken und Fehlanwendungen

Ein Guard ist nur so gut wie seine Signale. Falsch gewählte Metriken können sichere Releases blockieren oder gefährliche freigeben.

Enterprise-Java-Beispiel

Der Rollout stoppt, wenn Fehlerrate, p95-Latenz oder fachliche Ablehnungen gegenüber der Kontrollgruppe deutlich steigen.

Abgrenzung und verwandte Muster

Error Budget, SLO Evaluation, Canary Cohort, Release Confidence Gate.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Rollout Guard
// Pattern: Decision Object - Rollout-Freigabe und Abbruchgrund werden gemeinsam geliefert.
public record RolloutGuard(double maxErrorRate,long maxP95Millis,long minimumSamples){
  public record Metrics(long samples,long failures,long p95Millis){}
  public record Decision(boolean continueRollout,String reason){}
  public Decision evaluate(Metrics m){ if(m.samples()<minimumSamples) return new Decision(false,"insufficient-samples"); double rate=m.samples()==0?1d:(double)m.failures()/m.samples(); if(rate>maxErrorRate) return new Decision(false,"error-rate"); if(m.p95Millis()>maxP95Millis) return new Decision(false,"latency"); return new Decision(true,"healthy"); }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

194. Introduce Error Budget

Diagramm zu Introduce Error Budget

Fachliche Einordnung

Verfügbarkeit wird als abstraktes Ziel genannt, ohne zu bestimmen, wie viel Unzuverlässigkeit im Zeitraum toleriert wird. Produktentwicklung und Stabilitätsarbeit konkurrieren ohne gemeinsame Größe.

Erkennungsmerkmale

  • SLO-Verletzungen haben keine Auswirkung auf Releaseentscheidungen
  • Teams diskutieren über einzelne Ausfälle statt über Zeitfenster
  • jede Störung wird gleich gewichtet

Sicherer Ablauf

  1. SLI und SLO für eine Nutzerfähigkeit definieren
  2. zulässige Fehlerereignisse oder Ausfallzeit berechnen
  3. Verbrauch und Restbudget als Value Object modellieren
  4. Entscheidungsregeln für Releases und Stabilitätsarbeit ableiten
  5. Fensterwechsel, fehlende Daten und Budgetüberschreitung testen

Wann sinnvoll - und wann nicht?

Sinnvoll für betriebene Dienste mit messbarem SLO. Für nicht produktive Werkzeuge ohne Verfügbarkeitsversprechen wäre es unnötig.

Risiken und Fehlanwendungen

Ein Error Budget darf nicht zur Rechtfertigung vermeidbarer Ausfälle werden. Falsche SLI-Auswahl verschleiert tatsächliche Nutzerprobleme.

Enterprise-Java-Beispiel

Ein Dienst mit 99,9 Prozent Monats-SLO darf rund 43 Minuten Unverfügbarkeit verbrauchen; bei ausgeschöpftem Budget werden riskante Releases pausiert.

Abgrenzung und verwandte Muster

SLO Evaluation, Rollout Guard, Reliability Policy, Release Confidence Gate.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Error Budget
// Pattern: Value Object - tolerierte und verbrauchte Ausfallzeit werden fachlich benannt.
public record ErrorBudget(long allowedDowntimeMillis,long consumedDowntimeMillis){
  public ErrorBudget { if(allowedDowntimeMillis<0||consumedDowntimeMillis<0) throw new IllegalArgumentException(); }
  public long remainingMillis(){ return Math.max(0,allowedDowntimeMillis-consumedDowntimeMillis); }
  public double consumedRatio(){ return allowedDowntimeMillis==0 ? (consumedDowntimeMillis==0?0d:1d) : Math.min(1d,(double)consumedDowntimeMillis/allowedDowntimeMillis); }
  public boolean permitsRiskyChange(){ return consumedRatio()<0.8d; }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

195. Extract SLO Evaluation

Diagramm zu Extract SLO Evaluation

Fachliche Einordnung

SLO-Berechnung ist in Dashboardabfragen und Alarmregeln verteilt. Unterschiedliche Teams erhalten für denselben Zeitraum abweichende Ergebnisse.

Erkennungsmerkmale

  • SLI-Formeln werden mehrfach kopiert
  • Zeitzonen und Wartungsfenster werden uneinheitlich behandelt
  • Alarm und Release-Gate nutzen verschiedene Berechnungen

Sicherer Ablauf

  1. SLI, Ziel, Fenster und Ausschlüsse typisieren
  2. Messdatenzugriff über Port trennen
  3. Evaluation als deterministische Funktion extrahieren
  4. Ergebnis mit Datenvollständigkeit und Begründung versehen
  5. Fenstergrenzen, Missing Data und Wartungszeiten testen

Wann sinnvoll - und wann nicht?

Sinnvoll, wenn SLOs operative oder Releaseentscheidungen steuern. Bei rein informellen Dashboards genügt zunächst eine dokumentierte Abfrage.

Risiken und Fehlanwendungen

Zu viele oder unverständliche SLOs führen zu Alarmmüdigkeit. Die Evaluation muss die Nutzerperspektive abbilden.

Enterprise-Java-Beispiel

Die Evaluation berechnet Erfolgsquote und Latenzziel für Checkout-Anfragen über ein rollierendes 28-Tage-Fenster.

Abgrenzung und verwandte Muster

Error Budget, Rollout Guard, Health Model, Observability Context.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Extract SLO Evaluation
// Pattern: Specification - mehrere Serviceziele werden als kombinierbare Kriterien bewertet.
public final class SloEvaluator {
  public record Target(double minimumAvailability,long maximumP95Millis){}
  public record Measurement(double availability,long p95Millis){}
  public record Result(boolean availabilityMet,boolean latencyMet){ public boolean met(){ return availabilityMet&&latencyMet; } }
  public Result evaluate(Target t,Measurement m){ return new Result(m.availability()>=t.minimumAvailability(),m.p95Millis()<=t.maximumP95Millis()); }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

196. Introduce Capacity Reservation

Diagramm zu Introduce Capacity Reservation

Fachliche Einordnung

Kritische Arbeitslasten teilen Ressourcen mit optionalen Jobs. Bei Lastspitzen sind keine Kapazitäten für priorisierte Vorgänge reserviert.

Erkennungsmerkmale

  • Batchjobs verdrängen Online-Transaktionen
  • Kapazitätszusagen existieren nur in Tabellen
  • Autoscaling reagiert langsamer als Lastanstieg

Sicherer Ablauf

  1. kritische Klassen und Mindestkapazität definieren
  2. Reservation als typisierten Anspruch modellieren
  3. Zuteilung und Rückgabe explizit machen
  4. Überbuchung und Priorität regeln
  5. Spitzenlast, Ausfall und verwaiste Reservierungen testen

Wann sinnvoll - und wann nicht?

Sinnvoll bei vorhersehbaren kritischen Lasten oder knappen Ressourcen. Bei vollständig elastischen, billigen Ressourcen kann reine Skalierung genügen.

Risiken und Fehlanwendungen

Zu hohe Reservierung verschwendet Ressourcen; zu niedrige gibt falsche Sicherheit. Reservation und tatsächliche Plattformkapazität müssen abgestimmt sein.

Enterprise-Java-Beispiel

Für regulatorische Tagesabschlüsse werden feste Worker-Slots reserviert, die normale Reportjobs nicht belegen dürfen.

Abgrenzung und verwandte Muster

Admission Control, Bulkhead Policy, Load Shedding Policy, Cost Allocation Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Capacity Reservation
// Pattern: Value Object - zugesicherte Kapazität wird atomar geprüft statt implizit über Zähler.
public record CapacityReservation(String owner,int reservedUnits,int usedUnits){
  public CapacityReservation { if(reservedUnits<0||usedUnits<0||usedUnits>reservedUnits) throw new IllegalArgumentException(); }
  public int availableUnits(){ return reservedUnits-usedUnits; }
  public CapacityReservation allocate(int units){ if(units<0||units>availableUnits()) throw new IllegalStateException("capacity exceeded"); return new CapacityReservation(owner,reservedUnits,usedUnits+units); }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

197. Extract Load Shedding Policy

Diagramm zu Extract Load Shedding Policy

Fachliche Einordnung

Bei Überlast schlagen alle Anfragen gleichermaßen langsam fehl. Das System schützt weder kritische Funktionen noch nachgelagerte Abhängigkeiten.

Erkennungsmerkmale

  • Queues wachsen unbegrenzt
  • Timeouts treten erst tief in Abhängigkeiten auf
  • nichtkritische Requests verbrauchen Ressourcen kritischer Pfade

Sicherer Ablauf

  1. Lastindikatoren und Serviceklassen definieren
  2. ablehnbare Arbeit und Degradationsoptionen benennen
  3. Policy als frühe Entscheidung extrahieren
  4. Retry-After und beobachtbare Ablehnungsgründe liefern
  5. Überlast, Erholung und Prioritätsinversion testen

Wann sinnvoll - und wann nicht?

Sinnvoll, wenn Überlast realistisch ist und priorisierte Dienste geschützt werden müssen. Ohne messbare Kapazitätsgrenze kann die Policy zu willkürlichen Ablehnungen führen.

Risiken und Fehlanwendungen

Falsches Shedding trifft wichtige Nutzer oder verstärkt Retry-Stürme. Clients benötigen klare Backoff-Informationen.

Enterprise-Java-Beispiel

Bei erschöpfter Kapazität werden große Exporte abgelehnt, während Zahlungs- und Stornierungsanfragen weiterlaufen.

Abgrenzung und verwandte Muster

Admission Control, Rate Limiter Policy, Backpressure Policy, Capacity Reservation.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Load Shedding Policy
// Pattern: Policy - Überlastablehnung wird nach Priorität und Auslastung reproduzierbar.
public record LoadSheddingPolicy(double softLimit,double hardLimit){
  public enum Priority { CRITICAL, NORMAL, BULK }
  public boolean accept(Priority priority,double utilization){ if(utilization>=hardLimit) return priority==Priority.CRITICAL; if(utilization>=softLimit) return priority!=Priority.BULK; return true; }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

198. Introduce Admission Control

Diagramm zu Introduce Admission Control

Fachliche Einordnung

Neue Arbeit wird angenommen, obwohl Ressourcen oder Abhängigkeiten die Bearbeitung nicht zuverlässig schaffen. Überlast wird dadurch nur in Queues verschoben.

Erkennungsmerkmale

  • Annahmen erfolgen ohne Kostenabschätzung
  • Queues wachsen nach Lastspitzen über lange Zeit weiter
  • Systemzustand verschlechtert sich nach jeder zusätzlichen Anfrage

Sicherer Ablauf

  1. Eintrittskriterien und Arbeitskosten definieren
  2. Admission Decision mit Grund und Gültigkeit modellieren
  3. Prüfung an der Systemgrenze platzieren
  4. Reservations- oder Tokenmechanismus anbinden
  5. Grenzlast, Fairness und Freigabe testen

Wann sinnvoll - und wann nicht?

Sinnvoll bei begrenzten Ressourcen, langen Jobs oder teuren Abhängigkeiten. Für sehr kurze, elastisch skalierbare Requests reicht oft Rate Limiting.

Risiken und Fehlanwendungen

Admission Control kann unfair werden oder Kapazität ungenutzt lassen. Entscheidungen benötigen aktuelle, aber nicht flackernde Signale.

Enterprise-Java-Beispiel

Ein Import wird nur angenommen, wenn ausreichend Speicher, Worker-Kapazität und ein freier Mandanten-Slot verfügbar sind.

Abgrenzung und verwandte Muster

Capacity Reservation, Load Shedding Policy, Rate Limiter, Queue Capacity.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.concurrent.atomic.AtomicInteger;
// Refactoring: Introduce Admission Control
// Pattern: Guard - ein Permit begrenzt parallele Arbeit an der Systemgrenze.
public final class AdmissionControl {
  private final int limit; private final AtomicInteger active=new AtomicInteger();
  public AdmissionControl(int limit){ if(limit<1) throw new IllegalArgumentException(); this.limit=limit; }
  public Permit tryAcquire(){ while(true){ int now=active.get(); if(now>=limit) return null; if(active.compareAndSet(now,now+1)) return new Permit(); } }
  public final class Permit implements AutoCloseable { private boolean closed; private Permit(){} public void close(){ if(!closed){ closed=true; active.decrementAndGet(); } } }
  public int active(){ return active.get(); }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

199. Extract Cost Allocation Policy

Diagramm zu Extract Cost Allocation Policy

Fachliche Einordnung

Cloud- und Plattformkosten werden nachträglich mit unvollständigen Tags verteilt. Fachliche Nutzung und Kostenverursacher sind im Code nicht sichtbar.

Erkennungsmerkmale

  • gemeinsame Ressourcen landen pauschal bei einem Team
  • fehlende Tags verhindern belastbare Berichte
  • Kostenentscheidungen sind von technischen Ressourcennamen abhängig

Sicherer Ablauf

  1. Kostenobjekte, Mandanten und Verteilungsregeln definieren
  2. direkte und gemeinsame Kosten trennen
  3. Policy als deterministische Zuordnung extrahieren
  4. unbekannte Zuordnungen explizit behandeln
  5. Summenerhalt, Rundung und Regelversionen testen

Wann sinnvoll - und wann nicht?

Sinnvoll bei Multi-Tenant-Plattformen oder interner Leistungsverrechnung. Für kleine Einzelteam-Systeme kann eine einfache Kostenstelle genügen.

Risiken und Fehlanwendungen

Scheingenauigkeit führt zu falschen Anreizen. Gemeinsame Grundkosten und Spitzenkapazität müssen transparent behandelt werden.

Enterprise-Java-Beispiel

Messagingkosten werden nach Nachrichtenvolumen verteilt, reservierte Basisinfrastruktur nach festen Plattformanteilen.

Abgrenzung und verwandte Muster

Cost Center Value Object, Capacity Reservation, Tenant Policy, Usage Metering.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.math.*;
// Refactoring: Extract Cost Allocation Policy
// Pattern: Policy - fixe und variable Plattformkosten werden reproduzierbar zugeordnet.
public record CostAllocationPolicy(BigDecimal fixedSharePerService,BigDecimal pricePerUnit){
  public CostAllocationPolicy { if(fixedSharePerService.signum()<0||pricePerUnit.signum()<0) throw new IllegalArgumentException(); }
  public BigDecimal allocate(long consumedUnits){ if(consumedUnits<0) throw new IllegalArgumentException(); return fixedSharePerService.add(pricePerUnit.multiply(BigDecimal.valueOf(consumedUnits))).setScale(2,RoundingMode.HALF_UP); }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

200. Introduce Runtime Configuration Snapshot

Diagramm zu Introduce Runtime Configuration Snapshot

Fachliche Einordnung

Laufzeitkonfiguration wird während eines Vorgangs mehrfach aus veränderlichen Quellen gelesen. Ein einzelner Geschäftsprozess kann dadurch mit widersprüchlichen Werten laufen.

Erkennungsmerkmale

  • ein Request liest Featureflag und Grenzwert zu verschiedenen Zeitpunkten
  • Konfigurationsänderungen erzeugen nicht reproduzierbare Fehler
  • Tests müssen globale Konfiguration mutieren

Sicherer Ablauf

  1. für einen Vorgang benötigte Werte identifizieren
  2. validierten unveränderlichen Snapshot erzeugen
  3. Snapshot am Use-Case-Eingang binden
  4. Version und Erstellungszeit mitführen
  5. parallele Aktualisierung und alte Snapshot-Versionen testen

Wann sinnvoll - und wann nicht?

Sinnvoll bei dynamisch änderbarer Konfiguration und längeren Vorgängen. Für unveränderliche Startkonfiguration reicht ein normaler Konfigurationswert.

Risiken und Fehlanwendungen

Snapshots können veralten. Sicherheitskritische Widerrufe und Kill Switches benötigen gegebenenfalls einen bewusst unmittelbaren Pfad.

Enterprise-Java-Beispiel

Ein Preisberechnungslauf verwendet genau eine Version von Rundung, Gebühren und Featureflags, auch wenn sich die zentrale Konfiguration währenddessen ändert.

Abgrenzung und verwandte Muster

Immutable Snapshot, Configuration Version, Feature Toggle Policy, Dynamic Refresh Coordinator.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant; import java.util.*;
// Refactoring: Introduce Runtime Configuration Snapshot
// Pattern: Immutable Snapshot - ein Verarbeitungslauf sieht eine konsistente Konfigurationsversion.
public record RuntimeConfigurationSnapshot(long version,Instant loadedAt,Map<String,String> values){
  public RuntimeConfigurationSnapshot { if(version<0) throw new IllegalArgumentException(); Objects.requireNonNull(loadedAt); values=Map.copyOf(values); }
  public String required(String key){ var value=values.get(key); if(value==null||value.isBlank()) throw new IllegalStateException("missing "+key); return value; }
  public int requiredInt(String key){ return Integer.parseInt(required(key)); }
}
Teststrategie: Vertragstests sichern die fachliche Entscheidung; Szenariotests prüfen Normalbetrieb, Grenzwerte, Degradation, Ausfall und Rückkehr. Zeit, Regionen und Messwerte werden über deterministische Fakes eingebracht.

Deutschsprachige Fachgrundlagen

Kapitel 21 Secrets, Konfiguration und sichere Provider-Grenzen10 Refactorings

Refactorings 201–210 machen Secrets, Rotation, Konfigurationszustand, Overrides, Kill Switches, Audit und Drift explizit.

Refactoring-Katalog · Abschnitt 6

Konfigurations-, Secret- und Betriebsgrenzen

Refactorings 201–210 machen Secrets, Rotation, Konfigurationszustand, Overrides, Kill Switches, Audit und Drift explizit.

210 von 300 Refactorings abgeschlossen

90 Refactorings offen.

201. Extract Secret Provider

Fachliche Einordnung

Geheimnisse werden direkt aus Umgebungsvariablen, Properties oder statischen Hilfsklassen gelesen. Dadurch vermischt sich Geschäftslogik mit Speicherort, Authentifizierung und Fehlerbehandlung des Secret-Stores.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Ein Zahlungsadapter bezieht API-Schlüssel über einen SecretProvider-Port; Tests verwenden einen In-Memory-Provider ohne echte Zugangsdaten.

Abgrenzung und verwandte Muster

Secret Rotation Policy, Credential Port, Runtime Configuration Snapshot

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Secret Provider
// Pattern: Port/Adapter - Secret-Speicher bleibt austauschbar.
public interface SecretProvider { Optional<char[]> find(String key); default char[] require(String key){ return find(key).orElseThrow(() -> new IllegalStateException("missing secret: "+key)); } }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

202. Introduce Secret Rotation Policy

Fachliche Einordnung

Ablauf, Übergangsfrist und Aktivierung neuer Geheimnisversionen sind implizit. Rotation wird dadurch zu einer riskanten Betriebsaktion statt zu einer kontrollierten Policy.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Zwei Schlüsselversionen sind während eines kurzen Überlappungsfensters gültig; danach wird die alte Version gesperrt.

Abgrenzung und verwandte Muster

Secret Provider, Key Rotation Policy, Rollback Plan

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Introduce Secret Rotation Policy
// Pattern: Policy - Rotationsregeln werden deterministisch testbar.
public record SecretRotationPolicy(Duration maximumAge, Duration gracePeriod){ public SecretRotationPolicy{ if(maximumAge.isNegative()||maximumAge.isZero()||gracePeriod.isNegative()) throw new IllegalArgumentException(); } public boolean due(Instant createdAt,Instant now){ return !now.isBefore(createdAt.plus(maximumAge)); } public boolean expired(Instant createdAt,Instant now){ return now.isAfter(createdAt.plus(maximumAge).plus(gracePeriod)); } }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

203. Extract Configuration Validator

Fachliche Einordnung

Konfiguration wird erst dort geprüft, wo sie verwendet wird. Fehler erscheinen spät, uneinheitlich und oft erst unter Last.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Ein Validator prüft Endpunkte, Grenzwerte und Kombinationen beim Laden und liefert ein typisiertes Fehlerergebnis.

Abgrenzung und verwandte Muster

Configuration Snapshot, Safe Default Policy, Bootstrap Configuration

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Configuration Validator
// Pattern: Specification - mehrere Konfigurationsregeln werden gemeinsam ausgewertet.
public final class ConfigurationValidator { public record Config(String endpoint,int timeoutMillis,boolean tls){} public List<String> validate(Config c){ var e=new ArrayList<String>(); if(c.endpoint()==null||c.endpoint().isBlank()) e.add("endpoint"); if(c.timeoutMillis()<=0) e.add("timeout"); if(c.endpoint()!=null&&c.endpoint().startsWith("http:")&&c.tls()) e.add("tls-endpoint-mismatch"); return List.copyOf(e); } }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

204. Introduce Configuration Snapshot

Fachliche Einordnung

Ein Vorgang liest Konfiguration mehrfach aus veränderlichen Quellen. Innerhalb desselben Ablaufs können dadurch widersprüchliche Werte gelten.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Ein Preisberechnungslauf erhält genau einen unveränderlichen Snapshot mit Version und Zeitstempel.

Abgrenzung und verwandte Muster

Runtime Configuration Snapshot, Policy Version, Configuration Checksum

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*; import java.util.*;
// Refactoring: Introduce Configuration Snapshot
// Pattern: Immutable Snapshot - Leser sehen konsistente Versionen.
public record ConfigurationSnapshot(long version,Instant loadedAt,Map<String,String> values){ public ConfigurationSnapshot{ values=Map.copyOf(values); if(version<0) throw new IllegalArgumentException(); } public String require(String key){ var v=values.get(key); if(v==null) throw new IllegalArgumentException("missing "+key); return v; } }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

205. Extract Dynamic Refresh Coordinator

Fachliche Einordnung

Mehrere Komponenten laden Konfiguration unabhängig neu. Reihenfolge, Validierung und Fehlerbehandlung sind nicht koordiniert.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Der Coordinator lädt, validiert und veröffentlicht einen neuen Snapshot atomar; bei Fehlern bleibt der alte aktiv.

Abgrenzung und verwandte Muster

Configuration Snapshot, Reconciliation Loop, Change Set

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.concurrent.atomic.*;
// Refactoring: Extract Dynamic Refresh Coordinator
// Pattern: Coordinator - Refresh-Schritte werden explizit sequenziert.
public final class DynamicRefreshCoordinator { public interface Loader{ ConfigurationSnapshot load(); } public interface Validator{ boolean valid(ConfigurationSnapshot s); } private final Loader loader; private final Validator validator; private final AtomicReference<ConfigurationSnapshot> current; public DynamicRefreshCoordinator(Loader l,Validator v,ConfigurationSnapshot initial){loader=l;validator=v;current=new AtomicReference<>(initial);} public boolean refresh(){ var next=loader.load(); return validator.valid(next)&&current.compareAndSet(current.get(),next); } public ConfigurationSnapshot current(){return current.get();} }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

206. Introduce Tenant Override Policy

Fachliche Einordnung

Mandantenspezifische Abweichungen liegen als verstreute if-Abfragen und Schlüsselpräfixe vor.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Eine Policy entscheidet transparent, welche globalen Werte überschrieben werden dürfen und welche unveränderlich bleiben.

Abgrenzung und verwandte Muster

Configuration Namespace, Source Priority, Safe Default Policy

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Tenant Override Policy
// Pattern: Policy - Tenant-Overrides bleiben begrenzt und nachvollziehbar.
public record TenantOverridePolicy(Set<String> allowedKeys){ public TenantOverridePolicy{allowedKeys=Set.copyOf(allowedKeys);} public Map<String,String> merge(Map<String,String> base,Map<String,String> override){ var out=new HashMap<>(base); override.forEach((k,v)->{if(!allowedKeys.contains(k)) throw new IllegalArgumentException("forbidden override: "+k); out.put(k,v);}); return Map.copyOf(out); } }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

207. Extract Kill Switch

Fachliche Einordnung

Notabschaltungen sind als beliebige Feature-Flags implementiert und besitzen keine besondere Priorität oder Auditierbarkeit.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Ein KillSwitch-Port ermöglicht eine sofortige, nachvollziehbare Abschaltung kritischer Folgeaufrufe.

Abgrenzung und verwandte Muster

Feature Toggle Policy, Emergency Override Policy, Access Log

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Extract Kill Switch
// Pattern: Guard - riskante Verarbeitung wird früh und erklärbar blockiert.
public record KillSwitch(boolean enabled,String reason,Instant until){ public boolean blocks(Instant now){ return enabled&&(until==null||now.isBefore(until)); } public void check(Instant now){ if(blocks(now)) throw new IllegalStateException("disabled: "+reason); } }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

208. Introduce Configuration Change Set

Fachliche Einordnung

Einzelne Schlüsseländerungen werden ohne fachlichen Zusammenhang verteilt. Teiländerungen können inkonsistente Zustände erzeugen.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Zusammengehörige Änderungen werden als versioniertes Change Set validiert, genehmigt und atomar aktiviert.

Abgrenzung und verwandte Muster

Approval Workflow, Policy Version, Rollback Plan

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*; import java.util.*;
// Refactoring: Introduce Configuration Change Set
// Pattern: Command/Audit Record - Änderung und Motivation reisen gemeinsam.
public record ConfigurationChangeSet(String actor,String reason,Instant changedAt,Map<String,String> before,Map<String,String> after){ public ConfigurationChangeSet{before=Map.copyOf(before);after=Map.copyOf(after);} public Set<String> changedKeys(){ var keys=new HashSet<String>(); keys.addAll(before.keySet()); keys.addAll(after.keySet()); keys.removeIf(k->Objects.equals(before.get(k),after.get(k))); return Set.copyOf(keys); } }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

209. Introduce Policy Version

Fachliche Einordnung

Entscheidungen lassen sich später nicht der damals gültigen Regelversion zuordnen.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Jede Policy-Auswertung trägt eine stabile Version, die Audit, Reproduktion und schrittweise Migration ermöglicht.

Abgrenzung und verwandte Muster

Configuration Snapshot, Audit Trail, Compatibility Adapter

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Introduce Policy Version
// Pattern: Value Object - Regelstände werden reproduzierbar referenziert.
public record PolicyVersion(String name,int major,int minor,Instant activeFrom) implements Comparable<PolicyVersion>{ public PolicyVersion{if(major<0||minor<0) throw new IllegalArgumentException();} public boolean activeAt(Instant t){return !t.isBefore(activeFrom);} public int compareTo(PolicyVersion o){int n=name.compareTo(o.name); if(n!=0)return n; int m=Integer.compare(major,o.major); return m!=0?m:Integer.compare(minor,o.minor);} }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

210. Extract Configuration Drift Detector

Fachliche Einordnung

Soll- und Ist-Konfiguration weichen unbemerkt voneinander ab. Manuelle Änderungen bleiben bis zum Fehlerfall verborgen.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Der DriftDetector vergleicht normalisierte Snapshots und erzeugt strukturierte Abweichungsbefunde.

Abgrenzung und verwandte Muster

Reconciliation Loop, Checksum, Access Log

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Configuration Drift Detector
// Pattern: Differ - Soll/Ist-Abweichungen werden als typisiertes Ergebnis geliefert.
public final class ConfigurationDriftDetector { public record Drift(Set<String> missing,Set<String> unexpected,Set<String> changed){ public boolean clean(){return missing.isEmpty()&&unexpected.isEmpty()&&changed.isEmpty();} } public Drift compare(Map<String,String> desired,Map<String,String> actual){ var missing=new HashSet<>(desired.keySet());missing.removeAll(actual.keySet()); var unexpected=new HashSet<>(actual.keySet());unexpected.removeAll(desired.keySet()); var changed=new HashSet<String>(); desired.forEach((k,v)->{if(actual.containsKey(k)&&!Objects.equals(v,actual.get(k)))changed.add(k);}); return new Drift(Set.copyOf(missing),Set.copyOf(unexpected),Set.copyOf(changed)); } }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

Kapitel 22 Safe Defaults, Promotion und Konfigurationsschutz10 Refactorings

Refactorings 211–220 machen Defaults, Freigaben, Rollbacks, Wartungsfenster, Promotions, Quellenpriorität, Prüfsummen, Zugriffsprotokolle, Namespaces und Bootstrap-Konfiguration explizit.

Refactoring-Katalog · Abschnitt 7

Governance, Promotion und sichere Konfigurationsgrenzen

Refactorings 211–220 machen Defaults, Freigaben, Rollbacks, Wartungsfenster, Promotions, Quellenpriorität, Prüfsummen, Zugriffsprotokolle, Namespaces und Bootstrap-Konfiguration explizit.

220 von 300 Refactorings abgeschlossen

80 Refactorings offen.

211. Introduce Safe Default Policy

Fachliche Einordnung

Fehlende oder ungültige Konfiguration führt zu zufälligen Standardwerten oder Null-Verhalten.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Eine SafeDefaultPolicy definiert bewusst fail-safe oder fail-closed Verhalten je Konfigurationsklasse.

Abgrenzung und verwandte Muster

Configuration Validator, Kill Switch, Bootstrap Configuration

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Safe Default Policy
// Pattern: Policy - Defaults werden explizit, begrenzt und testbar.
public record SafeDefaultPolicy(Map<String,String> defaults, Set<String> requiredKeys){ public SafeDefaultPolicy{defaults=Map.copyOf(defaults);requiredKeys=Set.copyOf(requiredKeys);} public String resolve(String key,Map<String,String> values){ var value=values.get(key); if(value!=null)return value; if(requiredKeys.contains(key)&&!defaults.containsKey(key))throw new IllegalStateException("missing required: "+key); return defaults.get(key); } }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

212. Extract Configuration Approval Workflow

Fachliche Einordnung

Kritische Änderungen werden ohne klaren Genehmigungs- und Vier-Augen-Prozess aktiviert.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Ein Workflow modelliert Antrag, Prüfung, Freigabe, Aktivierung und Ablehnung mit Auditspur.

Abgrenzung und verwandte Muster

Change Set, Change Window Policy, Access Log

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Extract Configuration Approval Workflow
// Pattern: State Machine - Änderung durchläuft nachvollziehbare Freigabestufen.
public final class ConfigurationApprovalWorkflow { public enum State{DRAFT,APPROVED,ACTIVE,REJECTED} public record Change(String id,State state,String approver,Instant activatedAt){} public Change approve(Change c,String approver){if(c.state()!=State.DRAFT)throw new IllegalStateException();return new Change(c.id(),State.APPROVED,approver,null);} public Change activate(Change c,Instant now){if(c.state()!=State.APPROVED)throw new IllegalStateException();return new Change(c.id(),State.ACTIVE,c.approver(),now);} }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

213. Introduce Configuration Rollback Plan

Fachliche Einordnung

Rollback besteht nur aus der Hoffnung, alte Werte manuell wiederherzustellen.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Der Plan hält Vorgängerversion, Rückkehrkriterien und ausführbare Rollback-Schritte bereit.

Abgrenzung und verwandte Muster

Snapshot Reference, Policy Version, Change Set

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Configuration Rollback Plan
// Pattern: Command/Memento - Rückkehrziel und Aktion werden gemeinsam modelliert.
public record ConfigurationRollbackPlan(ConfigurationSnapshot previous,List<String> checks){ public ConfigurationRollbackPlan{checks=List.copyOf(checks);} public ConfigurationSnapshot execute(java.util.function.Consumer<ConfigurationSnapshot> activator){activator.accept(previous);return previous;} }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

214. Extract Change Window Policy

Fachliche Einordnung

Zeitliche Einschränkungen für riskante Änderungen sind in Kalendern und Betriebshandbüchern versteckt.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Eine Policy bewertet Zeitpunkt, Kritikalität, Bereitschaft und Ausnahmefreigabe.

Abgrenzung und verwandte Muster

Approval Workflow, Rollout Guard, Emergency Override Policy

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*; import java.util.*;
// Refactoring: Extract Change Window Policy
// Pattern: Policy - Betriebszeitfenster werden zentral bewertet.
public record ChangeWindowPolicy(Set<DayOfWeek> days,LocalTime from,LocalTime until,ZoneId zone){ public ChangeWindowPolicy{days=Set.copyOf(days);} public boolean allows(Instant instant){var z=instant.atZone(zone);return days.contains(z.getDayOfWeek())&&!z.toLocalTime().isBefore(from)&&z.toLocalTime().isBefore(until);} }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

215. Introduce Environment Promotion

Fachliche Einordnung

Konfiguration wird je Umgebung separat gepflegt und driftet auseinander.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Ein Promotion-Objekt überführt ein geprüftes Change Set nachvollziehbar von Test nach Produktion.

Abgrenzung und verwandte Muster

Change Set, Configuration Checksum, Source Priority

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Environment Promotion
// Pattern: Command - Promotion wird als auditable Aktion modelliert.
public record EnvironmentPromotion(String source,String target,long version,Map<String,String> payload){ public EnvironmentPromotion{payload=Map.copyOf(payload);if(source.equals(target))throw new IllegalArgumentException();} public EnvironmentPromotion to(String next){return new EnvironmentPromotion(target,next,version,payload);} }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

216. Extract Configuration Source Priority

Fachliche Einordnung

Datei, Umgebung, Remote-Store und Mandantenwerte überschreiben sich nach impliziter Reihenfolge.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Eine explizite Prioritätsregel macht Herkunft, Überschreibung und verbotene Konflikte sichtbar.

Abgrenzung und verwandte Muster

Tenant Override Policy, Configuration Namespace, Bootstrap Configuration

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Configuration Source Priority
// Pattern: Strategy - Quellen werden nach expliziter Priorität zusammengeführt.
public record ConfigurationSourcePriority(List<String> order){ public ConfigurationSourcePriority{order=List.copyOf(order);} public Map<String,String> merge(Map<String,Map<String,String>> sources){var result=new LinkedHashMap<String,String>(); for(String source:order){result.putAll(sources.getOrDefault(source,Map.of()));} return Map.copyOf(result);} }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

217. Introduce Configuration Checksum

Fachliche Einordnung

Änderungen und Übertragungsfehler sind nur durch kompletten Inhaltsvergleich erkennbar.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Eine kanonische Prüfsumme identifiziert exakt den aktivierten Konfigurationsstand.

Abgrenzung und verwandte Muster

Snapshot, Drift Detector, Environment Promotion

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.nio.charset.*; import java.security.*; import java.util.*;
// Refactoring: Introduce Configuration Checksum
// Pattern: Value Object - Snapshot-Identität wird inhaltlich bestimmt.
public record ConfigurationChecksum(String value){ public static ConfigurationChecksum of(Map<String,String> values){try{var md=MessageDigest.getInstance("SHA-256");values.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(e->md.update((e.getKey()+"="+e.getValue()+"\n").getBytes(StandardCharsets.UTF_8)));return new ConfigurationChecksum(HexFormat.of().formatHex(md.digest()));}catch(NoSuchAlgorithmException e){throw new IllegalStateException(e);}} }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

218. Extract Configuration Access Log

Fachliche Einordnung

Lese- und Änderungszugriffe auf sensible Konfiguration sind nicht nachvollziehbar.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Ein Audit-Port protokolliert wer welche Version gelesen, geändert oder aktiviert hat, ohne Geheimniswerte mitzuschreiben.

Abgrenzung und verwandte Muster

Secret Provider, Approval Workflow, Policy Version

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Extract Configuration Access Log
// Pattern: Audit Port - Zugriff wird entkoppelt und nachvollziehbar.
public interface ConfigurationAccessLog { record Access(String actor,String key,String purpose,Instant at){} void record(Access access); default String read(String actor,String key,String purpose,java.util.function.Supplier<String> supplier,Clock clock){record(new Access(actor,key,purpose,clock.instant()));return supplier.get();} }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

219. Introduce Configuration Namespace

Fachliche Einordnung

Schlüssel sind global, uneinheitlich benannt und kollidieren zwischen Modulen oder Mandanten.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Ein Namespace strukturiert Ownership, Sichtbarkeit und Lebenszyklus der Konfiguration.

Abgrenzung und verwandte Muster

Source Priority, Tenant Override Policy, Composition Root

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Configuration Namespace
// Pattern: Value Object - Schlüsselräume werden explizit und kollisionsfrei.
public record ConfigurationNamespace(String application,String module,String tenant){ public ConfigurationNamespace{if(application.isBlank()||module.isBlank())throw new IllegalArgumentException();} public String qualify(String key){if(key.isBlank())throw new IllegalArgumentException();return application+"."+module+(tenant==null||tenant.isBlank()?"":"."+tenant)+"."+key;} }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

220. Extract Bootstrap Configuration

Fachliche Einordnung

Startkritische Werte und dynamische Laufzeitkonfiguration sind vermischt. Ohne Remote-Store kann die Anwendung nicht kontrolliert starten.

Erkennungsmerkmale

  • Die Entscheidung oder technische Abhängigkeit ist über mehrere Klassen, Properties oder Betriebsanweisungen verteilt.
  • Fehler werden spät erkannt und sind nur schwer reproduzierbar.
  • Tests müssen globale Zustände, Umgebungsvariablen oder externe Systeme manipulieren.

Sicherer Ablauf

  1. Beobachtbares Verhalten und bestehende Konfigurationsquellen durch Characterization Tests sichern.
  2. Fachliche Entscheidung von Speicherort, Transport und Framework-API trennen.
  3. Einen kleinen typisierten Port oder ein unveränderliches Value Object einführen.
  4. Validierung, Versionierung und Fehlerverhalten explizit machen.
  5. Altpfad schrittweise ablösen und Rollback beziehungsweise Fallback testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Anwendungen, dynamischer Konfiguration, mehreren Umgebungen oder erhöhten Sicherheits- und Audit-Anforderungen. Für eine kleine Anwendung mit unveränderlichen Startwerten kann eine zusätzliche Abstraktionsschicht unnötig sein.

Risiken und Fehlanwendungen

Die neue Abstraktion darf keine Scheinsicherheit erzeugen. Geheimnisse dürfen weder geloggt noch in Snapshots oder Prüfsummen offengelegt werden. Dynamische Änderungen benötigen atomare Aktivierung, nachvollziehbare Versionen und ein bewusstes Fehlerverhalten.

Enterprise-Java-Beispiel

Eine kleine Bootstrap-Konfiguration enthält nur Identität, Vertrauensanker und Endpunkte zum Laden des validierten Hauptsnapshots.

Abgrenzung und verwandte Muster

Secret Provider, Configuration Validator, Safe Default Policy

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.net.*; import java.time.*;
// Refactoring: Extract Bootstrap Configuration
// Pattern: Bootstrap Object - minimale Startabhängigkeiten bleiben stabil.
public record BootstrapConfiguration(URI configurationEndpoint,Duration connectTimeout,String identity){ public BootstrapConfiguration{if(connectTimeout.isNegative()||connectTimeout.isZero()||identity.isBlank())throw new IllegalArgumentException();} public boolean secure(){return "https".equalsIgnoreCase(configurationEndpoint.getScheme());} }
Teststrategie: Vertragstests prüfen den Port. Szenariotests decken fehlende Werte, ungültige Kombinationen, Versionswechsel, parallele Aktualisierung, Rollback und sicheren Ausfall ab. Geheimnisse werden ausschließlich durch Fakes oder Platzhalter repräsentiert.

Deutschsprachige Fachgrundlagen

Kapitel 23 Ownership, Migration und kontrollierte Konfigurationsänderung10 Refactorings

Refactorings 221–230 machen Eigentum, Risiko, Abhängigkeiten, Migration, Deprecation, Rollout-Messung, Reconciliation, Konflikte, Leases und Notfallzugriff explizit.

Refactoring-Katalog · Abschnitt 8

Ownership, Migration und kontrollierte Konfigurationssteuerung

Refactorings 221–230 machen Eigentum, Risiko, Abhängigkeiten, Migration, Deprecation, Rollout-Messung, Reconciliation, Konflikte, Leases und Notfallzugriff explizit.

230 von 300 Refactorings abgeschlossen

70 Refactorings offen.

221. Introduce Configuration Ownership

Fachliche Einordnung

Konfiguration besitzt häufig keinen klaren fachlichen und technischen Verantwortungsbereich. Mehrere Teams ändern dieselben Schlüssel, während niemand verbindlich für Bedeutung, Freigabe, Lebenszyklus und Rückbau zuständig ist. Das führt zu widersprüchlichen Annahmen, verwaisten Schaltern und riskanten Änderungen ohne belastbare Entscheidungskette.

Erkennungsmerkmale

  • Schlüssel werden von mehreren Modulen oder Teams geschrieben
  • für Störungen existiert kein eindeutig zuständiger Owner
  • fachliche Bedeutung und technische Ablage werden getrennt dokumentiert
  • veraltete Konfiguration bleibt bestehen, weil niemand den Rückbau verantwortet

Sicherer Refactoring-Ablauf

  1. Alle Konfigurationsbereiche nach fachlicher Wirkung und technischer Nutzung inventarisieren.
  2. Für jeden Bereich einen verantwortlichen Owner, Stellvertretung und Eskalationsweg festlegen.
  3. Ownership als typisierten Vertrag mit Geltungsbereich, Änderungsrecht und Review-Pflicht modellieren.
  4. Freigabe-, Audit- und Deprecation-Prozesse an die Ownership-Information koppeln.
  5. Verwaiste, widersprüchliche und unzulässig geänderte Konfiguration durch automatisierte Regeln erkennen.

Wann sinnvoll - und wann nicht?

Sinnvoll in größeren Systemlandschaften, bei mehreren Teams oder geteilten Plattformdiensten. In einem kleinen, allein betreuten Modul reicht eine klar dokumentierte Zuständigkeit; ein eigenes Laufzeitmodell wäre dort unnötig.

Risiken und typische Fehlanwendungen

Ownership darf nicht zu einer organisatorischen Sperre für jede Kleinigkeit werden. Verantwortlichkeit, Änderungsrecht und technische Betriebszuständigkeit müssen unterscheidbar bleiben. Ein bloßes Namensfeld ohne Review- und Eskalationsprozess erzeugt Scheinsicherheit.

Enterprise-Java-Beispiel

Die Preisplattform ordnet Rabattschwellen dem Pricing-Team, Sicherheitsgrenzen dem Security-Team und technische Timeouts dem Plattformteam zu. Jede Änderung wird mit Owner, Grund, Version und Review-Frist gespeichert.

Abgrenzung und verwandte Muster

Configuration Namespace, Approval Workflow, Deprecation Policy, Access Log.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Configuration Ownership
// Pattern: Value Object / Ownership Policy - Verantwortung wird explizit und prüfbar.
public record ConfigurationOwner(String team,String contact,Set<String> namespaces){
  public ConfigurationOwner{if(team.isBlank()||contact.isBlank())throw new IllegalArgumentException();namespaces=Set.copyOf(namespaces);}
  public boolean owns(String namespace){return namespaces.stream().anyMatch(namespace::startsWith);}
}
Teststrategie: Vertragstests sichern Ownership, Versionierung und Entscheidungsgrenzen. Szenariotests decken konkurrierende Änderungen, veraltete Snapshots, Teilaktivierung, Rollback, Ablauf und nicht automatisch lösbare Konflikte ab.

Deutschsprachige Fachgrundlagen

222. Extract Configuration Risk Classifier

Fachliche Einordnung

Alle Konfigurationsänderungen durchlaufen denselben Prozess, obwohl ein Textlabel ein anderes Risiko besitzt als ein Authentisierungsmodus, ein Zahlungsgrenzwert oder ein Kill Switch. Dadurch werden harmlose Änderungen unnötig langsam und kritische Änderungen zu schwach kontrolliert.

Erkennungsmerkmale

  • Freigaben richten sich nur nach Umgebung oder Dateipfad
  • Ausfallwirkung, Sicherheitsrelevanz und Datenrisiko werden nicht bewertet
  • Notfalländerungen und normale Pflege verwenden denselben Prozess
  • Tests und Rollback-Anforderungen sind nicht vom Risiko abhängig

Sicherer Refactoring-Ablauf

  1. Auswirkungen auf Verfügbarkeit, Sicherheit, Datenintegrität und Compliance erfassen.
  2. Wenige verständliche Risikoklassen mit nachvollziehbaren Kriterien definieren.
  3. Die Klassifikation als reine, testbare Policy extrahieren.
  4. Freigabe, Change Window, Testtiefe und Rollback-Vorgaben aus der Risikoklasse ableiten.
  5. Grenzfälle und falsch klassifizierte Änderungen regelmäßig anhand realer Vorfälle überprüfen.

Wann sinnvoll - und wann nicht?

Sinnvoll, sobald Konfiguration unterschiedliche geschäftliche oder sicherheitsrelevante Auswirkungen besitzt. Bei ausschließlich kosmetischen, statischen Einstellungen genügt meist ein einfacher Review-Prozess.

Risiken und typische Fehlanwendungen

Zu viele Klassen machen die Entscheidung unverständlich. Die Klassifikation darf nicht allein auf Schlüsselpräfixen beruhen und muss bei unbekannten Änderungen konservativ reagieren.

Enterprise-Java-Beispiel

Eine Änderung der E-Mail-Betreffzeile wird als niedriges Risiko eingestuft; eine neue Token-Gültigkeit oder Zahlungsgrenze verlangt Vier-Augen-Freigabe, Canary-Rollout und getesteten Rollback.

Abgrenzung und verwandte Muster

Change Window Policy, Rollout Guard, Approval Workflow, Safe Default Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Configuration Risk Classifier
// Pattern: Strategy - Risikoentscheidungen werden zentral und austauschbar.
public final class ConfigurationRiskClassifier{
  public enum Risk{LOW,MEDIUM,HIGH,CRITICAL}
  public record Change(int affectedServices,boolean securitySensitive,boolean reversible){}
  public Risk classify(Change c){if(c.securitySensitive()&&!c.reversible())return Risk.CRITICAL;if(c.securitySensitive()||c.affectedServices()>20)return Risk.HIGH;if(c.affectedServices()>5||!c.reversible())return Risk.MEDIUM;return Risk.LOW;}
}
Teststrategie: Vertragstests sichern Ownership, Versionierung und Entscheidungsgrenzen. Szenariotests decken konkurrierende Änderungen, veraltete Snapshots, Teilaktivierung, Rollback, Ablauf und nicht automatisch lösbare Konflikte ab.

Deutschsprachige Fachgrundlagen

223. Introduce Configuration Dependency Graph

Fachliche Einordnung

Konfigurationswerte werden einzeln betrachtet, obwohl sie fachlich und technisch voneinander abhängen. Wird ein Endpunkt aktiviert, ohne Credentials, Timeout, Featurefreigabe und Schema-Version gemeinsam zu berücksichtigen, entsteht ein formal gültiger, aber nicht betriebsfähiger Zustand.

Erkennungsmerkmale

  • Änderungen müssen in einer geheimen Reihenfolge erfolgen
  • Validierung prüft nur einzelne Schlüssel
  • ein Wert ist ohne mehrere andere Werte bedeutungslos
  • Rollback eines Schlüssels lässt abhängige Werte inkonsistent zurück

Sicherer Refactoring-Ablauf

  1. Direkte und indirekte Abhängigkeiten zwischen Konfigurationswerten erfassen.
  2. Abhängigkeiten als gerichteten Graphen mit Pflicht-, Konflikt- und Reihenfolgebeziehungen modellieren.
  3. Zyklen und unvollständige Teiländerungen vor Aktivierung erkennen.
  4. Change Sets topologisch planen und atomar beziehungsweise stufenweise ausführen.
  5. Migration, Rollback und Ausfall einzelner Abhängigkeiten mit Szenariotests absichern.

Wann sinnvoll - und wann nicht?

Sinnvoll bei modularen Plattformen, Integrationen und mehrstufigen Rollouts. Für wenige unabhängige Startparameter wäre ein Graph überdimensioniert.

Risiken und typische Fehlanwendungen

Der Graph darf keine versteckten Laufzeitabhängigkeiten nachträglich legitimieren. Zu feingranulare Knoten erhöhen Pflegekosten; relevante fachliche Beziehungen müssen im Vordergrund stehen.

Enterprise-Java-Beispiel

Die Aktivierung eines neuen Fraud-Providers hängt von Endpoint, Credential-Version, Timeout-Policy, Circuit-Breaker-Profil und Event-Schema ab. Der Graph verhindert eine Teilaktivierung.

Abgrenzung und verwandte Muster

Configuration Validator, Change Set, Migration Strategy, Composition Root.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Configuration Dependency Graph
// Pattern: Dependency Graph - Aktivierungsreihenfolge wird aus expliziten Kanten abgeleitet.
public final class ConfigurationDependencyGraph{
  private final Map<String,Set<String>> dependencies;
  public ConfigurationDependencyGraph(Map<String,Set<String>> dependencies){var copy=new HashMap<String,Set<String>>();dependencies.forEach((k,v)->copy.put(k,Set.copyOf(v)));this.dependencies=Map.copyOf(copy);}
  public List<String> activationOrder(){var result=new ArrayList<String>();var visiting=new HashSet<String>();var done=new HashSet<String>();dependencies.keySet().forEach(k->visit(k,visiting,done,result));return List.copyOf(result);}
  private void visit(String key,Set<String> visiting,Set<String> done,List<String> result){if(done.contains(key))return;if(!visiting.add(key))throw new IllegalStateException("cycle: "+key);for(String d:dependencies.getOrDefault(key,Set.of()))visit(d,visiting,done,result);visiting.remove(key);done.add(key);result.add(key);}
}
Teststrategie: Vertragstests sichern Ownership, Versionierung und Entscheidungsgrenzen. Szenariotests decken konkurrierende Änderungen, veraltete Snapshots, Teilaktivierung, Rollback, Ablauf und nicht automatisch lösbare Konflikte ab.

Deutschsprachige Fachgrundlagen

224. Extract Configuration Migration

Fachliche Einordnung

Beim Umbenennen, Aufteilen oder Bedeutungswechsel von Konfigurationswerten werden alte und neue Formate ad hoc in Geschäftslogik ausgewertet. Übergangscode verteilt sich und bleibt oft dauerhaft erhalten.

Erkennungsmerkmale

  • mehrere Schlüsselvarianten werden parallel geprüft
  • Fallbacks auf alte Namen sind über viele Klassen verteilt
  • eine neue Version kann alte Werte nicht eindeutig interpretieren
  • Rollback und Vorwärtsmigration sind nicht getrennt beschrieben

Sicherer Refactoring-Ablauf

  1. Ausgangs- und Zielformat samt Semantik explizit beschreiben.
  2. Lesen, Transformieren, Validieren und Schreiben in eine Migration kapseln.
  3. Migration versionieren und idempotent ausführbar machen.
  4. Kompatibilitätszeitraum sowie Rückbau des Altformats festlegen.
  5. Beispiele aus realen Beständen, Wiederholung und Abbruch mit Tests absichern.

Wann sinnvoll - und wann nicht?

Sinnvoll bei produktiven Installationen mit langlebiger Konfiguration und mehreren Softwareversionen. Für eine noch nicht veröffentlichte Anwendung kann das alte Format meist direkt entfernt werden.

Risiken und typische Fehlanwendungen

Eine Migration darf Bedeutungsänderungen nicht als bloße Umbenennung behandeln. Ohne Idempotenz, Sicherung und Beobachtbarkeit kann ein Wiederanlauf Werte doppelt oder unvollständig transformieren.

Enterprise-Java-Beispiel

Aus `payment.timeoutSeconds` und `payment.retryCount` entsteht ein versioniertes Resilience-Profil. Die Migration erzeugt genau einen neuen Datensatz und markiert den Altbestand für späteren Rückbau.

Abgrenzung und verwandte Muster

Compatibility Adapter, Deprecation Policy, Rollback Plan, Policy Version.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Extract Configuration Migration
// Pattern: Migration Strategy - Versionsübergänge werden explizit und wiederholbar.
public interface ConfigurationMigration{
  int fromVersion(); int toVersion(); Map<String,String> migrate(Map<String,String> values);
  static ConfigurationMigration rename(int from,int to,String oldKey,String newKey){return new ConfigurationMigration(){public int fromVersion(){return from;}public int toVersion(){return to;}public Map<String,String> migrate(Map<String,String> values){var r=new HashMap<>(values);if(r.containsKey(oldKey)&&!r.containsKey(newKey))r.put(newKey,r.remove(oldKey));return Map.copyOf(r);}};}
}
Teststrategie: Vertragstests sichern Ownership, Versionierung und Entscheidungsgrenzen. Szenariotests decken konkurrierende Änderungen, veraltete Snapshots, Teilaktivierung, Rollback, Ablauf und nicht automatisch lösbare Konflikte ab.

Deutschsprachige Fachgrundlagen

225. Introduce Configuration Deprecation Policy

Fachliche Einordnung

Alte Schlüssel, Feature-Schalter und Kompatibilitätswerte bleiben unbegrenzt bestehen. Niemand weiß, ob sie noch verwendet werden, wann Warnungen beginnen und unter welchen Bedingungen sie entfernt werden dürfen.

Erkennungsmerkmale

  • alte und neue Namen werden dauerhaft parallel unterstützt
  • Verwendung veralteter Werte wird nicht gemessen
  • Entfernung erfolgt überraschend mit einem Release
  • Defaultwerte maskieren noch vorhandene Alt-Konfiguration

Sicherer Refactoring-Ablauf

  1. Veralteten Vertrag, Ersatz und betroffene Versionen dokumentieren.
  2. Warn-, Migrations- und Abschaltdatum als Policy modellieren.
  3. Nutzung des Altwerts messen, ohne sensible Inhalte zu protokollieren.
  4. Betreiber frühzeitig mit konkretem Migrationshinweis informieren.
  5. Entfernung erst nach nachgewiesener Nichtnutzung und getestetem Rollback durchführen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei öffentlichen Plattformen, mehreren Deployments oder langen Upgrade-Zyklen. Bei internem, atomar ausgeliefertem Code kann eine direkte Migration ausreichen.

Risiken und typische Fehlanwendungen

Eine Deprecation ohne Telemetrie bleibt eine Absichtserklärung. Zu lange Übergangszeiten erhöhen Komplexität, zu kurze Fristen gefährden Bestandsinstallationen.

Enterprise-Java-Beispiel

Der alte Schlüssel `auth.roleMapping` wird drei Releases lang akzeptiert. Jede Nutzung erzeugt eine strukturierte Warnung; danach verweigert die Validierung das Altformat mit einem klaren Migrationshinweis.

Abgrenzung und verwandte Muster

Configuration Migration, Rollout Metrics, Compatibility Adapter, Feature Cleanup Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Introduce Configuration Deprecation Policy
// Pattern: Policy - Warn-, Sperr- und Entfernungszeitpunkte werden zentral bewertet.
public record ConfigurationDeprecationPolicy(LocalDate deprecatedSince,LocalDate removalDate,String replacement){
  public enum Decision{ALLOW,WARN,REJECT}
  public ConfigurationDeprecationPolicy{if(!removalDate.isAfter(deprecatedSince)||replacement.isBlank())throw new IllegalArgumentException();}
  public Decision decide(LocalDate today){if(!today.isBefore(removalDate))return Decision.REJECT;if(!today.isBefore(deprecatedSince))return Decision.WARN;return Decision.ALLOW;}
}
Teststrategie: Vertragstests sichern Ownership, Versionierung und Entscheidungsgrenzen. Szenariotests decken konkurrierende Änderungen, veraltete Snapshots, Teilaktivierung, Rollback, Ablauf und nicht automatisch lösbare Konflikte ab.

Deutschsprachige Fachgrundlagen

226. Extract Configuration Rollout Metrics

Fachliche Einordnung

Ob eine Konfigurationsänderung erfolgreich ist, wird nur durch allgemeine Systemmetriken oder subjektive Beobachtung bewertet. Es fehlt die Verbindung zwischen konkreter Version, Zielgruppe und Auswirkung.

Erkennungsmerkmale

  • Fehleranstieg lässt sich keiner Konfigurationsversion zuordnen
  • Canary und Kontrollgruppe werden nicht getrennt ausgewertet
  • Rollback-Kriterien sind nicht messbar
  • Metriknamen und Schwellenwerte liegen im Änderungsskript

Sicherer Refactoring-Ablauf

  1. Erfolg, Fehler, Latenz und fachliche Ergebnisqualität je Änderung definieren.
  2. Konfigurationsversion, Kohorte und Zeitpunkt als Dimensionen erfassen.
  3. Messung über einen kleinen Metrics-Port vom Rollout entkoppeln.
  4. Guard-Regeln aus stabilen, fachlich bestätigten Schwellenwerten ableiten.
  5. Metrikausfall, verspätete Daten und falsche Korrelation explizit testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei dynamischen Änderungen, Canary-Rollouts und geschäftskritischen Policies. Für rein lokale Entwicklungswerte reicht Beobachtung über Tests.

Risiken und typische Fehlanwendungen

Zu viele Dimensionen können Kosten und Kardinalität explodieren lassen. Metriken dürfen keine personenbezogenen Daten oder Geheimnisse enthalten und müssen fachliche Wirkung statt nur technische Aktivität zeigen.

Enterprise-Java-Beispiel

Version 42 einer Betrugsschwelle wird für fünf Prozent des Verkehrs aktiviert. Der Metrics-Port liefert Ablehnungsquote, manuelle Nachprüfung und Fehlerrate getrennt nach Kontroll- und Canary-Gruppe.

Abgrenzung und verwandte Muster

Rollout Guard, Canary Cohort, SLO Evaluation, Configuration Change Set.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Extract Configuration Rollout Metrics
// Pattern: Port / Snapshot - operative Messwerte werden entkoppelt und gemeinsam bewertet.
public interface ConfigurationRolloutMetrics{
  record Sample(double errorRate,Duration p95Latency,long observations){public Sample{if(errorRate<0||errorRate>1||observations<0)throw new IllegalArgumentException();}}
  Sample sample(String rolloutId);
  default boolean healthy(String rolloutId,double maxError,Duration maxLatency,long minimum){var s=sample(rolloutId);return s.observations()>=minimum&&s.errorRate()<=maxError&&s.p95Latency().compareTo(maxLatency)<=0;}
}
Teststrategie: Vertragstests sichern Ownership, Versionierung und Entscheidungsgrenzen. Szenariotests decken konkurrierende Änderungen, veraltete Snapshots, Teilaktivierung, Rollback, Ablauf und nicht automatisch lösbare Konflikte ab.

Deutschsprachige Fachgrundlagen

227. Introduce Configuration Reconciliation Loop

Fachliche Einordnung

Nach einer Änderung wird angenommen, dass alle Instanzen den gewünschten Zustand erreicht haben. Teilausfälle, manuelle Eingriffe oder verlorene Benachrichtigungen erzeugen dauerhafte Abweichungen.

Erkennungsmerkmale

  • Soll- und Ist-Zustand werden nur einmal beim Deployment verglichen
  • Fehlerhafte Instanzen bleiben auf alter Konfiguration
  • manuelle Reparatur ist der normale Betriebsweg
  • Aktivierung und tatsächliche Anwendung werden verwechselt

Sicherer Refactoring-Ablauf

  1. Gewünschten und beobachteten Zustand getrennt modellieren.
  2. Abweichungen regelmäßig, idempotent und mit begrenzter Rate ermitteln.
  3. Erlaubte Reparaturaktionen und nicht automatisch lösbare Konflikte unterscheiden.
  4. Jeden Lauf mit Version, Ergebnis und nächstem Versuch protokollieren.
  5. Netzpartition, veraltete Beobachtung und konkurrierende Änderungen testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Systemen, vielen Instanzen und eventual-consistenter Verteilung. Bei einem einzelnen Prozess mit atomarem Startsnapshot reicht eine Startvalidierung.

Risiken und typische Fehlanwendungen

Eine aggressive Schleife kann Fehler verstärken oder manuelle Notmaßnahmen überschreiben. Sie braucht Ownership, Backoff, Abbruchgrenzen und eine eindeutige Quelle des Sollzustands.

Enterprise-Java-Beispiel

Der Coordinator erkennt, dass zwei von zwanzig Instanzen noch Version 41 verwenden. Er stößt einen begrenzten Refresh an und eskaliert nach wiederholtem Fehlschlag statt endlos zu schreiben.

Abgrenzung und verwandte Muster

Drift Detector, Desired State, Retry Schedule, Conflict Resolver.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Configuration Reconciliation Loop
// Pattern: Reconciliation Loop - gewünschter Zustand wird wiederholt und idempotent hergestellt.
public final class ConfigurationReconciler{
  public interface Port{Map<String,String> actual();void apply(Map<String,String> changes);}
  public Map<String,String> reconcile(Map<String,String> desired,Port port){var actual=port.actual();var changes=new HashMap<String,String>();desired.forEach((k,v)->{if(!Objects.equals(v,actual.get(k)))changes.put(k,v);});if(!changes.isEmpty())port.apply(Map.copyOf(changes));return Map.copyOf(changes);}
}
Teststrategie: Vertragstests sichern Ownership, Versionierung und Entscheidungsgrenzen. Szenariotests decken konkurrierende Änderungen, veraltete Snapshots, Teilaktivierung, Rollback, Ablauf und nicht automatisch lösbare Konflikte ab.

Deutschsprachige Fachgrundlagen

228. Extract Configuration Conflict Resolver

Fachliche Einordnung

Mehrere Quellen oder Administratoren ändern denselben Konfigurationsbereich gleichzeitig. Die zuletzt geschriebene Version gewinnt, ohne Priorität, Semantik oder fachliche Absicht zu berücksichtigen.

Erkennungsmerkmale

  • Last-write-wins ist die einzige Konfliktregel
  • Tenant-, Plattform- und Notfallwerte überschreiben sich unklar
  • Konflikte werden erst durch Laufzeitfehler sichtbar
  • manuelle Zusammenführung besitzt keine nachvollziehbare Begründung

Sicherer Refactoring-Ablauf

  1. Konfliktarten und beteiligte Quellen typisieren.
  2. Priorität, Merge-Fähigkeit und nicht automatisch lösbare Fälle definieren.
  3. Entscheidung als reinen Resolver mit Evidenz und Begründung extrahieren.
  4. Bei kritischen Konflikten Aktivierung stoppen und menschliche Freigabe verlangen.
  5. Gleichzeitige Updates, veraltete Versionen und Notfallübersteuerung testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei mehreren Konfigurationsquellen, Mandanten-Overrides oder offline arbeitenden Verwaltungswerkzeugen. Bei einer strikt zentralen Schreibstelle genügt optimistische Versionsprüfung.

Risiken und typische Fehlanwendungen

Automatisches Mergen ist gefährlich, wenn Werte gemeinsam eine Invariante bilden. Notfallpriorität darf nicht dauerhaft normale Governance umgehen.

Enterprise-Java-Beispiel

Ein globales Sicherheitsminimum kollidiert mit einem niedrigeren Mandantenwert. Der Resolver lehnt die Absenkung ab, dokumentiert die Policy-Version und bietet keinen stillen Last-write-wins-Fallback.

Abgrenzung und verwandte Muster

Source Priority, Tenant Override Policy, Policy Version, Access Decision.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Extract Configuration Conflict Resolver
// Pattern: Conflict Resolver - konkurrierende Kandidaten werden deterministisch entschieden.
public final class ConfigurationConflictResolver{
  public record Candidate(String value,long version,Instant changedAt,String source){}
  public Candidate resolve(Candidate left,Candidate right){if(left.version()!=right.version())return left.version()>right.version()?left:right;int time=left.changedAt().compareTo(right.changedAt());if(time!=0)return time>0?left:right;return left.source().compareTo(right.source())<=0?left:right;}
}
Teststrategie: Vertragstests sichern Ownership, Versionierung und Entscheidungsgrenzen. Szenariotests decken konkurrierende Änderungen, veraltete Snapshots, Teilaktivierung, Rollback, Ablauf und nicht automatisch lösbare Konflikte ab.

Deutschsprachige Fachgrundlagen

229. Introduce Configuration Lease

Fachliche Einordnung

Lang laufende Verwaltungs- oder Rolloutprozesse gehen davon aus, dass ihre gelesene Konfiguration bis zum Abschluss gültig bleibt. Paralleländerungen führen zu Operationen auf veralteter Grundlage.

Erkennungsmerkmale

  • ein Prozess liest Version A und schreibt später ohne Versionsprüfung
  • globale Sperren werden für lange Änderungen verwendet
  • abgebrochene Besitzer blockieren weitere Arbeiten
  • Gültigkeit einer Reservierung ist nicht zeitlich begrenzt

Sicherer Refactoring-Ablauf

  1. Die zu schützende Ressource und erwartete Version definieren.
  2. Eine zeitlich begrenzte Lease mit Besitzer, Token und Ablaufzeit einführen.
  3. Verlängerung und Freigabe explizit, idempotent und auditierbar machen.
  4. Jede schreibende Operation gegen Lease und Version prüfen.
  5. Ablauf, Besitzerverlust, doppelte Freigabe und Uhrabweichung testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten, länger dauernden Konfigurationsänderungen oder Migrationen. Für kurze atomare Datenbankupdates ist optimistisches Locking meist einfacher.

Risiken und typische Fehlanwendungen

Eine Lease ist keine dauerhafte Sperre. Falsch gewählte Laufzeiten, nicht monotone Zeit und fehlendes Fencing können weiterhin konkurrierende Schreiber zulassen.

Enterprise-Java-Beispiel

Eine mehrstufige Mandantenmigration erhält eine Lease auf Namespace und Version. Nach Ablauf darf ein neuer Prozess übernehmen; alte Schreibversuche werden anhand des Fencing-Tokens abgewiesen.

Abgrenzung und verwandte Muster

Leader Lease, Optimistic Locking, Migration Strategy, Reconciliation Loop.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Introduce Configuration Lease
// Pattern: Lease Object - exklusiver Schreibzugriff hat eine klare zeitliche Grenze.
public record ConfigurationLease(String resource,String holder,Instant expiresAt){
  public ConfigurationLease{if(resource.isBlank()||holder.isBlank())throw new IllegalArgumentException();}
  public boolean validFor(String candidate,Instant now){return holder.equals(candidate)&&now.isBefore(expiresAt);}
  public ConfigurationLease renew(String candidate,Instant now,Duration duration){if(!validFor(candidate,now)||duration.isNegative()||duration.isZero())throw new IllegalStateException();return new ConfigurationLease(resource,holder,now.plus(duration));}
}
Teststrategie: Vertragstests sichern Ownership, Versionierung und Entscheidungsgrenzen. Szenariotests decken konkurrierende Änderungen, veraltete Snapshots, Teilaktivierung, Rollback, Ablauf und nicht automatisch lösbare Konflikte ab.

Deutschsprachige Fachgrundlagen

230. Extract Emergency Override Policy

Fachliche Einordnung

In Störungen werden Werte direkt in Datenbanken, Umgebungsvariablen oder Feature-Stores überschrieben. Solche Notfalländerungen umgehen Validierung, Zeitbegrenzung und Rückführung.

Erkennungsmerkmale

  • Break-Glass-Änderungen besitzen keinen Ablaufzeitpunkt
  • Notfallwerte sind nicht von normalen Overrides unterscheidbar
  • nach dem Vorfall bleibt der Ausnahmezustand aktiv
  • Berechtigung und fachliche Zulässigkeit werden gemeinsam in Skripten geprüft

Sicherer Refactoring-Ablauf

  1. Zulässige Notfallfälle, Berechtigungen und maximale Dauer definieren.
  2. Override als typisierte Policy mit Grund, Genehmiger und Ablauf modellieren.
  3. Normale Validierung nur gezielt und nachvollziehbar übersteuern.
  4. Automatische Erinnerung, Rücknahme und nachträgliche Prüfung vorsehen.
  5. Missbrauch, Ablauf, parallele Normaländerung und Wiederherstellung testen.

Wann sinnvoll - und wann nicht?

Sinnvoll für geschäftskritische Systeme mit dokumentiertem Notfallverfahren. Ohne echte betriebliche Notwendigkeit sollte kein zusätzlicher Bypass existieren.

Risiken und typische Fehlanwendungen

Ein bequemer Notfallschalter wird schnell zum normalen Betriebsweg. Die Policy muss minimal, zeitlich begrenzt, streng autorisiert und vollständig auditiert sein.

Enterprise-Java-Beispiel

Bei einem fehlerhaften externen Provider darf ein Incident Commander für maximal 30 Minuten einen Fallback aktivieren. Danach wird automatisch auf den genehmigten Zustand zurückgestellt.

Abgrenzung und verwandte Muster

Kill Switch, Break Glass Policy, Configuration Lease, Security Audit Event.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Extract Emergency Override Policy
// Pattern: Policy / Expiring Command - Notfallzugriff ist begrenzt, begründet und testbar.
public record EmergencyOverridePolicy(Duration maximumDuration){
  public record Request(String actor,String reason,Instant requestedAt,Duration duration){}
  public record Permit(String actor,String reason,Instant expiresAt){}
  public Permit authorize(Request r,boolean authorized){if(!authorized||r.reason().isBlank()||r.duration().isNegative()||r.duration().isZero()||r.duration().compareTo(maximumDuration)>0)throw new IllegalArgumentException();return new Permit(r.actor(),r.reason(),r.requestedAt().plus(r.duration()));}
}
Teststrategie: Vertragstests sichern Ownership, Versionierung und Entscheidungsgrenzen. Szenariotests decken konkurrierende Änderungen, veraltete Snapshots, Teilaktivierung, Rollback, Ablauf und nicht automatisch lösbare Konflikte ab.

Deutschsprachige Fachgrundlagen

Kapitel 24 Authentication-Ports und Identitätsgrenzen10 Refactorings

Refactorings 231–240 lösen versteckte Sicherheitsabhängigkeiten auf und führen Ports, Value Objects, Policies, Decision Objects, Snapshots, Events und Cache-Regeln ein.

Refactoring-Katalog · Abschnitt 9

Identity, Authentication und Authorization explizit machen

Refactorings 231–240 lösen versteckte Sicherheitsabhängigkeiten auf und führen Ports, Value Objects, Policies, Decision Objects, Snapshots, Events und Cache-Regeln ein.

240 von 300 Refactorings abgeschlossen

60 Refactorings offen.

231. Extract Authentication Port

Fachliche Einordnung

Anwendungscode spricht direkt mit Framework-, Token- oder Verzeichnis-APIs. Damit werden Identitätsprüfung, Protokolldetails und fachliche Use Cases eng gekoppelt und schwer testbar.

Erkennungsmerkmale

  • Controller und Services parsen Token oder Sessions selbst
  • Framework-Sicherheitsobjekte wandern in den Anwendungskern
  • Tests benötigen echte Identity Provider oder komplexe Mocks
  • mehrere Authentisierungsverfahren erzeugen duplizierte Verzweigungen

Sicherer Refactoring-Ablauf

  1. Benötigte Authentisierungsergebnisse aus Sicht des Use Cases bestimmen.
  2. Einen kleinen Port für Identitätsnachweis und Fehlerfälle definieren.
  3. Protokoll-, Framework- und Providerdetails in Adapter verschieben.
  4. Aus externen Claims einen typisierten Principal erzeugen.
  5. ungültige, abgelaufene, fehlende und technisch nicht erreichbare Nachweise testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei austauschbaren Identity Providern, mehreren Zugangskanälen oder einer hexagonalen Architektur. In einer kleinen Randanwendung kann die Frameworkgrenze bereits ausreichend sein.

Risiken und typische Fehlanwendungen

Der Port darf keine Sicherheitsdetails verschlucken, die für die Entscheidung relevant sind. Authentisierung und Autorisierung müssen getrennt bleiben; ein gültiger Nachweis erteilt noch keine Fachberechtigung.

Enterprise-Java-Beispiel

Ein REST-Adapter validiert ein OAuth-Zugriffstoken und liefert dem Use Case nur einen Principal mit stabiler Subjekt-ID, Authentisierungsstärke und Mandant.

Abgrenzung und verwandte Muster

Principal Value Object, Token Issuer, Credential Validator, Anti-Corruption Layer.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Optional;
// Refactoring: Extract Authentication Port
// Pattern: Port / Adapter - Identitätsprüfung bleibt außerhalb der Fachlogik.
public interface AuthenticationPort {
  record Credentials(String scheme,String value){public Credentials{if(scheme.isBlank()||value.isBlank())throw new IllegalArgumentException();}}
  record AuthenticatedIdentity(String subject){public AuthenticatedIdentity{if(subject.isBlank())throw new IllegalArgumentException();}}
  Optional<AuthenticatedIdentity> authenticate(Credentials credentials);
}
Teststrategie: Vertragstests prüfen Port und Decision Objects. Sicherheitsfälle decken fehlende oder ungültige Nachweise, Mandantentrennung, Widerruf, Policywechsel, Kontextverlust, sensible Auditdaten und restriktives Fehlerverhalten ab.

Deutschsprachige Fachgrundlagen

232. Introduce Principal Value Object

Fachliche Einordnung

Identitäten werden als Benutzername, E-Mail, Map oder Frameworkobjekt weitergereicht. Dadurch sind Mandant, Subjekt-ID, Authentisierungsart und Vertrauensniveau uneinheitlich oder veränderbar.

Erkennungsmerkmale

  • Autorisierung nutzt wechselweise Name, E-Mail und Token-Claim
  • null oder leere Identitäten sind möglich
  • Frameworkobjekte gelangen tief in Domain- und Application-Code
  • Gleichheit einer Identität ist nicht eindeutig definiert

Sicherer Refactoring-Ablauf

  1. Stabile Identitätsmerkmale und ihre Invarianten festlegen.
  2. Ein unveränderliches Value Object mit klarer Gleichheit einführen.
  3. Optionale Anzeigenamen von sicherheitsrelevanter Subjekt-ID trennen.
  4. Mapping aus externen Claims ausschließlich am Adapter durchführen.
  5. Mandantenwechsel, Serviceidentität und anonyme Zugriffe als getrennte Fälle testen.

Wann sinnvoll - und wann nicht?

Sinnvoll, sobald Identität mehrere Eigenschaften besitzt oder an vielen fachlichen Entscheidungen beteiligt ist. Bei einem rein technischen Einzweckprozess kann eine stabile Service-ID genügen.

Risiken und typische Fehlanwendungen

E-Mail und Anzeigename sind meist keine dauerhaften Identifikatoren. Sensible Claims sollten nicht unnötig im Principal gehalten oder geloggt werden.

Enterprise-Java-Beispiel

Der Principal enthält `subjectId`, `tenantId`, `actorType` und Authentisierungsstärke. Rollen und Berechtigungen werden bewusst nicht als unveränderliche Identitätseigenschaften modelliert.

Abgrenzung und verwandte Muster

Authentication Port, Security Context Snapshot, Access Decision, Value Object.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Set;
// Refactoring: Introduce Principal Value Object
// Pattern: Value Object - Identität erhält Invarianten und fachliche Semantik.
public record Principal(String subject,String tenant,Set<String> factors){
  public Principal{if(subject.isBlank()||tenant.isBlank())throw new IllegalArgumentException();factors=Set.copyOf(factors);}
  public boolean strong(){return factors.size()>=2;}
}
Teststrategie: Vertragstests prüfen Port und Decision Objects. Sicherheitsfälle decken fehlende oder ungültige Nachweise, Mandantentrennung, Widerruf, Policywechsel, Kontextverlust, sensible Auditdaten und restriktives Fehlerverhalten ab.

Deutschsprachige Fachgrundlagen

233. Extract Authorization Policy

Fachliche Einordnung

Berechtigungslogik liegt als verstreute Rollenabfrage in Controllern, Services und UI. Fachliche Bedingungen wie Eigentümerschaft, Betrag, Mandant und Prozesszustand bleiben unvollständig oder widersprüchlich.

Erkennungsmerkmale

  • `hasRole` und Stringvergleiche dominieren Fachcode
  • derselbe Vorgang wird an mehreren Stellen unterschiedlich geprüft
  • Rollen werden direkt mit fachlichen Aktionen gleichgesetzt
  • Ablehnung besitzt keine nachvollziehbare Begründung

Sicherer Refactoring-Ablauf

  1. Fachliche Aktion, Ressource, Principal und Kontext explizit benennen.
  2. Regeln in eine reine, kombinierbare Authorization Policy extrahieren.
  3. Ein typisiertes Access Decision mit Grund und Evidenz zurückgeben.
  4. Frameworkrollen am Rand in fachliche Fähigkeiten übersetzen.
  5. positive, negative, mandantenfremde und zeitabhängige Fälle als Entscheidungsmatrix testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei nichttrivialen Fachrechten, mehreren Kanälen oder auditpflichtigen Entscheidungen. Für eine einfache administrative Route kann deklarative Methodensicherheit ausreichen.

Risiken und typische Fehlanwendungen

Eine zentrale Policy darf nicht zum allwissenden God Object werden. Regeln sollten nach Ressourcen oder Fähigkeiten geschnitten und standardmäßig restriktiv sein.

Enterprise-Java-Beispiel

Die Freigabe eines Schadenfalls verlangt nicht nur die Rolle Sachbearbeitung, sondern passenden Mandanten, Freigabelimit, Vier-Augen-Regel und einen zulässigen Fallzustand.

Abgrenzung und verwandte Muster

Capability Query, Specification, Access Decision, Policy Enforcement Point.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Map;
// Refactoring: Extract Authorization Policy
// Pattern: Policy / Specification - Zugriff wird zentral und testbar entschieden.
public interface AuthorizationPolicy {
  record Request(Principal principal,String action,Map<String,String> resource){public Request{resource=Map.copyOf(resource);}}
  AccessDecision decide(Request request);
}
Teststrategie: Vertragstests prüfen Port und Decision Objects. Sicherheitsfälle decken fehlende oder ungültige Nachweise, Mandantentrennung, Widerruf, Policywechsel, Kontextverlust, sensible Auditdaten und restriktives Fehlerverhalten ab.

Deutschsprachige Fachgrundlagen

234. Introduce Access Decision

Fachliche Einordnung

Autorisierung liefert nur `true` oder `false`. Für Audit, Fehlermeldung, Support und Tests fehlen Grund, geprüfte Policy-Version und relevante Evidenz.

Erkennungsmerkmale

  • Ablehnungen werden pauschal als 403 protokolliert
  • Tests prüfen nur einen Boolean
  • mehrere Gründe werden durch frühes Return verborgen
  • Entscheidungen lassen sich später nicht reproduzieren

Sicherer Refactoring-Ablauf

  1. Erlaubt, abgelehnt und technisch nicht entscheidbar unterscheiden.
  2. Grundcode, Policy-Version und minimale Evidenz im Decision Object modellieren.
  3. Sensible Details von benutzergeeigneten Meldungen trennen.
  4. Enforcement, Audit und UI auf dasselbe Ergebnis zugreifen lassen.
  5. kombinierte Regeln, Priorität von Ablehnungen und fehlende Evidenz testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei auditierbaren, erklärbaren oder komplexen Zugriffsentscheidungen. Für eine interne, triviale Guard-Clause kann ein Boolean ausreichend bleiben.

Risiken und typische Fehlanwendungen

Das Objekt darf keine geheimen Regelparameter oder personenbezogenen Daten offenlegen. Eine ausführliche Begründung darf einem Angreifer nicht unnötig die Sicherheitslogik erklären.

Enterprise-Java-Beispiel

Die Entscheidung enthält `DENIED_LIMIT_EXCEEDED`, Policy-Version 12 und die Information, dass eine zweite Freigabe erforderlich ist; interne Details bleiben im Auditkanal.

Abgrenzung und verwandte Muster

Authorization Policy, Decision Object, Security Audit Event, Policy Version.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Access Decision
// Pattern: Decision Object - Ergebnis und Begründung reisen gemeinsam.
public record AccessDecision(boolean allowed,String reason){
  public AccessDecision{if(reason==null||reason.isBlank())throw new IllegalArgumentException();}
  public static AccessDecision allow(String reason){return new AccessDecision(true,reason);}
  public static AccessDecision deny(String reason){return new AccessDecision(false,reason);}
}
Teststrategie: Vertragstests prüfen Port und Decision Objects. Sicherheitsfälle decken fehlende oder ungültige Nachweise, Mandantentrennung, Widerruf, Policywechsel, Kontextverlust, sensible Auditdaten und restriktives Fehlerverhalten ab.

Deutschsprachige Fachgrundlagen

235. Replace Role Checks with Capability Query

Fachliche Einordnung

Rollen werden im Fachcode direkt geprüft, obwohl Rollen organisatorische Bündel und keine stabilen Fachaktionen sind. Neue Rollen oder Ausnahmen erfordern Änderungen in vielen Use Cases.

Erkennungsmerkmale

  • Code enthält zahlreiche Vergleiche wie `role == ADMIN`
  • eine Rolle darf nur unter zusätzlichen Bedingungen handeln
  • mehrere Rollen teilen einzelne Fähigkeiten
  • Umbenennung oder Aufteilung einer Rolle verändert Fachcode

Sicherer Refactoring-Ablauf

  1. Konkrete fachliche Aktionen als Fähigkeiten benennen.
  2. Rollen, Gruppen und Claims am Sicherheitsrand auf Fähigkeiten abbilden.
  3. Use Cases ausschließlich nach benötigter Fähigkeit fragen lassen.
  4. Ressourcen- und Kontextbedingungen zusätzlich über eine Policy prüfen.
  5. neue Rollen, kombinierte Fähigkeiten und Entzug ohne Fachcodeänderung testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei wachsenden Berechtigungsmodellen und mehreren organisatorischen Rollen. Bei einem kleinen System mit einer einzigen stabilen Administrationsrolle kann direkte deklarative Prüfung genügen.

Risiken und typische Fehlanwendungen

Fähigkeiten dürfen nicht zu Tausenden unstrukturierten Strings werden. Capability Query ersetzt keine ressourcenbezogene Autorisierung und keine Mandantentrennung.

Enterprise-Java-Beispiel

Der Use Case verlangt `CLAIM_APPROVE`. Ob diese Fähigkeit aus Rolle, Delegation oder zeitlich begrenzter Freigabe stammt, entscheidet der Adapter beziehungsweise die Policy.

Abgrenzung und verwandte Muster

Authorization Policy, Permission Object, Entitlement Reconciler, RBAC.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Set;
// Refactoring: Replace Role Checks with Capability Query
// Pattern: Capability Model - Fachcode kennt Fähigkeiten statt Rollenbezeichnungen.
public record CapabilitySet(Set<String> values){
  public CapabilitySet{values=Set.copyOf(values);}
  public boolean allows(String capability){return values.contains(capability);}
}
Teststrategie: Vertragstests prüfen Port und Decision Objects. Sicherheitsfälle decken fehlende oder ungültige Nachweise, Mandantentrennung, Widerruf, Policywechsel, Kontextverlust, sensible Auditdaten und restriktives Fehlerverhalten ab.

Deutschsprachige Fachgrundlagen

236. Extract Credential Validator

Fachliche Einordnung

Passwort-, Zertifikats-, API-Key- oder Signaturprüfung liegt in einem großen Authentisierungsservice. Algorithmen, Formate, Rotation und Fehlerbehandlung sind miteinander vermischt.

Erkennungsmerkmale

  • ein `switch` wählt Credentialtypen
  • kryptografische Bibliotheksaufrufe liegen im Use Case
  • Rotation verlangt Änderungen an mehreren Stellen
  • Tests können einzelne Prüfverfahren nicht isolieren

Sicherer Refactoring-Ablauf

  1. Credentialtypen und gemeinsame Ergebnissemantik definieren.
  2. Je Verfahren einen Validator hinter einem kleinen Strategy-Vertrag kapseln.
  3. Auswahl anhand eines typisierten Credentialtyps oder Headers treffen.
  4. Bibliotheks- und Schlüsselzugriffe in Infrastrukturadapter verschieben.
  5. ungültig, abgelaufen, widerrufen, unbekannt und technisch fehlerhaft getrennt testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei mehreren Nachweisverfahren oder wechselnden kryptografischen Implementierungen. Bei genau einem stabilen Verfahren genügt eine einzelne klar abgegrenzte Komponente.

Risiken und typische Fehlanwendungen

Validatoren dürfen keine detaillierten Unterschiede nach außen leaken, die Enumeration erleichtern. Zeitvergleich, Schlüsselrotation und sichere Fehlerbehandlung müssen erhalten bleiben.

Enterprise-Java-Beispiel

Ein API-Gateway unterstützt mTLS-Zertifikate und signierte Service-Tokens. Eine Registry wählt den Validator; beide liefern dasselbe typisierte Validierungsergebnis.

Abgrenzung und verwandte Muster

Authentication Port, Strategy, Secret Rotation Policy, Revocation Registry.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Credential Validator
// Pattern: Strategy - Credential-Arten teilen einen stabilen Vertrag.
public interface CredentialValidator {
  record Result(boolean valid,String subject,String reason){}
  Result validate(String credential);
}
Teststrategie: Vertragstests prüfen Port und Decision Objects. Sicherheitsfälle decken fehlende oder ungültige Nachweise, Mandantentrennung, Widerruf, Policywechsel, Kontextverlust, sensible Auditdaten und restriktives Fehlerverhalten ab.

Deutschsprachige Fachgrundlagen

237. Introduce Security Context Snapshot

Fachliche Einordnung

Während eines Vorgangs werden Identität, Mandant, Delegation und Berechtigungsclaims mehrfach aus veränderlichen Thread-, Session- oder Request-Kontexten gelesen. Asynchrone Ausführung und parallele Requests können dadurch falsche oder inkonsistente Werte verwenden.

Erkennungsmerkmale

  • ThreadLocal oder Frameworkkontext wird tief im Fachcode gelesen
  • asynchrone Aufgaben verlieren oder übernehmen fremden Kontext
  • Claims ändern sich innerhalb eines langen Ablaufs unkontrolliert
  • Tests müssen globale Security-Kontexte manipulieren

Sicherer Refactoring-Ablauf

  1. Für den Use Case benötigte Sicherheitsinformationen minimal bestimmen.
  2. Beim Eintritt einen unveränderlichen Snapshot mit Principal, Mandant, Authentisierungsstärke und Delegation erzeugen.
  3. Snapshot explizit an Application Services und asynchrone Commands übergeben.
  4. Lebensdauer und Aktualitätsgrenzen definieren.
  5. Threadwechsel, parallele Requests, Ablauf und widerrufene Berechtigungen testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei asynchronen, parallelen oder längeren Abläufen. Für einen kurzen synchronen Adapter kann der lokale Frameworkkontext ausreichen, solange er nicht in den Kern gelangt.

Risiken und typische Fehlanwendungen

Ein Snapshot kann veralten und darf keine unbegrenzte Autorisierung garantieren. Hochkritische Entscheidungen benötigen gegebenenfalls eine erneute aktuelle Policy-Auswertung.

Enterprise-Java-Beispiel

Ein Exportauftrag speichert den auslösenden Principal und Mandantenkontext im Command. Die spätere Worker-Ausführung übernimmt keinen zufälligen ThreadLocal-Kontext.

Abgrenzung und verwandte Muster

Principal Value Object, Immutable Snapshot, Context Propagation, Authorization Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
// Refactoring: Introduce Security Context Snapshot
// Pattern: Immutable Snapshot - Sicherheitskontext ist explizit und threadsicher.
public record SecurityContextSnapshot(Principal principal,String correlationId,Instant authenticatedAt){
  public SecurityContextSnapshot{if(correlationId.isBlank())throw new IllegalArgumentException();}
}
Teststrategie: Vertragstests prüfen Port und Decision Objects. Sicherheitsfälle decken fehlende oder ungültige Nachweise, Mandantentrennung, Widerruf, Policywechsel, Kontextverlust, sensible Auditdaten und restriktives Fehlerverhalten ab.

Deutschsprachige Fachgrundlagen

238. Extract Token Issuer

Fachliche Einordnung

Token-Erzeugung, Claim-Auswahl, Signatur, Schlüsselzugriff und Ablaufzeit sind in Login- oder Servicecode eingebettet. Fachlogik kennt Bibliotheksdetails und kann Sicherheitsparameter unbeabsichtigt variieren.

Erkennungsmerkmale

  • Services bauen JWT-Maps direkt auf
  • Signaturalgorithmus und Key-ID werden im Use Case gewählt
  • verschiedene Tokenarten besitzen inkonsistente Claims
  • Tests benötigen echte private Schlüssel

Sicherer Refactoring-Ablauf

  1. Tokenzweck, Empfänger, Lebensdauer und notwendige Claims als typisierten Auftrag modellieren.
  2. Einen TokenIssuer-Port mit eindeutigem Ergebnis und Fehlern extrahieren.
  3. Signatur, Schlüsselwahl und Bibliothek in einen Adapter kapseln.
  4. Claim-Minimierung, Audience und Policy-Version zentral validieren.
  5. Ablauf, falsche Audience, Rotation, Uhrabweichung und Signaturfehler testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei eigenen Token, mehreren Tokenarten oder austauschbarer Identity-Infrastruktur. Wenn ausschließlich ein externer Identity Provider Token ausstellt, sollte die Anwendung nicht selbst zum Issuer werden.

Risiken und typische Fehlanwendungen

Selbst ausgestellte Token erhöhen Sicherheitsverantwortung erheblich. Der Port darf schwache Algorithmen, beliebige Claims oder unkontrollierte Laufzeiten nicht ermöglichen.

Enterprise-Java-Beispiel

Der Application Service fordert ein kurzlebiges Download-Token für genau eine Datei an. Der Adapter setzt Audience, Subject, Scope, Ablauf und Key-ID nach zentraler Policy.

Abgrenzung und verwandte Muster

Authentication Port, Factory, Key Rotation Policy, Token Validation Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Duration;
// Refactoring: Extract Token Issuer
// Pattern: Port / Factory - Tokenformat und Kryptografie bleiben Infrastrukturdetails.
public interface TokenIssuer {
  record Token(String value,Duration validFor){public Token{if(value.isBlank()||validFor.isNegative()||validFor.isZero())throw new IllegalArgumentException();}}
  Token issue(Principal principal,Duration validity);
}
Teststrategie: Vertragstests prüfen Port und Decision Objects. Sicherheitsfälle decken fehlende oder ungültige Nachweise, Mandantentrennung, Widerruf, Policywechsel, Kontextverlust, sensible Auditdaten und restriktives Fehlerverhalten ab.

Deutschsprachige Fachgrundlagen

239. Introduce Security Audit Event

Fachliche Einordnung

Sicherheitsrelevante Vorgänge werden als freie Logzeilen geschrieben. Struktur, Korrelation, Akteur, Ressource, Entscheidung und Policy-Version sind uneinheitlich; sensible Daten können versehentlich im Log landen.

Erkennungsmerkmale

  • Audit nutzt Stringverkettung
  • Erfolg und Ablehnung besitzen unterschiedliche Formate
  • Korrelation zu Request, Principal oder Ressource fehlt
  • Passwörter, Token oder vollständige Claims erscheinen in Logs

Sicherer Refactoring-Ablauf

  1. Auditpflichtige Ereignistypen und minimale Felder definieren.
  2. Ein unveränderliches SecurityAuditEvent mit Akteur, Aktion, Ressource, Ergebnis, Zeit und Korrelation einführen.
  3. Sensible Inhalte explizit ausschließen oder maskieren.
  4. Publikation hinter einen Audit-Port legen und Ausfallverhalten festlegen.
  5. Schema, Redaction, Reihenfolge und nicht blockierende Zustellung testen.

Wann sinnvoll - und wann nicht?

Sinnvoll für Authentisierung, Autorisierung, Privilegienänderung, Notfallzugriff und sensible Datenzugriffe. Reines Debug-Logging ist kein Ersatz.

Risiken und typische Fehlanwendungen

Auditdaten selbst sind schützenswert. Zu viele Ereignisse erschweren Auswertung; zu wenige verhindern Nachvollziehbarkeit. Kritische Aktionen dürfen bei Audit-Ausfall nicht unbemerkt fortgesetzt werden.

Enterprise-Java-Beispiel

Eine abgelehnte Freigabe erzeugt ein Event mit Principal-ID, Aktion, Fall-ID, Reason-Code, Policy-Version und Correlation-ID, jedoch ohne Token oder medizinische Falldaten.

Abgrenzung und verwandte Muster

Audit Port, Domain Event, Correlation Identifier, Access Decision.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant; import java.util.Map;
// Refactoring: Introduce Security Audit Event
// Pattern: Domain Event - Auditdaten erhalten ein stabiles Schema.
public record SecurityAuditEvent(String subject,String action,boolean allowed,String reason,Instant occurredAt,Map<String,String> attributes){
  public SecurityAuditEvent{if(subject.isBlank()||action.isBlank()||reason.isBlank())throw new IllegalArgumentException();attributes=Map.copyOf(attributes);}
}
Teststrategie: Vertragstests prüfen Port und Decision Objects. Sicherheitsfälle decken fehlende oder ungültige Nachweise, Mandantentrennung, Widerruf, Policywechsel, Kontextverlust, sensible Auditdaten und restriktives Fehlerverhalten ab.

Deutschsprachige Fachgrundlagen

240. Extract Access Decision Cache Policy

Fachliche Einordnung

Autorisierungsentscheidungen werden pauschal oder ad hoc gecacht. Rollenänderungen, Widerruf, Ressourcenstatus und Policy-Version können dadurch verspätet wirksam werden.

Erkennungsmerkmale

  • ein globales TTL gilt für alle Berechtigungen
  • Cache-Key enthält weder Ressource noch Policy-Version
  • Ablehnungen und Erlaubnisse werden gleich lang gespeichert
  • Widerruf kann den Cache nicht gezielt invalidieren

Sicherer Refactoring-Ablauf

  1. Stabilität und Widerrufsanforderung je Entscheidungstyp analysieren.
  2. Cache-Key aus Principal, Mandant, Fähigkeit, Ressource und Policy-Version ableiten.
  3. TTL, negative Caches und nicht cachebare Entscheidungen in einer Policy definieren.
  4. Invalidierung an Rollen-, Delegations- und Policyänderungen koppeln.
  5. Widerruf, Mandantentrennung, Versionswechsel und Cache-Ausfall testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei teuren, häufig wiederholten Policy-Auswertungen mit klaren Aktualitätsgrenzen. Hochkritische oder stark kontextabhängige Entscheidungen sollten häufig gar nicht gecacht werden.

Risiken und typische Fehlanwendungen

Ein schneller Cache darf niemals wichtiger als zeitnaher Entzug sein. Unvollständige Keys können Berechtigungen zwischen Ressourcen oder Mandanten vermischen.

Enterprise-Java-Beispiel

Leserechte auf einen öffentlichen Katalog werden kurz gecacht; Zahlungsfreigaben und Break-Glass-Zugriffe werden stets aktuell ausgewertet. Jede Policy-Version erzeugt einen neuen Cache-Namensraum.

Abgrenzung und verwandte Muster

Authorization Policy, Cache Policy, Policy Version, Revocation Registry.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Duration;
// Refactoring: Extract Access Decision Cache Policy
// Pattern: Cache Policy - Cachebarkeit ist eine bewusste Sicherheitsentscheidung.
public final class AccessDecisionCachePolicy {
  public record CacheDirective(boolean cacheable,Duration ttl){}
  public CacheDirective directive(AccessDecision decision,boolean sensitive){
    if(sensitive||!decision.allowed())return new CacheDirective(false,Duration.ZERO);
    return new CacheDirective(true,Duration.ofSeconds(30));
  }
}
Teststrategie: Vertragstests prüfen Port und Decision Objects. Sicherheitsfälle decken fehlende oder ungültige Nachweise, Mandantentrennung, Widerruf, Policywechsel, Kontextverlust, sensible Auditdaten und restriktives Fehlerverhalten ab.

Deutschsprachige Fachgrundlagen

Kapitel 25 Permissions, Policies und Autorisierungsentscheidungen10 Refactorings

Refactorings 241–250 führen explizite Berechtigungen, MFA-, Session-, Schlüssel-, Maskierungs- und Funktionstrennungsregeln sowie stabile Security-Ports ein.

Refactoring-Katalog · Abschnitt 10

Security Policies, Entscheidungen und Infrastrukturgrenzen

Refactorings 241–250 führen explizite Berechtigungen, MFA-, Session-, Schlüssel-, Maskierungs- und Funktionstrennungsregeln sowie stabile Security-Ports ein.

250 von 300 Refactorings abgeschlossen

50 Refactorings offen.

241. Introduce Permission Object

Introduce Permission Object

Fachliche Einordnung

Berechtigungen erscheinen in gewachsenen Java-Systemen häufig als freie Zeichenketten wie `invoice.read`, `READ_INVOICE` oder `invoice:read`. Dadurch bleiben Namensraum, Ressourcentyp und Aktion implizit; Tippfehler werden erst zur Laufzeit sichtbar und fachlich verschiedene Rechte können versehentlich gleich behandelt werden. Ein Permission Object macht die Berechtigung zu einem unveränderlichen Fachwert mit kanonischer Darstellung und klaren Invarianten.

Erkennungsmerkmale

  • Rollen- oder Rechteprüfungen vergleichen String-Literale in mehreren Modulen
  • Groß-/Kleinschreibung und Trennzeichen unterscheiden sich zwischen Systemen
  • Ressource, Aktion und Mandant werden in einem zusammengesetzten String kodiert
  • Tests sichern nur einzelne Literale statt die Semantik des Berechtigungsmodells

Sicherer Refactoring-Ablauf

  1. Alle tatsächlich verwendeten Berechtigungsbezeichnungen inventarisieren und Dubletten sichtbar machen.
  2. Ressource, Aktion und optionalen Geltungsbereich als getrennte Werte definieren.
  3. Kanonisierung und Validierung in einem unveränderlichen Value Object zentralisieren.
  4. Adapter für bestehende Rollen-, Claim- und Datenbankformate einführen.
  5. String-Vergleiche schrittweise ersetzen und unbekannte Werte restriktiv ablehnen.

Wann sinnvoll - und wann nicht?

Sinnvoll, sobald Rechte über mehrere Module, Services oder Identity Provider ausgetauscht werden. Für eine lokale Anwendung mit zwei stabilen, typisierten Enum-Werten kann ein eigenes Objekt unnötig sein.

Risiken und typische Fehlanwendungen

Das Value Object darf keine Autorisierungsentscheidung vorwegnehmen: Es beschreibt ein Recht, erteilt es aber nicht. Eine zu aggressive Normalisierung kann fachlich unterschiedliche Ressourcen zusammenführen; Mandanten- und Objektgrenzen müssen ausdrücklich modelliert werden.

Enterprise-Java-Beispiel

Ein Rechnungsservice übernimmt externe Claims, übersetzt sie im Security-Adapter in `Permission("invoice", "read")` und übergibt dem Use Case nur noch typisierte Berechtigungen. Unbekannte Schreibweisen werden nicht stillschweigend akzeptiert.

Abgrenzung und verwandte Muster

Principal Value Object, Capability Query, Authorization Policy, Resource Scope.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Permission Object
// Pattern: Value Object - Berechtigungen erhalten Invarianten und kanonische Semantik.
public record Permission(String resource, String action) {
  public Permission {
    if (resource == null || resource.isBlank() || action == null || action.isBlank()) throw new IllegalArgumentException();
    resource = resource.trim().toLowerCase(); action = action.trim().toLowerCase();
  }
  public String canonical() { return resource + ":" + action; }
}
Teststrategie: Property-basierte Tests prüfen Kanonisierung, Gleichheit und ungültige Werte. Vertragstests sichern die Übersetzung aus Token-Claims und Rollenregistern; Negativtests belegen, dass unbekannte Ressourcen oder Aktionen nicht automatisch freigeschaltet werden.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class PermissionTest {
    @Test void contract() {
        org.junit.jupiter.api.Assertions.assertEquals("invoice:read",new Permission("Invoice","Read").canonical());
    }
}

Deutschsprachige Fachgrundlagen

242. Extract Multi-Factor Authentication Policy

Extract Multi-Factor Authentication Policy

Fachliche Einordnung

Mehrfaktor-Authentisierung ist keine einzelne Boolean-Abfrage, sondern eine kontextabhängige Sicherheitsentscheidung. Schutzbedarf, Aktion, Risikosignal, vorhandene Faktoren und deren Stärke müssen gemeinsam bewertet werden. Werden diese Regeln über Controller, Filter und Identity-Provider-Konfiguration verteilt, entstehen widersprüchliche Anforderungen und schwer erklärbare Ausnahmen.

Erkennungsmerkmale

  • `if (risk > ...)` und Rollenprüfungen entscheiden lokal über MFA
  • verschiedene Kanäle verlangen unterschiedliche Faktoren für dieselbe Aktion
  • ein vorhandener zweiter Faktor wird pauschal als ausreichend angesehen
  • Notfall- und Servicekonten umgehen Regeln ohne dokumentierten Geltungsbereich

Sicherer Refactoring-Ablauf

  1. Geschäftskritische Aktionen und erforderliche Authentisierungsstärken klassifizieren.
  2. Faktoren, Vertrauensniveau und Risikosignale als typisierte Eingaben modellieren.
  3. Die Entscheidung als reine Policy mit begründetem Ergebnis extrahieren.
  4. Identity-Provider-spezifische Methoden in einem Adapter auf die fachlichen Faktoren abbilden.
  5. Rollout zunächst im Beobachtungsmodus durchführen und Fehlklassifikationen auswerten.

Wann sinnvoll - und wann nicht?

Sinnvoll bei risikobasiertem Zugriff, privilegierten Aktionen oder mehreren Authentisierungsverfahren. Eine pauschale MFA-Pflicht für jeden Zugriff kann einfacher direkt an der Plattformgrenze konfiguriert werden.

Risiken und typische Fehlanwendungen

MFA schützt nicht automatisch gegen jedes Phishing- oder Sitzungsrisiko. Faktorqualität, Wiederverwendung einer Sitzung und Recovery-Verfahren müssen berücksichtigt werden. Ausnahmen dürfen nicht als dauerhaftes Hintertürmodell enden.

Enterprise-Java-Beispiel

Für das Anzeigen einer Rechnung genügt eine bestehende starke Sitzung; das Ändern einer Bankverbindung verlangt phishingresistente Authentisierung oder eine erneute Bestätigung. Die Policy liefert `allow`, `step-up` oder `deny` samt Begründung.

Abgrenzung und verwandte Muster

Authentication Port, Session Policy, Risk Assessment, Access Decision.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Set;
// Refactoring: Extract Multi-Factor Authentication Policy
// Pattern: Policy / Specification - MFA-Anforderungen werden zentral und testbar.
public final class MultiFactorAuthenticationPolicy {
  public record Request(String action, int riskScore, Set<String> factors) { public Request { factors = Set.copyOf(factors); } }
  public AccessDecision decide(Request request) {
    boolean strong = request.factors().contains("password") && request.factors().contains("otp");
    if (request.riskScore() >= 70 && !strong) return AccessDecision.deny("strong-authentication-required");
    return AccessDecision.allow("authentication-sufficient");
  }
}
Teststrategie: Entscheidungstabellen decken Kombinationen aus Aktion, Risikostufe und Faktoren ab. Tests prüfen fehlende Faktoren, veraltete Nachweise, Break-Glass-Ausnahmen und das sichere Standardverhalten bei unbekannten Methoden.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import java.util.*;
import org.junit.jupiter.api.Test;
class MultiFactorAuthenticationPolicyTest {
    @Test void contract() {
        var r=new MultiFactorAuthenticationPolicy().decide(new MultiFactorAuthenticationPolicy.Request("payout",90,Set.of("password")));
        org.junit.jupiter.api.Assertions.assertFalse(r.allowed());
    }
}

Deutschsprachige Fachgrundlagen

243. Introduce Session Policy

Introduce Session Policy

Fachliche Einordnung

Sitzungen verbinden einen erfolgreichen Identitätsnachweis über einen längeren Zeitraum mit weiteren Zugriffen. Absolute Laufzeit, Inaktivitätsgrenze, erneute Authentisierung und Widerruf sind deshalb Sicherheitsregeln und keine bloßen Cookie-Einstellungen. Eine Session Policy bündelt diese Entscheidungen und trennt sie von HTTP-, Token- und Frameworkdetails.

Erkennungsmerkmale

  • Timeouts sind in Web-XML, Security-Filter, Tokenkonfiguration und Frontend verteilt
  • privilegierte und normale Sitzungen verwenden dieselben Laufzeiten
  • Aktivität verlängert Sitzungen unbegrenzt
  • Widerruf oder Risikoänderung wird erst nach Ablauf des Tokens wirksam

Sicherer Refactoring-Ablauf

  1. Bestehende Sitzungsarten, Laufzeiten und Re-Authentisierungswege erfassen.
  2. Absolute Dauer, Idle Timeout, Authentisierungsstärke und Risikokontext modellieren.
  3. Eine deterministische Policy mit `valid`, `reauthenticate` und `reason` einführen.
  4. HTTP-Session, OIDC-Token und Identity-Provider-Sitzung über Adapter anbinden.
  5. Zeitgrenzen, Uhrabweichung, Widerruf und parallele Geräte in Tests absichern.

Wann sinnvoll - und wann nicht?

Sinnvoll bei mehreren Sicherheitsstufen, langlebigen Websitzungen oder getrennten Token- und Anwendungssitzungen. Für einen zustandslosen, sehr kurzlebigen technischen Client kann die Tokenvalidierung allein genügen.

Risiken und typische Fehlanwendungen

Zu kurze Laufzeiten führen zu Umgehungsdruck, zu lange Laufzeiten erhöhen das Missbrauchsfenster. Aktivität darf nicht automatisch Vertrauenswürdigkeit bedeuten; sensible Aktionen benötigen gegebenenfalls eine frische Authentisierung.

Enterprise-Java-Beispiel

Ein Supportportal erlaubt acht Stunden Grundsitzung, verlangt nach zwanzig Minuten Inaktivität eine erneute Anmeldung und fordert vor Kundendatenexport eine frische starke Authentisierung. Die Anwendung erhält eine begründete Session-Entscheidung.

Abgrenzung und verwandte Muster

Token Validation Policy, Revocation Registry, Device Trust Policy, Continuous Access Evaluation.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Introduce Session Policy
// Pattern: Policy - Sessionlebensdauer wird als fachlich prüfbare Regel modelliert.
public final class SessionPolicy {
  public record Session(Instant createdAt, Instant lastSeenAt, boolean privileged) {}
  public record Decision(boolean valid, boolean reauthenticate, String reason) {}
  public Decision evaluate(Session session, Instant now) {
    Duration max = session.privileged() ? Duration.ofMinutes(30) : Duration.ofHours(8);
    if (Duration.between(session.createdAt(), now).compareTo(max) > 0) return new Decision(false,true,"absolute-timeout");
    if (Duration.between(session.lastSeenAt(), now).compareTo(Duration.ofMinutes(20)) > 0) return new Decision(false,true,"idle-timeout");
    return new Decision(true,false,"active");
  }
}
Teststrategie: Tests verwenden eine kontrollierte Clock und prüfen Grenzwerte exakt. Szenarien decken absolute und inaktive Abläufe, privilegierte Sitzungen, Risikoerhöhung, Widerruf und fehlende Zeitangaben ab.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import java.time.*;
import org.junit.jupiter.api.Test;
class SessionPolicyTest {
    @Test void contract() {
        var now=Instant.parse("2026-01-01T10:00:00Z");
        var d=new SessionPolicy().evaluate(new SessionPolicy.Session(now.minusSeconds(60),now.minusSeconds(30),false),now);
        org.junit.jupiter.api.Assertions.assertTrue(d.valid());
    }
}

Deutschsprachige Fachgrundlagen

244. Extract Password Hashing Port

Extract Password Hashing Port

Fachliche Einordnung

Passwort-Hashing ist kryptografische Infrastruktur mit hohen Änderungs- und Sicherheitsanforderungen. Fach- und Anwendungscode sollten weder Algorithmusparameter noch Salt-Format, Provider oder Upgradeverfahren kennen. Ein kleiner Port erlaubt sichere Implementierungen und kontrollierte Migrationen, ohne Passwörter oder kryptografische Details in Use Cases zu verteilen.

Erkennungsmerkmale

  • Services rufen `MessageDigest`, BCrypt- oder Provider-APIs direkt auf
  • Hashparameter werden als Konstanten in mehreren Klassen gepflegt
  • das gespeicherte Format verrät nicht, welcher Algorithmus verwendet wurde
  • ein Algorithmuswechsel erfordert Massenänderungen im Fachcode

Sicherer Refactoring-Ablauf

  1. Vorhandene Hashformate und Verifikationspfade inventarisieren.
  2. Port für Hashen, Verifizieren und optionalen Upgradebedarf definieren.
  3. Algorithmuskennung und Parameter im gespeicherten Hashformat versionieren.
  4. Konkrete Implementierung in einen Infrastrukturadapter verschieben.
  5. Migration beim erfolgreichen Login sowie Fehl- und Timingverhalten testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei eigener Passwortauthentisierung oder mehreren Speicherformaten. Wenn ein Identity Provider Passwörter vollständig verwaltet, sollte die Anwendung keinen zusätzlichen Hashing-Port erfinden.

Risiken und typische Fehlanwendungen

Der Port macht eine schwache Implementierung nicht sicher. Klartext und `char[]` dürfen nicht geloggt oder unnötig kopiert werden. Ein stiller Fallback auf veraltete Algorithmen oder ungebremste Verifikation kann Angriffe erleichtern.

Enterprise-Java-Beispiel

Ein Legacy-Hash wird beim Login verifiziert. Liefert der Adapter `upgradeRequired`, erzeugt er nach erfolgreichem Nachweis einen neuen Argon2id-basierten Hash; der Use Case kennt nur das Port-Ergebnis und nie Algorithmusdetails.

Abgrenzung und verwandte Muster

Credential Validator, Authentication Port, Cryptographic Agility Policy, Anti-Corruption Layer.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Password Hashing Port
// Pattern: Port / Adapter - Kryptografische Implementierungsdetails bleiben austauschbar.
public interface PasswordHashingPort {
  record PasswordHash(String algorithm, String encoded) { public PasswordHash { if(algorithm.isBlank()||encoded.isBlank()) throw new IllegalArgumentException(); } }
  PasswordHash hash(char[] password);
  boolean verify(char[] candidate, PasswordHash stored);
}
Teststrategie: Vertragstests prüfen korrekte und falsche Kandidaten, versionsabhängige Migration und ungültige Formate. Sicherheitstests stellen sicher, dass Klartext weder in Exceptions noch Logs erscheint und dass fehlende Provider restriktiv fehlschlagen.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class PasswordHashingPortTest {
    @Test void contract() {
        PasswordHashingPort p=new PasswordHashingPort() {
            public PasswordHash hash(char[] x) {
                return new PasswordHash("demo",new String(x));
            }
            public boolean verify(char[] x,PasswordHash h) {
                return h.encoded().equals(new String(x));
            }
        };
        org.junit.jupiter.api.Assertions.assertTrue(p.verify("x".toCharArray(),p.hash("x".toCharArray())));
    }
}

Deutschsprachige Fachgrundlagen

245. Introduce Key Rotation Policy

Introduce Key Rotation Policy

Fachliche Einordnung

Kryptografische Schlüssel besitzen einen Lebenszyklus: Aktivierung, reguläre Rotation, Überlappung, Sperrung und Notfallersatz. Werden diese Regeln nur über Datumsvergleiche in Jobs und Services ausgedrückt, ist unklar, welche Schlüssel noch zum Entschlüsseln benötigt werden und wann neue Versionen sicher verwendet werden dürfen.

Erkennungsmerkmale

  • Schlüssel werden manuell oder nur bei Ablauf ersetzt
  • Verschlüsselung und Entschlüsselung kennen keine Schlüsselversion
  • alte Schlüssel werden zu früh deaktiviert oder unbegrenzt behalten
  • kompromittierte Schlüssel folgen demselben Ablauf wie planmäßige Rotation

Sicherer Refactoring-Ablauf

  1. Schlüsselverwendungen, Schutzbedarf und Abhängigkeiten erfassen.
  2. Zustände und Ereignisse für planmäßige und Notfallrotation definieren.
  3. Rotation, Überlappungsfenster und Deaktivierung in einer Policy modellieren.
  4. Ciphertexte und Signaturen immer mit Schlüsselversion referenzieren.
  5. Rotation mit Parallelbetrieb, Rückabwicklung und Alt-Datenzugriff testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei langlebigen Daten, mehreren Schlüsselversionen oder externem KMS. Für kurzlebige, automatisch verwaltete Sitzungsschlüssel genügt oft die Plattformfunktion.

Risiken und typische Fehlanwendungen

Rotation bedeutet nicht automatisch Neuverschlüsselung aller Daten. Alte Versionen dürfen erst entzogen werden, wenn ihre Abhängigkeiten bekannt sind. Zeitbasierte Regeln ohne Kompromittierungs- und Recovery-Pfad sind unvollständig.

Enterprise-Java-Beispiel

Der Dokumentendienst verschlüsselt neue Dateien ab Stichtag mit Version K42, kann Bestandsdaten weiter mit K41 entschlüsseln und beendet die Überlappung erst, wenn ein Rewrap-Bericht vollständig ist. Ein kompromittierter Schlüssel überspringt das normale Fenster.

Abgrenzung und verwandte Muster

Encryption Port, Key Access Decision, Cryptographic Agility Policy, Versioned Key Reference.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Introduce Key Rotation Policy
// Pattern: Policy / Strategy - Rotation wird explizit, nachvollziehbar und testbar.
public final class KeyRotationPolicy {
  public record KeyMetadata(String keyId, Instant activatedAt, boolean compromised) {}
  public record Decision(boolean rotate, Duration overlap, String reason) {}
  public Decision decide(KeyMetadata key, Instant now) {
    if (key.compromised()) return new Decision(true,Duration.ZERO,"compromised");
    boolean due = Duration.between(key.activatedAt(),now).compareTo(Duration.ofDays(90)) >= 0;
    return due ? new Decision(true,Duration.ofHours(24),"scheduled") : new Decision(false,Duration.ZERO,"current");
  }
}
Teststrategie: Tests prüfen planmäßige Fälligkeit, Notfallrotation, Überlappung, Uhrgrenzen und blockierte Schlüssel. Integrationstests simulieren KMS-Ausfall und belegen, dass keine neue Verschlüsselung mit einer abgelaufenen Version erfolgt.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import java.time.*;
import org.junit.jupiter.api.Test;
class KeyRotationPolicyTest {
    @Test void contract() {
        var now=Instant.parse("2026-04-01T00:00:00Z");
        org.junit.jupiter.api.Assertions.assertTrue(new KeyRotationPolicy().decide(new KeyRotationPolicy.KeyMetadata("k",now.minus(Duration.ofDays(91)),false),now).rotate());
    }
}

Deutschsprachige Fachgrundlagen

246. Extract Data Masking Policy

Extract Data Masking Policy

Fachliche Einordnung

Datenmaskierung reduziert sichtbare Informationen abhängig von Zweck, Empfänger und Ausgabekanal. Ein pauschales `substring` in DTOs oder Logs kennt weder Schutzklasse noch Berechtigung und kann sensible Werte in Fehlerpfaden oder Exporten unmaskiert lassen. Eine Policy entscheidet zentral, welcher Ausschnitt für welchen Kontext zulässig ist.

Erkennungsmerkmale

  • unterschiedliche Maskierungslogik in Mappern, Templates und Loggern
  • Produktionslogs enthalten vollständige Identifikatoren oder Kontodaten
  • privilegierte Anzeige wird durch ein globales Boolean freigeschaltet
  • Export, Bildschirm und Supportansicht verwenden dieselbe Darstellung

Sicherer Refactoring-Ablauf

  1. Sensible Datentypen, Zwecke und Ausgabekanäle klassifizieren.
  2. Maskierungsstufen und erlaubte Sichtbarkeit als Policy-Ergebnis definieren.
  3. Darstellung vom Originalwert trennen und unveränderte Weitergabe verhindern.
  4. Logging-, Mapping- und Exportadapter an die Policy anbinden.
  5. Fehlerpfade, Listenansichten, Copy-Funktionen und strukturierte Logs testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei personenbezogenen, finanziellen oder geheimhaltungsbedürftigen Daten. Für Daten, die überhaupt nicht verarbeitet oder angezeigt werden dürfen, ist Maskierung kein Ersatz für Datenminimierung oder Zugriffskontrolle.

Risiken und typische Fehlanwendungen

Maskierung ist keine Anonymisierung und kann durch Kontextinformationen rückgängig gemacht werden. Ein privilegierter Schalter ohne Zweckbindung und Audit führt zu überbreitem Zugriff. Originalwerte dürfen nicht versehentlich in `toString()` oder Traces gelangen.

Enterprise-Java-Beispiel

Im Supportportal zeigt die Standardansicht nur die letzten vier Stellen einer IBAN; ein genehmigter Zahlungsfall erlaubt kurzzeitig die vollständige Anzeige. Logs und Exporte bleiben unabhängig davon stärker maskiert.

Abgrenzung und verwandte Muster

Privacy Classification Policy, Data Access Purpose, Authorization Policy, Redaction Strategy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Data Masking Policy
// Pattern: Policy / Strategy - Darstellung sensibler Daten ist kontextabhängig austauschbar.
public final class DataMaskingPolicy {
  public enum Context { DISPLAY, LOG, EXPORT }
  public String mask(String value, Context context, boolean privileged) {
    if (value == null) return "";
    if (privileged && context == Context.DISPLAY) return value;
    if (context == Context.EXPORT) return "[REDACTED]";
    int visible = Math.min(4, value.length());
    return "*".repeat(Math.max(0,value.length()-visible)) + value.substring(value.length()-visible);
  }
}
Teststrategie: Golden-Master-Tests sichern die Darstellung je Datentyp und Kontext. Negativtests prüfen kurze Werte, Nullwerte, Unicode, unberechtigte Vollanzeige sowie unbeabsichtigte Offenlegung in Exceptions und Logs.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class DataMaskingPolicyTest {
    @Test void contract() {
        org.junit.jupiter.api.Assertions.assertEquals("******7890",new DataMaskingPolicy().mask("1234567890",DataMaskingPolicy.Context.LOG,false));
    }
}

Deutschsprachige Fachgrundlagen

247. Introduce Consent Decision

Introduce Consent Decision

Fachliche Einordnung

Einwilligung ist mehr als ein Boolean. Zweck, Version der Erklärung, Zeitpunkt, Reichweite, Ablauf und Widerruf müssen gemeinsam nachvollziehbar sein. Wird nur `consent=true` gespeichert, kann die Anwendung weder Zweckbindung noch Beweisführung und Gültigkeit zuverlässig entscheiden.

Erkennungsmerkmale

  • ein einziges Flag steuert mehrere Verarbeitungszwecke
  • Textversion und Zeitpunkt der Erklärung fehlen
  • Widerruf überschreibt den historischen Zustand
  • nachgelagerte Systeme erhalten nur `true` oder `false` ohne Begründung

Sicherer Refactoring-Ablauf

  1. Verarbeitungszwecke und bestehende Einwilligungsnachweise inventarisieren.
  2. Gültigkeitskriterien und erforderliche Evidenz je Zweck definieren.
  3. Ein unveränderliches Decision Object mit Grund und Version einführen.
  4. Widerruf als neues Ereignis statt als Überschreiben modellieren.
  5. Alle Aufrufer auf zweckgebundene Entscheidungen und Ablaufprüfung umstellen.

Wann sinnvoll - und wann nicht?

Sinnvoll, wenn eine Verarbeitung tatsächlich auf Einwilligung beruht und mehrere Systeme den Status benötigen. Andere Rechtsgrundlagen dürfen nicht künstlich als Einwilligung modelliert werden.

Risiken und typische Fehlanwendungen

Eine Entscheidung darf fehlende Evidenz nicht als Zustimmung interpretieren. Ablauf und Widerruf müssen konsistent propagiert werden; eine UI-Bestätigung ohne nachweisbare Version und Zweck reicht nicht aus.

Enterprise-Java-Beispiel

Ein Marketing-Use-Case erhält eine `ConsentDecision` für den konkreten Zweck Newsletter. Supportanalyse und Produkttelemetrie fragen getrennte Zwecke ab und können nicht versehentlich dieselbe Zustimmung wiederverwenden.

Abgrenzung und verwandte Muster

Consent Evidence Store, Data Access Purpose, Domain Event, Decision Object.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
// Refactoring: Introduce Consent Decision
// Pattern: Decision Object - Einwilligungsstatus und Evidenz reisen gemeinsam.
public record ConsentDecision(boolean granted, String purpose, String policyVersion, Instant validUntil, String reason) {
  public ConsentDecision { if(purpose.isBlank()||policyVersion.isBlank()||reason.isBlank()) throw new IllegalArgumentException(); }
  public boolean validAt(Instant now) { return granted && now.isBefore(validUntil); }
}
Teststrategie: Tests prüfen Zwecktrennung, Ablauf, Widerruf, Versionen und fehlende Evidenz. Ereignisbasierte Integrationstests sichern, dass ein Widerruf nachgelagerte Caches und Kampagnen beendet, ohne den historischen Nachweis zu löschen.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import java.time.*;
import org.junit.jupiter.api.Test;
class ConsentDecisionTest {
    @Test void contract() {
        var d=new ConsentDecision(true,"analytics","v2",Instant.parse("2027-01-01T00:00:00Z"),"explicit");
        org.junit.jupiter.api.Assertions.assertTrue(d.validAt(Instant.parse("2026-01-01T00:00:00Z")));
    }
}

Deutschsprachige Fachgrundlagen

248. Extract Segregation of Duties Policy

Extract Segregation of Duties Policy

Fachliche Einordnung

Funktionstrennung verhindert, dass eine Person kritische Vorgänge allein beantragt, genehmigt und ausführt. Verstreute Rollenvergleiche bilden diese fachliche Invariante nur unvollständig ab, insbesondere bei Delegation, Stellvertretung und mehreren Genehmigungsstufen. Eine Policy macht die Unabhängigkeitsregel explizit und begründbar.

Erkennungsmerkmale

  • Antragsteller und Genehmiger werden nur über Benutzernamen verglichen
  • kritische Aktionen verwenden unterschiedliche Vier-Augen-Regeln
  • Delegationen umgehen unbeabsichtigt die Unabhängigkeit
  • Servicekonten oder Sammelrollen können mehrere Prozessschritte ausführen

Sicherer Refactoring-Ablauf

  1. Kritische Aktionen und unvereinbare Verantwortlichkeiten identifizieren.
  2. Beteiligte, Rollen, Delegationen und Organisationsgrenzen typisieren.
  3. Eine reine Policy für zulässige Kombinationen und Begründungen extrahieren.
  4. Workflow-Übergänge ausschließlich über das Policy-Ergebnis freigeben.
  5. Stellvertretung, Notfallzugriff und technische Konten gesondert testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei Zahlungsfreigaben, Berechtigungsänderungen oder regulatorisch kontrollierten Prozessen. Für risikoarme, vollständig reversible Aktionen kann eine zusätzliche Genehmigungsstufe unverhältnismäßig sein.

Risiken und typische Fehlanwendungen

Ein bloßer Vergleich von IDs erkennt verbundene Konten oder gemeinsame Verantwortungsbereiche nicht. Ausnahmen müssen zeitlich begrenzt, genehmigt und auditiert werden; sonst wird die Policy zur Formalität.

Enterprise-Java-Beispiel

Eine Person darf eine Lieferantenzahlung erfassen, aber weder freigeben noch den Zielbankdatensatz ändern. Die Policy bewertet Identität, Rolle, Delegation und Prozesshistorie und liefert eine nachvollziehbare Ablehnung.

Abgrenzung und verwandte Muster

Approval Workflow, Authorization Policy, Delegation Grant, Audit Event.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Segregation of Duties Policy
// Pattern: Policy / Specification - Funktionstrennung wird zentral entschieden.
public final class SegregationOfDutiesPolicy {
  public AccessDecision decide(String requester, String approver, boolean critical) {
    if (!critical) return AccessDecision.allow("single-approval-sufficient");
    if (requester.equals(approver)) return AccessDecision.deny("requester-cannot-approve");
    return AccessDecision.allow("independent-approval");
  }
}
Teststrategie: Entscheidungstabellen prüfen gleiche Identität, gleiche Organisationseinheit, Delegation und Mehrfachfreigabe. Workflowtests belegen, dass direkte Zustandsänderungen die Policy nicht umgehen können.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class SegregationOfDutiesPolicyTest {
    @Test void contract() {
        org.junit.jupiter.api.Assertions.assertFalse(new SegregationOfDutiesPolicy().decide("u1","u1",true).allowed());
    }
}

Deutschsprachige Fachgrundlagen

249. Introduce Security Boundary Facade

Introduce Security Boundary Facade

Fachliche Einordnung

Authentisierung, Autorisierung, Kontextprüfung und Audit müssen an einer Systemgrenze in konsistenter Reihenfolge stattfinden. Wenn jeder Use Case diese Schritte selbst orchestriert, entstehen Lücken, doppelte Prüfungen und unterschiedliche Fehlerbehandlung. Eine schmale Facade koordiniert den Ablauf, ohne die einzelnen Policies zu verschmelzen.

Erkennungsmerkmale

  • Controller rufen Security-Komponenten in unterschiedlicher Reihenfolge auf
  • manche Use Cases auditieren nur erfolgreiche Zugriffe
  • Framework-Sicherheitsobjekte gelangen bis in die Fachlogik
  • neue Kanäle kopieren bestehende Sicherheitsorchestrierung

Sicherer Refactoring-Ablauf

  1. Verbindliche Schritte und Verantwortlichkeiten der Sicherheitsgrenze definieren.
  2. Authentisierung, Autorisierung, Kontext und Audit als getrennte Ports oder Policies belassen.
  3. Eine Facade mit kleinem, use-case-orientiertem Vertrag einführen.
  4. Fehler in stabile, nicht informationspreisgebende Ergebnisse übersetzen.
  5. Alle Eintrittspunkte schrittweise auf die Facade umstellen und Bypass-Pfade sperren.

Wann sinnvoll - und wann nicht?

Sinnvoll bei mehreren Eingangskanälen oder wiederkehrender Sicherheitsorchestrierung. Eine Facade ist unnötig, wenn ein einziger Frameworkfilter die vollständige Grenze korrekt und testbar abbildet.

Risiken und typische Fehlanwendungen

Die Facade darf nicht zu einem allwissenden Security-God-Object wachsen. Sie koordiniert, entscheidet aber nicht jede Regel selbst. Audit muss sensible Daten minimieren und darf bei technischem Ausfall keine unkontrollierte Freigabe verursachen.

Enterprise-Java-Beispiel

REST, Batch und Messaging übergeben jeweils einen stabilen Security Request an dieselbe Facade. Diese erzeugt Principal und Access Decision, protokolliert das Ergebnis und ruft den Fach-Use-Case erst nach erfolgreicher Prüfung auf.

Abgrenzung und verwandte Muster

Facade, Application Service, Authentication Port, Authorization Policy, Audit Port.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant; import java.util.Map;
// Refactoring: Introduce Security Boundary Facade
// Pattern: Facade / Application Service - Sicherheitsablauf wird konsistent orchestriert.
public final class SecurityBoundaryFacade {
  public interface AuditPort { void record(SecurityAuditEvent event); }
  private final AuthorizationPolicy policy; private final AuditPort audit;
  public SecurityBoundaryFacade(AuthorizationPolicy policy, AuditPort audit){this.policy=policy;this.audit=audit;}
  public AccessDecision authorize(AuthorizationPolicy.Request request){
    AccessDecision decision=policy.decide(request);
    audit.record(new SecurityAuditEvent(request.principal().subject(),request.action(),decision.allowed(),decision.reason(),Instant.now(),Map.of()));
    return decision;
  }
}
Teststrategie: Komponententests prüfen Reihenfolge, Fehlerübersetzung und Audit für Erlaubnis wie Ablehnung. Architekturtests verhindern direkte Abhängigkeiten der Use Cases auf Framework-Securityklassen.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import java.util.*;
import org.junit.jupiter.api.Test;
class SecurityBoundaryFacadeTest {
    @Test void contract() {
        var p=new Principal("u","t",Set.of("pwd"));
        var q=new AuthorizationPolicy.Request(p,"read",Map.of());
        var events=new java.util.ArrayList<SecurityAuditEvent>();
        var f=new SecurityBoundaryFacade(r->AccessDecision.allow("ok"),events::add);
        org.junit.jupiter.api.Assertions.assertTrue(f.authorize(q).allowed());
        org.junit.jupiter.api.Assertions.assertEquals(1,events.size());
    }
}

Deutschsprachige Fachgrundlagen

250. Extract Threat Detection Port

Extract Threat Detection Port

Fachliche Einordnung

Bedrohungserkennung liefert Signale oder Bewertungen aus SIEM-, Fraud- oder Threat-Intelligence-Systemen. Direkte SDK-Aufrufe im Fachcode koppeln Geschäftsentscheidungen an Produktmodelle, Latenz und Ausfallverhalten. Ein Port übersetzt externe Befunde in ein kleines, fachlich relevantes Assessment.

Erkennungsmerkmale

  • Use Cases importieren Klassen eines SIEM- oder Fraud-SDKs
  • externe Scores werden ohne Interpretation als Freigabe benutzt
  • Providerwechsel verändert Geschäftslogik und Tests
  • Timeouts oder fehlende Daten führen inkonsistent zu allow oder deny

Sicherer Refactoring-Ablauf

  1. Benötigte Signale und fachliche Reaktionen vom konkreten Produkt trennen.
  2. Ein stabiles Assessment mit Score, Kategorie, Evidenz und Aktualität definieren.
  3. Providerintegration und Datenmapping in einen Adapter verschieben.
  4. Ausfall-, Timeout- und Unbekannt-Fälle als bewusste Policy festlegen.
  5. Shadow-Betrieb und Vergleich mit realen Entscheidungen vor produktiver Sperre durchführen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei austauschbaren Erkennungsdiensten oder mehreren Kanälen. Ein Port ist kein Mehrwert, wenn die Erkennung vollständig außerhalb der Anwendung erfolgt und nur ein fertiges, stabiles Ereignis konsumiert wird.

Risiken und typische Fehlanwendungen

Ein Score ist keine Wahrheit und darf ohne Schwellen-, Aktualitäts- und Evidenzprüfung keine irreversible Aktion auslösen. Falschpositive Befunde brauchen Review- und Einspruchswege; sensible Telemetrie muss minimiert werden.

Enterprise-Java-Beispiel

Der Login-Use-Case sendet ein typisiertes Signal. Der Adapter ruft die externe Engine auf und liefert `Assessment(82, "credential-abuse", ...)`; eine separate Risk Policy entscheidet anschließend über Step-up oder Blockierung.

Abgrenzung und verwandte Muster

Anomaly Detection Port, Risk Assessment, Circuit Breaker, Anti-Corruption Layer.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Map;
// Refactoring: Extract Threat Detection Port
// Pattern: Port / Adapter - externe Threat-Engines bleiben austauschbare Infrastruktur.
public interface ThreatDetectionPort {
  record Signal(String subject, String action, Map<String,String> attributes){public Signal{attributes=Map.copyOf(attributes);}}
  record Assessment(int score, String category, String evidence){public Assessment{if(score<0||score>100||category.isBlank())throw new IllegalArgumentException();}}
  Assessment assess(Signal signal);
}
Teststrategie: Vertragstests prüfen Mapping, Timeout und unbekannte Kategorien. Replay-Tests verwenden anonymisierte historische Signale; Resilienztests belegen das definierte Verhalten bei Provider-Ausfall.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import java.util.*;
import org.junit.jupiter.api.Test;
class ThreatDetectionPortTest {
    @Test void contract() {
        ThreatDetectionPort p=s->new ThreatDetectionPort.Assessment(80,"suspicious","new-device");
        org.junit.jupiter.api.Assertions.assertEquals(80,p.assess(new ThreatDetectionPort.Signal("u","login",Map.of())).score());
    }
}

Deutschsprachige Fachgrundlagen

Kapitel 26 Privacy, Token-Vertrauen und Incident Response10 Refactorings

Refactorings 251–260 führen explizite Datenschutz-, Kryptografie-, Token-, Geräte-, Risiko- und Incident-Grenzen ein.

Refactoring-Katalog · Abschnitt 11

Privacy, Token Trust und Incident Response

Refactorings 251–260 führen explizite Datenschutz-, Kryptografie-, Token-, Geräte-, Risiko- und Incident-Grenzen ein.

260 von 300 Refactorings abgeschlossen

40 Refactorings offen.

251. Extract Privacy Classification Policy

Extract Privacy Classification Policy

Fachliche Einordnung

Datenschutz- und Vertraulichkeitsklassen bestimmen, wie Daten gespeichert, angezeigt, exportiert und protokolliert werden dürfen. Freie Labels in DTOs oder Tabellen erzeugen keine verlässliche Schutzentscheidung. Eine Classification Policy bewertet Datentyp, Zweck und Kontext und liefert eine nachvollziehbare Schutzklasse mit zulässigen Verwendungen.

Erkennungsmerkmale

  • unterschiedliche Labels wie `sensitive`, `pii` oder `confidential` ohne gemeinsame Semantik
  • Schutzklasse wird erst im Export oder Logging entschieden
  • neue Datenfelder erben unbeabsichtigt die Klasse ihres Containers
  • Klassifizierung und technische Maßnahmen sind nicht miteinander verknüpft

Sicherer Refactoring-Ablauf

  1. Datenarten, Zwecke und regulatorische Anforderungen inventarisieren.
  2. Wenige verständliche Klassen und ihre Schutzwirkung definieren.
  3. Klassifikation als reine Policy mit begründetem Decision Object extrahieren.
  4. Maskierung, Verschlüsselung, Logging und Export an das Ergebnis koppeln.
  5. Neue Felder und unbekannte Kategorien standardmäßig restriktiv behandeln.

Wann sinnvoll - und wann nicht?

Sinnvoll in datenreichen Plattformen, mehreren Ausgabekanälen oder regulierten Domänen. Bei einem kleinen Datensatz mit durchgehend gleichem Schutzbedarf kann eine einheitliche technische Schutzklasse genügen.

Risiken und typische Fehlanwendungen

Klassifizierung ersetzt weder Rechtsgrundlage noch Autorisierung. Zu grobe Klassen über- oder unterschützen Daten; zu viele Klassen werden nicht verstanden. Automatische Klassifikatoren benötigen Review und nachvollziehbare Evidenz.

Enterprise-Java-Beispiel

Kundennummer und Kontaktadresse werden als vertraulich, Gesundheits- oder Zahlungsdaten als besonders geschützt klassifiziert. Die Entscheidung steuert Verschlüsselung, Maskierung und zulässige Supportnutzung.

Abgrenzung und verwandte Muster

Data Masking Policy, Data Access Purpose, Encryption Port, Policy Enforcement Point.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Set;
// Refactoring: Extract Privacy Classification Policy
// Pattern: Policy / Decision Object - Datenschutzregeln werden zentral und auditierbar entschieden.
public final class PrivacyClassificationPolicy {
  public enum Classification { PUBLIC, INTERNAL, CONFIDENTIAL, RESTRICTED }
  public record Decision(Classification classification, Set<String> allowedUses, boolean encryptionRequired) { public Decision { allowedUses=Set.copyOf(allowedUses); } }
  public Decision classify(boolean personal, boolean financial, boolean health) {
    if (health || financial) return new Decision(Classification.RESTRICTED, Set.of("service"), true);
    if (personal) return new Decision(Classification.CONFIDENTIAL, Set.of("service","support"), true);
    return new Decision(Classification.INTERNAL, Set.of("service","analytics"), false);
  }
}
Teststrategie: Entscheidungstabellen prüfen Datenart, Zweck und Kontext. Schema-Tests verlangen für jedes neue sensible Feld eine explizite Klassifikation; Negativtests sichern das restriktive Verhalten bei unbekannten Kategorien.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class PrivacyClassificationPolicyTest { @Test void contract(){ var d=new PrivacyClassificationPolicy().classify(true,false,false); org.junit.jupiter.api.Assertions.assertTrue(d.encryptionRequired()); } }

Deutschsprachige Fachgrundlagen

252. Introduce Data Retention Decision

Introduce Data Retention Decision

Fachliche Einordnung

Aufbewahrung und Löschung hängen von Zweck, Rechtsgrundlage, Fristbeginn, Sperren und Nachweispflichten ab. Einzelne Zahlenwerte oder Booleans verlieren diesen Zusammenhang. Ein Retention Decision Object transportiert Löschzeitpunkt, Grundlage, Legal Hold und Begründung als unveränderliche, auditierbare Entscheidung.

Erkennungsmerkmale

  • TTL-Werte stehen in Tabellen oder Properties ohne Fristbeginn
  • Löschjobs interpretieren dieselben Daten unterschiedlich
  • Legal Hold wird als globales Flag ohne Geltungsbereich geführt
  • nach einer Löschung ist die zugrunde liegende Entscheidung nicht mehr nachvollziehbar

Sicherer Refactoring-Ablauf

  1. Datenkategorien, Zwecke, Fristbeginne und Sperrgründe erfassen.
  2. Retention-Regeln von Scheduler und Speichertechnologie trennen.
  3. Eine Entscheidung mit Version, Löschzeitpunkt, Grundlage und Hold modellieren.
  4. Lösch- und Archivprozesse ausschließlich aus dieser Entscheidung ableiten.
  5. Friständerung, Hold, Freigabe und nachweisbare Löschung testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei mehreren Datenkategorien, gesetzlichen Fristen oder verteilten Speichern. Für rein technische Kurzzeitcaches kann eine einfache TTL ausreichend sein.

Risiken und typische Fehlanwendungen

Ein Decision Object löst keine widersprüchlichen Rechtsanforderungen. Legal Holds dürfen nicht global und unbegrenzt wirken. Fristbeginn und Zeitzone müssen eindeutig sein; Löschbestätigung darf keine unnötigen Originaldaten enthalten.

Enterprise-Java-Beispiel

Ein Schadenfall bleibt bis zum Abschluss und einer anschließenden Frist gespeichert. Ein aktiver Legal Hold verschiebt die Löschung begründet; nach Freigabe wird ein neuer, versionierter Löschzeitpunkt berechnet.

Abgrenzung und verwandte Muster

Retention Scheduler, Legal Hold, Erasure Workflow, Decision Object.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.*;
// Refactoring: Introduce Data Retention Decision
// Pattern: Decision Object - Aufbewahrung und Löschung reisen als konsistente Entscheidung.
public record DataRetentionDecision(Instant deleteAt, String legalBasis, boolean legalHold, String reason) {
  public DataRetentionDecision { if(deleteAt==null||legalBasis.isBlank()||reason.isBlank()) throw new IllegalArgumentException(); }
  public boolean deletableAt(Instant now){ return !legalHold && !now.isBefore(deleteAt); }
}
Teststrategie: Tests prüfen Fristbeginn, Schaltjahre, Hold und Regelversion. Integrationsszenarien belegen idempotente Löschung über Primärspeicher, Suchindex und Archiv sowie einen datensparsamen Nachweis.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class DataRetentionDecisionTest { @Test void contract(){ var d=new DataRetentionDecision(java.time.Instant.EPOCH,"contract",false,"expired"); org.junit.jupiter.api.Assertions.assertTrue(d.deletableAt(java.time.Instant.EPOCH)); } }

Deutschsprachige Fachgrundlagen

253. Extract Encryption Port

Extract Encryption Port

Fachliche Einordnung

Verschlüsselung schützt Daten nur, wenn Algorithmuswahl, Schlüsselzugriff, Kontextbindung und Fehlerverhalten konsistent umgesetzt sind. Direkte Cipher- oder KMS-Aufrufe im Fachcode verteilen diese Verantwortung. Ein Encryption Port hält die Anwendung auf der Ebene von Klartext, typisiertem Ciphertext und Zweckkontext.

Erkennungsmerkmale

  • Fachservices importieren JCA-, Provider- oder KMS-Klassen
  • Ciphertexte speichern weder Schlüsselversion noch Algorithmuskontext
  • AAD, Nonce oder Encoding werden unterschiedlich behandelt
  • Tests benötigen echte Schlüssel oder deaktivieren Verschlüsselung vollständig

Sicherer Refactoring-Ablauf

  1. Schutzobjekte, Zwecke und erforderliche kryptografische Eigenschaften bestimmen.
  2. Portvertrag für Verschlüsseln und Entschlüsseln mit typisiertem Kontext definieren.
  3. Schlüsselwahl, Nonce, AAD und Provider in einen Adapter verschieben.
  4. Ciphertextformat versionieren und Integrität verbindlich prüfen.
  5. Fehler, Schlüsselrotation, beschädigte Daten und Altformate testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei mehreren KMS-Providern, langlebigen Daten oder hexagonaler Architektur. Wenn eine Datenbank transparent und vollständig ausreichend verschlüsselt, sollte keine zusätzliche Anwendungsschicht ohne konkreten Zweck eingeführt werden.

Risiken und typische Fehlanwendungen

Der Port darf kryptografisch relevante Parameter nicht verstecken, wenn der fachliche Zweck sie bestimmt. Entschlüsselungsfehler dürfen keine Klartextfragmente preisgeben. Eigenentwickelte Kryptografie und unsichere Fallbacks sind zu vermeiden.

Enterprise-Java-Beispiel

Der Dokument-Use-Case übergibt Inhalt und Zweck `customer-document`; der Adapter wählt den passenden Schlüssel, bindet Mandant und Dokument-ID als Kontext und liefert einen versionierten Ciphertext. Tests verwenden einen kontrollierten Fake, nicht Klartextproduktion.

Abgrenzung und verwandte Muster

Key Rotation Policy, Key Access Decision, Cryptographic Agility Policy, Port/Adapter.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Encryption Port
// Pattern: Port / Adapter - Kryptografische Infrastruktur bleibt austauschbar.
public interface EncryptionPort {
  record Ciphertext(String keyId, byte[] value) { public Ciphertext { value=value.clone(); } public byte[] value(){ return value.clone(); } }
  Ciphertext encrypt(byte[] plaintext);
  byte[] decrypt(Ciphertext ciphertext);
}
Teststrategie: Vertragstests prüfen Rundlauf, Kontextbindung, unveränderliche Byte-Arrays und Fehlermapping. Integrationstests decken beschädigten Ciphertext, falsche Schlüsselversion, KMS-Ausfall und Rotation ab.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class EncryptionPortTest { @Test void contract(){ EncryptionPort p=new EncryptionPort(){ public Ciphertext encrypt(byte[] x){return new Ciphertext("k",x);} public byte[] decrypt(Ciphertext c){return c.value();}}; org.junit.jupiter.api.Assertions.assertArrayEquals(new byte[]{1},p.decrypt(p.encrypt(new byte[]{1}))); } }

Deutschsprachige Fachgrundlagen

254. Introduce Key Access Decision

Introduce Key Access Decision

Fachliche Einordnung

Zugriff auf kryptografische Schlüssel ist eine besonders schützenswerte Entscheidung. Ein Boolean verliert Zweck, Operation, Schlüsselversion, Gültigkeit und Begründung. Ein Key Access Decision Object hält diese Informationen zusammen und ermöglicht least-privilege-orientierte, zeitlich begrenzte Freigaben.

Erkennungsmerkmale

  • Dienste erhalten pauschalen Zugriff auf einen gesamten Tresor
  • Verschlüsseln, Entschlüsseln und Signieren werden nicht unterschieden
  • Zugriff wird nur über technische Rolle statt fachlichen Zweck entschieden
  • Audit kann nicht erklären, warum eine Schlüsseloperation erlaubt wurde

Sicherer Refactoring-Ablauf

  1. Schlüssel, Operationen und legitime Zwecke inventarisieren.
  2. Entscheidungsattribute wie Mandant, Datenklasse, Operation und Gültigkeit definieren.
  3. Policy-Ergebnis als unveränderliches Decision Object modellieren.
  4. KMS-Adapter nur mit einer gültigen Entscheidung aufrufen.
  5. Ablauf, Widerruf, falschen Zweck und Cross-Tenant-Zugriff testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei zentralem KMS, mehreren Schlüsseln oder strengem Audit. Für einen isolierten Prozess mit einem ausschließlich technisch gebundenen Schlüssel kann die Plattformberechtigung ausreichen.

Risiken und typische Fehlanwendungen

Die Entscheidung darf keine langlebige Kopie eines geheimen Schlüssels enthalten. Zeitliche Gültigkeit allein schützt nicht vor Kontextwechsel; Operation, Zweck und Ressource müssen Teil der Prüfung und des Audit sein.

Enterprise-Java-Beispiel

Ein Exportdienst darf mit Schlüssel K17 nur für den Zweck `regulated-export` und nur zum Verschlüsseln arbeiten. Entschlüsselung oder Nutzung für einen anderen Mandanten wird bereits vor dem KMS-Aufruf abgelehnt.

Abgrenzung und verwandte Muster

Authorization Policy, Resource Scope, Key Rotation Policy, Access Decision.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
// Refactoring: Introduce Key Access Decision
// Pattern: Decision Object - Schlüsselzugriff wird nachvollziehbar und zeitlich begrenzt.
public record KeyAccessDecision(boolean allowed, String keyId, String purpose, Instant validUntil, String reason) {
  public KeyAccessDecision { if(keyId.isBlank()||purpose.isBlank()||reason.isBlank()) throw new IllegalArgumentException(); }
  public boolean validAt(Instant now){ return allowed && now.isBefore(validUntil); }
}
Teststrategie: Entscheidungstabellen prüfen Operation, Zweck, Mandant, Datenklasse und Zeitfenster. Audit-Tests belegen, dass Gründe sichtbar sind, aber keine Schlüssel- oder Klartextdaten protokolliert werden.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class KeyAccessDecisionTest { @Test void contract(){ var d=new KeyAccessDecision(true,"k1","decrypt",java.time.Instant.parse("2027-01-01T00:00:00Z"),"approved"); org.junit.jupiter.api.Assertions.assertTrue(d.validAt(java.time.Instant.parse("2026-01-01T00:00:00Z"))); } }

Deutschsprachige Fachgrundlagen

255. Extract Token Validation Policy

Extract Token Validation Policy

Fachliche Einordnung

Tokenvalidierung umfasst Signatur, Aussteller, Zielgruppe, Zeitgrenzen, Typ, Scope und gegebenenfalls Widerruf. Werden nur einzelne Claims in Filtern geprüft, können gültig signierte, aber für einen anderen Zweck ausgestellte Tokens akzeptiert werden. Eine Policy bündelt die vollständigen Invarianten und liefert ein typisiertes Ergebnis.

Erkennungsmerkmale

  • Controller lesen `sub`, `aud` oder `scope` direkt aus Claims
  • Signaturprüfung und fachliche Claimprüfung liegen in verschiedenen Schichten
  • mehrere APIs akzeptieren unterschiedliche Issuer- oder Audience-Werte
  • abgelaufene, noch nicht gültige oder widerrufene Tokens werden uneinheitlich behandelt

Sicherer Refactoring-Ablauf

  1. Alle akzeptierten Tokentypen, Issuer, Audience und Scopes dokumentieren.
  2. Kryptografische Prüfung im Protokolladapter von fachlicher Policy trennen.
  3. Zeit, Typ, Zielgruppe und benötigte Capability zentral validieren.
  4. Widerruf und Schlüsselversion als explizite Abhängigkeiten einbinden.
  5. Fehler in stabile Gründe übersetzen, ohne sicherheitsrelevante Details preiszugeben.

Wann sinnvoll - und wann nicht?

Sinnvoll bei mehreren APIs, Identity Providern oder Tokentypen. Ein Framework kann die kryptografische Basisprüfung übernehmen, ersetzt aber nicht automatisch die anwendungsspezifische Audience- und Scope-Policy.

Risiken und typische Fehlanwendungen

Das bloße Parsen eines JWT ist keine Validierung. Uhrabweichung muss begrenzt sein; unbekannte Algorithmen, Issuer oder Claimtypen sind abzulehnen. Fehlermeldungen dürfen Angreifern keine unnötige Diagnose liefern.

Enterprise-Java-Beispiel

Der REST-Adapter prüft Signatur und normalisiert Claims. Die Policy akzeptiert nur Tokens des vorgesehenen Issuers, für Audience `claims-api`, mit Scope `claim:read`, gültigem Zeitraum und ohne aktiven Widerruf.

Abgrenzung und verwandte Muster

Authentication Port, Revocation Registry, Permission Object, Principal Value Object.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant; import java.util.Set;
// Refactoring: Extract Token Validation Policy
// Pattern: Policy / Specification - Tokenregeln werden zentral und testbar.
public final class TokenValidationPolicy {
  public record Token(String issuer, String audience, Instant expiresAt, Set<String> scopes){ public Token { scopes=Set.copyOf(scopes); } }
  public record Decision(boolean valid, String reason){}
  public Decision validate(Token token, String issuer, String audience, String scope, Instant now){
    if(!issuer.equals(token.issuer())) return new Decision(false,"issuer");
    if(!audience.equals(token.audience())) return new Decision(false,"audience");
    if(!now.isBefore(token.expiresAt())) return new Decision(false,"expired");
    return token.scopes().contains(scope)?new Decision(true,"valid"):new Decision(false,"scope");
  }
}
Teststrategie: Tests prüfen jeden Claim einzeln und in Kombination: falscher Issuer, Audience, Scope, Ablauf, `nbf`, Uhrabweichung und Widerruf. Mutations- oder Negativtests sichern, dass keine einzelne Prüfung versehentlich entfernt werden kann.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class TokenValidationPolicyTest { @Test void contract(){ var t=new TokenValidationPolicy.Token("i","a",java.time.Instant.parse("2027-01-01T00:00:00Z"),java.util.Set.of("read")); org.junit.jupiter.api.Assertions.assertTrue(new TokenValidationPolicy().validate(t,"i","a","read",java.time.Instant.EPOCH).valid()); } }

Deutschsprachige Fachgrundlagen

256. Introduce Revocation Registry

Introduce Revocation Registry

Fachliche Einordnung

Widerruf beendet Vertrauen vor dem regulären Ablauf eines Tokens, Schlüssels oder einer Sitzung. Lokale Sets oder unversionierte Caches sind dafür ungeeignet, weil Verteilung, Ablauf und Konsistenz unklar bleiben. Eine Registry definiert einen expliziten Vertrag für Widerruf, Grund, Geltungsbereich und zeitliche Wirksamkeit.

Erkennungsmerkmale

  • jeder Prozess führt eine eigene Blockliste
  • Widerrufe gehen nach Neustart oder Cache-Eviction verloren
  • Ablauf und Geltungsbereich werden nicht gespeichert
  • Validierung kennt keine definierte Reaktion auf Registry-Ausfall

Sicherer Refactoring-Ablauf

  1. Widerrufbare Subjekte und erforderliche Reaktionszeit festlegen.
  2. Eintrag mit ID, Typ, Grund, Zeitpunkt, Ablauf und Version modellieren.
  3. Registry-Port und konsistente Abfrage in Validierungs-Policies einführen.
  4. Verteilung, Cache und Invalidierung als Infrastruktur behandeln.
  5. Ausfall, verspätete Replikation, Ablauf und Wiederholung testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei lang lebenden Tokens, Sitzungen, Schlüsseln oder Zertifikaten. Sehr kurzlebige Tokens können durch geringe Laufzeit und fortlaufende Zugriffsevaluierung einfacher abgesichert werden.

Risiken und typische Fehlanwendungen

Eine Registry kann zum zentralen Ausfallpunkt werden. Caches dürfen den Sicherheitszweck nicht durch zu lange TTL unterlaufen. Löschung abgelaufener Einträge muss von forensischer Nachweisführung getrennt werden.

Enterprise-Java-Beispiel

Nach einem kompromittierten Konto schreibt der Incident-Workflow einen Widerruf für Sitzung und Refresh-Token. Alle API-Instanzen prüfen die Registry über einen kurzlebigen Cache; bei Unsicherheit gilt für privilegierte Aktionen fail closed.

Abgrenzung und verwandte Muster

Token Validation Policy, Session Policy, Repository, Cache Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant; import java.util.Optional;
// Refactoring: Introduce Revocation Registry
// Pattern: Repository / Registry - Widerrufe erhalten eine explizite Persistenzgrenze.
public interface RevocationRegistry {
  record Revocation(String subjectId, Instant revokedAt, Instant expiresAt, String reason){}
  void revoke(Revocation revocation);
  Optional<Revocation> findActive(String subjectId, Instant now);
}
Teststrategie: Vertragstests prüfen idempotentes Widerrufen, Ablauf und Abfragen. Verteilungstests simulieren verzögerte Replikation und Registry-Ausfall; Sicherheitsfälle verifizieren das definierte fail-open/fail-closed-Verhalten je Aktion.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class RevocationRegistryTest { @Test void contract(){ java.util.Map<String,RevocationRegistry.Revocation> m=new java.util.HashMap<>(); RevocationRegistry r=new RevocationRegistry(){public void revoke(Revocation x){m.put(x.subjectId(),x);} public java.util.Optional<Revocation> findActive(String id,java.time.Instant now){return java.util.Optional.ofNullable(m.get(id)).filter(x->now.isBefore(x.expiresAt()));}}; r.revoke(new RevocationRegistry.Revocation("t",java.time.Instant.EPOCH,java.time.Instant.MAX,"x")); org.junit.jupiter.api.Assertions.assertTrue(r.findActive("t",java.time.Instant.EPOCH).isPresent()); } }

Deutschsprachige Fachgrundlagen

257. Extract Device Trust Policy

Extract Device Trust Policy

Fachliche Einordnung

Gerätevertrauen entsteht aus mehreren Signalen: Verwaltung, Verschlüsselung, Patchstand, Attestierung, Standort und beobachtetes Verhalten. User-Agent oder ein einzelnes `trustedDevice`-Flag bilden dies nicht belastbar ab. Eine Policy kombiniert Signale zu einer erklärbaren Vertrauensstufe und einer möglichen Step-up-Anforderung.

Erkennungsmerkmale

  • Geräteprüfung basiert nur auf Cookie, User-Agent oder IP
  • MDM-, Compliance- und Risikosignale werden getrennt ausgewertet
  • ein ehemals vertrauenswürdiges Gerät bleibt unbegrenzt freigeschaltet
  • fehlende oder widersprüchliche Signale werden automatisch als vertrauenswürdig behandelt

Sicherer Refactoring-Ablauf

  1. Benötigte Gerätesignale und deren Aktualität definieren.
  2. Signale über Adapter in ein stabiles Eingabemodell übersetzen.
  3. Vertrauensstufen und Reaktionen als Policy-Ergebnis modellieren.
  4. Fehlende, veraltete und widersprüchliche Evidenz restriktiv behandeln.
  5. Rollout mit Berichtmodus und kontrollierten Ausnahmen durchführen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei Zero-Trust-Zugriff, mobilen Geräten oder privilegierten Anwendungen. Für öffentliche, nicht personalisierte Inhalte darf Gerätevertrauen keine unnötige Hürde werden.

Risiken und typische Fehlanwendungen

Gerätevertrauen ist keine Benutzeridentität und darf Autorisierung nicht ersetzen. Signale können veraltet oder manipulierbar sein; BYOD und Barrierefreiheit benötigen bewusste Alternativen statt pauschaler Sperre.

Enterprise-Java-Beispiel

Ein verwaltetes, verschlüsseltes und aktuelles Gerät erhält `TRUSTED`; ein privates Gerät darf Rechnungen lesen, muss aber für Export oder Stammdatenänderung eine starke Step-up-Prüfung erfüllen.

Abgrenzung und verwandte Muster

Risk Assessment, MFA Policy, Conditional Access, Security Context Snapshot.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Device Trust Policy
// Pattern: Policy / Specification - Gerätesignale werden als testbare Entscheidung kombiniert.
public final class DeviceTrustPolicy {
  public enum Trust { TRUSTED, LIMITED, UNTRUSTED }
  public record Signals(boolean managed, boolean encrypted, boolean patched, int anomalyScore){}
  public record Decision(Trust trust, boolean stepUpRequired, String reason){}
  public Decision evaluate(Signals s){
    if(s.anomalyScore()>=80) return new Decision(Trust.UNTRUSTED,true,"anomaly");
    if(s.managed()&&s.encrypted()&&s.patched()) return new Decision(Trust.TRUSTED,false,"managed");
    return new Decision(Trust.LIMITED,true,"insufficient-signals");
  }
}
Teststrategie: Entscheidungstabellen prüfen Kombinationen und Aktualität der Signale. Tests sichern restriktives Verhalten bei fehlender Attestierung, hohem Anomaliescore und widersprüchlicher Compliance-Meldung.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class DeviceTrustPolicyTest { @Test void contract(){ var d=new DeviceTrustPolicy().evaluate(new DeviceTrustPolicy.Signals(true,true,true,0)); org.junit.jupiter.api.Assertions.assertEquals(DeviceTrustPolicy.Trust.TRUSTED,d.trust()); } }

Deutschsprachige Fachgrundlagen

258. Introduce Risk Assessment

Introduce Risk Assessment

Fachliche Einordnung

Ein einzelner Risikoscore ohne Faktoren, Modellversion und empfohlene Maßnahme ist nicht erklärbar und wird leicht falsch interpretiert. Ein Risk Assessment bündelt Bewertung, Stufe, Evidenz, Aktualität und Reaktion. Damit bleibt sichtbar, ob eine Aktion erlaubt, verstärkt geprüft, manuell bewertet oder blockiert werden soll.

Erkennungsmerkmale

  • verschiedene Systeme verwenden Scores mit unterschiedlichen Skalen
  • Schwellenwerte stehen verteilt in Controllern und Regeln
  • Entscheidungen speichern den Score, aber nicht die Faktoren
  • Modell- oder Regeländerungen sind im Audit nicht erkennbar

Sicherer Refactoring-Ablauf

  1. Risikoobjekt, Schutzbedarf und mögliche Auswirkungen definieren.
  2. Signale normalisieren und Modell- beziehungsweise Regelversion erfassen.
  3. Score, Stufe, Faktoren und empfohlene Aktion gemeinsam modellieren.
  4. Schwellen in einer separaten, versionierten Policy halten.
  5. Ergebnisse gegen reale Vorfälle und Fehlalarme kalibrieren.

Wann sinnvoll - und wann nicht?

Sinnvoll bei risikobasiertem Zugriff, Betrugserkennung oder Priorisierung. Ein Assessment ist überdimensioniert, wenn eine klare deterministische Regel ohne Unsicherheit entscheidet.

Risiken und typische Fehlanwendungen

Ein Score darf keine scheinbare mathematische Genauigkeit vortäuschen. Modelle können verzerrt, veraltet oder nicht erklärbar sein. Kritische Entscheidungen benötigen menschliche Prüfung, Einspruch und nachvollziehbare Faktoren.

Enterprise-Java-Beispiel

Ein Login erhält Score 76 mit Faktoren „neues Gerät“ und „untypischer Standort“. Das Assessment empfiehlt Step-up; erst die MFA Policy entscheidet, welcher zusätzliche Nachweis erforderlich ist.

Abgrenzung und verwandte Muster

Threat Detection Port, Device Trust Policy, Decision Object, Human Review Workflow.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.List;
// Refactoring: Introduce Risk Assessment
// Pattern: Decision Object - Score, Evidenz und Maßnahme bleiben zusammen.
public record RiskAssessment(int score, Level level, List<String> factors, Action action) {
  public enum Level { LOW, MEDIUM, HIGH, CRITICAL } public enum Action { ALLOW, STEP_UP, REVIEW, BLOCK }
  public RiskAssessment { if(score<0||score>100) throw new IllegalArgumentException(); factors=List.copyOf(factors); }
  public static RiskAssessment of(int score,List<String> factors){
    if(score>=90) return new RiskAssessment(score,Level.CRITICAL,factors,Action.BLOCK);
    if(score>=70) return new RiskAssessment(score,Level.HIGH,factors,Action.REVIEW);
    if(score>=40) return new RiskAssessment(score,Level.MEDIUM,factors,Action.STEP_UP);
    return new RiskAssessment(score,Level.LOW,factors,Action.ALLOW);
  }
}
Teststrategie: Grenzwerttests prüfen jede Stufe. Replay- und Kalibrierungstests vergleichen Ergebnisse mit gekennzeichneten Fällen; Versionswechsel müssen im Audit und in reproduzierbaren Testdaten sichtbar bleiben.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class RiskAssessmentTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertEquals(RiskAssessment.Action.BLOCK,RiskAssessment.of(95,java.util.List.of("velocity")).action()); } }

Deutschsprachige Fachgrundlagen

259. Extract Anomaly Detection Port

Extract Anomaly Detection Port

Fachliche Einordnung

Anomalieerkennung vergleicht Beobachtungen mit erwartetem Verhalten und liefert einen Befund, keine endgültige Geschäftsentscheidung. Wird die Anwendung direkt an eine Regelengine oder ein ML-Produkt gekoppelt, wandern proprietäre Features, Kategorien und Ausfallsemantik in den Use Case. Ein Port hält die Grenze klein und austauschbar.

Erkennungsmerkmale

  • Fachcode erstellt produktabhängige Feature-Vektoren
  • ein ML-Score steuert direkt Blockierung oder Auszahlung
  • Training, Modellversion und Aktualität fehlen im Ergebnis
  • Provider-Ausfall führt zu unkontrolliertem Standardverhalten

Sicherer Refactoring-Ablauf

  1. Fachlich relevante Beobachtungen und minimale Daten festlegen.
  2. Finding mit Score, Kategorie, Evidenz, Modellversion und Zeit definieren.
  3. Feature Engineering und Provideraufruf in Adapter verschieben.
  4. Ausfall und Unsicherheit an eine separate Risk Policy übergeben.
  5. Shadow-, Replay- und Drift-Überwachung vor automatischer Reaktion etablieren.

Wann sinnvoll - und wann nicht?

Sinnvoll bei mehreren Erkennungsverfahren oder externen Plattformen. Wenn eine zentrale Detection-Plattform ausschließlich Ereignisse publiziert, kann ein stabiler Eventvertrag statt eines synchronen Ports geeigneter sein.

Risiken und typische Fehlanwendungen

Anomalie bedeutet nicht automatisch Angriff. Personenbezogene Telemetrie muss minimiert werden; Modell- und Datenänderungen können Drift erzeugen. Blockierungen ohne Review- oder Recovery-Pfad sind riskant.

Enterprise-Java-Beispiel

Der Zahlungsdienst übergibt Betragsabweichung, Gerätewechsel und Frequenz als typisierte Beobachtung. Der Adapter liefert einen Finding; die Risk Policy kombiniert ihn mit Kunden- und Prozesskontext, bevor eine Maßnahme gewählt wird.

Abgrenzung und verwandte Muster

Threat Detection Port, Risk Assessment, Adapter, Event-Driven Detection.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant; import java.util.Map;
// Refactoring: Extract Anomaly Detection Port
// Pattern: Port / Adapter - Erkennungsprodukte bleiben Infrastrukturdetails.
public interface AnomalyDetectionPort {
  record Observation(String subjectId, Instant occurredAt, Map<String,Double> metrics){ public Observation { metrics=Map.copyOf(metrics); } }
  record Finding(int score, String category, String evidence){}
  Finding assess(Observation observation);
}
Teststrategie: Vertragstests prüfen Mapping und Fehlerfälle. Replay-Tests verwenden versionierte, anonymisierte Beobachtungen; Drift-Tests vergleichen Verteilungen und verhindern unbemerkte Änderung der Kategorien.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class AnomalyDetectionPortTest { @Test void contract(){ AnomalyDetectionPort p=o->new AnomalyDetectionPort.Finding(10,"normal","baseline"); org.junit.jupiter.api.Assertions.assertEquals(10,p.assess(new AnomalyDetectionPort.Observation("u",java.time.Instant.EPOCH,java.util.Map.of())).score()); } }

Deutschsprachige Fachgrundlagen

260. Introduce Security Incident Command

Introduce Security Incident Command

Fachliche Einordnung

Bei Sicherheitsvorfällen müssen Sperren, Widerrufe, Isolation, Benachrichtigung und Beweissicherung kontrolliert ausgelöst werden. Direkte Serviceaufrufe aus Alert-Handlern sind schwer wiederholbar und verlieren Reihenfolge, Idempotenz und Verantwortlichkeit. Ein Incident Command beschreibt eine beabsichtigte, auditierbare Aktion mit stabiler Incident-ID.

Erkennungsmerkmale

  • Alert-Handler ruft mehrere Systeme nacheinander ohne Wiederholschutz auf
  • Teilfehler hinterlassen unbekannten Reaktionszustand
  • manuelle und automatische Maßnahmen verwenden unterschiedliche Wege
  • Incident-ID, Grund und anfordernde Rolle fehlen im technischen Auftrag

Sicherer Refactoring-Ablauf

  1. Erlaubte Reaktionsaktionen und notwendige Freigaben definieren.
  2. Command mit Incident-ID, Subjekt, Aktion, Grund und Idempotenzschlüssel modellieren.
  3. Ausführung in einen Handler oder Workflow mit Statusübergängen verschieben.
  4. Seiteneffekte über Ports ausführen und Ergebnis je Schritt protokollieren.
  5. Wiederholung, Teilfehler, Reihenfolge, Kompensation und manuelle Übernahme testen.

Wann sinnvoll - und wann nicht?

Sinnvoll bei mehreren Reaktionsschritten, automatisierten Playbooks oder verteilten Systemen. Eine lokale, atomare Sperre kann direkt ausgeführt werden, sofern Audit und Fehlerbehandlung klar bleiben.

Risiken und typische Fehlanwendungen

Automatisierung darf keine Beweise zerstören oder unverhältnismäßige Aktionen auslösen. Commands benötigen Autorisierung, Priorität und gegebenenfalls Vier-Augen-Freigabe. Idempotenz darf fachlich verschiedene Wiederholungen nicht fälschlich zusammenfassen.

Enterprise-Java-Beispiel

Ein bestätigter Tokenmissbrauch erzeugt Commands zum Widerruf der Sitzung, Isolieren des Geräts und Sichern relevanter Auditdaten. Jeder Schritt ist idempotent, statusgeführt und kann nach einem Teilausfall sicher fortgesetzt werden.

Abgrenzung und verwandte Muster

Command, Process Manager, Incident Workflow, Outbox, Revocation Registry.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
// Refactoring: Introduce Security Incident Command
// Pattern: Command / Workflow - Incident-Reaktionen werden wiederholbar und auditierbar.
public record SecurityIncidentCommand(String incidentId, Action action, String subjectId, Instant requestedAt, String reason) {
  public enum Action { SUSPEND, REVOKE, ISOLATE, NOTIFY, PRESERVE_EVIDENCE }
  public SecurityIncidentCommand { if(incidentId.isBlank()||subjectId.isBlank()||reason.isBlank()) throw new IllegalArgumentException(); }
  public String idempotencyKey(){ return incidentId+":"+action+":"+subjectId; }
}
Teststrategie: Workflowtests simulieren Wiederholung, Teilausfall und verspätete Antworten. Autorisierungstests prüfen automatische und manuelle Aktionen; Audit-Tests sichern vollständige Reihenfolge ohne unnötige sensible Nutzdaten.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class SecurityIncidentCommandTest { @Test void contract(){ var c=new SecurityIncidentCommand("i1",SecurityIncidentCommand.Action.REVOKE,"u1",java.time.Instant.EPOCH,"compromise"); org.junit.jupiter.api.Assertions.assertEquals("i1:REVOKE:u1",c.idempotencyKey()); } }

Deutschsprachige Fachgrundlagen

Kapitel 27 Tenant-Isolation und Zero-Trust-Grenzen10 Refactorings

Refactorings 261–270 führen Tenant-Isolation, delegierte Fähigkeiten, privilegierte Workflows, Access Reviews und explizite Trust Boundaries ein.

Refactoring-Katalog · Abschnitt 12

Identity Governance und Zero-Trust-Grenzen

Refactorings 261–270 führen Tenant-Isolation, delegierte Fähigkeiten, privilegierte Workflows, Access Reviews und explizite Trust Boundaries ein.

270 von 300 Refactorings abgeschlossen

30 Refactorings offen.

261. Extract Tenant Isolation Policy Policy / Specification

Extract Tenant Isolation Policy

Fachliche Einordnung

In mandantenfähigen Enterprise-Systemen ist die Tenant-Grenze eine fachliche Sicherheitsinvariante. Bleibt sie als String-Vergleich in Repositorys, Services und Filtern verteilt, entstehen leicht Querzugriffe, inkonsistente Sonderfälle und schwer prüfbare Umgehungspfade. Eine Tenant Isolation Policy bündelt die Entscheidung über Akteur, Ressource und Operation an einer expliziten, testbaren Grenze.

Erkennungsmerkmale

  • Tenant-IDs werden an vielen Stellen direkt verglichen
  • Repository-Filter und Service-Prüfung widersprechen sich
  • Support- oder Admin-Sonderwege umgehen normale Grenzen
  • Cross-Tenant-Zugriffe sind nur über Logsuche nachvollziehbar

Sicherer Refactoring-Ablauf

  1. bestehende Tenant-Prüfungen und Sonderfälle inventarisieren
  2. Request und Decision als typisierte Werte definieren
  3. Default-Deny sowie erlaubte Ausnahmen ausdrücklich modellieren
  4. Policy an Controller-, Service- und Repository-Grenzen erzwingen
  5. Negativ-, Mutation- und Mandantenwechseltests ergänzen

Wann sinnvoll - und wann nicht?

Sinnvoll bei SaaS-, Plattform- und Shared-Database-Systemen. Für vollständig getrennte Deployments ohne gemeinsame Daten- oder Prozessgrenzen kann die Policy kleiner ausfallen.

Risiken und typische Fehlanwendungen

Eine zentrale Policy darf nicht nur als Hilfsmethode existieren, während alte Direktvergleiche weiterlaufen. Besonders gefährlich sind globale Administratorrollen ohne begrenzten Scope und fehlende Objektprüfung.

Enterprise-Java-Beispiel

Ein Claims-Service bewertet vor jedem Lesen und Ändern, ob Bearbeiter-Tenant, Schaden-Tenant und Operation zusammenpassen. Supportzugriffe benötigen einen separat dokumentierten Ausnahmegrund.

Abgrenzung und verwandte Muster

Specification, Policy Enforcement Point, Resource Scope, Default Deny.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Extract Tenant Isolation Policy
// Pattern: Policy / Specification - Mandantengrenzen werden zentral und testbar entschieden.
public final class TenantIsolationPolicy {
  public record Request(String actorTenant, String resourceTenant, String operation) {
    public Request { if(actorTenant.isBlank()||resourceTenant.isBlank()||operation.isBlank()) throw new IllegalArgumentException(); }
  }
  public record Decision(boolean allowed, String reason) {}
  public Decision evaluate(Request r) {
    return r.actorTenant().equals(r.resourceTenant())
        ? new Decision(true, "same-tenant")
        : new Decision(false, "cross-tenant-denied");
  }
}
Teststrategie: Tabellentests decken Same-Tenant, Cross-Tenant, Supportausnahme und fehlende Tenant-ID ab. Integrationstests prüfen, dass auch Repository-Abfragen keinen fremden Datensatz liefern.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class TenantIsolationPolicyTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new TenantIsolationPolicy().evaluate(new TenantIsolationPolicy.Request("a","a","read")).allowed()); } }

Deutschsprachige Fachgrundlagen

262. Introduce Resource Scope Value Object

Introduce Resource Scope

Fachliche Einordnung

Unstrukturierte Ressourcenpfade vermischen Tenant, Typ und ID und lassen Präfixvergleiche wie `/customer/12` gegen `/customer/123` zu. Ein Resource Scope Value Object trennt die Bestandteile, validiert sie beim Erzeugen und stellt semantische Vergleichsoperationen bereit.

Erkennungsmerkmale

  • Scopes werden als Pfad oder zusammengesetzter String transportiert
  • startsWith- oder contains-Prüfungen entscheiden über Zugriff
  • Mandant und Ressourcentyp sind nicht getrennt auswertbar
  • Scope-Fehler erscheinen erst in produktiven Autorisierungsfällen

Sicherer Refactoring-Ablauf

  1. verwendete Scope-Formate und Wildcards erfassen
  2. Tenant, Ressourcentyp und ID als Pflichtwerte modellieren
  3. kanonische Gleichheit und contains-Semantik definieren
  4. Parser nur an Integrationsgrenzen zulassen
  5. alte String-Prüfungen schrittweise entfernen

Wann sinnvoll - und wann nicht?

Sinnvoll, sobald Ressourcen über mehrere Module oder Tokens referenziert werden. Bei einem einzelnen, lokal typisierten Identifier kann ein bestehender Value Type genügen.

Risiken und typische Fehlanwendungen

contains darf keine implizite Hierarchie vortäuschen. Wildcards, globale Scopes und Tenant-übergreifende Ressourcen brauchen eigene, restriktive Typen.

Enterprise-Java-Beispiel

Ein Dokumentendienst wandelt externe Claim-Pfade im Adapter in ResourceScope um; der Use Case arbeitet nur noch mit validierten Werten.

Abgrenzung und verwandte Muster

Value Object, Permission Object, Capability, Authorization Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Resource Scope
// Pattern: Value Object - Ressourcengrenzen erhalten eine validierte Identität.
public record ResourceScope(String tenantId, String resourceType, String resourceId) {
  public ResourceScope {
    if(tenantId.isBlank()||resourceType.isBlank()||resourceId.isBlank()) throw new IllegalArgumentException();
  }
  public boolean contains(ResourceScope other){ return tenantId.equals(other.tenantId) && resourceType.equals(other.resourceType); }
}
Teststrategie: Property-basierte Tests prüfen Gleichheit, Kanonisierung und ungültige IDs. Sicherheitsfälle decken Präfixkollisionen und Cross-Tenant-Scope ab.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ResourceScopeTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new ResourceScope("t","order","1").contains(new ResourceScope("t","order","2"))); } }

Deutschsprachige Fachgrundlagen

263. Extract Privileged Access Workflow Workflow / State Machine

Extract Privileged Access Workflow

Fachliche Einordnung

Privilegierter Zugriff ist kein einzelner Boolean, sondern ein zeitlich begrenzter Prozess aus Antrag, Genehmigung, Aktivierung und Ablauf. Eine explizite State Machine verhindert direkte Sprünge und macht jede Zustandsänderung nachvollziehbar.

Erkennungsmerkmale

  • Genehmigung und Aktivierung erfolgen in getrennten Services ohne gemeinsamen Zustand
  • Ablaufzeiten werden nur in Hintergrundjobs geprüft
  • direkte Datenbankupdates setzen privilegierte Rollen
  • abgelehnte oder abgelaufene Anträge können reaktiviert werden

Sicherer Refactoring-Ablauf

  1. fachliche Zustände und erlaubte Übergänge definieren
  2. Invarianten für Genehmiger, Dauer und Zweck festlegen
  3. Übergänge in Methoden des Workflows kapseln
  4. Zeit über Clock/Port statt Instant.now kontrollieren
  5. Persistenz und Audit auf Zustandsereignisse ausrichten

Wann sinnvoll - und wann nicht?

Sinnvoll für Just-in-Time-Administration, Produktionszugriff und sensible Betriebsrollen. Ein dauerhaftes, niedrig privilegiertes Standardrecht benötigt keinen solchen Workflow.

Risiken und typische Fehlanwendungen

Zeitprüfung im Domänenobjekt darf nicht untestbar an die Systemuhr gekoppelt sein. Selbstgenehmigung und Wiederaktivierung abgelaufener Grants müssen ausgeschlossen werden.

Enterprise-Java-Beispiel

Ein SRE beantragt 20 Minuten Produktionszugriff; nach Vier-Augen-Freigabe wird der Grant aktiviert und automatisch abgelaufen.

Abgrenzung und verwandte Muster

State Machine, Process Manager, Break Glass Policy, Temporal Grant.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
// Refactoring: Extract Privileged Access Workflow
// Pattern: Workflow / State Machine - Privilegierter Zugriff folgt überprüfbaren Übergängen.
public final class PrivilegedAccessWorkflow {
  public enum State { REQUESTED, APPROVED, ACTIVE, EXPIRED, REJECTED }
  private State state=State.REQUESTED;
  private Instant validUntil;
  public State state(){ return state; }
  public void approve(){ require(State.REQUESTED); state=State.APPROVED; }
  public void activate(Instant until){ require(State.APPROVED); validUntil=until; state=State.ACTIVE; }
  public void expire(Instant now){ if(state==State.ACTIVE && !now.isBefore(validUntil)) state=State.EXPIRED; }
  private void require(State expected){ if(state!=expected) throw new IllegalStateException(state.name()); }
}
Teststrategie: Übergangstests prüfen erlaubte und verbotene Zustandswechsel, Ablaufgrenzen, doppelte Genehmigung und Wiederholung nach Persistenzfehlern.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class PrivilegedAccessWorkflowTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new PrivilegedAccessWorkflow().state()==PrivilegedAccessWorkflow.State.REQUESTED); } }

Deutschsprachige Fachgrundlagen

264. Introduce Delegation Grant Value Object / Capability

Introduce Delegation Grant

Fachliche Einordnung

Delegation ist fachlich mehr als eine Rolle mit Ablaufdatum. Ein Delegation Grant hält Aussteller, Empfänger, Capability, Ressourcen-Scope und Gültigkeit als unveränderlichen Wert zusammen und verhindert unkontrollierte Rechteweitergabe.

Erkennungsmerkmale

  • delegierte Rollen verlieren Aussteller und Zweck
  • Scope wird getrennt vom Grant gespeichert
  • Ablauf wird nur beim Erzeugen, nicht beim Verwenden geprüft
  • Empfänger kann mehr delegieren als der Aussteller besitzt

Sicherer Refactoring-Ablauf

  1. zulässige delegierbare Fähigkeiten bestimmen
  2. Grant als unveränderlichen Fachwert modellieren
  3. Scope- und Zeitprüfung in validFor bündeln
  4. Weiterdelegation ausdrücklich verbieten oder begrenzen
  5. Widerruf und Audit über stabile Grant-ID ergänzen

Wann sinnvoll - und wann nicht?

Sinnvoll bei Stellvertretung, Service-zu-Service-Fähigkeiten und zeitweiser Aufgabenübernahme. Nicht nötig für dauerhaft zugewiesene Standardrollen.

Risiken und typische Fehlanwendungen

Ein Grant darf nie mehr Rechte enthalten als der Aussteller wirksam besitzt. Uhrzeit, Zeitzone, Widerruf und Scope-Unterordnung müssen konsistent ausgewertet werden.

Enterprise-Java-Beispiel

Eine Teamleitung delegiert die Freigabe eines konkreten Schadensfalls bis Schichtende an einen Vertreter, nicht die gesamte Managerrolle.

Abgrenzung und verwandte Muster

Capability, Value Object, Temporal Authorization, Resource Scope.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
// Refactoring: Introduce Delegation Grant
// Pattern: Value Object / Capability - Delegation wird begrenzt, zeitlich gültig und auditierbar.
public record DelegationGrant(String issuer, String subject, String capability, ResourceScope scope, Instant validUntil) {
  public DelegationGrant { if(issuer.isBlank()||subject.isBlank()||capability.isBlank()||scope==null||validUntil==null) throw new IllegalArgumentException(); }
  public boolean validFor(String actor, String requestedCapability, ResourceScope requestedScope, Instant now){
    return subject.equals(actor) && capability.equals(requestedCapability) && scope.contains(requestedScope) && now.isBefore(validUntil);
  }
}
Teststrategie: Tests prüfen falschen Empfänger, falsche Capability, abgelaufenen Grant, kleineren/größeren Scope und Widerruf.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class DelegationGrantTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new DelegationGrant("a","b","read",new ResourceScope("t","order","*"),java.time.Instant.parse("2027-01-01T00:00:00Z")).validFor("b","read",new ResourceScope("t","order","1"),java.time.Instant.parse("2026-01-01T00:00:00Z"))); } }

Deutschsprachige Fachgrundlagen

265. Extract Break Glass Policy Policy / Command

Extract Break Glass Policy

Fachliche Einordnung

Break-Glass-Zugriffe sind notwendige Ausnahmewege, dürfen aber normale Kontrollen nicht unsichtbar umgehen. Eine eigene Policy bewertet Incident-Bezug, Genehmigung, maximale Dauer und Folgepflichten wie Benachrichtigung und Review.

Erkennungsmerkmale

  • versteckte Feature-Flags oder Admin-Header öffnen Notfallzugriff
  • kein Incident oder Genehmiger wird erfasst
  • Notfallrechte bleiben nach Ende des Vorfalls aktiv
  • Security erfährt erst nachträglich von der Nutzung

Sicherer Refactoring-Ablauf

  1. zulässige Notfallszenarien und Maximaldauer festlegen
  2. Request und Decision mit Incident-ID und Genehmigung modellieren
  3. technischen Ausnahmeweg vom Normalweg trennen
  4. automatische Benachrichtigung, Ablauf und Nachreview erzwingen
  5. Missbrauchs- und Wiederholtests durchführen

Wann sinnvoll - und wann nicht?

Sinnvoll für kritische Produktions- oder Patienten-/Finanzsysteme mit echter Betriebsnotwendigkeit. Nicht als bequeme Alternative zu regulären Berechtigungsprozessen.

Risiken und typische Fehlanwendungen

Break Glass ohne unabhängiges Audit wird zur Hintertür. Die Policy muss restriktiv, zeitlich begrenzt und gegen Selbstgenehmigung geschützt sein.

Enterprise-Java-Beispiel

Bei einem Produktionsausfall erhält ein benannter Operator 15 Minuten Zugriff auf einen begrenzten Diagnose-Scope; Security und Incident Lead werden automatisch informiert.

Abgrenzung und verwandte Muster

Policy, Emergency Access, Privileged Workflow, Audit Trail.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Duration;
// Refactoring: Extract Break Glass Policy
// Pattern: Policy / Command - Notfallzugriff wird eng begrenzt und auditierbar.
public final class BreakGlassPolicy {
  public record Request(String actor, String incidentId, Duration duration, boolean approverPresent) {}
  public record Decision(boolean allowed, Duration grantedFor, boolean notifySecurity, String reason) {}
  public Decision evaluate(Request r){
    if(r.actor()==null||r.actor().isBlank()||r.incidentId()==null||r.incidentId().isBlank()) return new Decision(false,Duration.ZERO,false,"missing-evidence");
    if(!r.approverPresent() || r.duration().compareTo(Duration.ofMinutes(30))>0) return new Decision(false,Duration.ZERO,true,"approval-or-duration");
    return new Decision(true,r.duration(),true,"incident-response");
  }
}
Teststrategie: Negativtests prüfen fehlenden Incident, zu lange Dauer, fehlenden Genehmiger und Wiederverwendung alter Requests. End-to-End-Tests sichern automatisches Ablaufverhalten.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class BreakGlassPolicyTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new BreakGlassPolicy().evaluate(new BreakGlassPolicy.Request("u","INC-1",java.time.Duration.ofMinutes(10),true)).allowed()); } }

Deutschsprachige Fachgrundlagen

266. Introduce Authorization Evidence Decision Object

Introduce Authorization Evidence

Fachliche Einordnung

Ein reines allow/deny verliert, welche Policy-Version, Faktoren und Gründe zur Entscheidung führten. Authorization Evidence konserviert die Entscheidungsgrundlage für Audit, Support und reproduzierbare Analyse, ohne die Policy selbst in Logs nachzubauen.

Erkennungsmerkmale

  • Auditlogs enthalten nur Ergebnis und Benutzer-ID
  • Policyänderungen machen alte Entscheidungen unerklärbar
  • Support kann abgelehnte Zugriffe nicht reproduzieren
  • sensible Rohattribute werden unkontrolliert protokolliert

Sicherer Refactoring-Ablauf

  1. minimal erforderliche Evidenzfelder definieren
  2. Policy-Version und Faktorbezeichnungen stabilisieren
  3. sensible Werte pseudonymisieren oder weglassen
  4. Evidence atomar mit Entscheidung speichern
  5. Retention, Zugriff und Export der Evidenz regeln

Wann sinnvoll - und wann nicht?

Sinnvoll bei regulierten oder komplexen Autorisierungsentscheidungen. Für triviale lokale Checks kann ein strukturierter Reason Code ausreichen.

Risiken und typische Fehlanwendungen

Evidenz darf kein zweites Geheimnis- oder Personendatenlager werden. Nur entscheidungsrelevante, minimierte Informationen speichern und Integrität schützen.

Enterprise-Java-Beispiel

Eine Zahlungsfreigabe speichert Policy-Version, geprüfte Faktoren und Reason Code; bei Reklamation lässt sich die damalige Entscheidung nachvollziehen.

Abgrenzung und verwandte Muster

Decision Object, Audit Log, Policy Decision Point, Observability.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant; import java.util.List;
// Refactoring: Introduce Authorization Evidence
// Pattern: Decision Object - Autorisierung bleibt mit ihrer Beweiskette nachvollziehbar.
public record AuthorizationEvidence(boolean allowed, String policyVersion, List<String> evaluatedFactors, String reason, Instant decidedAt) {
  public AuthorizationEvidence { if(policyVersion.isBlank()||reason.isBlank()||decidedAt==null) throw new IllegalArgumentException(); evaluatedFactors=List.copyOf(evaluatedFactors); }
}
Teststrategie: Vertragstests sichern unveränderliche Faktorenlisten, Pflichtfelder und Policy-Version. Datenschutztests prüfen, dass Tokens und Rohattribute nicht enthalten sind.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class AuthorizationEvidenceTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new AuthorizationEvidence(true,"v1",java.util.List.of("tenant","scope"),"matched",java.time.Instant.EPOCH).allowed()); } }

Deutschsprachige Fachgrundlagen

267. Extract Entitlement Reconciler Reconciliation Loop

Extract Entitlement Reconciler

Fachliche Einordnung

Berechtigungsabgleiche werden oft als unübersichtliche Schleifen implementiert, die Änderungen sofort ausführen. Ein Entitlement Reconciler erzeugt zuerst einen expliziten Plan aus grant, revoke und unchanged; Ausführung, Freigabe und Audit können danach kontrolliert erfolgen.

Erkennungsmerkmale

  • periodische Jobs ändern Rechte während des Vergleichs
  • Teilfehler erzeugen unbekannten Zwischenstand
  • gleiche Differenzen werden bei jedem Lauf erneut ausgelöst
  • manuelle und automatische Quellen widersprechen sich

Sicherer Refactoring-Ablauf

  1. gewünschten und tatsächlichen Zustand eindeutig bestimmen
  2. reinen Reconcile-Schritt ohne Seiteneffekte extrahieren
  3. Plan als unveränderliches Ergebnis modellieren
  4. Ausführung idempotent und separat implementieren
  5. Konflikte, Ownership und Drift-Metriken ergänzen

Wann sinnvoll - und wann nicht?

Sinnvoll bei IAM-Synchronisation, Gruppenabgleich und deklarativen Plattformen. Für eine einzelne atomare Rollenänderung wäre der Reconciler überdimensioniert.

Risiken und typische Fehlanwendungen

Falsche Quelle der Wahrheit kann massenhaft Rechte entziehen. Revoke-Pläne brauchen Schutzschwellen, Dry Run und gegebenenfalls Freigabe.

Enterprise-Java-Beispiel

Ein nächtlicher Job vergleicht HR-Sollrollen mit Identity-Provider-Istrollen und erzeugt einen prüfbaren Änderungsplan.

Abgrenzung und verwandte Muster

Reconciliation Loop, Desired State, Diff, Idempotent Command.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Set;
// Refactoring: Extract Entitlement Reconciler
// Pattern: Reconciliation Loop - Soll/Ist-Abweichungen werden als expliziter Plan behandelt.
public final class EntitlementReconciler {
  public record Plan(Set<String> grant, Set<String> revoke, Set<String> unchanged){ public Plan { grant=Set.copyOf(grant); revoke=Set.copyOf(revoke); unchanged=Set.copyOf(unchanged); } }
  public Plan reconcile(Set<String> desired, Set<String> actual){
    var grant=new java.util.HashSet<>(desired); grant.removeAll(actual);
    var revoke=new java.util.HashSet<>(actual); revoke.removeAll(desired);
    var unchanged=new java.util.HashSet<>(desired); unchanged.retainAll(actual);
    return new Plan(grant,revoke,unchanged);
  }
}
Teststrategie: Mengentests prüfen grant/revoke/unchanged, leere Mengen, Wiederholung und große Datenmengen. Safety-Tests blockieren ungewöhnlich große Revoke-Pläne.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class EntitlementReconcilerTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new EntitlementReconciler().reconcile(java.util.Set.of("a"),java.util.Set.of()).grant().contains("a")); } }

Deutschsprachige Fachgrundlagen

268. Introduce Access Review Campaign Aggregate / Workflow

Introduce Access Review Campaign

Fachliche Einordnung

Access Reviews bestehen aus vielen Einzelentscheidungen, gehören aber fachlich zu einer Kampagne mit Fälligkeit, Verantwortlichen und Abschlussregel. Ein Aggregate verhindert den Abschluss mit offenen Items und bündelt Statusänderungen konsistent.

Erkennungsmerkmale

  • Review-Zeilen besitzen keinen gemeinsamen Kampagnenstatus
  • Kampagnen werden trotz offener Entscheidungen geschlossen
  • Fälligkeit und Eskalation sind nur in Batchjobs kodiert
  • Änderungen nach Abschluss sind möglich

Sicherer Refactoring-Ablauf

  1. Kampagnengrenze und Review-Item-Identität festlegen
  2. Entscheidungsstatus und Abschlussinvariante modellieren
  3. Änderungen nur über Aggregatmethoden zulassen
  4. Eskalation und Fristprüfung über Anwendungsservice steuern
  5. Ergebnis in Revoke/Keep-Kommandos übersetzen

Wann sinnvoll - und wann nicht?

Sinnvoll für periodische Rezertifizierung vieler Entitlements. Für eine einzelne Ad-hoc-Genehmigung genügt ein kleiner Workflow.

Risiken und typische Fehlanwendungen

Ein zu großes Aggregat kann bei tausenden Items unpraktisch werden; dann Kampagne und Batches trennen, Abschlussinvariante aber erhalten.

Enterprise-Java-Beispiel

Eine Quartalskampagne umfasst alle privilegierten Rechte eines Bereichs und kann erst abgeschlossen werden, wenn jedes Item KEEP oder REVOKE trägt.

Abgrenzung und verwandte Muster

Aggregate, Workflow, Review Campaign, Reconciliation.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant; import java.util.*;
// Refactoring: Introduce Access Review Campaign
// Pattern: Aggregate / Workflow - Review-Items werden konsistent abgeschlossen.
public final class AccessReviewCampaign {
  public enum Decision { PENDING, KEEP, REVOKE }
  private final Map<String,Decision> items=new LinkedHashMap<>(); private final Instant dueAt;
  public AccessReviewCampaign(Instant dueAt, Collection<String> entitlementIds){ this.dueAt=Objects.requireNonNull(dueAt); entitlementIds.forEach(id->items.put(id,Decision.PENDING)); }
  public void decide(String id, Decision decision){ if(!items.containsKey(id)||decision==Decision.PENDING) throw new IllegalArgumentException(); items.put(id,decision); }
  public boolean complete(){ return items.values().stream().noneMatch(d->d==Decision.PENDING); }
  public boolean overdue(Instant now){ return now.isAfter(dueAt) && !complete(); }
}
Teststrategie: Tests prüfen offene Items, doppelte Entscheidungen, Abschluss, Fälligkeit und nachträgliche Mutation. Persistenztests sichern Parallelbearbeitung.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class AccessReviewCampaignTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new AccessReviewCampaign(java.time.Instant.MAX,java.util.List.of("e")).complete()==false); } }

Deutschsprachige Fachgrundlagen

269. Extract Identity Proofing Port Port / Adapter

Extract Identity Proofing Port

Fachliche Einordnung

Identitätsnachweis ist externe Infrastruktur mit unterschiedlichen Providern, Qualitätsstufen und Fehlerbildern. Ein Port trennt den Onboarding-Use-Case von Video-Ident, Dokumentenprüfung oder eID und liefert ein typisiertes Ergebnis.

Erkennungsmerkmale

  • Provider-SDKs erscheinen im Domänen- oder Application-Code
  • fachliche Logik reagiert auf providerinterne Statuscodes
  • Providerwechsel erfordert Änderungen in mehreren Modulen
  • Tests benötigen echte externe Identitätsdienste

Sicherer Refactoring-Ablauf

  1. fachlichen Request und erforderliches Assurance Level definieren
  2. providerneutrales Resultat mit Referenz und Grund modellieren
  3. Port im Application Layer einführen
  4. SDK und Mapping in Adapter verschieben
  5. Vertragstests für alle Adapter aufbauen

Wann sinnvoll - und wann nicht?

Sinnvoll bei externen Identitäts- oder KYC-Providern. Bei rein interner, stabiler Verzeichnisprüfung kann ein vorhandener Identity-Port erweitert werden.

Risiken und typische Fehlanwendungen

Der Port darf Providerdetails nicht nur umbenennen. Assurance Levels, manuelle Prüfung und asynchrone Ergebnisse müssen fachlich sauber abgebildet werden.

Enterprise-Java-Beispiel

Das Kunden-Onboarding fordert Level substantial; Adapter A nutzt eID, Adapter B Video-Ident, der Use Case bleibt unverändert.

Abgrenzung und verwandte Muster

Port/Adapter, Anti-Corruption Layer, Strategy, Identity Proofing.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant; import java.util.Map;
// Refactoring: Extract Identity Proofing Port
// Pattern: Port / Adapter - Identitätsprovider bleiben austauschbare Infrastruktur.
public interface IdentityProofingPort {
  record Request(String subjectId, String assuranceLevel, Map<String,String> attributes){ public Request { attributes=Map.copyOf(attributes); } }
  record Result(boolean verified, String providerReference, String achievedLevel, Instant verifiedAt, String reason) {}
  Result verify(Request request);
}
Teststrategie: Contract Tests prüfen verified, rejected, pending/technischen Fehler und Mapping des erreichten Assurance Levels.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class IdentityProofingPortTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(((IdentityProofingPort)r->new IdentityProofingPort.Result(true,"ref","high",java.time.Instant.EPOCH,"ok")).verify(new IdentityProofingPort.Request("u","high",java.util.Map.of())).verified()); } }

Deutschsprachige Fachgrundlagen

270. Introduce Trust Boundary Map Architecture Decision Model

Introduce Trust Boundary Map

Fachliche Einordnung

Architekturdiagramme zeigen Zonen, erzwingen sie aber nicht. Eine Trust Boundary Map macht erlaubte Übergänge und erforderliche Kontrollen maschinenlesbar und kann Architekturtests, Deployment-Prüfungen und Security Reviews unterstützen.

Erkennungsmerkmale

  • Netzwerk- und Modulgrenzen existieren nur in Folien
  • neue Integrationen umgehen Gateway oder Authentisierung
  • erforderliche Kontrollen sind nicht versioniert
  • Security Reviews erkennen Drift erst spät

Sicherer Refactoring-Ablauf

  1. reale Zonen und Datenflüsse inventarisieren
  2. Transition mit erforderlichen Controls modellieren
  3. Default-Deny für unbekannte Übergänge festlegen
  4. Map in Architektur- und Deploymenttests einbinden
  5. Ausnahmen als dokumentierte Entscheidungen behandeln

Wann sinnvoll - und wann nicht?

Sinnvoll bei verteilten Systemen, Zero-Trust- und Plattformarchitekturen. Für eine kleine In-Process-Anwendung genügt meist eine Modulabhängigkeitsregel.

Risiken und typische Fehlanwendungen

Das Modell darf nicht von der realen Infrastruktur abweichen. Ein dokumentierter Übergang ohne technisch durchgesetzte Kontrolle erzeugt falsche Sicherheit.

Enterprise-Java-Beispiel

Ein Zugriff von PUBLIC nach DATA ist unzulässig; PUBLIC nach EDGE verlangt TLS und Rate Limit, EDGE nach APPLICATION zusätzlich starke Identität.

Abgrenzung und verwandte Muster

Architecture Decision Model, Zero Trust, Policy as Code, Fitness Function.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.*;
// Refactoring: Introduce Trust Boundary Map
// Pattern: Architecture Decision Model - Zonenübergänge werden explizit und prüfbar.
public final class TrustBoundaryMap {
  public enum Zone { PUBLIC, EDGE, APPLICATION, DATA, SECURITY }
  public record Transition(Zone from, Zone to, Set<String> controls){ public Transition { controls=Set.copyOf(controls); } }
  private final Set<Transition> allowed;
  public TrustBoundaryMap(Collection<Transition> allowed){ this.allowed=Set.copyOf(allowed); }
  public boolean permits(Zone from, Zone to, String control){ return allowed.stream().anyMatch(t->t.from()==from && t.to()==to && t.controls().contains(control)); }
}
Teststrategie: Architekturtests prüfen erlaubte und verbotene Übergänge sowie Pflichtkontrollen. Deploymenttests vergleichen deklarierte Routen mit der Map.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class TrustBoundaryMapTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new TrustBoundaryMap(java.util.List.of(new TrustBoundaryMap.Transition(TrustBoundaryMap.Zone.EDGE,TrustBoundaryMap.Zone.APPLICATION,java.util.Set.of("mTLS")))).permits(TrustBoundaryMap.Zone.EDGE,TrustBoundaryMap.Zone.APPLICATION,"mTLS")); } }

Deutschsprachige Fachgrundlagen

Kapitel 28 Policy Enforcement und Security Governance10 Refactorings

Refactorings 271–280 machen Policy Enforcement, ABAC, Consent Evidence, Zweckbindung, DLP, Security Exceptions, Compliance-Regeln, Legal Holds, kryptografische Agilität und Security Posture explizit.

Refactoring-Katalog · Abschnitt 13

Security Governance und Compliance-Grenzen

Refactorings 271–280 machen Policy Enforcement, ABAC, Consent Evidence, Zweckbindung, DLP, Security Exceptions, Compliance-Regeln, Legal Holds, kryptografische Agilität und Security Posture explizit.

280 von 300 Refactorings abgeschlossen

20 Refactorings offen.

271. Extract Policy Enforcement Point Policy / Facade

Extract Policy Enforcement Point

Fachliche Einordnung

Verteilte Prüfungen in Controllern, Services und Repositorys führen zu Lücken und inkonsistenten Reason Codes. Ein Policy Enforcement Point bildet eine klare Grenze, an der jeder geschützte Use Case eine typisierte Anfrage bewertet und die Entscheidung tatsächlich erzwingt.

Erkennungsmerkmale

  • gleiche Aktion wird an verschiedenen Stellen unterschiedlich geprüft
  • einzelne interne Aufrufe umgehen Controller-Security
  • Deny-Gründe und Audit unterscheiden sich je Modul
  • Tests prüfen Policy, aber nicht deren Durchsetzung

Sicherer Refactoring-Ablauf

  1. geschützte Eintrittspunkte und Aktionen inventarisieren
  2. Request/Decision-Vertrag definieren
  3. PEP vor dem Use Case platzieren
  4. Policy-Auswertung über Interface anbinden
  5. Bypass-Pfade entfernen und Enforcement-Tests ergänzen

Wann sinnvoll - und wann nicht?

Sinnvoll bei mehreren Use Cases und zentraler Policy-Entscheidung. Nicht als globaler God Service für fachlich unterschiedliche Sicherheitsdomänen.

Risiken und typische Fehlanwendungen

Ein PEP darf keine fachlichen Objektprüfungen verlieren. Caching und Fehlerbehandlung müssen fail closed sein; technische Ausfälle dürfen nicht zu allow führen.

Enterprise-Java-Beispiel

Jeder Claims-Use-Case ruft authorize(principal, action, claimId) auf; ein Deny beendet den Ablauf vor Repositoryzugriff.

Abgrenzung und verwandte Muster

Policy Enforcement Point, Policy Decision Point, Facade, Default Deny.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Objects;
// Refactoring: Extract Policy Enforcement Point
// Pattern: Policy / Facade - Autorisierungsentscheidungen werden an einer zentralen Grenze erzwungen.
public final class PolicyEnforcementPoint {
  public interface Policy { Decision evaluate(Request request); }
  public record Request(String principalId, String action, String resourceId) {
    public Request { if(principalId.isBlank() || action.isBlank() || resourceId.isBlank()) throw new IllegalArgumentException(); }
  }
  public record Decision(boolean allowed, String reason) {}
  private final Policy policy;
  public PolicyEnforcementPoint(Policy policy){ this.policy=Objects.requireNonNull(policy); }
  public Decision authorize(Request request){ return policy.evaluate(request); }
}
Teststrategie: Integrationstests rufen Use Cases auch ohne Controller auf und erwarten Deny. Fehler- und Timeouttests sichern Fail-Closed-Verhalten.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class PolicyEnforcementPointTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new PolicyEnforcementPoint(r -> new PolicyEnforcementPoint.Decision(true,"ok")).authorize(new PolicyEnforcementPoint.Request("u","read","r")).allowed()); } }

Deutschsprachige Fachgrundlagen

272. Introduce Attribute-Based Access Control Specification / Decision Object

Introduce Attribute-Based Access Control

Fachliche Einordnung

Immer feinere Rollen können Ressourceneigenschaften, Mandant, Netzwerkvertrauen oder Risiko nicht sauber ausdrücken. ABAC bewertet explizite Subject-, Resource- und Environment-Attribute und liefert eine begründete Entscheidung.

Erkennungsmerkmale

  • Rollenanzahl wächst für jede Kombination aus Region, Abteilung und Kontext
  • Policy enthält verschachtelte Rollen-Sonderfälle
  • Ressourcenattribute werden erst nach der Entscheidung geprüft
  • Umgebungskontext ist implizit in Infrastrukturcode versteckt

Sicherer Refactoring-Ablauf

  1. relevante Attribute und vertrauenswürdige Quellen bestimmen
  2. Context unveränderlich und vollständig modellieren
  3. Regeln als kombinierbare Specifications implementieren
  4. fehlende Attribute restriktiv behandeln
  5. Policy-Version, Evidenz und Performance messen

Wann sinnvoll - und wann nicht?

Sinnvoll bei dynamischen, kontextabhängigen Entscheidungen. Für wenige stabile Berechtigungen bleibt RBAC oft verständlicher.

Risiken und typische Fehlanwendungen

Unkontrollierte Attribute erzeugen schwer erklärbare Policies. Herkunft, Aktualität und Manipulationsschutz jedes Attributs müssen bekannt sein.

Enterprise-Java-Beispiel

Ein Sachbearbeiter darf einen Schaden nur im eigenen Tenant und aus einem vertrauenswürdigen Netz bearbeiten; beide Bedingungen sind explizite Attribute.

Abgrenzung und verwandte Muster

ABAC, Specification, Decision Object, Policy Information Point.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Map;
// Refactoring: Introduce Attribute-Based Access Control
// Pattern: Specification / Decision Object - Zugriff wird aus typisierten Attributen entschieden.
public final class AttributeBasedAccessControl {
  public record Context(Map<String,String> subject, Map<String,String> resource, Map<String,String> environment) {
    public Context { subject=Map.copyOf(subject); resource=Map.copyOf(resource); environment=Map.copyOf(environment); }
  }
  public record Decision(boolean allowed, String reason) {}
  public Decision evaluate(Context c){
    boolean sameTenant = c.subject().getOrDefault("tenant","").equals(c.resource().getOrDefault("tenant","!"));
    boolean trusted = "trusted".equals(c.environment().get("network"));
    return new Decision(sameTenant && trusted, sameTenant && trusted ? "attributes-match" : "attribute-denied");
  }
}
Teststrategie: Tabellentests kombinieren Tenant, Ressource, Netzwerk und fehlende Attribute. Mutationstests prüfen, dass jede Bedingung wirklich wirksam ist.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class AttributeBasedAccessControlTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new AttributeBasedAccessControl().evaluate(new AttributeBasedAccessControl.Context(java.util.Map.of("tenant","a"),java.util.Map.of("tenant","a"),java.util.Map.of("network","trusted"))).allowed()); } }

Deutschsprachige Fachgrundlagen

273. Extract Consent Evidence Store Repository / Audit Log

Extract Consent Evidence Store

Fachliche Einordnung

Ein Boolean kann nicht belegen, welcher Text, Zweck, Kanal und Zeitpunkt einer Einwilligung zugrunde lagen. Ein append-only Consent Evidence Store bewahrt unveränderliche Evidenz und ermöglicht Widerruf sowie historische Rekonstruktion.

Erkennungsmerkmale

  • consent=true überschreibt frühere Zustände
  • Policy-Version oder Quelle fehlen
  • Widerruf löscht den ursprünglichen Nachweis
  • mehrere Systeme führen widersprüchliche Consent-Stände

Sicherer Refactoring-Ablauf

  1. Evidenzfelder und Zweckschlüssel definieren
  2. append-only Repository-Vertrag extrahieren
  3. aktiven Status aus Historie ableiten
  4. Integrität, Zeitquelle und Aufbewahrung regeln
  5. Adapter für bestehende Consent-Daten migrieren

Wann sinnvoll - und wann nicht?

Sinnvoll bei rechtlich oder regulatorisch relevanten Einwilligungen. Für rein technische, nicht personenbezogene Featurepräferenzen ist ein einfacherer Preference Store ausreichend.

Risiken und typische Fehlanwendungen

Evidenz muss minimiert, unveränderlich und zugriffsgeschützt sein. Ein Store ersetzt keine Prüfung, ob die Einwilligung fachlich wirksam und freiwillig war.

Enterprise-Java-Beispiel

Marketing-Einwilligung speichert Zweck, Textversion, Quelle und Zeitpunkt; der spätere Widerruf wird als neues Ereignis angehängt.

Abgrenzung und verwandte Muster

Audit Log, Event Log, Repository, Purpose Limitation.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
import java.util.List;
// Refactoring: Extract Consent Evidence Store
// Pattern: Repository / Audit Log - Einwilligung wird als unveränderliche Evidenz gespeichert.
public interface ConsentEvidenceStore {
  record Evidence(String subjectId, String purpose, String policyVersion, boolean granted, Instant recordedAt, String source) {}
  void append(Evidence evidence);
  List<Evidence> history(String subjectId, String purpose);
}
Teststrategie: Tests prüfen unveränderliche Historie, Reihenfolge, Widerruf und getrennte Zwecke. Integritätstests erkennen nachträgliche Mutation.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ConsentEvidenceStoreTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(((ConsentEvidenceStore)(new ConsentEvidenceStore(){ public void append(ConsentEvidenceStore.Evidence e){} public java.util.List<ConsentEvidenceStore.Evidence> history(String s,String p){return java.util.List.of();}})).history("u","analytics").isEmpty()); } }

Deutschsprachige Fachgrundlagen

274. Introduce Data Access Purpose Value Object / Policy

Introduce Data Access Purpose

Fachliche Einordnung

Technische Operationen wie READ oder EXPORT erklären nicht, warum personenbezogene oder sensible Daten benötigt werden. Ein Data Access Purpose macht den fachlichen Zweck zu einem validierten Wert und koppelt ihn an erlaubte Datenklassen.

Erkennungsmerkmale

  • Datenzugriffe werden nur mit CRUD-Operation protokolliert
  • gleicher Endpunkt dient vielen fachlichen Zwecken
  • Exportpfade umgehen Zweckprüfung
  • Purpose wird als freier Kommentar übergeben

Sicherer Refactoring-Ablauf

  1. stabile fachliche Zwecke katalogisieren
  2. Purpose und erlaubte Datenklassen als Value Object modellieren
  3. Zweck am Use-Case-Eingang verpflichtend machen
  4. Policy vor Datenauflösung prüfen
  5. Audit und Reporting auf Purpose Codes ausrichten

Wann sinnvoll - und wann nicht?

Sinnvoll bei Datenschutz, Datenplattformen und bereichsübergreifenden APIs. Nicht als Ersatz für Berechtigung, Rechtsgrundlage oder Datenminimierung.

Risiken und typische Fehlanwendungen

Zu breite Zwecke wie business oder support sind wertlos. Zweckcodes müssen governancefähig, versioniert und gegen missbräuchliche Auswahl geschützt sein.

Enterprise-Java-Beispiel

Ein Support-Use-Case darf Kontaktdaten für INCIDENT_SUPPORT lesen, aber keine Gesundheitsdaten oder Marketingprofile.

Abgrenzung und verwandte Muster

Value Object, Purpose Limitation, Authorization Policy, Data Classification.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Set;
// Refactoring: Introduce Data Access Purpose
// Pattern: Value Object / Policy - Zweckbindung wird als fachlicher Wert prüfbar.
public record DataAccessPurpose(String code, Set<String> allowedDataClasses) {
  public DataAccessPurpose {
    if(code==null || code.isBlank()) throw new IllegalArgumentException("code");
    allowedDataClasses=Set.copyOf(allowedDataClasses);
  }
  public boolean permits(String dataClass){ return allowedDataClasses.contains(dataClass); }
}
Teststrategie: Tests prüfen erlaubte und verbotene Datenklassen, unbekannte Zwecke und unveränderliche Mengen. End-to-End-Tests verifizieren Audit-Purpose.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class DataAccessPurposeTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new DataAccessPurpose("support",java.util.Set.of("contact")).permits("contact")); } }

Deutschsprachige Fachgrundlagen

275. Extract Data Loss Prevention Port Port / Adapter

Extract Data Loss Prevention Port

Fachliche Einordnung

DLP-Erkennung ist Infrastruktur und variiert zwischen Providern. Ein Port hält Export- und Upload-Use-Cases frei von SDKs, Regex-Listen und providerinternen Kategorien und liefert typisierte Findings sowie eine fachliche Entscheidung.

Erkennungsmerkmale

  • Controller enthält DLP-SDK-Aufrufe
  • Regex-Listen sind über mehrere Services verteilt
  • Providerstatus wird direkt an Benutzer ausgegeben
  • Tests benötigen Cloud-Zugang oder echte Dateien

Sicherer Refactoring-Ablauf

  1. zu prüfende Content-Metadaten und Größenlimits festlegen
  2. Finding und ScanResult providerneutral modellieren
  3. Port vor Export/Upload einführen
  4. Provideradapter und lokale Testimplementierung bauen
  5. Timeout, Quarantäne und Fail-Closed-Regel definieren

Wann sinnvoll - und wann nicht?

Sinnvoll bei Datei-, Nachrichten- und Datenexporten mit sensiblen Inhalten. Für kleine, rein interne Textvalidierung kann eine lokale Specification genügen.

Risiken und typische Fehlanwendungen

DLP ist probabilistisch und darf nicht als einzige Schutzschicht gelten. Timeouts, große Inhalte, False Positives und Quarantäne benötigen explizite Behandlung.

Enterprise-Java-Beispiel

Vor einem Dokumentexport scannt der Use Case den Inhalt über den Port; kritische Findings blockieren, mittlere führen in manuelle Prüfung.

Abgrenzung und verwandte Muster

Port/Adapter, Content Inspection, Quarantine, Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.List;
// Refactoring: Extract Data Loss Prevention Port
// Pattern: Port / Adapter - DLP-Provider bleiben austauschbare Infrastruktur.
public interface DataLossPreventionPort {
  record Content(String name, String mediaType, byte[] bytes){ public Content { bytes=bytes.clone(); } }
  record Finding(String category, int severity, String location) {}
  record ScanResult(boolean allowed, List<Finding> findings){ public ScanResult { findings=List.copyOf(findings); } }
  ScanResult scan(Content content);
}
Teststrategie: Contract Tests prüfen Allow, Block, Findings, Providerfehler und Timeout. Byte-Arrays werden auf defensive Kopie getestet.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class DataLossPreventionPortTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(((DataLossPreventionPort)c -> new DataLossPreventionPort.ScanResult(true,java.util.List.of())).scan(new DataLossPreventionPort.Content("a","text/plain",new byte[0])).allowed()); } }

Deutschsprachige Fachgrundlagen

276. Introduce Security Exception Workflow Workflow / State Machine

Introduce Security Exception Workflow

Fachliche Einordnung

Sicherheitsausnahmen per E-Mail haben keinen konsistenten Zustand, keine automatische Befristung und kein sauberes Widerrufsmodell. Ein Workflow macht Antrag, Risiko, Genehmigung, Ablauf und Revocation explizit.

Erkennungsmerkmale

  • Ausnahmen besitzen kein Ablaufdatum
  • Genehmigungen sind nicht mit Risiko oder Kontrollkompensation verbunden
  • abgelaufene Ausnahmen bleiben technisch aktiv
  • Widerruf und Verlängerung überschreiben Historie

Sicherer Refactoring-Ablauf

  1. Zustände und Pflichtinformationen definieren
  2. Genehmigung und Dauer begrenzen
  3. Workflowmethoden für approve/reject/revoke/expire kapseln
  4. technische Enforcement-Konfiguration aus Status ableiten
  5. Eskalation und regelmäßige Review einbauen

Wann sinnvoll - und wann nicht?

Sinnvoll für dokumentierte Abweichungen von Security Baselines. Nicht für dauerhafte Architekturentscheidungen, die als Standard neu bewertet werden müssen.

Risiken und typische Fehlanwendungen

Exception-Prozesse dürfen keine Schatten-Policy werden. Unbefristete oder automatisch verlängerte Ausnahmen sind besonders kritisch.

Enterprise-Java-Beispiel

Eine veraltete Schnittstelle erhält für 30 Tage eine genehmigte TLS-Ausnahme mit Kompensationskontrolle und festem Migrationsowner.

Abgrenzung und verwandte Muster

Workflow, State Machine, Risk Acceptance, Expiring Grant.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
// Refactoring: Introduce Security Exception Workflow
// Pattern: Workflow / State Machine - Ausnahmen werden zeitlich begrenzt und nachvollziehbar verwaltet.
public final class SecurityExceptionWorkflow {
  public enum State { REQUESTED, APPROVED, REJECTED, EXPIRED, REVOKED }
  private State state=State.REQUESTED; private Instant validUntil;
  public void approve(Instant until){ if(state!=State.REQUESTED || !until.isAfter(Instant.now())) throw new IllegalStateException(); state=State.APPROVED; validUntil=until; }
  public void reject(){ if(state!=State.REQUESTED) throw new IllegalStateException(); state=State.REJECTED; }
  public void revoke(){ if(state!=State.APPROVED) throw new IllegalStateException(); state=State.REVOKED; }
  public boolean active(Instant now){ if(state==State.APPROVED && !now.isBefore(validUntil)) state=State.EXPIRED; return state==State.APPROVED; }
  public State state(){ return state; }
}
Teststrategie: Zustandstests prüfen Genehmigung, Ablehnung, Ablauf, Widerruf, Verlängerungsverbot und Zeitgrenzen mit kontrollierter Clock.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class SecurityExceptionWorkflowTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new SecurityExceptionWorkflow().state()==SecurityExceptionWorkflow.State.REQUESTED); } }

Deutschsprachige Fachgrundlagen

277. Extract Compliance Rule Specification / Composite

Extract Compliance Rule

Fachliche Einordnung

Lange if-Ketten vermischen mehrere regulatorische Anforderungen und liefern kaum nachvollziehbare Gründe. Einzelne Compliance Rules können kombiniert, separat getestet und mit strukturierten Reasons ausgewertet werden.

Erkennungsmerkmale

  • eine Methode enthält viele unabhängige Regelfälle
  • Änderung einer Regel beeinflusst andere Zweige
  • Ergebnis ist nur boolean ohne Begründung
  • gleiche Regel wird in mehreren Prozessen kopiert

Sicherer Refactoring-Ablauf

  1. Regeln nach fachlicher Aussage trennen
  2. Result mit compliant und Reasons definieren
  3. Komposition wie and/or/not bewusst implementieren
  4. Regelkontext und Version stabilisieren
  5. Entscheidungsevidenz und Tests je Regel ergänzen

Wann sinnvoll - und wann nicht?

Sinnvoll bei vielen unabhängigen, kombinierbaren Prüfungen. Für einen kleinen linearen Algorithmus wäre ein Specification-Framework unnötig.

Risiken und typische Fehlanwendungen

Zu feingranulare Regeln können Kontext verlieren. Reason Codes müssen stabil bleiben; Komposition darf widersprüchliche oder doppelte Gründe nicht verschleiern.

Enterprise-Java-Beispiel

Eine Aufbewahrungsfreigabe kombiniert Region, Datenklasse und Legal-Hold-Regel und liefert alle verletzten Anforderungen.

Abgrenzung und verwandte Muster

Specification, Composite, Decision Object, Rules Engine.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.List;
// Refactoring: Extract Compliance Rule
// Pattern: Specification / Composite - Compliance-Regeln werden kombinierbar und erklärbar.
@FunctionalInterface
public interface ComplianceRule<T> {
  record Result(boolean compliant, List<String> reasons){ public Result { reasons=List.copyOf(reasons); } }
  Result evaluate(T candidate);
  default ComplianceRule<T> and(ComplianceRule<T> other){
    return value -> { var a=evaluate(value); var b=other.evaluate(value); var reasons=new java.util.ArrayList<String>(); reasons.addAll(a.reasons()); reasons.addAll(b.reasons()); return new Result(a.compliant() && b.compliant(), reasons); };
  }
}
Teststrategie: Unit Tests je Regel plus Kombinationstests für and/or, leere Reasons und Reihenfolge. Mutationstests erhöhen Aussagekraft.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ComplianceRuleTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(((ComplianceRule<String>)v -> new ComplianceRule.Result(!v.isBlank(),java.util.List.of())).evaluate("ok").compliant()); } }

Deutschsprachige Fachgrundlagen

278. Introduce Legal Hold Policy / Aggregate

Introduce Legal Hold

Fachliche Einordnung

Normale Retention darf Daten nicht löschen, die wegen Untersuchung oder Rechtsstreit auf Legal Hold stehen. Ein eigenes Aggregat hält Fall, betroffene Ressourcen, Aktivstatus und Freigabe zusammen und übersteuert Löschentscheidungen.

Erkennungsmerkmale

  • Retention-Jobs kennen laufende Verfahren nicht
  • Sperrlisten liegen als manuelle Tabellen vor
  • Freigabe löscht oder überschreibt die Historie
  • Teilressourcen eines Falls werden übersehen

Sicherer Refactoring-Ablauf

  1. Scope und Identität eines Holds definieren
  2. Hold als unveränderliche Zuordnung plus kontrollierte Release-Aktion modellieren
  3. Deletion Policy vor jeder physischen Löschung abfragen
  4. Änderungen auditieren und gegen unberechtigte Freigabe schützen
  5. Migration bestehender Sperrlisten verifizieren

Wann sinnvoll - und wann nicht?

Sinnvoll in dokumenten-, revisions- und rechtsrelevanten Systemen. Nicht für normale fachliche Sperren ohne rechtlichen Charakter.

Risiken und typische Fehlanwendungen

Zu breite Holds blockieren unnötig große Datenmengen; zu enge verlieren Beweise. Release benötigt starke Autorisierung und unveränderliches Audit.

Enterprise-Java-Beispiel

Alle Dokumente eines Streitfalls werden vor dem Retention-Job geprüft und bleiben bis zur autorisierten Freigabe unverändert erhalten.

Abgrenzung und verwandte Muster

Aggregate, Retention Policy, Legal Hold, Audit Log.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
import java.util.Set;
// Refactoring: Introduce Legal Hold
// Pattern: Policy / Aggregate - rechtliche Sperren übersteuern normale Retention.
public final class LegalHold {
  private final String caseId; private final Set<String> resourceIds; private final Instant createdAt; private boolean released;
  public LegalHold(String caseId, Set<String> resourceIds, Instant createdAt){ if(caseId.isBlank()||resourceIds.isEmpty()) throw new IllegalArgumentException(); this.caseId=caseId; this.resourceIds=Set.copyOf(resourceIds); this.createdAt=createdAt; }
  public boolean blocksDeletion(String resourceId){ return !released && resourceIds.contains(resourceId); }
  public void release(){ released=true; }
  public String caseId(){ return caseId; }
}
Teststrategie: Tests prüfen betroffene/unbetroffene Ressourcen, aktive/freigegebene Holds, doppelte Freigabe und konkurrierende Löschversuche.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class LegalHoldTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new LegalHold("case",java.util.Set.of("r"),java.time.Instant.EPOCH).blocksDeletion("r")); } }

Deutschsprachige Fachgrundlagen

279. Extract Cryptographic Agility Policy Strategy / Policy

Extract Cryptographic Agility Policy

Fachliche Einordnung

Fest verdrahtete Algorithmen und Schlüssellängen erschweren Migration bei neuen Vorgaben oder Schwachstellen. Eine versionierte Cryptographic Agility Policy bewertet zulässige Verfahren, Mindeststärken und Übergangsfristen zentral.

Erkennungsmerkmale

  • Algorithmusnamen und Key Sizes stehen als Konstanten in vielen Services
  • Legacy- und Zielverfahren können nicht parallel bewertet werden
  • Ablauffristen werden manuell verfolgt
  • preferred-Entscheidung ist nicht testbar oder versioniert

Sicherer Refactoring-Ablauf

  1. eingesetzte Verfahren und Datenformate inventarisieren
  2. Algorithmusmetadaten und Fristen modellieren
  3. Policy getrennt von Kryptografieimplementierung einführen
  4. Dual-Read/Dual-Write- oder Re-Encryption-Strategie planen
  5. Telemetrie und Abschaltung alter Verfahren testen

Wann sinnvoll - und wann nicht?

Sinnvoll bei langlebigen Daten, mehreren Services und Compliance-Vorgaben. Für eine einzelne lokale Hashfunktion kann eine kleinere Konfiguration ausreichen.

Risiken und typische Fehlanwendungen

Policy-Auswahl ersetzt keine sichere Implementierung und kein Key Management. Ungeprüfte Algorithmen dürfen nicht allein durch Konfiguration aktivierbar sein.

Enterprise-Java-Beispiel

Ein Tokenservice akzeptiert während einer Übergangsfrist alte Signaturen, signiert neu aber ausschließlich mit dem bevorzugten Verfahren.

Abgrenzung und verwandte Muster

Strategy, Policy, Crypto Agility, Key Rotation.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
import java.util.Map;
// Refactoring: Extract Cryptographic Agility Policy
// Pattern: Strategy / Policy - Kryptografie kann ohne Fachcodeänderung migriert werden.
public final class CryptographicAgilityPolicy {
  public record Algorithm(String name, int minimumKeyBits, Instant allowedUntil) {}
  private final Map<String,Algorithm> algorithms;
  public CryptographicAgilityPolicy(Map<String,Algorithm> algorithms){ this.algorithms=Map.copyOf(algorithms); }
  public boolean allowed(String name, int keyBits, Instant now){ var a=algorithms.get(name); return a!=null && keyBits>=a.minimumKeyBits() && now.isBefore(a.allowedUntil()); }
  public String preferred(){ return algorithms.values().stream().max(java.util.Comparator.comparingInt(Algorithm::minimumKeyBits)).orElseThrow().name(); }
}
Teststrategie: Zeitgrenzentests prüfen Mindeststärke, Ablauf, unbekannte Verfahren und preferred. Integrationsprüfungen sichern Migration alter Daten.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class CryptographicAgilityPolicyTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new CryptographicAgilityPolicy(java.util.Map.of("A",new CryptographicAgilityPolicy.Algorithm("A",256,java.time.Instant.MAX))).allowed("A",256,java.time.Instant.EPOCH)); } }

Deutschsprachige Fachgrundlagen

280. Introduce Security Posture Snapshot Immutable Snapshot / Decision Object

Introduce Security Posture Snapshot

Fachliche Einordnung

Wenn eine Entscheidung mehrere Live-Systeme nacheinander abfragt, können Geräte-, Kontroll- und Risikostatus währenddessen wechseln. Ein unveränderlicher Security Posture Snapshot friert die relevante Sicht mit Erfassungszeitpunkt ein und macht Entscheidungen reproduzierbar.

Erkennungsmerkmale

  • Risk Score und Controls werden separat geladen
  • ein Request sieht unterschiedliche Zeitstände
  • Audit kann die damalige Lage nicht rekonstruieren
  • Map-Inhalte werden nach Erzeugung weiter verändert

Sicherer Refactoring-Ablauf

  1. entscheidungsrelevante Kontrollen und Aktualitätsgrenze bestimmen
  2. Snapshot unveränderlich und defensiv kopiert modellieren
  3. Erfassung in einen koordinierten Application Service verlagern
  4. Staleness und fehlende Signale explizit bewerten
  5. Snapshot-ID oder Hash in Decision Evidence referenzieren

Wann sinnvoll - und wann nicht?

Sinnvoll bei Zero-Trust-, Geräte- und Risikoprüfungen mit mehreren Quellen. Für eine einzelne atomare Datenbankabfrage ist ein Snapshot oft nicht nötig.

Risiken und typische Fehlanwendungen

Ein Snapshot kann bereits beim Erzeugen veraltet sein. capturedAt, maximale Gültigkeit und unvollständige Controls müssen Teil der Policy sein.

Enterprise-Java-Beispiel

Vor einer privilegierten Aktion werden Gerätekonformität, MFA, Netzvertrauen und Risikoscore in einem Snapshot gebündelt und gemeinsam bewertet.

Abgrenzung und verwandte Muster

Immutable Snapshot, Decision Object, Zero Trust, Authorization Evidence.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
import java.util.Map;
// Refactoring: Introduce Security Posture Snapshot
// Pattern: Immutable Snapshot / Decision Object - Sicherheitsentscheidungen nutzen einen konsistenten Zustand.
public record SecurityPostureSnapshot(String subjectId, Instant capturedAt, Map<String,Boolean> controls, int riskScore) {
  public SecurityPostureSnapshot {
    if(subjectId.isBlank() || riskScore<0 || riskScore>100) throw new IllegalArgumentException();
    controls=Map.copyOf(controls);
  }
  public boolean trusted(){ return riskScore<30 && controls.values().stream().allMatch(Boolean::booleanValue); }
}
Teststrategie: Tests prüfen defensive Map-Kopie, Scoregrenzen, fehlende/false Controls und Staleness in der aufrufenden Policy.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class SecurityPostureSnapshotTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new SecurityPostureSnapshot("u",java.time.Instant.EPOCH,java.util.Map.of("mfa",true),10).trusted()); } }

Deutschsprachige Fachgrundlagen

Kapitel 29 Audit, Evidenz und regulatorische Abläufe10 Refactorings

Refactorings 281–290 machen Audit-Trails, Evidenz, Data Lineage, Retention, Erasure, Breach Notification, Fristen, Kontrollwirksamkeit und Compliance-Snapshots explizit.

Refactoring-Katalog · Abschnitt 14

Audit, Evidenz und regulatorische Abläufe

Refactorings 281–290 machen Audit-Trails, Evidenz, Data Lineage, Retention, Erasure, Breach Notification, Fristen, Kontrollwirksamkeit und Compliance-Snapshots explizit.

290 von 300 Refactorings abgeschlossen

10 Refactorings offen.

281. Extract Audit Trail Port Port / Adapter

Extract Audit Trail Port

Fachliche Einordnung

Auditdaten sind fachliche Nachweise und nicht bloß technische Logzeilen. Werden Domänenservices direkt an Logger, SIEM-Clients oder Hersteller-SDKs gekoppelt, vermischen sich Fachentscheidung, Transportformat und Betriebsinfrastruktur. Ein Audit Trail Port hält den fachlichen Vertrag stabil und erlaubt manipulationsgeschützte, nachvollziehbare Ablage über austauschbare Adapter.

Erkennungsmerkmale

  • Domänencode importiert Logging- oder SIEM-Bibliotheken
  • Ereignisse bestehen aus frei formatierten Strings
  • Zeitpunkt, Akteur oder betroffenes Objekt fehlen
  • Tests müssen reale Infrastruktur nachbilden

Sicherer Refactoring-Ablauf

  1. auditrelevante Fachereignisse und Pflichtattribute bestimmen
  2. unveränderlichen Ereignisvertrag mit Zeit, Akteur, Subjekt und Typ definieren
  3. Port im Anwendungskern platzieren und technische Adapter nach außen verschieben
  4. Fehlerstrategie für nicht verfügbare Audit-Infrastruktur festlegen
  5. Integrität, Reihenfolge und Aufbewahrung unabhängig testen

Wann sinnvoll - und wann nicht?

Sinnvoll bei regulatorisch oder sicherheitsrelevanten Aktionen, privilegierten Zugriffen und fachlichen Zustandswechseln. Für reine Debug-Ausgaben ohne Nachweiswert ist ein eigener Audit-Port unnötig.

Risiken und typische Fehlanwendungen

Ein Port allein garantiert weder Unveränderbarkeit noch Vollständigkeit. Ereignisschema, Zeitsynchronisation, Mandantenbezug, Datenschutz und fehlgeschlagene Übertragung müssen verbindlich geregelt werden.

Enterprise-Java-Beispiel

Ein Schadenservice schreibt die Freigabe einer Hochrisikozahlung als typisiertes AuditEvent. Ein Adapter speichert den Datensatz revisionssicher und liefert ihn parallel an das SIEM, ohne dass der Fachservice Transportdetails kennt.

Abgrenzung und verwandte Muster

Ports and Adapters, Domain Event, Append-only Log, Outbox, Audit Evidence.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
import java.util.Map;
// Refactoring: Extract Audit Trail Port
// Pattern: Port / Adapter - Audit-Ziele bleiben austauschbare Infrastruktur.
public interface AuditTrailPort {
  record AuditEvent(String type, String actor, String subject, Instant occurredAt, Map<String,String> attributes) {
    public AuditEvent { if(type.isBlank() || actor.isBlank()) throw new IllegalArgumentException(); attributes=Map.copyOf(attributes); }
  }
  void append(AuditEvent event);
}
Teststrategie: Tests prüfen Pflichtfelder, defensive Kopien, Reihenfolge, Adapterfehler und dass kein technischer Logger Teil des fachlichen Vertrags ist.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class AuditTrailPortTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(((AuditTrailPort)e -> {}).getClass()!=null); } }

Deutschsprachige Fachgrundlagen

282. Introduce Evidence Bundle Value Object / Composite

Introduce Evidence Bundle

Fachliche Einordnung

Prüfnachweise verlieren ihren Wert, wenn Dateien, Prüfsummen, Herkunft und fachlicher Bezug getrennt abgelegt werden. Ein Evidence Bundle bündelt die zusammengehörigen Artefakte als unveränderliche Einheit und macht sichtbar, welche Belege eine konkrete Entscheidung tragen.

Erkennungsmerkmale

  • Nachweise liegen in verschiedenen Speichern ohne gemeinsame Identität
  • Dateinamen dienen als einzige Zuordnung
  • Prüfsummen oder Erfassungszeitpunkte fehlen
  • ein Bericht kann seine Quellen nicht vollständig benennen

Sicherer Refactoring-Ablauf

  1. fachlichen Evidenzgegenstand und erforderliche Artefakttypen festlegen
  2. Metadaten, Herkunft und kryptografische Prüfsumme typisieren
  3. Bundle unveränderlich und vollständig validierbar modellieren
  4. große Binärdaten über Referenzen statt im Aggregat führen
  5. Erzeugung, Signatur und spätere Verifikation getrennt testen

Wann sinnvoll - und wann nicht?

Sinnvoll für Audits, Freigaben, Kontrollnachweise und Untersuchungen mit mehreren zusammengehörigen Belegen. Für ein einzelnes kurzlebiges Dokument ohne Nachweischarakter reicht eine einfache Dateiablage.

Risiken und typische Fehlanwendungen

Ein Bundle kann personenbezogene oder vertrauliche Daten bündeln und dadurch den Schutzbedarf erhöhen. Zugriff, Aufbewahrung, Löschung und Integritätsprüfung müssen deshalb gemeinsam entworfen werden.

Enterprise-Java-Beispiel

Für die Genehmigung eines Sonderzugriffs enthält das Bundle Antrag, Vier-Augen-Freigabe, Policy-Entscheidung, Systemzustand und Prüfsummen. Der Auditbericht referenziert genau diese unveränderliche Version.

Abgrenzung und verwandte Muster

Value Object, Composite, Content Addressing, Digital Signature, Chain of Custody.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.List;
// Refactoring: Introduce Evidence Bundle
// Pattern: Value Object / Composite - zusammengehörige Evidenz wird atomar behandelt.
public record EvidenceBundle(String controlId, String checksum, List<EvidenceItem> items) {
  public record EvidenceItem(String name, String source, String mediaType) {}
  public EvidenceBundle { if(controlId.isBlank() || checksum.isBlank() || items.isEmpty()) throw new IllegalArgumentException(); items=List.copyOf(items); }
  public int size(){ return items.size(); }
}
Teststrategie: Tests prüfen Vollständigkeit, stabile Reihenfolge, unveränderliche Metadaten, Prüfsummenabweichungen und den Umgang mit fehlenden Artefakten.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class EvidenceBundleTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new EvidenceBundle("c","abc",java.util.List.of(new EvidenceBundle.EvidenceItem("n","s","text/plain"))).size()==1); } }

Deutschsprachige Fachgrundlagen

283. Extract Compliance Reporting Adapter Adapter / Mapper

Extract Compliance Reporting Adapter

Fachliche Einordnung

Regulatorische Berichte ändern Format, Übertragungsweg und Empfängerspezifikation häufiger als die Fachlogik. Erzeugen Fachservices CSV-Spalten, XML-Strukturen oder API-Payloads selbst, wird jede Formatänderung zu einer Domänenänderung. Ein Reporting Adapter trennt fachlichen Bericht von äußerer Repräsentation.

Erkennungsmerkmale

  • Domänenservices kennen CSV-, XML- oder Behördenfelder
  • Mappingregeln sind über mehrere Services verteilt
  • Transportfehler verändern fachliche Transaktionen
  • Formatversionen lassen sich nicht parallel betreiben

Sicherer Refactoring-Ablauf

  1. stabilen fachlichen Reporting-Port und Berichtstyp definieren
  2. externes Schema in einem expliziten Mapper isolieren
  3. Transport, Authentisierung und Wiederholung im Adapter kapseln
  4. Formatversionen über Vertragstests absichern
  5. fachliche Erzeugung und technische Zustellung getrennt beobachten

Wann sinnvoll - und wann nicht?

Sinnvoll bei Behördenmeldungen, Prüfberichten und externen Compliance-Schnittstellen. Für rein interne, stabile Projektionen kann ein eigener Adapter überdimensioniert sein.

Risiken und typische Fehlanwendungen

Der Adapter darf keine fachlichen Entscheidungen erfinden. Mappingverluste, Rundung, Zeitzonen, Zeichensätze und Versionierung sind besonders kritisch und brauchen nachvollziehbare Fehlerberichte.

Enterprise-Java-Beispiel

Der Kern erzeugt einen typisierten Geldwäschebericht. Ein Adapter mappt ihn wahlweise auf CSV-Version 2 oder eine REST-Schnittstelle und protokolliert die technische Zustellung separat.

Abgrenzung und verwandte Muster

Adapter, Anti-Corruption Layer, Mapper, Contract Test, Versioned Schema.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.LocalDate;
// Refactoring: Extract Compliance Reporting Adapter
// Pattern: Adapter / Mapper - regulatorische Formate bleiben außerhalb des Fachmodells.
public interface ComplianceReportingAdapter {
  record Report(String regulation, LocalDate periodEnd, String payload) {}
  String submit(Report report);
}
Teststrategie: Tests prüfen vollständiges Mapping, Pflichtfelder, Formatversionen, Rundung, Zeitzonen sowie Wiederholung ohne doppelte fachliche Meldung.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ComplianceReportingAdapterTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(((ComplianceReportingAdapter)r -> "accepted").submit(new ComplianceReportingAdapter.Report("R",java.time.LocalDate.now(),"{}" )).equals("accepted")); } }

Deutschsprachige Fachgrundlagen

284. Introduce Data Lineage Graph / Value Object

Introduce Data Lineage

Fachliche Einordnung

Datenherkunft ist eine fachliche Eigenschaft, sobald Entscheidungen, Berichte oder Modelle auf transformierten Daten beruhen. Ohne Lineage lässt sich weder erklären, wo ein Wert entstand, noch welche Verarbeitung ihn verändert hat. Eine typisierte Lineage-Struktur macht Quelle und Transformationskette explizit.

Erkennungsmerkmale

  • Berichte enthalten Werte ohne nachvollziehbare Quelle
  • Transformationen sind nur in ETL-Skripten dokumentiert
  • Verantwortliche Systeme sind nicht eindeutig
  • Fehler lassen sich nicht bis zum Ursprung zurückverfolgen

Sicherer Refactoring-Ablauf

  1. Quelle, Transformation, Zeitpunkt und Verantwortlichkeit als Knoten/Kanten modellieren
  2. stabile Identitäten für Datensätze und Verarbeitungsschritte vergeben
  3. Lineage beim Übergang zwischen Systemgrenzen fortschreiben
  4. unveränderliche Historie statt überschreibbarer Metadaten verwenden
  5. Vollständigkeit und Zyklen automatisiert prüfen

Wann sinnvoll - und wann nicht?

Sinnvoll bei Reporting, ML, regulatorischen Datenflüssen und komplexen Integrationen. Für lokale, kurzlebige Berechnungen ohne Weitergabe kann vollständige Lineage zu teuer sein.

Risiken und typische Fehlanwendungen

Lineage darf nicht zur unkontrollierten Kopie sensibler Nutzdaten werden. Metadatenumfang, Aufbewahrung, Pseudonymisierung und Performance müssen bewusst begrenzt werden.

Enterprise-Java-Beispiel

Eine Risikokennzahl referenziert Quellsystem, Rohdatensatz, Normalisierung und Aggregation. Bei einer Abweichung kann der Prüfer den Wert bis zur ursprünglichen Buchung zurückverfolgen.

Abgrenzung und verwandte Muster

Graph, Provenance, Event Sourcing, Data Product, Audit Trail.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
import java.util.List;
// Refactoring: Introduce Data Lineage
// Pattern: Graph / Value Object - Datenherkunft wird explizit und nachvollziehbar.
public record DataLineage(String datasetId, List<Step> steps) {
  public record Step(String operation, String sourceId, Instant at) {}
  public DataLineage { if(datasetId.isBlank()) throw new IllegalArgumentException(); steps=List.copyOf(steps); }
  public DataLineage append(Step step){ var copy=new java.util.ArrayList<>(steps); copy.add(step); return new DataLineage(datasetId,copy); }
}
Teststrategie: Tests prüfen lückenlose Ketten, stabile IDs, verbotene Zyklen, fehlende Quellen und dass Transformationen die Vorgängerreferenz erhalten.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class DataLineageTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new DataLineage("d",java.util.List.of()).append(new DataLineage.Step("map","s",java.time.Instant.EPOCH)).steps().size()==1); } }

Deutschsprachige Fachgrundlagen

285. Extract Retention Scheduler Policy / Scheduler Port

Extract Retention Scheduler

Fachliche Einordnung

Aufbewahrungsregeln sind fachliche Policies, Cron-Ausführung ist Infrastruktur. Werden Fristberechnung, Kalenderlogik, Auswahl und Löschung in einem Scheduler vermischt, sind Regeln kaum testbar und Betriebsfehler gefährlich. Die Trennung macht die Entscheidung deterministisch und die Ausführung steuerbar.

Erkennungsmerkmale

  • Cron-Code enthält Fristen und Ausnahmen als Konstanten
  • Zeitzonen und Geschäftskalender sind implizit
  • ein Job löscht direkt ohne Vorschau oder Freigabe
  • Tests hängen von Systemzeit und Scheduler ab

Sicherer Refactoring-Ablauf

  1. Retention-Policy mit Clock und expliziten Fristregeln extrahieren
  2. Kandidatenentscheidung von physischer Löschung trennen
  3. Scheduler nur als auslösenden Port modellieren
  4. Dry-Run, Sperren und Legal Holds vor Ausführung prüfen
  5. Ergebnis, Umfang und Fehler pro Lauf auditieren

Wann sinnvoll - und wann nicht?

Sinnvoll bei rechtlichen Aufbewahrungs- und Löschpflichten sowie großen Datenbeständen. Für rein technische Cache-TTLs genügt oft eine einfache Ablaufkonfiguration.

Risiken und typische Fehlanwendungen

Falsche Zeitzone, fehlende Sperren oder unvollständige Replikatkenntnis können unzulässige Löschung verursachen. Entscheidungen müssen reproduzierbar und vorab sichtbar sein.

Enterprise-Java-Beispiel

Die Policy bestimmt zum Stichtag alle löschbaren Schadenakten. Der Scheduler startet einen kontrollierten Lauf, der Legal Holds prüft, zuerst simuliert und anschließend batchweise löscht.

Abgrenzung und verwandte Muster

Policy, Scheduler Port, Clock, Dry Run, Retention Aggregate.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
import java.util.List;
// Refactoring: Extract Retention Scheduler
// Pattern: Policy / Scheduler Port - fachliche Fälligkeit bleibt von Jobtechnik getrennt.
public final class RetentionScheduler {
  public interface Policy { boolean due(String resourceId, Instant now); }
  public interface DeletionPort { void delete(String resourceId); }
  private final Policy policy; private final DeletionPort deletion;
  public RetentionScheduler(Policy policy, DeletionPort deletion){ this.policy=policy; this.deletion=deletion; }
  public long run(List<String> ids, Instant now){ return ids.stream().filter(id->policy.due(id,now)).peek(deletion::delete).count(); }
}
Teststrategie: Tests prüfen Stichtage, Zeitzonen, Schaltjahre, Holds, Dry-Run und dass Schedulerfehler keine Policy-Ergebnisse verändern.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class RetentionSchedulerTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new RetentionScheduler((id,n)->true,id->{}).run(java.util.List.of("a"),java.time.Instant.EPOCH)==1); } }

Deutschsprachige Fachgrundlagen

286. Introduce Erasure Workflow Workflow / State Machine

Introduce Erasure Workflow

Fachliche Einordnung

Eine Löschanfrage ist selten ein einzelner Datenbankbefehl. Identitätsprüfung, gesetzliche Ausnahmen, verteilte Datenspeicher, Bestätigung und Nachweis bilden einen langlebigen Geschäftsprozess. Ein expliziter Workflow verhindert unsichtbare Teilerfolge und ermöglicht Wiederaufnahme.

Erkennungsmerkmale

  • Löschung erfolgt synchron über mehrere Systeme
  • Teilfehler bleiben ohne Status zurück
  • Ausnahmen und Sperren sind verstreute if-Abfragen
  • Betroffene erhalten keine belastbare Abschlussbestätigung

Sicherer Refactoring-Ablauf

  1. Zustände und Übergänge von Eingang bis Abschluss modellieren
  2. Prüfung, Freigabe, Ausführung und Verifikation als getrennte Schritte definieren
  3. idempotente Kommandos und Wiederaufnahme vorsehen
  4. Ausnahmen, Holds und nicht löschbare Daten explizit dokumentieren
  5. Abschlussnachweis und Benachrichtigung an den Workflow koppeln

Wann sinnvoll - und wann nicht?

Sinnvoll für DSGVO-Löschung und verteilte Datenlandschaften. Für rein lokale Testdaten ohne rechtliche Relevanz ist eine State Machine meist unnötig.

Risiken und typische Fehlanwendungen

Ein Workflow kann fälschlich abgeschlossen melden, obwohl Replikate, Backups oder Drittsysteme fehlen. Vollständigkeitskriterien und Verantwortlichkeiten müssen fachlich definiert sein.

Enterprise-Java-Beispiel

Eine Kundenanfrage durchläuft Identitätsprüfung, Hold-Prüfung, Löschung in CRM, Archiv und Data Lake sowie abschließende Verifikation. Jeder Schritt ist wiederholbar und auditierbar.

Abgrenzung und verwandte Muster

Workflow, State Machine, Saga, Idempotent Command, Process Manager.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
// Refactoring: Introduce Erasure Workflow
// Pattern: Workflow / State Machine - Löschanforderungen erhalten kontrollierte Übergänge.
public final class ErasureWorkflow {
  public enum State { REQUESTED, VERIFIED, BLOCKED, EXECUTED, CONFIRMED }
  private State state=State.REQUESTED;
  public void verify(boolean legalHold){ if(state!=State.REQUESTED) throw new IllegalStateException(); state=legalHold?State.BLOCKED:State.VERIFIED; }
  public void execute(){ if(state!=State.VERIFIED) throw new IllegalStateException(); state=State.EXECUTED; }
  public void confirm(){ if(state!=State.EXECUTED) throw new IllegalStateException(); state=State.CONFIRMED; }
  public State state(){ return state; }
}
Teststrategie: Tests prüfen erlaubte Übergänge, Wiederholung, Teilausfälle, Holds, Fristüberschreitung und dass Abschluss erst nach Verifikation möglich ist.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ErasureWorkflowTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new ErasureWorkflow().state()==ErasureWorkflow.State.REQUESTED); } }

Deutschsprachige Fachgrundlagen

287. Extract Breach Notification Policy Policy / Decision Object

Extract Breach Notification Policy

Fachliche Einordnung

Ob ein Vorfall meldepflichtig ist, hängt von Risiko, Datenarten, Betroffenen, Schutzmaßnahmen und Fristen ab. Verstreute Schwellenwerte führen zu widersprüchlichen Entscheidungen. Eine Breach Notification Policy liefert ein erklärbares Ergebnis mit Begründung und Empfängern.

Erkennungsmerkmale

  • Meldepflicht wird in mehreren Tickets oder Tabellen entschieden
  • Fristen sind als Magic Numbers implementiert
  • Risikoargumente werden nicht gespeichert
  • Empfängerlisten hängen von manueller Erinnerung ab

Sicherer Refactoring-Ablauf

  1. Entscheidungskriterien und erforderliche Fakten typisieren
  2. Policy als reine, versionierte Funktion formulieren
  3. Ergebnis mit Begründungen, Frist und Empfängergruppe modellieren
  4. fehlende Informationen als Unsicherheit statt false behandeln
  5. Policy-Version und Evidenz im Incident hinterlegen

Wann sinnvoll - und wann nicht?

Sinnvoll bei Datenschutz- und Sicherheitsvorfällen mit regulatorischer Bewertung. Für interne Betriebsstörungen ohne Personen- oder Meldebezug ist die Policy nicht zuständig.

Risiken und typische Fehlanwendungen

Automatisierung ersetzt keine rechtliche Bewertung. Die Policy muss konservativ mit Unsicherheit umgehen und eine Eskalation an Datenschutz, Recht und Incident Management ermöglichen.

Enterprise-Java-Beispiel

Ein Incident enthält Datenkategorie, Anzahl Betroffener, Verschlüsselungsstatus und Entdeckungszeit. Die Policy entscheidet meldepflichtig, berechnet die Frist und nennt Aufsicht sowie Betroffenenkommunikation.

Abgrenzung und verwandte Muster

Policy, Decision Object, Incident Response, Evidence Bundle, Regulatory Deadline.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Duration;
import java.util.Set;
// Refactoring: Extract Breach Notification Policy
// Pattern: Policy / Decision Object - Meldepflichten werden zentral und erklärbar entschieden.
public final class BreachNotificationPolicy {
  public record Incident(int affectedSubjects, boolean sensitiveData, boolean crossBorder) {}
  public record Decision(boolean reportable, Duration deadline, Set<String> recipients, String reason) {}
  public Decision evaluate(Incident i){ boolean report=i.sensitiveData() || i.affectedSubjects()>1000; return new Decision(report,report?Duration.ofHours(72):Duration.ZERO,report?Set.of("authority"):Set.of(),report?"threshold-met":"not-reportable"); }
}
Teststrategie: Tests prüfen Grenzwerte, fehlende Fakten, verschlüsselte Daten, Policy-Versionen und nachvollziehbare Begründungen statt bloßer Booleans.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class BreachNotificationPolicyTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new BreachNotificationPolicy().evaluate(new BreachNotificationPolicy.Incident(1,true,false)).reportable()); } }

Deutschsprachige Fachgrundlagen

288. Introduce Regulatory Deadline Value Object / Clock Policy

Introduce Regulatory Deadline

Fachliche Einordnung

Regulatorische Fristen sind mehr als eine Zahl von Stunden. Startauslöser, Zeitzone, Hemmung, Geschäftskalender und Überschreitung müssen gemeinsam modelliert werden. Ein Value Object verhindert uneinheitliche Datumsarithmetik und macht Fristentscheidungen reproduzierbar.

Erkennungsmerkmale

  • plusHours-Aufrufe sind über den Code verteilt
  • lokale Zeit und UTC werden vermischt
  • Startzeitpunkt ist fachlich unklar
  • Warn- und Eskalationspunkte fehlen

Sicherer Refactoring-Ablauf

  1. fachlichen Fristbeginn und Zeitbasis festlegen
  2. Dauer, Deadline und Warnschwellen in einem Value Object bündeln
  3. Clock injizieren und lokale Systemzeit vermeiden
  4. Hemmung oder Kalenderregeln explizit modellieren
  5. Berechnung und Darstellung getrennt testen

Wann sinnvoll - und wann nicht?

Sinnvoll bei Melde-, Antwort-, Aufbewahrungs- und Eskalationsfristen. Für rein technische Timeouts ist ein einfaches Duration-Objekt ausreichend.

Risiken und typische Fehlanwendungen

Ein korrektes Value Object kann eine falsch verstandene Rechtsregel nicht reparieren. Regelquelle, Version und Zuständigkeit müssen dokumentiert und überprüfbar sein.

Enterprise-Java-Beispiel

Für einen Datenschutzvorfall wird aus Kenntniszeitpunkt und Policy eine Deadline erzeugt. Das System zeigt verbleibende Zeit, Warnstufe und Überschreitung unabhängig von UI oder Scheduler.

Abgrenzung und verwandte Muster

Value Object, Clock, Temporal Policy, Deadline, Escalation.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Duration;
import java.time.Instant;
// Refactoring: Introduce Regulatory Deadline
// Pattern: Value Object / Clock Policy - Fristlogik wird reproduzierbar und testbar.
public record RegulatoryDeadline(Instant startedAt, Duration allowed) {
  public RegulatoryDeadline { if(allowed.isNegative() || allowed.isZero()) throw new IllegalArgumentException(); }
  public Instant dueAt(){ return startedAt.plus(allowed); }
  public boolean overdue(Instant now){ return now.isAfter(dueAt()); }
}
Teststrategie: Tests prüfen UTC/Lokalzeit, Sommerzeitwechsel, Grenzsekunden, Hemmung, bereits überschrittene Fristen und deterministische Clock-Nutzung.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class RegulatoryDeadlineTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new RegulatoryDeadline(java.time.Instant.EPOCH,java.time.Duration.ofHours(1)).overdue(java.time.Instant.EPOCH.plusSeconds(3601))); } }

Deutschsprachige Fachgrundlagen

289. Extract Control Effectiveness Evaluator Strategy / Scorecard

Extract Control Effectiveness Evaluator

Fachliche Einordnung

Kontrollwirksamkeit darf nicht aus frei interpretierten Prozentwerten entstehen. Messmethode, Evidenz, Gewichtung und Schwellenwert müssen nachvollziehbar sein. Ein Evaluator kapselt die Bewertungsstrategie und liefert neben dem Score eine erklärbare Einstufung.

Erkennungsmerkmale

  • Kontrollbewertungen stammen aus manuellen Tabellen
  • verschiedene Teams verwenden andere Skalen
  • fehlende Evidenz wird als null oder 0 behandelt
  • ein Prozentwert enthält keine Begründung

Sicherer Refactoring-Ablauf

  1. Bewertungsdimensionen und Skalen fachlich definieren
  2. Evidenzanforderungen pro Kontrolle festlegen
  3. Evaluator als austauschbare Strategie extrahieren
  4. Score und qualitative Einstufung gemeinsam zurückgeben
  5. Version, Datenstand und Begründung im Ergebnis speichern

Wann sinnvoll - und wann nicht?

Sinnvoll für interne Kontrollen, Security Controls und Compliance-Reife. Für binäre technische Health Checks wäre eine Scorecard unnötig komplex.

Risiken und typische Fehlanwendungen

Ein mathematisch präziser Score kann Scheingenauigkeit erzeugen. Gewichtungen, Unsicherheit und fehlende Evidenz müssen sichtbar bleiben und regelmäßig fachlich validiert werden.

Enterprise-Java-Beispiel

Der Evaluator bewertet MFA-Abdeckung, Ausnahmerate, Testnachweise und Aktualität. Das Ergebnis lautet teilweise wirksam und enthält die konkreten Defizite statt nur 72 Prozent.

Abgrenzung und verwandte Muster

Strategy, Scorecard, Specification, Evidence Bundle, Quality Gate.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.List;
// Refactoring: Extract Control Effectiveness Evaluator
// Pattern: Strategy / Scorecard - Kontrollwirksamkeit wird einheitlich berechnet.
public final class ControlEffectivenessEvaluator {
  public record Observation(boolean passed, int weight) { public Observation { if(weight<=0) throw new IllegalArgumentException(); } }
  public record Score(int percent, String rating) {}
  public Score evaluate(List<Observation> observations){ int total=observations.stream().mapToInt(Observation::weight).sum(); int passed=observations.stream().filter(Observation::passed).mapToInt(Observation::weight).sum(); int pct=total==0?0:(passed*100/total); return new Score(pct,pct>=90?"effective":pct>=70?"partial":"ineffective"); }
}
Teststrategie: Tests prüfen Gewichtungen, fehlende Evidenz, Grenzwerte, Strategiewechsel und dass gleiche Eingangsdaten reproduzierbare Begründungen liefern.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ControlEffectivenessEvaluatorTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new ControlEffectivenessEvaluator().evaluate(java.util.List.of(new ControlEffectivenessEvaluator.Observation(true,1))).percent()==100); } }

Deutschsprachige Fachgrundlagen

290. Introduce Compliance Snapshot Immutable Snapshot / Aggregate

Introduce Compliance Snapshot

Fachliche Einordnung

Eine Compliance-Bewertung über Live-Systeme kann während der Abfrage ihren Zustand ändern. Ein versionierter Snapshot friert Datenstand, Policy-Version und Ergebnisse als belastbare Sicht ein. Dadurch lassen sich Berichte und Entscheidungen später exakt rekonstruieren.

Erkennungsmerkmale

  • Dashboards zeigen bei Wiederholung andere Zahlen
  • Abfragen kombinieren unterschiedliche Zeitstände
  • Policy-Versionen sind nicht am Ergebnis sichtbar
  • Audits können historische Bewertungen nicht reproduzieren

Sicherer Refactoring-Ablauf

  1. Scope, Stichtag und erforderliche Datenquellen festlegen
  2. Snapshot unveränderlich und versioniert modellieren
  3. Datenaufnahme orchestrieren und unvollständige Quellen markieren
  4. Bewertungen gegen genau diesen Snapshot ausführen
  5. Hash, Erfassungszeit und Policy-Version im Bericht referenzieren

Wann sinnvoll - und wann nicht?

Sinnvoll für Audits, Managementberichte und Stichtagsbewertungen. Für rein operative Echtzeitüberwachung bleibt eine Live-Sicht geeigneter.

Risiken und typische Fehlanwendungen

Ein Snapshot ist nur so belastbar wie seine Vollständigkeit und Aktualität. Fehlende Quellen, verspätete Daten und nachträgliche Korrekturen müssen explizit gekennzeichnet werden.

Enterprise-Java-Beispiel

Zum Quartalsende werden Kontrollstatus, Ausnahmen, offene Maßnahmen und Evidenzstände in einem Snapshot gebündelt. Der Bericht bleibt auch nach späteren Systemänderungen reproduzierbar.

Abgrenzung und verwandte Muster

Immutable Snapshot, Aggregate, Materialized View, Evidence Bundle, Versioned Policy.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.Instant;
import java.util.Map;
// Refactoring: Introduce Compliance Snapshot
// Pattern: Immutable Snapshot / Aggregate - Berichte beruhen auf konsistentem Bewertungsstand.
public record ComplianceSnapshot(String scope, long version, Instant capturedAt, Map<String,String> controlRatings) {
  public ComplianceSnapshot { if(scope.isBlank() || version<1) throw new IllegalArgumentException(); controlRatings=Map.copyOf(controlRatings); }
  public boolean compliant(){ return controlRatings.values().stream().noneMatch("ineffective"::equals); }
}
Teststrategie: Tests prüfen defensive Kopien, stabile Version, fehlende Quellen, Hash-Stabilität und dass spätere Live-Änderungen den Snapshot nicht verändern.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class ComplianceSnapshotTest { @Test void contract(){ org.junit.jupiter.api.Assertions.assertTrue(new ComplianceSnapshot("eu",1,java.time.Instant.EPOCH,java.util.Map.of("c","effective")).compliant()); } }

Deutschsprachige Fachgrundlagen

Kapitel 30 Characterization Tests und sichere Migrations-Seams10 Refactorings

Refactorings 291–300 schließen den Master-Katalog mit Sicherheitsnetz, Baseline, Migration, Kompatibilität, Deprecation, Rollback, Datenplan, Feature-Cleanup, Architektur-Fitness und einer erklärbaren Readiness-Entscheidung ab.

Refactoring-Katalog · Abschnitt 15

Sichere Migration und Refactoring-Readiness

Refactorings 291–300 schließen den Master-Katalog mit Sicherheitsnetz, Baseline, Migration, Kompatibilität, Deprecation, Rollback, Datenplan, Feature-Cleanup, Architektur-Fitness und einer erklärbaren Readiness-Entscheidung ab.

300 von 300 Refactorings abgeschlossen

0 Refactorings offen · Katalog vollständig.

291. Extract Characterization Test Seam Characterization Test / Seam

Extract Characterization Test Seam

Fachliche Einordnung

Vor einem Legacy-Refactoring muss das tatsächlich beobachtbare Verhalten eingefroren werden, besonders wenn Spezifikation und Tests fehlen. Ein Characterization Test Seam schafft einen kontrollierbaren Zugang zu statischen, zeitabhängigen oder externen Pfaden, ohne das Verhalten vorschnell umzubauen.

Erkennungsmerkmale

  • Legacy-Verhalten ist nur über produktive Infrastruktur erreichbar
  • statische Aufrufe verhindern deterministische Tests
  • niemand kann Soll- und Ist-Verhalten sicher unterscheiden
  • erste Umbauten ändern gleichzeitig Struktur und Ergebnis

Sicherer Refactoring-Ablauf

  1. kritische Ein- und Ausgaben des bestehenden Pfads auswählen
  2. kleinsten möglichen Seam um Zeit, Datei, Netzwerk oder statischen Aufruf legen
  3. aktuelles Verhalten als Charakterisierungstest festhalten
  4. bekannte Fehler bewusst als Baseline dokumentieren
  5. erst danach schrittweise Struktur ändern und Tests grün halten

Wann sinnvoll - und wann nicht?

Sinnvoll vor riskanten Änderungen an schlecht getestetem Legacy-Code. Bei gut spezifiziertem Code mit belastbaren Unit- und Vertragstests ist ein zusätzlicher Characterization Seam oft nicht nötig.

Risiken und typische Fehlanwendungen

Charakterisierungstests konservieren auch falsches Verhalten. Sie sind Sicherheitsnetz, keine fachliche Freigabe; bekannte Abweichungen müssen sichtbar markiert und später gezielt geändert werden.

Enterprise-Java-Beispiel

Ein statischer Tarifrechner wird über einen kleinen Adapter erreichbar gemacht. Tests erfassen repräsentative Altverträge inklusive Rundungsbesonderheiten, bevor die Berechnung intern zerlegt wird.

Abgrenzung und verwandte Muster

Characterization Test, Seam, Sprout Method, Wrap Method, Legacy Code.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Objects;
// Refactoring: Extract Characterization Test Seam
// Pattern: Characterization Test / Seam - Legacy-Verhalten wird vor Änderungen beobachtbar.
public final class CharacterizationTestSeam {
  @FunctionalInterface public interface LegacyOperation { String execute(String input); }
  private final LegacyOperation operation;
  public CharacterizationTestSeam(LegacyOperation operation){ this.operation=Objects.requireNonNull(operation); }
  public Observation observe(String input){
    try { return new Observation(input, operation.execute(input), null); }
    catch(RuntimeException ex){ return new Observation(input, null, ex.getClass().getSimpleName()); }
  }
  public record Observation(String input, String output, String failureType) {
    public Observation { if(input==null) throw new IllegalArgumentException("input"); }
    public boolean successful(){ return failureType==null; }
  }
}
Teststrategie: Tests prüfen reproduzierbare Eingaben, deterministische Zeit/IO, bekannte Sonderfälle und dass der Seam keine fachliche Logik hinzufügt.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class CharacterizationTestSeamTest { @Test void capturesCurrentBehavior(){ var o=new CharacterizationTestSeam(String::toUpperCase).observe("legacy"); org.junit.jupiter.api.Assertions.assertEquals("LEGACY",o.output()); } }

Deutschsprachige Fachgrundlagen

292. Introduce Behavioral Baseline Golden Master / Value Object

Introduce Behavioral Baseline

Fachliche Einordnung

Bei komplexen Ausgaben reichen einzelne Assertions häufig nicht aus. Eine Behavioral Baseline speichert die kanonische beobachtete Ausgabe samt Version und Prüfsumme, sodass strukturelle Änderungen gegen einen stabilen Golden Master verglichen werden können.

Erkennungsmerkmale

  • Vorher/Nachher wird manuell über Dateien verglichen
  • Ausgaben enthalten nicht deterministische Reihenfolge oder Zeitwerte
  • Baseline-Dateien haben keine Version oder Herkunft
  • Änderungen werden pauschal akzeptiert statt fachlich geprüft

Sicherer Refactoring-Ablauf

  1. repräsentative Eingaben und beobachtbare Ausgaben festlegen
  2. nicht fachliche Volatilität kanonisieren oder maskieren
  3. Baseline mit Schema-, Daten- und Generatorversion versehen
  4. Prüfsumme und Freigabeprozess etablieren
  5. Abweichungen semantisch prüfen statt blind neu aufzuzeichnen

Wann sinnvoll - und wann nicht?

Sinnvoll bei Berichten, Serialisierung, Parsern und Legacy-Berechnungen mit breiter Ausgabe. Für kleine reine Funktionen bleiben präzise Assertions verständlicher.

Risiken und typische Fehlanwendungen

Golden Masters können große, unverständliche Snapshots erzeugen. Unkontrolliertes Aktualisieren macht sie wertlos; Baseline-Änderungen brauchen Review und fachliche Begründung.

Enterprise-Java-Beispiel

Für 200 repräsentative Schadenfälle wird das kanonische Berechnungsergebnis versioniert. Nach dem Refactoring zeigt der Vergleich nur echte fachliche Abweichungen, nicht Zeitstempel oder Sortierreihenfolge.

Abgrenzung und verwandte Muster

Golden Master, Snapshot Test, Value Object, Approval Test, Characterization Test.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
// Refactoring: Introduce Behavioral Baseline
// Pattern: Golden Master / Value Object - Verhalten wird kanonisch und prüfbar festgehalten.
public record BehavioralBaseline(String scenario, String canonicalOutput, String sha256) {
  public BehavioralBaseline {
    if(scenario==null || scenario.isBlank() || canonicalOutput==null) throw new IllegalArgumentException();
    String expected=hash(canonicalOutput); if(!expected.equals(sha256)) throw new IllegalArgumentException("checksum");
  }
  public static BehavioralBaseline capture(String scenario,String output){ String canonical=output.strip().replace("\r\n","\n"); return new BehavioralBaseline(scenario,canonical,hash(canonical)); }
  public boolean matches(String output){ return sha256.equals(hash(output.strip().replace("\r\n","\n"))); }
  private static String hash(String value){ try { var d=MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8)); return java.util.HexFormat.of().formatHex(d); } catch(Exception e){ throw new IllegalStateException(e); } }
}
Teststrategie: Tests prüfen Kanonisierung, Prüfsumme, Versionswechsel, gezielte Freigabe und aussagekräftige Diffs bei Abweichungen.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class BehavioralBaselineTest { @Test void comparesCanonicalOutput(){ var b=BehavioralBaseline.capture("invoice","A\nB"); org.junit.jupiter.api.Assertions.assertTrue(b.matches(" A\nB ")); } }

Deutschsprachige Fachgrundlagen

293. Extract Migration Facade Facade / Strangler

Extract Migration Facade

Fachliche Einordnung

Während einer Migration dürfen Aufrufer nicht selbst entscheiden, ob Alt- oder Neusystem zuständig ist. Eine Migration Facade bündelt Routing, Fallback, Vergleich und Telemetrie hinter einem stabilen Vertrag und ermöglicht schrittweise Verlagerung nach dem Strangler-Prinzip.

Erkennungsmerkmale

  • jeder Client enthält eigene Alt/Neu-Schalter
  • Fallback-Regeln unterscheiden sich je Aufrufer
  • Vergleichsergebnisse werden nicht zentral gemessen
  • Rückbau des Altsystems erfordert Änderungen in vielen Clients

Sicherer Refactoring-Ablauf

  1. stabilen fachlichen Fassadenvertrag definieren
  2. Routingregel und Migrationskohorte zentralisieren
  3. Shadow Read oder Ergebnisvergleich optional kapseln
  4. Fallback nur für klar definierte Fehler zulassen
  5. Metriken und Abschaltkriterien für den Altpfad festlegen

Wann sinnvoll - und wann nicht?

Sinnvoll bei schrittweiser Ablösung eines Legacy-Systems. Für einen atomaren Big-Bang-Wechsel ohne Parallelbetrieb ist die zusätzliche Fassade oft nicht erforderlich.

Risiken und typische Fehlanwendungen

Eine Fassade kann zum dauerhaften Dual-Run-Monolithen werden. Eigentümer, Enddatum, beobachtbare Qualitätskriterien und klare Entfernungsschritte sind notwendig.

Enterprise-Java-Beispiel

Alle Vertragsabfragen laufen über eine Fassade. Bestimmte Kundengruppen werden bereits aus dem neuen Service bedient; bei Abweichungen werden Ergebnisse verglichen und kontrolliert zurückgeroutet.

Abgrenzung und verwandte Muster

Facade, Strangler Fig, Branch by Abstraction, Routing Policy, Anti-Corruption Layer.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Objects;
// Refactoring: Extract Migration Facade
// Pattern: Facade / Strangler - Migration bleibt hinter einem stabilen Vertrag kontrollierbar.
public final class MigrationFacade {
  public interface Service { String process(String request); }
  public enum Route { LEGACY, MODERN, SHADOW_COMPARE }
  private final Service legacy, modern;
  public MigrationFacade(Service legacy, Service modern){ this.legacy=Objects.requireNonNull(legacy); this.modern=Objects.requireNonNull(modern); }
  public Result process(String request, Route route){
    return switch(route){
      case LEGACY -> new Result(legacy.process(request),true);
      case MODERN -> new Result(modern.process(request),true);
      case SHADOW_COMPARE -> { String a=legacy.process(request), b=modern.process(request); yield new Result(b,a.equals(b)); }
    };
  }
  public record Result(String response, boolean equivalent) {}
}
Teststrategie: Tests prüfen Kohortenrouting, erlaubte Fallbacks, identische Verträge, Telemetrie und vollständige Abschaltbarkeit des Legacy-Pfads.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class MigrationFacadeTest { @Test void shadowCompares(){ var f=new MigrationFacade(x->"ok",x->"ok"); org.junit.jupiter.api.Assertions.assertTrue(f.process("x",MigrationFacade.Route.SHADOW_COMPARE).equivalent()); } }

Deutschsprachige Fachgrundlagen

294. Introduce Compatibility Adapter Adapter / Anti-Corruption Layer

Introduce Compatibility Adapter

Fachliche Einordnung

Ein neues Domänenmodell verliert seine Qualität, wenn es Legacy-Feldnamen, Nullkonventionen und Statuscodes übernimmt. Ein Compatibility Adapter übersetzt den alten Vertrag an einer klaren Grenze und schützt das neue Modell als Anti-Corruption Layer.

Erkennungsmerkmale

  • neue Klassen tragen alte technische Feldnamen
  • Null und Sondercodes sickern in die Domäne
  • Legacy-Enums werden überall importiert
  • jede Fachregel enthält Kompatibilitätsabfragen

Sicherer Refactoring-Ablauf

  1. Legacy-Vertrag und neue Fachsprache explizit gegenüberstellen
  2. Übersetzung in einem Adapter mit typisierten Fehlern kapseln
  3. Null-, Default- und Statusregeln dokumentieren
  4. bidirektionale Abbildung nur dort erlauben, wo nötig
  5. Vertragstests mit realistischen Legacy-Nachrichten aufbauen

Wann sinnvoll - und wann nicht?

Sinnvoll bei Migrationen, Fremdsystemen und langlebigen Altverträgen. Wenn beide Modelle bewusst dieselbe Sprache und Semantik besitzen, wäre ein Adapter nur zusätzliche Schicht.

Risiken und typische Fehlanwendungen

Ein Adapter darf Semantikverluste nicht verbergen. Nicht abbildbare Zustände brauchen explizite Fehler oder Quarantäne statt stiller Defaults.

Enterprise-Java-Beispiel

Der Adapter übersetzt alte Statuscodes 0, 4 und 9 in fachliche Zustände und behandelt leere Datumswerte explizit. Das neue Vertragsmodell bleibt frei von Altcode-Konventionen.

Abgrenzung und verwandte Muster

Adapter, Anti-Corruption Layer, Mapper, Translator, Canonical Model.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Map;
// Refactoring: Introduce Compatibility Adapter
// Pattern: Adapter / Anti-Corruption Layer - Legacy-Semantik bleibt außerhalb des neuen Modells.
public final class CompatibilityAdapter {
  public record LegacyCustomer(Map<String,String> fields) { public LegacyCustomer { fields=Map.copyOf(fields); } }
  public record Customer(String id,String displayName,Status status) { public Customer { if(id.isBlank()||displayName.isBlank()) throw new IllegalArgumentException(); } }
  public enum Status { ACTIVE, SUSPENDED }
  public Customer adapt(LegacyCustomer legacy){
    String id=required(legacy,"customer_no"); String name=required(legacy,"full_name");
    Status status="A".equals(legacy.fields().get("state"))?Status.ACTIVE:Status.SUSPENDED;
    return new Customer(id,name,status);
  }
  private static String required(LegacyCustomer value,String key){ String v=value.fields().get(key); if(v==null||v.isBlank()) throw new IllegalArgumentException(key); return v; }
}
Teststrategie: Tests prüfen alle Legacy-Codes, Nullkonventionen, unbekannte Werte, Rundung und verlustfreie Rückabbildung nur für unterstützte Fälle.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import java.util.Map; import org.junit.jupiter.api.Test;
class CompatibilityAdapterTest { @Test void isolatesLegacyCodes(){ var c=new CompatibilityAdapter().adapt(new CompatibilityAdapter.LegacyCustomer(Map.of("customer_no","1","full_name","A","state","A"))); org.junit.jupiter.api.Assertions.assertEquals(CompatibilityAdapter.Status.ACTIVE,c.status()); } }

Deutschsprachige Fachgrundlagen

295. Extract Deprecation Policy Policy / Versioning

Extract Deprecation Policy

Fachliche Einordnung

Abkündigung ist ein zeitlich gesteuerter Produkt- und Vertragsprozess. Verstreute Versions- und Datumsprüfungen führen zu uneinheitlichen Warnungen und überraschenden Sperren. Eine Deprecation Policy bündelt Ersatz, Fristen, Zielgruppen und erlaubte Übergangsphasen.

Erkennungsmerkmale

  • jede API prüft Sunset-Daten anders
  • Warnungen enthalten keinen Ersatzpfad
  • Sperren werden ohne Telemetrie aktiviert
  • Ausnahmen sind als dauerhafte Sonderfälle eingebaut

Sicherer Refactoring-Ablauf

  1. abgekündigte Fähigkeit, Ersatz und betroffene Verträge benennen
  2. Warn-, Sunset- und Sperrphase typisieren
  3. Policy mit Clock und Versionen deterministisch machen
  4. Nutzung und verbleibende Verbraucher messen
  5. Ausnahmen befristen und Entfernungskriterium definieren

Wann sinnvoll - und wann nicht?

Sinnvoll für APIs, Events, Features und Datenformate mit Übergangszeit. Für sofort zu entfernenden internen toten Code ist eine Policy nicht nötig.

Risiken und typische Fehlanwendungen

Eine Deprecation Policy ohne Durchsetzung verlängert Altlasten. Umgekehrt kann zu aggressive Sperrung Verbraucher brechen; Telemetrie und Kommunikation sind Teil der Lösung.

Enterprise-Java-Beispiel

Version 1 einer Partner-API liefert zunächst Warnheader, später nur noch für genehmigte Ausnahmen und wird nach gemessener Nullnutzung gesperrt. Die Policy liefert für jede Anfrage eine erklärbare Phase.

Abgrenzung und verwandte Muster

Policy, Semantic Versioning, Sunset, Feature Toggle, Consumer Telemetry.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.LocalDate;
// Refactoring: Extract Deprecation Policy
// Pattern: Policy / Versioning - Lebenszyklusregeln werden zentral und erklärbar.
public record DeprecationPolicy(LocalDate announcedOn, LocalDate blockedOn, String replacement) {
  public DeprecationPolicy { if(announcedOn==null||blockedOn==null||blockedOn.isBefore(announcedOn)||replacement.isBlank()) throw new IllegalArgumentException(); }
  public Decision evaluate(LocalDate today){
    if(!today.isBefore(blockedOn)) return new Decision(false,true,"use "+replacement);
    if(!today.isBefore(announcedOn)) return new Decision(true,true,"deprecated; use "+replacement);
    return new Decision(true,false,"supported");
  }
  public record Decision(boolean allowed, boolean warning, String message) {}
}
Teststrategie: Tests prüfen Phasengrenzen, Clock, Versionen, befristete Ausnahmen, Warnungen und Verhalten vor sowie nach dem Sunset-Datum.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import java.time.LocalDate; import org.junit.jupiter.api.Test;
class DeprecationPolicyTest { @Test void blocksAfterDeadline(){ var p=new DeprecationPolicy(LocalDate.of(2026,1,1),LocalDate.of(2026,6,1),"v2"); org.junit.jupiter.api.Assertions.assertFalse(p.evaluate(LocalDate.of(2026,7,1)).allowed()); } }

Deutschsprachige Fachgrundlagen

296. Introduce Rollback Command Command / Memento

Introduce Rollback Command

Fachliche Einordnung

Rollback darf nicht von Erinnerungen und implizitem Zustand abhängen. Ein Rollback Command kapselt die rückwärts gerichtete Aktion, ihren Ausgangssnapshot und ein idempotentes Ergebnis. Dadurch wird Rückabwicklung planbar, prüfbar und automatisierbar.

Erkennungsmerkmale

  • Betriebshandbuch enthält manuelle SQL-Schritte
  • Rollback benötigt unbekannten Vorzustand
  • Wiederholung kann zusätzlichen Schaden erzeugen
  • Erfolg wird nur anhand fehlender Fehlermeldung angenommen

Sicherer Refactoring-Ablauf

  1. rücksetzbaren Umfang und notwendigen Vorzustand bestimmen
  2. Snapshot oder Memento vor der Änderung erzeugen
  3. Rollback als idempotentes Command modellieren
  4. Vorbedingungen und irreversible Schritte explizit markieren
  5. Ergebnis verifizieren und auditieren

Wann sinnvoll - und wann nicht?

Sinnvoll bei riskanten Releases, Migrationen und Konfigurationsänderungen. Für vollständig additive, folgenlose Änderungen kann ein explizites Rollback Command unnötig sein.

Risiken und typische Fehlanwendungen

Nicht jede Änderung ist reversibel. Datenverlust, externe Effekte und parallele Änderungen müssen erkannt werden; ein Rollback darf keinen falschen Sicherheitsanspruch erzeugen.

Enterprise-Java-Beispiel

Vor einer Tarifmigration wird der relevante Zustand versioniert. Das Rollback Command stellt nur unveränderte Datensätze zurück, ist wiederholbar und liefert eine Liste nicht sicher rücksetzbarer Konflikte.

Abgrenzung und verwandte Muster

Command, Memento, Compensating Transaction, Idempotency, Release Rollback.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.Objects;
// Refactoring: Introduce Rollback Command
// Pattern: Command / Memento - Rückabwicklung wird wiederholbar und auditierbar.
public final class RollbackCommand {
  public interface Target { String currentVersion(); void restore(String snapshotId); }
  private final String commandId, expectedVersion, snapshotId;
  public RollbackCommand(String commandId,String expectedVersion,String snapshotId){ this.commandId=require(commandId); this.expectedVersion=require(expectedVersion); this.snapshotId=require(snapshotId); }
  public Result execute(Target target){ Objects.requireNonNull(target); if(!expectedVersion.equals(target.currentVersion())) return new Result(false,"version-conflict"); target.restore(snapshotId); return new Result(true,"restored:"+snapshotId); }
  private static String require(String v){ if(v==null||v.isBlank()) throw new IllegalArgumentException(); return v; }
  public record Result(boolean applied,String message) {}
}
Teststrategie: Tests prüfen Wiederholung, Vorbedingungsverletzung, Teilfehler, konkurrierende Änderungen und Verifikation des wiederhergestellten Zustands.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class RollbackCommandTest { @Test void checksExpectedVersion(){ var t=new RollbackCommand.Target(){ public String currentVersion(){return "2";} public void restore(String id){} }; org.junit.jupiter.api.Assertions.assertFalse(new RollbackCommand("c","1","s").execute(t).applied()); } }

Deutschsprachige Fachgrundlagen

297. Extract Data Migration Plan Pipeline / Migration Object

Extract Data Migration Plan

Fachliche Einordnung

Datenmigrationen sind Produktionssoftware und brauchen einen expliziten Plan. Lose Skripte ohne Vorbedingungen, Reihenfolge und Verifikation sind schwer reproduzierbar. Ein Data Migration Plan modelliert Schritte, Checkpoints und Abnahmekriterien als geordnete Pipeline.

Erkennungsmerkmale

  • Skripte werden manuell in unbekannter Reihenfolge ausgeführt
  • Vorbedingungen und Datenvolumen sind nicht dokumentiert
  • es gibt keine Checkpoints oder Wiederaufnahme
  • Erfolg wird nur über Exit-Code bewertet

Sicherer Refactoring-Ablauf

  1. Quell- und Zielzustand sowie Invarianten festlegen
  2. Schritte mit Vorbedingung, Aktion und Verifikation typisieren
  3. idempotente Checkpoints und Wiederaufnahme definieren
  4. Dry-Run, Stichprobe und Mengenabgleich integrieren
  5. Rollback- oder Kompensationsstrategie pro Schritt dokumentieren

Wann sinnvoll - und wann nicht?

Sinnvoll für produktive Schema- und Bestandsdatenmigrationen. Für kleine lokale Entwicklungsdaten genügt ein einfaches einmaliges Skript.

Risiken und typische Fehlanwendungen

Ein sauberer Plan ersetzt keine Kapazitäts- und Laufzeitprüfung. Sperren, Replikation, Teilfortschritt und unerwartete Datenqualität müssen vorab getestet werden.

Enterprise-Java-Beispiel

Die Migration normalisiert Kundenschlüssel in fünf Schritten. Jeder Schritt prüft Vorbedingungen, schreibt einen Checkpoint und verifiziert Anzahl, Prüfsummen sowie fachliche Invarianten.

Abgrenzung und verwandte Muster

Pipeline, Migration Object, Expand-Contract, Checkpoint, Reconciliation.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.List;
// Refactoring: Extract Data Migration Plan
// Pattern: Pipeline / Migration Object - Datenänderungen werden geordnet und verifizierbar.
public record DataMigrationPlan(String planId,List<Step> steps) {
  public interface Step { String name(); boolean precondition(MigrationContext context); void apply(MigrationContext context); boolean verify(MigrationContext context); }
  public interface MigrationContext { long count(String dataset); }
  public DataMigrationPlan { if(planId.isBlank()||steps.isEmpty()) throw new IllegalArgumentException(); steps=List.copyOf(steps); }
  public Result execute(MigrationContext context){ for(Step s:steps){ if(!s.precondition(context)) return new Result(false,s.name()+":precondition"); s.apply(context); if(!s.verify(context)) return new Result(false,s.name()+":verify"); } return new Result(true,"complete"); }
  public record Result(boolean successful,String detail) {}
}
Teststrategie: Tests prüfen Reihenfolge, Wiederaufnahme, Idempotenz, Dry-Run, Mengenabgleich, Dateninvarianten und Verhalten nach Teilfehlern.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import java.util.List; import org.junit.jupiter.api.Test;
class DataMigrationPlanTest { @Test void executesVerifiedStep(){ var s=new DataMigrationPlan.Step(){ public String name(){return "copy";} public boolean precondition(DataMigrationPlan.MigrationContext c){return true;} public void apply(DataMigrationPlan.MigrationContext c){} public boolean verify(DataMigrationPlan.MigrationContext c){return true;} }; org.junit.jupiter.api.Assertions.assertTrue(new DataMigrationPlan("p",List.of(s)).execute(x->1).successful()); } }

Deutschsprachige Fachgrundlagen

298. Introduce Feature Cleanup Policy Policy / Feature Toggle

Introduce Feature Cleanup Policy

Fachliche Einordnung

Feature Toggles sind temporäre Migrationswerkzeuge, keine dauerhafte Architektur. Ohne Eigentümer, Ablaufdatum und Entfernungskriterium bleiben beide Codepfade bestehen. Eine Cleanup Policy macht technische Schuld sichtbar und entscheidet nachvollziehbar, wann Flag und Altpfad entfernt werden dürfen.

Erkennungsmerkmale

  • Toggles besitzen kein Ablaufdatum
  • niemand kennt den verantwortlichen Owner
  • Altpfade werden trotz 100-Prozent-Rollout weiter gepflegt
  • Tests müssen dauerhaft alle Kombinationen abdecken

Sicherer Refactoring-Ablauf

  1. Owner, Zweck, Erzeugungsdatum und Zieltermin erfassen
  2. Messkriterium für stabilen Rollout definieren
  3. Abhängigkeiten und notwendige Rückfallfrist dokumentieren
  4. Policy für warnen, überfällig und entfernbar implementieren
  5. Entfernung als eigener geprüfter Refactoring-Schritt planen

Wann sinnvoll - und wann nicht?

Sinnvoll bei Release-, Experiment- und Migrations-Toggles. Für langfristige fachliche Konfigurationen ist ein Feature Toggle meist das falsche Modell.

Risiken und typische Fehlanwendungen

Zu frühes Entfernen nimmt die Rückfallmöglichkeit, zu spätes Entfernen vervielfacht Komplexität. Messdaten und klarer fachlicher Eigentümer sind entscheidend.

Enterprise-Java-Beispiel

Nach vier Wochen stabilem Vollrollout, null Fallbacks und abgeschlossener Datenmigration bewertet die Policy das Flag als entfernbar. Ein Ticket enthält Altpfad, Tests und Konfiguration, die gemeinsam gelöscht werden.

Abgrenzung und verwandte Muster

Policy, Feature Toggle, Technical Debt, Branch by Abstraction, Cleanup Refactoring.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.time.LocalDate;
// Refactoring: Introduce Feature Cleanup Policy
// Pattern: Policy / Feature Toggle - temporäre Varianten erhalten ein verbindliches Ende.
public record FeatureCleanupPolicy(String feature,String owner,LocalDate removeAfter) {
  public FeatureCleanupPolicy { if(feature.isBlank()||owner.isBlank()||removeAfter==null) throw new IllegalArgumentException(); }
  public Decision evaluate(LocalDate today,double enabledShare,int rollbackIncidents){
    boolean ready=!today.isBefore(removeAfter)&&enabledShare>=1.0&&rollbackIncidents==0;
    return new Decision(ready, ready?"remove-toggle":"retain-until-safe");
  }
  public record Decision(boolean removable,String reason) {}
}
Teststrategie: Tests prüfen Ablaufdatum, fehlenden Owner, Rolloutmetriken, Rückfallfrist und dass fachliche Langzeitkonfiguration nicht versehentlich als Toggle behandelt wird.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import java.time.LocalDate; import org.junit.jupiter.api.Test;
class FeatureCleanupPolicyTest { @Test void removesOnlyAfterStableRollout(){ var p=new FeatureCleanupPolicy("new-price","team",LocalDate.of(2026,1,1)); org.junit.jupiter.api.Assertions.assertTrue(p.evaluate(LocalDate.of(2026,2,1),1.0,0).removable()); } }

Deutschsprachige Fachgrundlagen

299. Extract Architecture Fitness Rule Specification / Fitness Function

Extract Architecture Fitness Rule

Fachliche Einordnung

Architekturregeln, die nur in Dokumenten stehen, verlieren gegen den täglichen Code. Eine ausführbare Fitness Rule beschreibt erlaubte Abhängigkeiten oder Qualitätsgrenzen als Specification und liefert konkrete Verstöße im Build.

Erkennungsmerkmale

  • Schichten werden trotz Dokumentation zyklisch abhängig
  • Reviews entdecken dieselben Regelverletzungen wiederholt
  • Architekturqualität ist nicht messbar
  • Ausnahmen werden stillschweigend zur neuen Regel

Sicherer Refactoring-Ablauf

  1. eine präzise, technisch prüfbare Architekturabsicht auswählen
  2. Scope und erlaubte Ausnahmen explizit definieren
  3. Regel als deterministische Specification implementieren
  4. Verstöße mit betroffenen Elementen und Begründung ausgeben
  5. Regel in CI betreiben und Ausnahmen befristen

Wann sinnvoll - und wann nicht?

Sinnvoll für Modulgrenzen, Abhängigkeitsrichtungen, Namensregeln und messbare Qualitätsziele. Für subjektive Designurteile bleibt menschliches Review notwendig.

Risiken und typische Fehlanwendungen

Zu viele starre Regeln können sinnvolle Entwicklung blockieren. Fitness Functions müssen Architekturabsicht schützen, nicht Stilpräferenzen automatisieren.

Enterprise-Java-Beispiel

Eine Regel stellt sicher, dass Domänenmodule keine Spring- oder Persistenzpakete importieren. Der Build nennt jede verletzende Klasse und erlaubt nur dokumentierte, befristete Ausnahmen.

Abgrenzung und verwandte Muster

Specification, Fitness Function, Architecture Test, ArchUnit, Quality Gate.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.List;
// Refactoring: Extract Architecture Fitness Rule
// Pattern: Specification / Fitness Function - Architekturgrenzen werden automatisch prüfbar.
public interface ArchitectureFitnessRule {
  Result evaluate(Model model);
  record Model(List<Dependency> dependencies) { public Model { dependencies=List.copyOf(dependencies); } }
  record Dependency(String sourceModule,String targetModule) {}
  record Result(boolean passed,List<String> violations) { public Result { violations=List.copyOf(violations); } }
  static ArchitectureFitnessRule noDependency(String sourcePrefix,String forbiddenTarget){
    return model -> { var v=model.dependencies().stream().filter(d->d.sourceModule().startsWith(sourcePrefix)&&d.targetModule().equals(forbiddenTarget)).map(d->d.sourceModule()+" -> "+d.targetModule()).toList(); return new Result(v.isEmpty(),v); };
  }
}
Teststrategie: Tests prüfen positive und negative Beispielmodule, verständliche Verletzungsmeldungen, Scope, befristete Ausnahmen und stabile CI-Ausführung.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import java.util.List; import org.junit.jupiter.api.Test;
class ArchitectureFitnessRuleTest { @Test void reportsForbiddenDependency(){ var r=ArchitectureFitnessRule.noDependency("domain","infra").evaluate(new ArchitectureFitnessRule.Model(List.of(new ArchitectureFitnessRule.Dependency("domain.order","infra")))); org.junit.jupiter.api.Assertions.assertFalse(r.passed()); } }

Deutschsprachige Fachgrundlagen

300. Introduce Refactoring Readiness Decision Decision Object / Quality Gate

Introduce Refactoring Readiness Decision

Fachliche Einordnung

Ein großes Refactoring sollte erst starten, wenn Sicherheitsnetz, Beobachtbarkeit, Rückfall und begrenzter Änderungsumfang vorhanden sind. Eine Readiness Decision bündelt diese Faktoren als erklärbares Quality Gate statt einer diffusen Bauchentscheidung.

Erkennungsmerkmale

  • Refactorings beginnen ohne belastbare Tests
  • Rollback und Migrationspfad sind ungeklärt
  • Erfolgskriterien und Telemetrie fehlen
  • mehrere riskante Änderungen werden gleichzeitig geplant

Sicherer Refactoring-Ablauf

  1. notwendige Kriterien für Tests, Scope, Rollback und Observability definieren
  2. Evidenz pro Kriterium statt bloßer Selbstauskunft verlangen
  3. Ergebnis als ready, conditional oder blocked modellieren
  4. Auflagen und fehlende Nachweise im Decision Object zurückgeben
  5. Entscheidung vor Start und vor Rollout erneut bewerten

Wann sinnvoll - und wann nicht?

Sinnvoll vor umfangreichen Legacy-, Daten- oder Architekturrefactorings. Für kleine lokale Umbenennungen wäre das Gate unverhältnismäßig.

Risiken und typische Fehlanwendungen

Ein Gate kann zur Bürokratie werden, wenn Kriterien nicht risikobasiert sind. Es darf fehlende technische Sicherheit nicht durch formale Häkchen ersetzen.

Enterprise-Java-Beispiel

Vor der Ablösung eines Kernmoduls bewertet das Gate Charakterisierungstests, Baseline, Migrationsfassade, Rollback, Metriken und Eigentümer. Das Ergebnis conditional nennt zwei konkrete Auflagen vor dem ersten Produktionsanteil.

Abgrenzung und verwandte Muster

Decision Object, Quality Gate, Risk Assessment, Characterization Test, Rollback Command.

Vorhandener Java-21-Zielcode

Vorhandener Java-21-Zielcode
package com.aydinsude.workbench.catalog;
import java.util.ArrayList;
// Refactoring: Introduce Refactoring Readiness Decision
// Pattern: Decision Object / Quality Gate - Refactoring-Start wird evidenzbasiert entschieden.
public record RefactoringReadinessDecision(boolean ready,java.util.List<String> blockers) {
  public RefactoringReadinessDecision { blockers=java.util.List.copyOf(blockers); if(ready&&!blockers.isEmpty()) throw new IllegalArgumentException(); }
  public static RefactoringReadinessDecision evaluate(Input in){
    var blockers=new ArrayList<String>();
    if(in.characterizationCoverage()<0.8) blockers.add("behavioral-baseline");
    if(!in.rollbackTested()) blockers.add("rollback");
    if(!in.observabilityReady()) blockers.add("observability");
    if(in.changeSurface()>20) blockers.add("change-surface");
    return new RefactoringReadinessDecision(blockers.isEmpty(),blockers);
  }
  public record Input(double characterizationCoverage,boolean rollbackTested,boolean observabilityReady,int changeSurface) { public Input { if(characterizationCoverage<0||characterizationCoverage>1||changeSurface<0) throw new IllegalArgumentException(); } }
}
Teststrategie: Tests prüfen alle Entscheidungszustände, fehlende Evidenz, widersprüchliche Kriterien, Auflagen und reproduzierbare Entscheidungen bei identischem Input.

Vorhandener JUnit-Testvertrag

Vorhandener JUnit-Testvertrag
package com.aydinsude.workbench.catalog;
import org.junit.jupiter.api.Test;
class RefactoringReadinessDecisionTest { @Test void explainsMissingSafetyNet(){ var d=RefactoringReadinessDecision.evaluate(new RefactoringReadinessDecision.Input(.5,false,true,4)); org.junit.jupiter.api.Assertions.assertTrue(d.blockers().contains("behavioral-baseline")); } }

Deutschsprachige Fachgrundlagen

Katalog vollständig

Der Refactoring Catalog umfasst jetzt 300 nachvollziehbare Refactorings. Der Katalog ist vollständig. Weitere Arbeiten betreffen nur noch fachliche Konsolidierung, Navigation und Qualitätsprüfung.

⌂ Cockpit