#Deployment, Runtime, Konfiguration und Secrets

EAR/WAR/EJB-JAR, Application Server, JNDI, Umgebungskonfiguration, Secrets, Runbooks und Smoke Tests.

#Deployment, Application Server, Container, Config, Secrets und Runtime-Betrieb

Dieser Abschnitt ergänzt den Master-Lernpfad um den operativen Teil der Legacy-Modernisierung. Viele Java-Enterprise-Projekte scheitern nicht am Refactoring selbst, sondern an Deployment, Server-Konfiguration, Classloading, JNDI, Secrets, Umgebungsunterschieden und fehlender Betriebsfähigkeit. Deshalb wird dieser Bereich als eigenes Modernisierungs-Slice behandelt.


#Warum Deployment ein eigenes Modernisierungs-Slice ist

Bei alten Java-Enterprise-Systemen ist Deployment selten nur mvn package. Häufig besteht das System aus EAR-, WAR-, EJB-JAR- und Shared-Library-Artefakten, die auf einem Application Server laufen. Dazu kommen Datasources, JMS-Destinations, Security Realms, Keystores, Truststores, System Properties, JNDI-Namen und Server-spezifische Deployment-Deskriptoren.

Typische Legacy-Realität:

text
legacy-app.ear
  ├── legacy-web.war
  ├── legacy-ejb.jar
  ├── legacy-soap.war
  ├── lib/
  │   ├── shared-model.jar
  │   ├── old-vendor-client.jar
  │   └── generated-soap-client.jar
  └── META-INF/application.xml

Modernisierung bedeutet daher nicht nur Code ändern, sondern auch:

text
- Deployment reproduzierbar machen
- Runtime-Konfiguration dokumentieren
- Umgebungen vergleichbar machen
- Secrets aus Code und Artefakten entfernen
- Server-Abhängigkeiten sichtbar machen
- Rollback-fähige Deployments vorbereiten
- Monitoring und Health Checks ergänzen

Wichtigster Grundsatz:

text
Wenn du nicht reproduzierbar deployen kannst,
kannst du nicht sicher modernisieren.

#Deployment-Inventar erstellen

Lege im Projekt diese Datei an:

text
docs/deployment-inventory.md

Vorlage:

markdown
# Deployment Inventory

## Application Server

| Umgebung | Server | Version | Java | Betriebssystem | Verantwortlich |
|---|---|---|---|---|---|
| DEV | WebLogic/WildFly/WebSphere/... | ... | ... | ... | ... |
| TEST | ... | ... | ... | ... | ... |
| PROD | ... | ... | ... | ... | ... |

## Deployables

| Artefakt | Typ | Quelle | Zielserver | Kontext / Name |
|---|---|---|---|---|
| legacy-app.ear | EAR | Maven/Ant/manual | app-cluster-1 | legacy-app |
| legacy-web.war | WAR | web-module | im EAR | /legacy |
| legacy-ejb.jar | EJB-JAR | ejb-module | im EAR | legacy-ejb |

## Deployment-Prozess

| Schritt | Tool | Manuell/Automatisch | Risiko |
|---|---|---|---|
| Build | Maven/Ant | manuell | hoch |
| Artefakt kopieren | SCP/Console/CI | manuell | hoch |
| Server restart | Admin Console | manuell | mittel |
| Smoke Test | Browser | manuell | hoch |

## Rollback

- Gibt es altes Artefakt?
- Wie schnell kann zurückgerollt werden?
- Müssen DB-Migrationen zurückgerollt werden?
- Müssen Queues geleert oder Events neu verarbeitet werden?

Dieses Inventar ist die Grundlage für jeden Cutover.


#EAR/WAR/EJB-JAR-Struktur verstehen

Bei Legacy-Systemen ist Packaging oft Teil der Architektur. Ein EAR bestimmt Classloader-Grenzen, Sichtbarkeit von Klassen und Reihenfolge der Module.

Dokumentiere:

markdown
# EAR Structure

| Modul | Typ | Enthält | Darf zugreifen auf | Risiko |
|---|---|---|---|---|
| legacy-web.war | WAR | JSP, Servlet, Filter | EJB Interfaces, Shared DTOs | mittel |
| legacy-soap.war | WAR | SOAP Endpoints | EJB Services, JAXB | hoch |
| legacy-ejb.jar | EJB-JAR | Business Beans, MDBs | Persistence, JMS | hoch |
| legacy-core.jar | JAR | DTOs, Utils, Domain | keine Server APIs idealerweise | niedrig |
| lib/vendor-client.jar | JAR | externer Client | SOAP/Security libs | hoch |

