Legacy-Abbau-Lab

31 Java-Dateien. 8 zentrale Dateien werden direkt mit echtem Quellcode und ihrem Zusammenspiel erklärt.

Zurück zu Code-Labs

Kapitel 14 · Legacy-Abbau

Was dieses Lab zeigt

Führt die Strangler-Migration bis zum kontrollierten Abschalten. Traffic Routing, Cutover-Gates, Removal Guards und Cleanup-Regeln verhindern einen verfrühten Legacy-Abbau.

Lernziele

  • Cutover-Kriterien definieren
  • Traffic sicher umschalten
  • Legacy kontrolliert entfernen

Technik und Schwerpunkte

JDK-Lab31 Java-Dateien1 Tests/RunnerStranglerCutoverDecommission
Echter Quellcode aus diesem Lab

Geführter Codepfad

Der letzte Legacy-Schritt verbindet Traffic-Routing, Parity, Cutover-Gates, Datenmigration und Cleanup. Der Codepfad zeigt, dass Abschaltung erst nach messbaren Freigabekriterien erfolgt.

Run7DDemoStranglerOrderFacadeTrafficRouterParityLedgerCutoverGateEvaluatorDataMigrationVerifierCleanupPlannerRemovalGuard
Lesereihenfolge der zentralen Klassen. Die Pfeile zeigen den didaktischen Weg durch den realen Quellcode, nicht zwingend jeden Laufzeitaufruf.
1. Run7DDemoFührt Routing, Gate-Prüfung, Migration und Cleanup als zusammenhängendes Cutover-Szenario aus.
2. StranglerOrderFacadeBietet einen stabilen Einstieg und delegiert anhand der Routing-Entscheidung an Alt- oder Neusystem.
3. TrafficRouterBestimmt den aktiven Pfad und berücksichtigt RoutingMode sowie FeatureFlags.
4. ParityLedgerSammelt Vergleichsergebnisse als Grundlage für die Cutover-Freigabe.
5. CutoverGateEvaluatorBewertet Parity, Nutzung und Betriebsbereitschaft gegen explizite Gates.
6. DataMigrationVerifierPrüft Vollständigkeit und Konsistenz der migrierten Daten.
7. CleanupPlannerErstellt aus Restnutzung, Flags und Abhängigkeiten einen geordneten Abbauplan.
8. RemovalGuardVerhindert die Entfernung, solange fachliche oder technische Schutzbedingungen nicht erfüllt sind.

1. Run7DDemo

src/main/java/com/example/enterprise/run7d/Run7DDemo.java
Java-Datei öffnen
Rolle im Ablauf

Führt Routing, Gate-Prüfung, Migration und Cleanup als zusammenhängendes Cutover-Szenario aus.

Im Lesepfad folgt StranglerOrderFacade: Bietet einen stabilen Einstieg und delegiert anhand der Routing-Entscheidung an Alt- oder Neusystem.

Typ
class Run7DDemo
Verwendet
StranglerOrderFacade, TrafficRouter, ParityLedger, CutoverGateEvaluator, DataMigrationVerifier
Verwendet von
Einstiege
main(String[] args)
package com.example.enterprise.run7d;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

public final class Run7DDemo {
    public static void main(String[] args) {
        var flags = new FeatureFlagRegistry()
                .enable(FeatureFlag.USE_NEW_ORDER_ENGINE)
                .enable(FeatureFlag.SHADOW_LEGACY_ORDER_ENGINE);
        var parity = new ParityLedger();
        var usage = new LegacyUsageMeter();
        var facade = new StranglerOrderFacade(new LegacyOrderProcessor(), new NewOrderProcessor(), new TrafficRouter(flags), parity, usage);
        var requests = List.of(
                new OrderRequest("O-1", "C-1", List.of("A","B","C"), new BigDecimal("100.00"), "AT"),
                new OrderRequest("O-2", "C-2", List.of("A"), new BigDecimal("120.00"), "DE"),
                new OrderRequest("O-3", "C-3", List.of("A","B","C","D"), new BigDecimal("99.99"), "AT")
        );
        requests.forEach(facade::process);
        flags.enable(FeatureFlag.ENABLE_HARD_CUTOVER);
        var finalReceipt = facade.process(new OrderRequest("O-4", "C-4", List.of("A"), new BigDecimal("40.00"), "AT"));
        var data = new DataMigrationVerifier().verify(Map.of("O-1","ok"), Map.of("O-1","ok"));
        var gates = new CutoverGateEvaluator().evaluate(parity, usage, data, new OperationsReadiness(true,true,true,true));
        System.out.println("RUN7D_DEMO parity=" + parity.parityRate() + " finalEngine=" + finalReceipt.engine() + " gatePassed=" + gates.passed());
    }
}

