Legacy-Modernisierung mit Spring: Blueprint für Java EE, EJB, SOAP, JMS und Batch
Dieses Kapitel verbindet die Bausteine zu einem Modernisierungsplan: Legacy verstehen, Grenzen schneiden, Tests aufbauen, Adapter kapseln und schrittweise ersetzen.
Fachliche Einordnung
Spring-Migration ist keine Annotationsersetzung. EJB, JTA, SOAP, JMS, JSP und Batch müssen fachlich verstanden und technisch sicher umgezogen werden.
Technische Darstellung
Kernkonzepte
- Strangler Fig Pattern über Gateway und Anti-Corruption Layer.
- Ports & Adapters für Legacy-Adapter.
- Characterization Tests vor Refactoring.
- Transactional Outbox für Event-Migration.
- Modulith als Zwischenstufe vor Microservices.
Wann einsetzen?
- Du migrierst WebSphere/JBoss/GlassFish Anwendungen.
- Du willst alte SOAP/JMS/Batches in Spring kapseln.
- Du brauchst einen risikoarmen Phasenplan.
Typische Fehler und Risiken
- Big Bang Rewrite ohne fachlichen Beweis.
- Legacy-Datenmodell ungeprüft in neue Services kopieren.
- Nur technische Migration ohne fachliche Modulgrenzen.
Legacy- und Modernisierungssicht
Phase 1: lesen und testen. Phase 2: Adapter kapseln. Phase 3: neue Use Cases in Spring. Phase 4: Daten- und Eventgrenzen stabilisieren. Phase 5: Legacy Stück für Stück abschalten.
Ausführliches Beispiel
Das Beispiel zeigt bewusst nicht nur Annotationen, sondern auch die Verantwortung der Schicht. In echten Projekten sollte der technische Spring-Code an Adapter- oder Konfigurationsrändern bleiben, während die Fachlogik testbar und möglichst frameworkarm bleibt.
// Ports & Adapters: Domäne kennt SOAP/JMS/JPA nicht.
public interface LegacyCustomerPort {
CustomerSnapshot findCustomer(String customerNumber);
}
@Component
class SoapLegacyCustomerAdapter implements LegacyCustomerPort {
private final LegacyCustomerSoapClient client;
@Override
public CustomerSnapshot findCustomer(String customerNumber) {
LegacyCustomerResponse response = client.getCustomer(customerNumber);
return CustomerMapper.fromLegacy(response); // Anti-Corruption Layer
}
}
@Service
class ModernOrderUseCase {
private final LegacyCustomerPort customers;
private final OrderRepositoryPort orders;
@Transactional
public OrderId place(PlaceOrder command) {
CustomerSnapshot customer = customers.findCustomer(command.customerNumber());
Order order = Order.placeFor(customer, command.lines());
orders.save(order);
return order.id();
}
}
Checkliste für Reviews
- Ist die Verantwortung des Bausteins klar: Framework steuert Lebenszyklus, Library wird gezielt benutzt?
- Ist die Fachlogik außerhalb von Controller, Listener, Repository-Implementierung oder Konfiguration?
- Sind Fehlerfälle, Timeouts, Security, Monitoring und Tests sichtbar modelliert?
- Gibt es klare Grenzen zwischen DTO, Domäne, Persistence und Infrastruktur?