Typische Probleme:

text
- gleiche Library mehrfach in WAR und EAR/lib
- verschiedene Versionen von JAXB, JAX-WS, Hibernate oder Logging
- App Server bringt eigene APIs mit
- ClassCastException durch unterschiedliche Classloader
- generierte SOAP-Klassen in mehreren Modulen
- DTOs sowohl in Web als auch EJB dupliziert

Modernisierungsziel:

text
Shared Code reduzieren.
Fachlichen Kern in ein klares Modul ziehen.
Server-APIs an den Rändern halten.

Gute Zielstruktur im Übergang:

text
legacy-modernization/
  legacy-ear/
  legacy-web/
  legacy-ejb/
  order-core/          # Plain Java Domain + Use Cases + Ports
  order-adapters/      # JPA/JMS/SOAP Adapter für Legacy Runtime

#Application-Server-Konfiguration sichtbar machen

Viele kritische Einstellungen liegen nicht im Git-Repo, sondern im Application Server.

Zu erfassen sind:

text
- Datasources
- XA Datasources
- JMS Connection Factories
- Queues und Topics
- Security Realms
- LDAP/AD-Konfiguration
- Keystores und Truststores
- System Properties
- JVM Arguments
- Thread Pools
- EJB Pools
- Transaction Timeout
- JMS Redelivery Settings
- Classloader Policies
- Deployment Order

Vorlage:

markdown
# Runtime Server Configuration

## JVM

| Umgebung | Java | Heap | GC | System Properties |
|---|---|---|---|---|
| PROD | 8/11/17 | -Xmx... | ... | ... |

## Transaction Manager

| Einstellung | Wert | Risiko |
|---|---|---|
| Default Transaction Timeout | ... | hoch |
| XA Recovery enabled | ja/nein | hoch |
| Object Store Location | ... | mittel |

## Thread Pools

| Pool | Größe | Genutzt von | Risiko |
|---|---|---|---|
| default | ... | HTTP/EJB | mittel |
| jms-consumer | ... | MDB | hoch |

## EJB Pools

| Bean | Pool Size | Timeout | Risiko |
|---|---|---|---|
| OrderServiceBean | ... | ... | mittel |
| InvoiceMDB | ... | ... | hoch |

Wichtig:

text
Server-Konfiguration ist Teil des Systems.
Wenn sie nicht versioniert ist, ist sie technischer Blindflug.

#JNDI-Konfiguration externalisieren

Legacy-Code enthält oft direkte JNDI-Lookups:

java
Queue queue = (Queue) new InitialContext()
        .lookup("java:/jms/queue/OrderCreatedQueue");

Oder:

java
DataSource ds = (DataSource) new InitialContext()
        .lookup("java:/jdbc/OrderDS");

Problem:

text
- Namen sind hart codiert
- Umgebungen unterscheiden sich
- Tests werden schwer
- Migration nach Spring/Quarkus wird schwer

Zwischenschritt:

java
public interface RuntimeResourceNames {
    String orderDatasource();
    String orderPaidQueue();
    String paymentConnectionFactory();
}

Legacy-Implementierung:

java
public class PropertiesRuntimeResourceNames implements RuntimeResourceNames {

    private final Properties properties;

    public PropertiesRuntimeResourceNames(Properties properties) {
        this.properties = properties;
    }

    @Override
    public String orderDatasource() {
        return properties.getProperty("jndi.datasource.order");
    }

    @Override
    public String orderPaidQueue() {
        return properties.getProperty("jndi.queue.orderPaid");
    }

    @Override
    public String paymentConnectionFactory() {
        return properties.getProperty("jndi.jms.connectionFactory");
    }
}

Konfiguration:

properties
jndi.datasource.order=java:/jdbc/OrderDS
jndi.queue.orderPaid=java:/jms/queue/OrderPaidQueue
jndi.jms.connectionFactory=java:/ConnectionFactory

Ziel:

text
JNDI bleibt am Rand.
Der fachliche Kern kennt keine JNDI-Namen.

#Umgebungen vergleichbar machen

Ein häufiger Legacy-Satz lautet:

text
In TEST funktioniert es, in PROD nicht.

Deshalb brauchst du eine Environment-Matrix:

markdown
# Environment Matrix

