Lab 05

SOAP Legacy Integration

SOAP-Anbindung als Anti-Corruption Layer statt Legacy-Kopplung im Domain-Code.

SOAPACLAdapterTimeout
SchwierigkeitMittel+
Dauer75–105 Min
LernstufeStufe 6 – Praxislabor und Capstone
Arbeitsweise: Erst den Vorher-Code öffnen, dann Analyse lesen, anschließend die Nachher-Lösung und den Test vergleichen. Alle Abschnitte sind standardmäßig geschlossen.

Workshop-Slice

Dieses Lab zeigt einen konkreten Enterprise-Fehler mit Vorher/Nachher-Code. Die Beispiele sind bewusst fachlich benannt, damit du Architekturentscheidungen und nicht nur Syntax übst.

Lernziel

Du kapselst SOAP-Legacy-Systeme hinter einem Port und schützt Domain und Use Cases vor WSDL-/XML-Details.

Ausgangslage

Ein Billing-Use-Case ruft direkt einen generierten SOAP-Client auf. Dadurch hängen Fachlogik, XML-Struktur, Timeout und Fehlerbehandlung zusammen.

Vorher: problematischer Code

Die generierten SOAP-Klassen sickern in die Application-Schicht. Fachliche Tests benötigen plötzlich SOAP-Stubs.

BillingService.javaJAVA
class BillingService {
    private final LegacyBillingPortType soap;

    void createInvoice(Order order) {
        CreateInvoiceRequest request = new CreateInvoiceRequest();
        request.setCustomerNo(order.customerId().value());
        request.setAmount(order.total().asBigDecimal());

        CreateInvoiceResponse response = soap.createInvoice(request);
        if (!"OK".equals(response.getStatus())) {
            throw new RuntimeException(response.getErrorText());
        }
    }
}
Analyse: Was ist daran schlecht?
  • Application-Schicht kennt generierte SOAP-Klassen.
  • Legacy-Statuscodes werden nicht fachlich übersetzt.
  • Timeout/Retry-Verhalten ist nicht dokumentiert.
  • Änderungen an der WSDL brechen fachliche Services.
Nachher: bessere Lösung

Der SOAP-Client sitzt in einem Adapter. Der Use Case spricht nur mit einem fachlichen Port.

InvoicePort.javaJAVA
// Pattern: Port
// Zweck: Application-Schicht kennt nur fachliche Operationen, nicht SOAP/XML.
public interface InvoicePort {
    InvoiceId createInvoiceFor(ConfirmedOrder order);
}
SoapInvoiceAdapter.javaJAVA
// Pattern: Adapter / Anti-Corruption Layer
// Zweck: Legacy SOAP wird in ein modernes fachliches Modell übersetzt.
public final class SoapInvoiceAdapter implements InvoicePort {
    private final LegacyBillingPortType soap;
    private final SoapInvoiceMapper mapper;

    public InvoiceId createInvoiceFor(ConfirmedOrder order) {
        CreateInvoiceRequest request = mapper.toSoap(order);
        CreateInvoiceResponse response = soap.createInvoice(request);

        if ("DUPLICATE".equals(response.getStatus())) {
            throw new InvoiceAlreadyExists(order.orderId());
        }
        if (!"OK".equals(response.getStatus())) {
            throw new LegacyBillingUnavailable(response.getErrorText());
        }
        return new InvoiceId(response.getInvoiceNo());
    }
}
soap-client-timeout.ymlYAML
legacy:
  billing:
    endpoint: https://billing-legacy.local/ws
    connect-timeout: 2s
    read-timeout: 5s
    retry:
      max-attempts: 2
      retry-on: CONNECT_TIMEOUT,HTTP_503
Test / Prüfnachweis

Dieser Abschnitt zeigt, wie du die Verbesserung nachweist. Es ist bewusst kein reiner Happy-Path-Test, sondern prüft ein Risiko aus dem Vorher-Teil.

SoapInvoiceAdapterTest.javaJAVA
@Test
void mapsDuplicateStatusToDomainException() {
    var soap = new FakeLegacyBillingPort("DUPLICATE", "Invoice exists");
    var adapter = new SoapInvoiceAdapter(soap, new SoapInvoiceMapper());

    assertThrows(InvoiceAlreadyExists.class,
        () -> adapter.createInvoiceFor(confirmedOrder("O-77")));
}
Typische Fehler
  • SOAP-Klassen im Domain-Modell verwenden.
  • Jeden Legacy-Fehler als RuntimeException weiterreichen.
  • Timeouts nur implizit über Container-Defaults lassen.
  • XML-Felder 1:1 als interne API übernehmen.
Deep-Learning-Bezug

Die Links führen zum ausführlichen Inhalt; die Deep-Learning-Seite bleibt nur die Lernlandkarte und kopiert den Inhalt nicht doppelt.

Prüfcheckliste
  • Use Case importiert keine SOAP-Klasse.
  • Adapter übersetzt Legacy-Fehler fachlich.
  • Timeouts sind konfiguriert und dokumentiert.
  • Mapper enthält keine fachliche Entscheidung.
⌂ Cockpit