Diese Seite bleibt auch ohne JavaScript lesbar. Suche und Buttons sind Zusatzkomfort.
Legacy-Migration zu Spring Enterprise
Von EJB, SOAP, JSP, JTA und EAR zu Spring Boot, REST, Events, Observability und Cloud-Betrieb.
1. Nicht Big Bang, sondern kontrollierter Schnitt
Alte Java-Enterprise-Systeme enthalten oft EJB-Services, SOAP-Schnittstellen, JTA-Transaktionen, JMS, JSP/JSF, EAR-Deployment und Application-Server-spezifische Konfiguration. Eine direkte Komplettmigration ist riskant, weil Fachlogik, technische Infrastruktur und Betriebswissen vermischt sind.
Ein Strangler-Ansatz schneidet fachliche Fähigkeiten nach und nach heraus. Neue Spring-Services übernehmen klar begrenzte Funktionen, während Adapter die alte Welt schützen. Jeder Schritt braucht Vertrag, Messung, Rollback und Datenstrategie.
@Component
class LegacyCustomerAdapter implements CustomerPort {
private final LegacyCustomerSoapClient soap;
LegacyCustomerAdapter(LegacyCustomerSoapClient soap) {
this.soap = soap;
}
@Override
public CustomerSnapshot load(CustomerId id) {
LegacyCustomerResponse response = soap.getCustomer(id.value());
return new CustomerSnapshot(
CustomerId.of(response.customerNumber()),
response.fullName(),
CustomerStatus.fromLegacyCode(response.statusCode())
);
}
}
2. Migration von EJB Service zu Spring Use Case
Ein alter EJB-Service mischt häufig Remote-Schnittstelle, Transaktion, Fachlogik und technische Lookups. Bei der Migration sollte zuerst die Fachoperation isoliert werden. Danach wird sie als Spring Service mit expliziten Ports, Transaktionsgrenze und Tests aufgebaut.
@Stateless
public class OrderFacadeBean implements OrderFacadeRemote {
@Resource private SessionContext ctx;
@EJB private PricingBean pricing;
@PersistenceContext private EntityManager em;
public String submit(OrderDto dto) {
// Validierung, Preisberechnung, Persistenz, Audit und JMS in einer Methode
// schwer testbar und stark an Container gekoppelt
return "OK";
}
}
@Service
class SubmitOrderUseCase {
private final PricingPort pricing;
private final OrderRepository orders;
private final EventPublisher events;
@Transactional
public OrderId submit(SubmitOrder command) {
Price price = pricing.calculate(command.lines());
Order order = Order.submit(command.customerId(), command.lines(), price);
orders.save(order);
events.publishAfterCommit(new OrderSubmitted(order.id()));
return order.id();
}
}
3. Datenstrategie
Die schwierigste Frage ist oft nicht REST oder Boot, sondern Daten. Gemeinsame Datenbank, geteilte Tabellen, Stored Procedures und implizite Nebenwirkungen müssen sichtbar gemacht werden. Optionen sind Read-only-Zugriff, separate Read Models, Outbox Pattern, CDC oder schrittweise Tabellenhoheit.
CREATE TABLE outbox_event (
id UUID PRIMARY KEY,
aggregate_id UUID NOT NULL,
event_type VARCHAR(120) NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMP NOT NULL,
published_at TIMESTAMP NULL
);
Enterprise-Prüffragen
- Fachliche Fähigkeit klar geschnitten?
- Adapter schützt neue Domäne vor Legacy-Modell?
- Datenhoheit und Synchronisation geklärt?
- Rollback und Monitoring pro Migrationsschritt vorhanden?