Modul 07 · Migration Deep Dive · Stufe 5 Enterprise Runtime

Messaging

javax.jms und ActiveMQ Classic wird nachvollziehbar in Spring JMS mit jakarta.jms überführt. Die Akte zeigt nicht nur das Ziel, sondern die tatsächlichen Dateien, APIs, Dependencies, Codebelege, Tests, Risiken und Cutover-Schritte.

Messaging GatewayMessage-Driven Consumer
2 → 4Produktionsdateien
63 → 60Java-Zeilen
1 → 1Testdateien
4 / 3Dependencies entfernt / neu
MITTELRisiko · Score 7

Was sich konkret ändert

DimensionLegacyModernMigrationskonsequenz
Programmiermodellimperativ / ohne Framework-AnnotationenComponent, JmsListener, Override, SpringBootApplicationAnnotationen und Containerfunktionen werden nur dort eingesetzt, wo sie eine konkrete technische Verantwortung übernehmen.
Abhängigkeiten4 direkte Dependencies3 direkte Dependencies3 neu, 4 entfernt; Versionen und transitive Auswirkungen im erfolgreichen Online-Build prüfen.
Öffentliche API3 erkannte Methoden4 erkannte MethodenMethoden werden nach fachlicher Rolle gemappt; reine Bootstrap- und Framework-Methoden sind kein fachlicher Vertrag.
Datenmodellclass LegacyJmsGateway, class MessagingLegacyApplicationclass MessageDemoRunner, class MessagingModernApplication, class OrderMessageListener, class OrderMessagePublisherFeldnamen, IDs, Null-Semantik, Gleichheit und Serialisierungsform werden separat regressionstestet.
FehlerverhaltenLegacy-Exceptions und Rückgabewerteexplizitere Fach-/Framework-FehlerabbildungFehler dürfen nicht nur technisch übersetzt werden; Status, Ursache, Retrybarkeit und Client-Vertrag müssen erhalten oder versioniert werden.
TestsBestands- und Golden-Master-TestsUnit-, Slice-, Contract- und IntegrationstestsDer Modern-Pfad wird zuerst gegen denselben fachlichen Vektor geprüft und danach um neue technische Risiken ergänzt.
BetriebLegacy-Start/Lifecyclemodernes Packaging, Health und externe KonfigurationParallelbetrieb, Replay/Retry und Rückrouting müssen vor Abschaltung des Legacy-Pfads erprobt sein.
RollbackLegacy-Artefakt bleibt unverändertModern-Artefakt getrennt deploybarKein Rollback über Datenverlust: Schema, Nachrichten und verschlüsselte Daten müssen rückwärtslesbar oder durch Dual-Read abgesichert sein.

Fachlicher Vertrag: unverändert zu erhalten

  • Nachrichtenformat und Destination werden versioniert und rückwärtskompatibel behandelt.
  • At-least-once-Zustellung führt nicht zu doppelter fachlicher Wirkung.
  • Poison Messages gelangen kontrolliert in eine DLQ.

Hauptrisiko und Testfokus

duplicate delivery, poison messages and vulnerable legacy broker

redelivery, idempotency, DLQ and serialization tests

Die Modernisierung gilt erst als abgeschlossen, wenn der fachliche Vertrag automatisiert belegt ist.

Verbindliches Code- und Rollenmapping

Die Zuordnung ist semantisch: Eine Legacy-Klasse kann in mehrere moderne Rollen zerlegt werden.

Legacy-Rolle / DateiModern-Rolle / DateiBedeutung
LegacyJmsGateway.javaOrderMessagePublisher.java + OrderMessageListener.javaEin JMS Gateway wird in Producer und Consumer getrennt.
MessagingLegacyApplication.javaMessagingModernApplication.java + MessageDemoRunner.javaEmbedded-Broker-Demo wird Spring-Boot-Komposition.

Legacy-Quellinventar

DateiTypAnnotationenöffentliche APIPatternZeilen
LegacyJmsGateway.javaclass LegacyJmsGatewayvoid send(String queueName, String payload)
String receive(String queueName, long timeoutMillis)
Messaging Gateway39
MessagingLegacyApplication.javaclass MessagingLegacyApplicationvoid main(String[] args)24

Modern-Quellinventar

DateiTypAnnotationenöffentliche APIPatternZeilen
MessageDemoRunner.javaclass MessageDemoRunnerComponent, Overridevoid run(String ... args)17
MessagingModernApplication.javaclass MessagingModernApplicationSpringBootApplicationvoid main(String[] args)11
OrderMessageListener.javaclass OrderMessageListenerComponent, JmsListenervoid receive(String orderNumber)Message-Driven Consumer14
OrderMessagePublisher.javaclass OrderMessagePublisherComponentvoid publish(String orderNumber)Messaging Gateway18