| Kategorie | DEV | TEST | STAGE | PROD | Abweichung kritisch? |
|---|---|---|---|---|---|
| Java Version | ... | ... | ... | ... | ja/nein |
| App Server Version | ... | ... | ... | ... | ja/nein |
| DB Version | ... | ... | ... | ... | ja/nein |
| JMS Provider | ... | ... | ... | ... | ja/nein |
| SOAP Endpoint URLs | ... | ... | ... | ... | ja/nein |
| Transaction Timeout | ... | ... | ... | ... | ja/nein |
| Queue Redelivery | ... | ... | ... | ... | ja/nein |
| Security Realm | ... | ... | ... | ... | ja/nein |

Besonders kritisch sind Unterschiede bei:

text
- Transaction Timeout
- XA / non-XA Datasources
- JMS Redelivery Count
- Thread Pool Größe
- SOAP Timeouts
- Encoding und Locale
- Timezone
- DB Isolation Level

Empfehlung:

text
Stage sollte PROD so ähnlich wie möglich sein.
DEV darf einfacher sein, aber Abweichungen müssen bewusst sein.

#Konfiguration in Code, Artefakt und Runtime trennen

Schlechtes Legacy-Muster:

java
private static final String PAYMENT_URL = "https://prod-payment.example.com/soap";

Oder:

xml
<property name="password" value="secret123" />

Zielstruktur:

text
Code:
- enthält keine Umgebung
- enthält keine Secrets
- enthält keine PROD-URLs

Artefakt:
- ist für alle Umgebungen gleich

Runtime Config:
- unterscheidet DEV/TEST/STAGE/PROD
- wird beim Deployment injiziert

Konfigurationskategorien:

markdown
| Kategorie | Beispiel | Darf ins Git? | Bemerkung |
|---|---|---|---|
| Fachliche Defaults | max retry = 3 | ja | wenn nicht geheim |
| Umgebungsspezifische URL | payment endpoint | ja, wenn nicht geheim | besser als Config |
| Passwort | DB password | nein | Secret Store |
| Zertifikat Private Key | keystore key | nein | Secret Store / Vault |
| Feature Flag | new payment flow enabled | ja/Config | kontrollierter Rollout |

#Secrets inventarisieren und entfernen

Suche nach:

text
password
passwd
secret
token
apikey
api_key
client_secret
keystore
truststore
javax.net.ssl
BasicAuth
Authorization
username
credential

Dokumentiere:

markdown
# Secrets Inventory

| Secret | Typ | Aktueller Ort | Zielort | Rotation möglich? | Risiko |
|---|---|---|---|---|---|
| DB password | Passwort | Server config | Secret Store | ja | hoch |
| SOAP cert password | Keystore-Passwort | properties file | Vault/K8s Secret | prüfen | hoch |
| Payment API key | Token | Code? | Secret Store | ja | kritisch |

Regeln:

text
- Keine Secrets im Code
- Keine Secrets im Git
- Keine Secrets im Log
- Keine Secrets in Fehlermeldungen
- Rotation planen
- Zugriffe auditieren

Für Legacy-Umgebungen kann ein erster Schritt sein:

text
- Secrets aus Properties-Dateien entfernen
- über Server-Konfiguration injizieren
- Dateirechte härten
- Zugriff dokumentieren

Später:

text
- Vault
- Kubernetes Secrets
- Cloud Secret Manager
- externe Secret Injection

#Keystore und Truststore kontrollieren

SOAP- und Enterprise-Systeme nutzen oft Zertifikate.

Zu erfassen:

markdown
# TLS / Keystore Inventory

| Zweck | Datei / Alias | Umgebung | Ablaufdatum | Passwort-Ort | Risiko |
|---|---|---|---|---|---|
| Payment SOAP Client Cert | payment-client.jks / payment | PROD | ... | Server secret | hoch |
| Truststore Partner CA | truststore.jks | PROD | ... | Server secret | mittel |

Prüfe:

text
- Welche Zertifikate laufen bald ab?
- Sind Keystores pro Umgebung getrennt?
- Werden Passwörter geloggt?
- Gibt es Dokumentation für Rotation?
- Gibt es Monitoring für Ablaufdatum?
- Werden Truststores hart im Artefakt gepackt?

Sicheres Ziel:

text
Zertifikate und Passwörter gehören nicht in das Anwendungsartefakt.
Die Anwendung bekommt Pfade/Secrets über Runtime-Konfiguration.

#Feature Flags für risikoarme Migration

Bei Modernisierung brauchst du kontrollierte Umschaltpunkte.

Beispiele:

