5. PortalIntegrationFacade: ein fachlicher Einstiegspunkt
Die Facade bündelt den Portal Use Case und verhindert, dass Controller technische Backend-Reihenfolgen kennen müssen.
Die PortalIntegrationFacade ist keine gigantische God Class. Sie ist ein schmaler fachlicher Einstiegspunkt. Sie nimmt Commands entgegen, startet einen Prozess und gibt ein Ergebnis zurück, das für die Oberfläche geeignet ist. Der Controller muss nicht wissen, ob zuerst REST, dann SOAP, dann JMS oder erst SOAP, dann Datenbank aufgerufen wird.
In Version 3 ist die Facade der wichtigste Schutz gegen eine Portal-Schicht, die mit jedem neuen Backend unübersichtlicher wird.
PortalIntegrationFacade mit markiertem Facade Pattern
package com.example.portal.integration;
import com.example.portal.domain.SubmitPortalOrderCommand;
import com.example.portal.integration.process.PortalOrderProcessManager;
/**
* Design Pattern: Facade.
* Zweck: Das Portal bekommt einen einfachen fachlichen Einstiegspunkt
* und kennt keine SOAP-, REST-, JMS- oder DB-Details.
*/
public final class PortalIntegrationFacade {
private final PortalOrderProcessManager processManager;
public PortalIntegrationFacade(PortalOrderProcessManager processManager) {
this.processManager = processManager;
}
public PortalOrderResult submitOrder(SubmitPortalOrderCommand command) {
return processManager.start(command);
}
public PortalOrderStatusView status(String trackingId) {
return processManager.loadStatus(trackingId);
}
}
6. Adapter pro Zielsystem: SOAP, REST, JMS und DB sauber trennen
Jedes Zielsystem bekommt einen Adapter, der Protokoll, Datenformat, Timeout, Fehler und Mapping kapselt.
Ein SOAP Adapter verarbeitet WSDL-Port, SOAP Headers, Faults und JAXB Mapping. Ein REST Adapter verarbeitet HTTP-Statuscodes, JSON-Strukturen und API-Versionen. Ein JMS Adapter verarbeitet Queues, Topics, Message Properties und Wiederholbarkeit. Diese Unterschiede dürfen nicht in Controller oder Prozesslogik zerstreut werden.
Die Prozesslogik ruft eine fachliche Schnittstelle auf: reserveInventory, createOrder, createInvoice, publishNotification. Wie das konkret technisch geschieht, ist Adapter-Sache.
- Adapter übersetzen Richtung Backend und zurück.
- Adapter mappen Backend-Fehler in stabile Prozessfehler.
- Adapter enthalten technische Policies wie Timeout, Retry und Header.
- Adapter sind sehr gut testbar, weil sie klare Grenzen haben.
OrderSoapAdapter mit Adapter Pattern und Fault Mapping
package com.example.portal.integration.adapter;
import com.example.portal.domain.OrderCommand;
import com.example.portal.integration.fault.BackendUnavailableException;
import com.example.portal.integration.fault.BusinessRejectedException;
import java.time.Duration;
/**
* Design Pattern: Adapter + Anti-Corruption Layer.
* Zweck: Der Prozess sieht eine fachliche createOrder-Methode.
* SOAP-Port, JAXB-Klassen und SOAP-Faults bleiben hier gekapselt.
*/
public final class OrderSoapAdapter implements OrderSystemPort {
private final GeneratedOrderSoapPort soapPort;
private final OrderSoapMapper mapper;
private final Duration timeout;
public OrderSoapAdapter(GeneratedOrderSoapPort soapPort,
OrderSoapMapper mapper,
Duration timeout) {
this.soapPort = soapPort;
this.mapper = mapper;
this.timeout = timeout;
}
@Override
public OrderSystemResult createOrder(OrderCommand command) {
try {
SubmitOrderRequest request = mapper.toSoapRequest(command);
SubmitOrderResponse response = soapPort.submitOrder(request, timeout);
return mapper.toDomainResult(response);
} catch (BusinessFaultException fault) {
throw new BusinessRejectedException(fault.code(), fault.message());
} catch (SoapTimeoutException timeoutException) {
throw new BackendUnavailableException("ORDER_SOAP_TIMEOUT", timeoutException);
}
}
}
7. Process Manager: die Reihenfolge der Systemaufrufe bewusst steuern
Der Prozess-Manager entscheidet, welche Systemaufrufe notwendig sind, welche synchron sein müssen und welche asynchron entkoppelt werden.
In einem Portal wirkt der Benutzerwunsch einfach: „Bestellung absenden“. Im Backend ist daraus ein Ablauf aus Prüfung, Reservierung, Bestellung, Rechnungsanlage, Audit und Benachrichtigung zu machen. Der Process Manager hält diesen Ablauf zusammen.
Ein Process Manager ist besonders wertvoll, wenn ein Schritt wiederholt werden darf, ein anderer nicht, ein fachlicher Fehler sofort angezeigt werden muss und ein technischer Fehler später erneut versucht werden kann.
- Fachliche Ablehnung: Benutzer bekommt sofort eine verständliche Meldung.
- Technischer Fehler: Prozessstatus wird RetryTechnical oder Pending.
- Nichtkritische Nebenwirkung: per Outbox/JMS entkoppeln.
- Kritische Backend-Referenzen: speichern, damit der Prozess fortsetzbar bleibt.
PortalOrderProcessManager mit Process Manager / Saga Pattern
package com.example.portal.integration.process;
import com.example.portal.domain.SubmitPortalOrderCommand;
import com.example.portal.integration.PortalOrderResult;
import com.example.portal.integration.adapter.BillingSystemPort;
import com.example.portal.integration.adapter.InventorySystemPort;
import com.example.portal.integration.adapter.OrderSystemPort;
import com.example.portal.integration.outbox.OutboxRepository;
/**
* Design Pattern: Process Manager / Saga.
* Zweck: Multi-System-Ablauf koordinieren, Status persistieren,
* technische Wiederholungen ermöglichen und fachliche Fehler stabil mappen.
*/
public final class PortalOrderProcessManager {
private final InventorySystemPort inventory;
private final OrderSystemPort orderSystem;
private final BillingSystemPort billing;
private final OutboxRepository outbox;
private final ProcessStateRepository states;
public PortalOrderResult start(SubmitPortalOrderCommand command) {
ProcessState state = states.create(command.correlationId(), command.partnerOrderId());
try {
inventory.reserve(command.toReservationCommand());
state.markInventoryReserved();
var orderResult = orderSystem.createOrder(command.toOrderCommand());
state.markOrderCreated(orderResult.backendOrderId());
var invoice = billing.createPreInvoice(orderResult.backendOrderId(), command.customerNumber());
state.markInvoiceCreated(invoice.invoiceNumber());
outbox.enqueueOrderAccepted(state.trackingId(), command.userId());
state.markCompleted();
return PortalOrderResult.accepted(state.trackingId(), orderResult.backendOrderId());
} catch (BusinessRejectedException rejected) {
state.markBusinessRejected(rejected.code(), rejected.getMessage());
return PortalOrderResult.rejected(state.trackingId(), rejected.code(), rejected.getMessage());
} catch (RuntimeException technical) {
state.markRetryTechnical(technical.getClass().getSimpleName());
return PortalOrderResult.pending(state.trackingId());
} finally {
states.save(state);
}
}
public PortalOrderStatusView loadStatus(String trackingId) {
return PortalOrderStatusView.from(states.find(trackingId));
}
}
14. Fault Mapping: Fehler über Systemgrenzen übersetzen
Ein Multi-System-Portal braucht ein zentrales Fehlerverständnis. Backend-Fehler dürfen nicht ungefiltert in die Oberfläche laufen.
SOAP Faults sind technisch nützlich, aber für Benutzer oft zu roh. REST-HTTP-Statuscodes sind ebenfalls keine Portal-Meldungen. Das Portal braucht eine stabile Fehler-Taxonomie: fachlich abgelehnt, technisch später versuchen, manuelle Prüfung, unerwarteter Fehler.
Der Adapter übersetzt Backenddetails in technische oder fachliche Prozessfehler. Die Oberfläche zeigt daraus verständliche Statusmeldungen, aber niemals interne Stacktraces, WSDL-Klassen oder Infrastrukturdetails.
- BusinessRejected: Benutzer kann Eingabe korrigieren oder weiß den Grund.
- RetryTechnical: Prozess wird später erneut versucht oder bleibt pending.
- ManualReview: Fachliche Klärung erforderlich.
- SecurityRejected: Berechtigung, Mandant oder Policy verletzt.
- Unexpected: Supportfall mit Tracking-ID.
FaultMapper als Strategy Pattern
/**
* Design Pattern: Strategy.
* Zweck: Je Backend/Fault-Typ kann eine andere Mapping-Strategie verwendet werden.
*/
public interface FaultMappingStrategy {
PortalProcessException map(Throwable backendException);
}
public final class SoapFaultMappingStrategy implements FaultMappingStrategy {
public PortalProcessException map(Throwable backendException) {
if (backendException instanceof BusinessFaultException business) {
return new BusinessRejectedException(business.code(), business.getMessage());
}
if (backendException instanceof SoapTimeoutException timeout) {
return new BackendUnavailableException("SOAP_TIMEOUT", timeout);
}
return new BackendUnavailableException("SOAP_UNKNOWN", backendException);
}
}
27. Modernisierung: Portal stabil halten, Kernsysteme schrittweise ersetzen
Das Portal kann eine Modernisierungsbrücke sein, wenn es nicht direkt an Legacy-Details klebt.
Mit Facade, Adapter und Anti-Corruption Layer kann das Portal zunächst weiter SOAP Backends nutzen. Später kann ein Adapter intern auf REST, Events oder einen neuen Service umgestellt werden, ohne die Oberfläche und den fachlichen Prozess komplett zu ändern.
Diese Architektur unterstützt Strangler-Modernisierung: Der alte SOAP-Vertrag bleibt so lange stabil, wie Partner und Legacy ihn brauchen, während intern neue Services entstehen.
- Phase 1: SOAP stabilisieren, Faults und Observability verbessern.
- Phase 2: Portal-Facade und Adapter einführen.
- Phase 3: Prozessstatus und Outbox einführen.
- Phase 4: einzelne Backend-Funktionen modern ersetzen.
- Phase 5: alte SOAP-Operationen kontrolliert ablösen.
28. Beispielprojekt: portal-soap-integration-v3
Das Beispielprojekt zeigt ein Maven-Multi-Modul-Skelett für Portal, Domäne, Integration und SOAP-Verträge.
Das Beispiel ist bewusst als Lernprojekt strukturiert. Es trennt portal-web, portal-domain, portal-integration und soap-contracts. Die Klassen sind kommentiert, damit sichtbar ist, welches Entwurfsmuster an welcher Stelle eingesetzt wird.
Die Architektur ist nicht als vollständige Produktionslösung gedacht, sondern als verständlicher Enterprise-Schnitt. Sie zeigt die Grenzen, die in echten Systemen entscheidend sind.
- portal-web: Controller, Formulare, ViewModels.
- portal-domain: Commands, Ergebnisse, fachliche Modelle.
- portal-integration: Facade, Process Manager, Adapter, Fault Mapping, Outbox.
- soap-contracts: WSDL, XSD, JAXB Binding Dateien.
- docs/design-patterns.md: Entwurfsmuster mit Zweck, Einsatzort und Begründung.
Maven Parent POM
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.enterprise</groupId>
<artifactId>portal-soap-integration-v3</artifactId>
<version>3.0.0</version>
<packaging>pom</packaging>
<modules>
<module>portal-domain</module>
<module>soap-contracts</module>
<module>portal-integration</module>
<module>portal-web</module>
</modules>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>