2. StranglerOrderFacade

src/main/java/com/example/enterprise/run7d/StranglerOrderFacade.java
Java-Datei öffnen
Rolle im Ablauf

Bietet einen stabilen Einstieg und delegiert anhand der Routing-Entscheidung an Alt- oder Neusystem.

Im Lesepfad folgt TrafficRouter: Bestimmt den aktiven Pfad und berücksichtigt RoutingMode sowie FeatureFlags.

Typ
class StranglerOrderFacade
Verwendet
TrafficRouter, ParityLedger
Verwendet von
Run7DDemo
Einstiege
process(OrderRequest request)
Facade
package com.example.enterprise.run7d;

// Pattern: Facade - kapselt Parallelbetrieb, Shadow Mode und Umschaltung fuer Aufrufer.
public final class StranglerOrderFacade {
    private final OrderProcessor legacy;
    private final OrderProcessor modern;
    private final TrafficRouter router;
    private final ParityLedger parityLedger;
    private final LegacyUsageMeter usageMeter;

    public StranglerOrderFacade(OrderProcessor legacy, OrderProcessor modern, TrafficRouter router,
                                ParityLedger parityLedger, LegacyUsageMeter usageMeter) {
        this.legacy = legacy; this.modern = modern; this.router = router;
        this.parityLedger = parityLedger; this.usageMeter = usageMeter;
    }

    public OrderReceipt process(OrderRequest request) {
        RouteDecision decision = router.decide();
        return switch (decision.mode()) {
            case LEGACY_ONLY -> {
                usageMeter.markLegacyRequest();
                yield legacy.process(request);
            }
            case SHADOW -> {
                usageMeter.markLegacyRequest();
                OrderReceipt legacyReceipt = legacy.process(request);
                OrderReceipt modernReceipt = modern.process(request);
                parityLedger.compare(legacyReceipt, modernReceipt);
                yield legacyReceipt;
            }
            case NEW_PRIMARY -> {
                OrderReceipt modernReceipt = modern.process(request);
                OrderReceipt legacyReceipt = legacy.process(request);
                usageMeter.markLegacyRequest();
                parityLedger.compare(legacyReceipt, modernReceipt);
                yield modernReceipt;
            }
            case NEW_ONLY -> modern.process(request);
        };
    }
}

3. TrafficRouter

src/main/java/com/example/enterprise/run7d/TrafficRouter.java
Java-Datei öffnen
Rolle im Ablauf

Bestimmt den aktiven Pfad und berücksichtigt RoutingMode sowie FeatureFlags.

Im Lesepfad folgt ParityLedger: Sammelt Vergleichsergebnisse als Grundlage für die Cutover-Freigabe.

Typ
class TrafficRouter
Verwendet
Verwendet von
Run7DDemo, StranglerOrderFacade
Einstiege
decide()
Strategy
package com.example.enterprise.run7d;

// Pattern: Strategy - Routing-Regeln bleiben austauschbar und testbar.
public final class TrafficRouter {
    private final FeatureFlagRegistry flags;

    public TrafficRouter(FeatureFlagRegistry flags) { this.flags = flags; }