properties
feature.order.useNewCreateOrderUseCase=true
feature.payment.useOutbox=false
feature.invoice.consumeOrderPaid=true
feature.security.useNewRoleMapping=false

Feature-Flag-Regeln:

text
- Flags müssen dokumentiert sein
- Default muss bewusst sein
- Flags brauchen Owner
- Flags brauchen Ablaufdatum
- Flags dürfen keine dauerhafte Architektur werden
- Kritische Flags müssen auditierbar sein

Feature-Flag-Dokumentation:

markdown
# Feature Flags

| Flag | Zweck | Default | PROD Wert | Owner | Entfernen bis |
|---|---|---|---|---|---|
| feature.payment.useOutbox | Payment über Outbox statt direkt | false | false | Team Order | nach Cutover |
| feature.invoice.consumeOrderPaid | Invoice auf OrderPaid statt OrderCreated | false | false | Team Billing | nach Parallel Run |

Beispiel im Code:

java
public class CreateOrderFacade {

    private final boolean useNewUseCase;
    private final LegacyOrderService legacyOrderService;
    private final CreateOrderApplicationService newOrderService;

    public String createOrder(CreateOrderRequest request) {
        if (useNewUseCase) {
            return newOrderService.createOrder(request);
        }

        return legacyOrderService.createOrder(request);
    }
}

#Blue-Green und Rolling Deployment für Legacy realistisch bewerten

Nicht jedes Legacy-System ist sofort rolling-deploy-fähig.

Prüfe:

markdown
# Deployment Strategy Assessment

| Frage | Ja/Nein | Risiko |
|---|---|---|
| Ist die App stateless? | ... | hoch |
| Gibt es HTTP Session State? | ... | hoch |
| Sind DB-Migrationen rückwärtskompatibel? | ... | hoch |
| Können zwei Versionen parallel laufen? | ... | hoch |
| Sind JMS Consumer versionstolerant? | ... | hoch |
| Sind SOAP Contracts kompatibel? | ... | hoch |

Wenn HTTP Session State stark genutzt wird, ist Rolling Deployment schwieriger.

Mögliche Strategien:

text
Big Stop Deployment:
- einfach, aber Downtime
- oft Legacy-Realität

Rolling Deployment:
- nur möglich, wenn Versionen kompatibel sind
- Session und DB beachten

Blue-Green:
- zwei komplette Umgebungen
- schneller Cutover
- braucht Datenbankstrategie

Canary:
- kleiner Traffic-Anteil auf neue Version
- braucht Routing und Observability

Für Legacy-Modernisierung ist oft der erste realistische Schritt:

text
Reproduzierbarer Stop/Start-Deploy mit automatischem Smoke Test.

Danach:

text
Blue-Green oder Rolling vorbereiten.

#Rollback-Plan pro Modernisierungs-Slice

Jeder Slice braucht einen Rollback-Plan.

Vorlage:

markdown
# Rollback Plan: Slice 001 Create Order

## Änderung

- CreateOrderUseCase extrahiert
- Outbox PaymentRequested eingeführt
- Feature Flag: feature.payment.useOutbox

## Rollback-Auslöser

- Fehlerquote > 2%
- PENDING Outbox Events älter als 10 Minuten
- Payment UNKNOWN steigt stark
- Invoice-Erzeugung bleibt aus

## Technischer Rollback

1. Feature Flag zurück auf false
2. Neue Worker deaktivieren
3. Alte OrderCreatedQueue Consumer aktiv lassen
4. Smoke Test ausführen
5. Monitoring prüfen

## Daten-Rollback

- Bereits erzeugte Outbox Events bleiben erhalten
- Nicht veröffentlichte Events auf HOLD setzen
- Keine Daten löschen ohne manuelle Prüfung

## Nacharbeit

- Fehleranalyse
- betroffene Orders identifizieren
- manuelle Korrektur falls nötig

Wichtig:

text
Rollback ist nicht nur Artefakt zurückspielen.
Daten, Queues und externe Side Effects müssen mitgedacht werden.

#Smoke-Test nach Deployment automatisieren

Ein Deployment ohne Smoke Test ist blind.

Minimum-Smoke-Test:

text
- App startet
- Health Endpoint erreichbar
- DB Verbindung funktioniert
- JMS Verbindung funktioniert
- SOAP Client Config geladen
- Login funktioniert
- ein read-only Use Case funktioniert

Beispiel-Script:

bash
#!/usr/bin/env bash
set -euo pipefail

BASE_URL="${BASE_URL:-https://test.example.com/legacy}"