Dependency- und Laufzeitmigration

StatusDependencyVersionScopePrüfung
ENTFERNTjavax.jms:javax.jms-api2.0.1compileLegacy-Laufzeit entfällt oder wird durch Plattform/BOM ersetzt.
ENTFERNTorg.apache.activemq:activemq-broker${activemq.version}compileLegacy-Laufzeit entfällt oder wird durch Plattform/BOM ersetzt.
ENTFERNTorg.apache.activemq:activemq-client${activemq.version}compileLegacy-Laufzeit entfällt oder wird durch Plattform/BOM ersetzt.
ENTFERNTorg.junit.jupiter:junit-jupiterBOM/ParenttestLegacy-Laufzeit entfällt oder wird durch Plattform/BOM ersetzt.
NEUorg.apache.activemq:artemis-jakarta-serverBOM/ParentruntimeNeue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen.
NEUorg.springframework.boot:spring-boot-starter-artemisBOM/ParentcompileNeue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen.
NEUorg.springframework.boot:spring-boot-starter-testBOM/ParenttestNeue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen.
Direkte POM-Daten ersetzen keinen erfolgreichen Online-Build mit transitiver SBOM- und CVE-Prüfung.

Vorher-/Nachher-Codebelege

Die folgenden Ausschnitte stammen direkt aus den enthaltenen Projekten. Dadurch ist sichtbar, welche Verantwortung tatsächlich verschoben wurde.

Legacy
LegacyJmsGateway.java
Modern
OrderMessagePublisher.java + OrderMessageListener.java

Migrationsbedeutung: Ein JMS Gateway wird in Producer und Consumer getrennt.

Legacy-Code

projects/07-messaging/legacy/src/main/java/at/aydin/lab/messaging/legacy/LegacyJmsGateway.java

package at.aydin.lab.messaging.legacy;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

// Design Pattern: Messaging Gateway
// Zweck: JMS-Infrastruktur wird hinter einfachen Sende-/Empfangsmethoden gekapselt.
public final class LegacyJmsGateway {
    private final ConnectionFactory connectionFactory;
    public LegacyJmsGateway(ConnectionFactory connectionFactory) {
        this.connectionFactory = connectionFactory;
    }

    public void send(String queueName, String payload) throws Exception {
        try (Connection connection = connectionFactory.createConnection(); Session session = connection.createSession(false,
            Session.AUTO_ACKNOWLEDGE)) {
            Queue queue = session.createQueue(queueName);
            try (MessageProducer producer = session.createProducer(queue)) {
                producer.send(session.createTextMessage(payload));
            }
        }
    }

    public String receive(String queueName, long timeoutMillis) throws Exception {
        try (Connection connection = connectionFactory.createConnection(); Session session = connection.createSession(false,
            Session.AUTO_ACKNOWLEDGE)) {
            connection.start();
            Queue queue = session.createQueue(queueName);
            try (MessageConsumer consumer = session.createConsumer(queue)) {
                return ((TextMessage) consumer.receive(timeoutMillis)).getText();
            }
        }
    }
}

Modern-Code

projects/07-messaging/modern/src/main/java/at/aydin/lab/messaging/modern/OrderMessagePublisher.java

package at.aydin.lab.messaging.modern;

import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;

@Component
// Design Pattern: Messaging Gateway
// Zweck: Fachcode muss weder Connection noch Session verwalten.
public class OrderMessagePublisher {
    private final JmsTemplate jmsTemplate;
    public OrderMessagePublisher(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void publish(String orderNumber) {
        jmsTemplate.convertAndSend("orders", orderNumber);
    }
}

Modern-Code

projects/07-messaging/modern/src/main/java/at/aydin/lab/messaging/modern/OrderMessageListener.java

package at.aydin.lab.messaging.modern;

import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

@Component
// Design Pattern: Message-Driven Consumer
// Zweck: Nachrichten werden ereignisorientiert verarbeitet.
public class OrderMessageListener {
    @JmsListener(destination = "orders")
    public void receive(String orderNumber) {
        System.out.println("Empfangen: " + orderNumber);
    }
}
Legacy
MessagingLegacyApplication.java
Modern
MessagingModernApplication.java + MessageDemoRunner.java

Migrationsbedeutung: Embedded-Broker-Demo wird Spring-Boot-Komposition.

Legacy-Code

projects/07-messaging/legacy/src/main/java/at/aydin/lab/messaging/legacy/MessagingLegacyApplication.java

package at.aydin.lab.messaging.legacy;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;

public final class MessagingLegacyApplication {
    private MessagingLegacyApplication() {
    }