    public RouteDecision decide() {
        if (flags.enabled(FeatureFlag.ENABLE_HARD_CUTOVER)) {
            return new RouteDecision(RoutingMode.NEW_ONLY, "hard cutover enabled");
        }
        if (flags.enabled(FeatureFlag.USE_NEW_ORDER_ENGINE) && flags.enabled(FeatureFlag.SHADOW_LEGACY_ORDER_ENGINE)) {
            return new RouteDecision(RoutingMode.NEW_PRIMARY, "new primary with legacy shadow");
        }
        if (flags.enabled(FeatureFlag.SHADOW_LEGACY_ORDER_ENGINE)) {
            return new RouteDecision(RoutingMode.SHADOW, "legacy primary with new shadow");
        }
        return new RouteDecision(RoutingMode.LEGACY_ONLY, "legacy fallback until gates pass");
    }
}

4. ParityLedger

src/main/java/com/example/enterprise/run7d/ParityLedger.java
Java-Datei öffnen
Rolle im Ablauf

Sammelt Vergleichsergebnisse als Grundlage für die Cutover-Freigabe.

Im Lesepfad folgt CutoverGateEvaluator: Bewertet Parity, Nutzung und Betriebsbereitschaft gegen explizite Gates.

Typ
class ParityLedger
Verwendet
Verwendet von
Run7DDemo, StranglerOrderFacade, CutoverGateEvaluator
Einstiege
compare(OrderReceipt legacy, OrderReceipt modern), compared(), diffs(), parityRate()
package com.example.enterprise.run7d;

import java.util.ArrayList;
import java.util.List;

public final class ParityLedger {
    private final List<ParityDiff> diffs = new ArrayList<>();
    private int compared;

    public void compare(OrderReceipt legacy, OrderReceipt modern) {
        compared++;
        if (legacy.grossAmount().compareTo(modern.grossAmount()) != 0) {
            diffs.add(new ParityDiff(legacy.orderId(), "AMOUNT", legacy.grossAmount() + " != " + modern.grossAmount()));
        }
    }

    public int compared() { return compared; }
    public List<ParityDiff> diffs() { return List.copyOf(diffs); }
    public double parityRate() { return compared == 0 ? 0.0 : (compared - diffs.size()) / (double) compared; }
}

5. CutoverGateEvaluator

src/main/java/com/example/enterprise/run7d/CutoverGateEvaluator.java
Java-Datei öffnen
Rolle im Ablauf

Bewertet Parity, Nutzung und Betriebsbereitschaft gegen explizite Gates.

Im Lesepfad folgt DataMigrationVerifier: Prüft Vollständigkeit und Konsistenz der migrierten Daten.

Typ
class CutoverGateEvaluator
Verwendet
ParityLedger
Verwendet von
Run7DDemo
Einstiege
evaluate(ParityLedger parity, LegacyUsageMeter usage, DataMigrationReport data, OperationsReadiness readiness)
package com.example.enterprise.run7d;

import java.util.ArrayList;

public final class CutoverGateEvaluator {
    public GateReport evaluate(ParityLedger parity, LegacyUsageMeter usage, DataMigrationReport data, OperationsReadiness readiness) {
        var reasons = new ArrayList<String>();
        if (parity.compared() < 3) reasons.add("too few parity samples");
        if (parity.parityRate() < 1.0) reasons.add("parity diffs remain: " + parity.diffs().size());
        if (!usage.zeroLegacyTraffic()) reasons.add("legacy traffic remains");
        if (!data.ready()) reasons.add("data migration not ready: " + data.problems());
        if (!readiness.ready()) reasons.add("operations not ready: " + readiness.missing());
        return new GateReport(reasons.isEmpty(), reasons);
    }
}

6. DataMigrationVerifier

src/main/java/com/example/enterprise/run7d/DataMigrationVerifier.java
Java-Datei öffnen
Rolle im Ablauf

Prüft Vollständigkeit und Konsistenz der migrierten Daten.

Im Lesepfad folgt CleanupPlanner: Erstellt aus Restnutzung, Flags und Abhängigkeiten einen geordneten Abbauplan.

