12. Billing SOAP: Rechnung als zweiter Legacy-Vertrag
Billing ist oft ein anderes Legacy-System mit eigener WSDL, eigener Fehlerlogik und eigener Transaktionssicht.
Billing darf nicht automatisch Teil derselben technischen Transaktion sein wie die Bestellung. In verteilten Systemen sind globale XA-Transaktionen über mehrere Legacy-Systeme riskant und schwer betreibbar. Stattdessen wird eine fachliche Prozesssicht geführt: Bestellung angelegt, Rechnung vorbereitet, Rechnung fehlgeschlagen, erneuter Versuch notwendig.
Das Portal sollte dem Benutzer nicht unbedingt eine technische Billing-Störung als Bestellabbruch anzeigen, wenn die Bestellung bereits angelegt wurde. Dann ist ein Pending-Status oft ehrlicher.
- Order erfolgreich, Billing erfolgreich: Completed.
- Order erfolgreich, Billing Timeout: PendingBillingRetry.
- Order erfolgreich, Billing fachlich abgelehnt: manuelle Klärung oder Kompensation.
- Order nicht erfolgreich: Billing nicht starten.
Billing SOAP Fault Detail
<soap:Fault xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Code><soap:Value>soap:Sender</soap:Value></soap:Code>
<soap:Reason><soap:Text xml:lang="de">Rechnung fachlich abgelehnt</soap:Text></soap:Reason>
<soap:Detail>
<bil:BusinessFault xmlns:bil="http://example.com/enterprise/billing/v1">
<bil:Code>CUSTOMER_BILLING_BLOCKED</bil:Code>
<bil:Message>Kunde ist für Rechnungserstellung gesperrt.</bil:Message>
</bil:BusinessFault>
</soap:Detail>
</soap:Fault>
16. Transaktionen: keine naive Gesamttransaktion über alle Systeme
Eine Portalbestellung ist ein verteilter Prozess. Eine einzige technische Datenbanktransaktion über Portal, SOAP, REST und JMS ist selten sinnvoll.
Früher wurden Enterprise-Systeme oft mit JTA/XA als globale Transaktion gedacht. In modernen Multi-System-Landschaften ist das häufig zu starr. SOAP Legacy, REST Services, Broker und Datenbank haben unterschiedliche Transaktionsmodelle. Der Portalprozess muss deshalb fachlich konsistent sein, nicht nur technisch atomar.
Die richtige Frage lautet: Welche Schritte müssen vor Benutzerantwort abgeschlossen sein? Welche Schritte dürfen später passieren? Welche Schritte brauchen Kompensation? Welche Schritte sind idempotent wiederholbar?
- Lokale Transaktion: Prozessstatus + Outbox speichern.
- Synchrone Calls: nur für direkt entscheidende Schritte.
- Asynchrone Calls: Benachrichtigung, Read-Model, Folgeprozesse.
- Kompensation: Reservierung freigeben, Bestellung stornieren, manuelle Klärung.
- Status statt Illusion: Pending ist oft ehrlicher als Schein-Erfolg.
17. Idempotenz: Doppelklick, Retry und verlorene Antworten beherrschen
Portalbenutzer klicken doppelt, Browser senden erneut, Netzwerke verlieren Antworten und Backends liefern Timeouts. Ohne Idempotenz entstehen doppelte Bestellungen.
Jede Portalbestellung braucht eine fachliche Idempotency-Key-Strategie. Ein PartnerOrderId, kombiniert mit Mandant und Benutzerkontext, kann verhindern, dass derselbe Benutzerwunsch mehrfach verarbeitet wird. Der Prozess prüft vor dem Start, ob bereits ein Prozess für diese fachliche Anfrage existiert.
Auch SOAP Backends sollten eine externe Referenz erhalten. Wenn die Antwort verloren geht, kann das Portal später über Status oder Suchoperation prüfen, ob die Bestellung bereits angelegt wurde.
- Idempotency Key = tenant + partnerOrderId + processType.
- Vor Start prüfen, ob Prozess schon existiert.
- Backend-Referenz speichern, sobald bekannt.
- Retry nur für technische Fehler, nicht für fachliche Ablehnungen.
- Antwortverlust als eigener Fall behandeln.
Idempotenz im Prozessstart
public PortalOrderResult start(SubmitPortalOrderCommand command) {
String idempotencyKey = command.tenant() + ":" + command.partnerOrderId();
return states.findByIdempotencyKey(idempotencyKey)
.map(existing -> PortalOrderResult.fromExisting(existing.trackingId(), existing.status()))
.orElseGet(() -> startNewProcess(command, idempotencyKey));
}
24. Testing: Portal-Integration zuverlässig testen
Multi-System-Kommunikation braucht Tests auf mehreren Ebenen: Mapping, Adapter, Prozess und End-to-End-Simulation.
Nicht jeder Test muss echte Backends starten. Mapper-Tests prüfen XML/DTO-Transformation. Adapter-Tests prüfen Fault- und Timeout-Mapping gegen Mockserver. Prozess-Tests prüfen Reihenfolge und Statusübergänge. Contract-Tests prüfen, ob WSDL/XSD noch kompatibel sind.
Für SOAP ist besonders wichtig, Beispiel-Envelopes und Faults als Testdaten zu versionieren. So erkennt man, ob eine scheinbar kleine Änderung die Interoperabilität bricht.
- Mapper-Test: Command -> SOAP Request -> Command Roundtrip.
- Adapter-Test: BusinessFault wird BusinessRejectedException.
- Process-Test: Billing Timeout führt zu PendingBillingRetry.
- Contract-Test: WSDL/XSD bleiben kompatibel.
- UI-Test: Doppelklick erzeugt keinen zweiten Prozess.
Prozess-Test mit Fake-Adaptern
@Test
void billingTimeoutKeepsOrderAndReturnsPendingStatus() {
InventorySystemPort inventory = new FakeInventory().alwaysReserved("R-1");
OrderSystemPort order = new FakeOrderSystem().created("O-4711");
BillingSystemPort billing = new FakeBilling().timeout();
PortalOrderProcessManager manager = testManager(inventory, order, billing);
PortalOrderResult result = manager.start(sampleCommand("PORTAL-1"));
assertEquals(PortalOrderState.RETRY_TECHNICAL, result.state());
assertEquals("Bitte nicht erneut absenden.", result.userHint());
}