curl -fsS "$BASE_URL/health" > /dev/null
curl -fsS "$BASE_URL/health/db" > /dev/null
curl -fsS "$BASE_URL/health/jms" > /dev/null
curl -fsS "$BASE_URL/health/config" > /dev/null

echo "Smoke test passed"

Für Legacy ohne Health Endpoints:

text
- Login-Seite abrufen
- statische Ressource abrufen
- einfache Suchseite aufrufen
- technisches Ping-Servlet ergänzen

Später:

text
- synthetischer Use Case
- Test-Order anlegen in Testumgebung
- JMS Test Message
- SOAP Mock Call

#Health Checks für Runtime-Ressourcen

Gute Health Checks unterscheiden:

text
Liveness:
- Ist der Prozess lebendig?

Readiness:
- Kann die App Traffic verarbeiten?

Dependency Health:
- DB, JMS, SOAP, Keystore, Outbox okay?

Beispiel:

markdown
# Health Checks

| Check | Kritisch für Readiness? | Beschreibung |
|---|---|---|
| app alive | ja | Prozess läuft |
| db connectivity | ja | SELECT 1 |
| jms connectivity | abhängig | ConnectionFactory erreichbar |
| outbox backlog | nein/ja | PENDING Events nicht zu alt |
| payment soap endpoint | nein | nicht immer extern blockieren |
| keystore loaded | ja | Zertifikat verfügbar |

Achtung:

text
Externe Partner-Systeme sollten Readiness nicht immer hart blockieren.
Sonst nimmt ein Payment-Ausfall deine ganze App aus dem Load Balancer.

Besser:

text
- kritische interne Ressourcen: readiness-relevant
- externe Systeme: degraded status + Alarm

#Containerisierung von Legacy-Systemen realistisch planen

Nicht jede EAR-Anwendung sollte sofort in Docker/Kubernetes wandern. Aber Containerisierung kann hilfreich sein, um Builds, Testumgebungen und Deployments reproduzierbar zu machen.

Bewerte:

markdown
# Containerization Assessment

| Frage | Antwort | Risiko |
|---|---|---|
| Läuft der App Server headless? | ... | mittel |
| Kann Konfiguration automatisiert eingespielt werden? | ... | hoch |
| Sind Logs stdout-kompatibel? | ... | mittel |
| Sind Sessions extern oder stateless? | ... | hoch |
| Sind Keystores/Secrets injizierbar? | ... | hoch |
| Ist Startup-Zeit akzeptabel? | ... | mittel |

Mögliche Zwischenziele:

text
Stufe 1:
- Container nur für lokale Entwicklungsumgebung

Stufe 2:
- Container für Integration Tests

Stufe 3:
- Container für Test/Staging Deployment

Stufe 4:
- Produktion, wenn Betrieb bereit ist

Nicht zuerst:

text
Riesiges Legacy-EAR ohne Tests direkt nach Kubernetes verschieben.

#Docker Compose für lokale Legacy-Abhängigkeiten

Auch wenn die App selbst noch im alten Server läuft, kannst du lokale Abhängigkeiten containerisieren:

yaml
version: "3.9"

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: legacy
      POSTGRES_USER: legacy
      POSTGRES_PASSWORD: legacy
    ports:
      - "5432:5432"

  activemq:
    image: apache/activemq-classic:latest
    ports:
      - "61616:61616"
      - "8161:8161"

  wiremock:
    image: wiremock/wiremock:latest
    ports:
      - "8089:8080"

Ziel:

text
Entwickler können lokal testen,
ohne auf geteilte TEST-Umgebung angewiesen zu sein.

Dokumentiere:

markdown
# Local Runtime

## Start

```bash
docker compose up -d

#Services

Service URL Zweck
DB localhost:5432 Legacy Test DB
JMS localhost:61616 Queue Tests
WireMock localhost:8089 SOAP/HTTP Mock
text
---

## Konfigurationsdrift verhindern

Konfigurationsdrift entsteht, wenn DEV, TEST und PROD manuell unterschiedlich gepflegt werden.

Gegenmaßnahmen:

```text
- Server-Konfiguration exportieren
- Runtime-Inventar versionieren
- Infrastrukturänderungen über Pull Requests
- Drift-Checks in CI/CD
- regelmäßiger Vergleich von Environment-Matrix

Beispiel Drift-Check:

markdown
# Config Drift Check