    public static void main(String[] args) throws Exception {
        BrokerService broker = new BrokerService();
        broker.setBrokerName("legacy-demo");
        broker.setPersistent(false);
        broker.addConnector("vm://legacy-demo");
        broker.start();
        try {
            LegacyJmsGateway gateway = new LegacyJmsGateway(new ActiveMQConnectionFactory("vm://legacy-demo?create=false"));
            gateway.send("orders", "ORDER-1001");
            System.out.println("Empfangen: " + gateway.receive("orders", 2000));
        } finally {
            broker.stop();
        }
    }
}

Modern-Code

projects/07-messaging/modern/src/main/java/at/aydin/lab/messaging/modern/MessagingModernApplication.java

package at.aydin.lab.messaging.modern;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MessagingModernApplication {
    public static void main(String[] args) {
        SpringApplication.run(MessagingModernApplication.class, args);
    }
}

Modern-Code

projects/07-messaging/modern/src/main/java/at/aydin/lab/messaging/modern/MessageDemoRunner.java

package at.aydin.lab.messaging.modern;

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class MessageDemoRunner implements CommandLineRunner {
    private final OrderMessagePublisher publisher;
    public MessageDemoRunner(OrderMessagePublisher publisher) {
        this.publisher = publisher;
    }

    @Override
    public void run(String ... args) {
        publisher.publish("ORDER-2001");
    }
}

Umsetzungsplan mit Qualitäts-Gates

  1. Arbeitspaket 1
    JMS-Header, Payload, Queue-Namen und Acknowledgement-Modus inventarisieren. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  2. Arbeitspaket 2
    Publisher und Listener hinter fachliche Ports legen. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  3. Arbeitspaket 3
    Serializer/Deserializer mit Schema- und Versionsprüfung einführen. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  4. Arbeitspaket 4
    Idempotenzspeicher, Redelivery und DLQ-Regeln konfigurieren. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  5. Arbeitspaket 5
    Legacy ActiveMQ Classic nur für Vergleich/Übergang nutzen und Broker-Upgrade planen. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  6. Arbeitspaket 6
    Produzenten und Konsumenten gestaffelt migrieren; gemischte Versionen testen. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.

Konkreter Test- und Abnahmekatalog

IDEbenePrüfungerforderlicher Nachweis
07-MESSAGING-A01Integration/ContractDoppelte Nachricht erzeugt nur eine fachliche Änderung.Automatisierter Test und CI-Protokoll
07-MESSAGING-A02Integration/ContractFehlerhafte Nachricht landet nach definierter Anzahl Versuche in der DLQ.Automatisierter Test und CI-Protokoll
07-MESSAGING-A03Integration/ContractCorrelation-ID bleibt erhalten.Automatisierter Test und CI-Protokoll
07-MESSAGING-A04Integration/ContractShutdown verliert keine bestätigten Nachrichten.Automatisierter Test und CI-Protokoll
07-MESSAGING-A05Integration/ContractBroker- und Clientversion sind security-geprüft.Automatisierter Test und CI-Protokoll
07-MESSAGING-T01UnitFachlogik ohne Container oder externen Dienst testen.Unit-Test
07-MESSAGING-T02RegressionLegacy- und Modern-Ergebnis für denselben Golden-Master-Vektor vergleichen.Vergleichsreport
07-MESSAGING-T03NegativeFehlerhafte, leere und grenzwertige Eingaben prüfen.Negativtest
07-MESSAGING-T04OperationsStart, Health, Shutdown und Konfigurationsfehler prüfen.Deployment-/Startprotokoll
07-MESSAGING-T05ContractProtokoll, Status/Headers, Payload und Timeout gegen Consumer Contract prüfen.Contract-Test
07-MESSAGING-F01Fokusredelivery, idempotency, DLQ and serialization testsModulspezifischer Testreport

Risikoregister des Moduls

RisikoAuswirkungGegenmaßnahmeGate
duplicate delivery, poison messages and vulnerable legacy brokerMITTELredelivery, idempotency, DLQ and serialization testsvor Cutover
Neue Framework-/Library-LaufzeitabhängigkeitenMITTELSBOM, Lizenz-, CVE- und transitive Dependency-Prüfung im Online-Buildvor Release
Legacy-Verhalten war implizit an entfernte Library gekoppeltMITTELContract-/Golden-Master-Tests und gezielte Fehlerpfadevor Abschaltung

Rollback und Koexistenz

Über eine Bridge oder dualen Publisher kann auf den bisherigen Broker zurückgeschaltet werden; Idempotenz muss über beide Pfade gelten.

Abbruchkriterien

  • Fachlicher Golden-Master weicht ab.
  • Daten-, Nachrichten- oder API-Kompatibilität ist ungeklärt.
  • Fehlerquote, Latenz oder Ressourcenverbrauch überschreiten das vereinbarte Limit.
  • Monitoring oder Rückfallpfad ist nicht funktionsfähig.

Definition of Done

Direkte Arbeitslinks

⌂ Cockpit