Typ
class DataMigrationVerifier
Verwendet
Verwendet von
Run7DDemo
Einstiege
verify(Map<String, String> legacyRows, Map<String, String> newRows)
package com.example.enterprise.run7d;

import java.util.ArrayList;
import java.util.Map;

public final class DataMigrationVerifier {
    public DataMigrationReport verify(Map<String, String> legacyRows, Map<String, String> newRows) {
        var problems = new ArrayList<String>();
        for (var entry : legacyRows.entrySet()) {
            if (!newRows.containsKey(entry.getKey())) problems.add("missing target row " + entry.getKey());
            else if (!entry.getValue().equals(newRows.get(entry.getKey()))) problems.add("row mismatch " + entry.getKey());
        }
        for (String id : newRows.keySet()) {
            if (!legacyRows.containsKey(id)) problems.add("unexpected target row " + id);
        }
        return new DataMigrationReport(legacyRows.size(), newRows.size(), problems);
    }
}

7. CleanupPlanner

src/main/java/com/example/enterprise/run7d/CleanupPlanner.java
Java-Datei öffnen
Rolle im Ablauf

Erstellt aus Restnutzung, Flags und Abhängigkeiten einen geordneten Abbauplan.

Im Lesepfad folgt RemovalGuard: Verhindert die Entfernung, solange fachliche oder technische Schutzbedingungen nicht erfüllt sind.

Typ
class CleanupPlanner
Verwendet
Verwendet von
Einstiege
plan()
package com.example.enterprise.run7d;

import java.util.ArrayList;

public final class CleanupPlanner {
    public CleanupPlan plan() {
        var c = new ArrayList<CleanupCandidate>();
        c.add(new CleanupCandidate("flag.USE_NEW_ORDER_ENGINE", CleanupType.FEATURE_FLAG, "Flag entfernen, weil neuer Pfad Standard ist", true));
        c.add(new CleanupCandidate("LegacyOrderProcessor", CleanupType.LEGACY_CLASS, "alte Monster-Methode loeschen", true));
        c.add(new CleanupCandidate("legacy_order_audit", CleanupType.DATABASE_OBJECT, "alte Audit-Tabelle nach Retention entfernen", false));
        c.add(new CleanupCandidate("legacy-nightly-reprice", CleanupType.BATCH_JOB, "Batchjob nach neuer Pipeline entfernen", true));
        c.add(new CleanupCandidate("dashboard.legacy-order", CleanupType.DASHBOARD, "Legacy-Dashboard durch neues SLO-Dashboard ersetzen", true));
        c.add(new CleanupCandidate("runbook-legacy-fallback", CleanupType.DOCUMENTATION, "Fallback-Runbook archivieren", true));
        return new CleanupPlan(c);
    }
}

8. RemovalGuard

src/main/java/com/example/enterprise/run7d/RemovalGuard.java
Java-Datei öffnen
Rolle im Ablauf

Verhindert die Entfernung, solange fachliche oder technische Schutzbedingungen nicht erfüllt sind.

Damit ist der zentrale Pfad abgeschlossen; der Test-/Runner-Code und die vollständige Dateiliste darunter zeigen die übrigen Varianten.

Typ
class RemovalGuard
Verwendet
Verwendet von
Einstiege
canRemove(CleanupPlan plan, GateReport gates, List<String> remainingFlagReferences)
package com.example.enterprise.run7d;

import java.util.ArrayList;
import java.util.List;

public final class RemovalGuard {
    public RemovalDecision canRemove(CleanupPlan plan, GateReport gates, List<String> remainingFlagReferences) {
        var blockers = new ArrayList<String>();
        if (!gates.passed()) blockers.addAll(gates.reasons());
        if (!remainingFlagReferences.isEmpty()) blockers.add("flag references remain: " + remainingFlagReferences);
        if (plan.irreversibleCount() > 0 && !gates.passed()) blockers.add("irreversible database cleanup requires passed gates");
        return new RemovalDecision(blockers.isEmpty(), blockers);
    }
}
Alle Projektdateien öffnen (34 Einträge)
⌂ Cockpit