| Einstellung | TEST | PROD | Gleich? | Aktion |
|---|---|---|---|---|
| tx timeout | 300s | 60s | nein | klären |
| JMS redelivery | 5 | 10 | nein | dokumentieren |
| Payment timeout | 30s | 10s | nein | prüfen |

Wichtig:

text
Nicht jede Abweichung ist falsch.
Aber jede Abweichung muss bewusst sein.

#Runtime-Runbooks schreiben

Ein Runbook beschreibt, was der Betrieb bei Problemen tun soll.

Für Complex Slice 001 brauchst du mindestens:

text
- Outbox backlog wächst
- Payment UNKNOWN steigt
- JMS Consumer verarbeitet nicht
- Invoice doppelt erzeugt
- SOAP Payment Provider down
- DB Migration fehlgeschlagen

Vorlage:

markdown
# Runbook: Outbox Backlog

## Symptom

- Metrik `outbox.pending.count` steigt
- ältestes PENDING Event > 10 Minuten

## Impact

- Payments oder Invoices verzögern sich

## Prüfung

```sql
SELECT event_type, status, COUNT(*)
FROM outbox_event
GROUP BY event_type, status;
sql
SELECT *
FROM outbox_event
WHERE status = 'FAILED'
ORDER BY created_at
FETCH FIRST 20 ROWS ONLY;

#Sofortmaßnahmen

  1. Logs des Outbox Publishers prüfen
  2. JMS Broker Health prüfen
  3. SOAP Endpoint Health prüfen
  4. DEAD Events nicht automatisch löschen
  5. Bei Bedarf Publisher pausieren

#Recovery

  • Ursache beheben
  • FAILED Events auf PENDING zurücksetzen, wenn sicher
  • Consumer-Idempotenz prüfen
  • Monitoring beobachten
text
Runbooks sind ein großer Professionalisierungsschritt.

---

## Deployment-Plan für Complex Slice 001

Für den Order/Payment/Invoice-Slice könnte ein sicherer Deployment-Plan so aussehen:

```markdown
# Deployment Plan: Complex Slice 001

## Vorbedingungen

- Tests grün
- DB Migrationen in STAGE getestet
- Outbox Tabelle vorhanden
- Processed Message Tabelle vorhanden
- Feature Flags vorhanden
- Monitoring Dashboard vorhanden
- Rollback-Plan geprüft

## Schritt 1: Passive Infrastruktur deployen

- Outbox Tabelle hinzufügen
- Processed Message Tabelle hinzufügen
- neue Codepfade deployen, aber Flags aus
- keine Verhaltensänderung in PROD

## Schritt 2: Shadow Mode

- neuer Use Case schreibt optional Vergleichslogs
- keine externen Side Effects
- Metriken prüfen

## Schritt 3: Outbox für kleinen Traffic aktivieren

- Flag für internen Testnutzer oder Mandant aktivieren
- PaymentRequested Events beobachten
- OrderPaid Events beobachten
- Invoice-Erzeugung prüfen

## Schritt 4: Ausrollen

- Traffic-Anteil erhöhen
- Backlog beobachten
- Fehlerquote beobachten
- Payment UNKNOWN beobachten

## Schritt 5: Alte Pfade deaktivieren

- direkter JMS Send aus CreateOrder entfernen
- OrderCreated Consumer deaktivieren
- alte Flags entfernen

## Rollback

- Flag zurücksetzen
- Worker pausieren
- Outbox Events auf HOLD setzen
- Legacy Flow wieder aktivieren

#Runtime-Plan für dein echtes Projekt

Als nächstes solltest du für dein echtes Projekt diese Dateien erstellen:

text
docs/deployment-inventory.md
docs/runtime-server-configuration.md
docs/environment-matrix.md
docs/secrets-inventory.md
docs/feature-flags.md
docs/rollback-plan-template.md
docs/runbooks/outbox-backlog.md
docs/runbooks/jms-consumer-failure.md
docs/runbooks/payment-provider-down.md

Arbeitsreihenfolge:

text
1. Deployables identifizieren
2. App-Server-Konfiguration erfassen
3. JNDI-Ressourcen dokumentieren
4. Secrets inventarisieren
5. Environment-Matrix bauen
6. Deployment-Prozess dokumentieren
7. Smoke-Test automatisieren
8. Rollback-Plan für ersten Slice schreiben
9. Runbook für kritischsten Fehlerfall schreiben
10. Erst dann produktionsnahe Modernisierung deployen

Merksatz:

text
Eine Modernisierung ist erst dann produktionsfähig,
wenn Deployment, Config, Secrets, Rollback und Betrieb verstanden sind.