Migration
SAP PI 7.4 uitfaseren: ons EDI-cutoverplan in negen weken
Een SAP PI 7.4-omgeving, 14 AS2- en X.400-partners, één EDI-coördinator die al maanden niet goed slaapt. Dit is het cutoverplan dat we echt geleverd hebben.

Het is een dinsdagochtend in februari. Marleen, de EDI-coördinator bij een Nederlandse logistieke integrator met 14 partnerflows, stuurt ons een SAP-melding door. De extended maintenance voor PI 7.4 eindigt binnen hun boekjaar. Drie van hun grootste partners eisen nu contractueel AS2-MDN's binnen tien seconden. De huidige middleware haalt die norm op een rustige dag niet, en op Black Friday loopt het uit naar minuten.
In april hadden we het SAP PI-cluster uitgefaseerd en routeerden we elke AS2- en X.400-flow via Temporal-workflows achter een Cloudflare Workers ingress. De cutover duurde negen weken parallel draaien, en het meeste werk was niet wat we in week nul hadden verwacht. Dit is het draaiboek, in de volgorde die ertoe deed.
Het landschap waar we binnenliepen
Eén fysiek SAP PI 7.4-cluster op RHEL, twee nodes, Oracle 19c eronder. Java-mappings geschreven tussen 2013 en 2019, voornamelijk door mensen die inmiddels weg waren. Veertien partnerkanalen: negen AS2 over HTTPS, vier X.400 via een OpenText BizManager-gateway, één privé FTPS-endpoint dat niemand volledig kon uitleggen. Gemiddeld inkomend volume rond de 8.000 berichten per dag, piek 40.000 rond kwartaaleinde.
De 'documentatie' was een Excel-bestand met 47 tabbladen uit 2017, een AS2-partnermatrix in Confluence in drie verschillende, elkaar tegensprekende versies, en de rest in de hoofden van twee mensen. Eén van die twee had zijn pensioen aangekondigd.
De opdracht: alle bestaande partnerrelaties intact houden zonder certificaatonderhandelingen af te dwingen, de SAP-licentie schrappen, de tien-seconden-MDN-klok halen, en klaar zijn voor het einde van het boekjaar. De impliciete subtekst: verlies onder geen enkele omstandigheid een leveringsbon.
Waarom we geen andere middleware hebben gekocht
Het eerlijke antwoord is dat we het wel hebben overwogen. SAP Integration Suite, MuleSoft, Boomi en een OpenText-replatform kregen elk een afwegingsexercitie van één pagina. Drie dingen deden de balans doorslaan richting een combinatie van Temporal en Cloudflare Workers:
- EDI-flows draaien lang en zijn met regelmaat instabiel. Het durable-execution-model van Temporal sluit precies aan op die realiteit, zonder dat we retry, timeout en history opnieuw hoeven uit te vinden. De workflow-primitieven coderen al de semantiek die we anders zelf zouden bouwen.
- AS2 is in de kern HTTPS plus signed payloads en MDN-bevestigingen, gedefinieerd in RFC 4130. Cloudflare Workers termineren TLS aan de edge, geven ons client-certificaten per partner via één configuratie-object, en draaien nooit een JVM die om 2 uur 's nachts gepatcht moet worden.
- Het ops-team van de klant bestaat uit twee mensen. Zij moesten op een zondagmiddag het runbook kunnen lezen zonder bang te worden. JavaScript en TypeScript waren al in huis. Een ESB-DSL niet.
X.400 was de lastige. We hielden de OpenText-gateway op zijn plek en behandelden hem als een MTA, met een dunne shim die inkomend X.400 omzette naar AS2-vormige envelopes voordat ze bij Temporal binnenkwamen. Het vervangen van X.400-terminatie is een apart project waar niemand budget voor had, en dat was prima.
De vorm van die negen weken
We knipten de kalender in vier fasen. Weken 1 tot 2 waren observatie. Weken 3 tot 4 waren bouwen. Weken 5 tot 6 waren shadow mode. Weken 7 tot 8 waren cutover, partner voor partner. Week 9 was decommissioning en overdracht. Geen enkele fase werd van begin tot eind door hetzelfde team gedraaid. Observatie vroeg een senior integration engineer naast Marleen. Bouwen vroeg twee engineers met de kop in het werk. Shadow vroeg iemand die dashboards in de gaten hield. Cutover vroeg dezelfde engineer die het kanaal voor die partner had gebouwd.
Weken 1 en 2: de waarheid op de lijn in kaart brengen
Voordat we ook maar één regel nieuwe code schreven, tapten we het bestaande PI-cluster af. Elke inkomende AS2-request en elke uitgaande MDN werd twee volle weken lang gemirrord naar een S3-bucket, raw bytes plus headers. Aan het einde van week twee hadden we 110.000 echte berichten op disk: geanonimiseerd, gehasht per partner, opnieuw afspeelbaar.
Dit is de stap die niemand begroot en iedereen nodig heeft. De Excel-mapping zat op 11 plekken fout. Twee partners stuurden content-types waarvan de documentatie zei dat ze onmogelijk waren. Eén partner hergebruikte hetzelfde Message-ID bij retries, en dat had het PI-cluster per ongeluk opgevangen.
Begin een EDI-migratie nooit alleen vanuit de documentatie. Vang twee weken echt verkeer op voordat je beslist wat je nieuwe systeem moet accepteren. De lijn is het contract. De wiki is de wens.
Weken 3 en 4: de Workers-ingress en de Temporal-kern
De architectuur was bewust saai. Per partner-facing hostname draaide één Cloudflare Worker die TLS termineerde en het client-certificaat van de partner valideerde. De Worker deed drie dingen en stopte: controleren of de request op AS2 leek, de raw body in R2 droppen, en een Temporal-workflow starten.
export default {
async fetch(req: Request, env: Env): Promise<Response> {
if (req.method !== 'POST') return new Response('method not allowed', { status: 405 });
const partnerId = await resolvePartner(req, env);
if (!partnerId) return new Response('unknown partner', { status: 401 });
const body = await req.arrayBuffer();
const messageKey = `inbound/${partnerId}/${crypto.randomUUID()}`;
await env.R2.put(messageKey, body, {
httpMetadata: { contentType: req.headers.get('content-type') ?? '' }
});
const headers = Object.fromEntries(req.headers);
const handle = await env.TEMPORAL.startWorkflow('inboundAs2', {
taskQueue: 'edi-as2',
args: [{ partnerId, messageKey, headers }]
});
return new Response(`accepted ${handle.workflowId}`, { status: 202 });
}
};De Temporal-workflow zelf is even klein. Hij decodeert, dispatcht naar de back-office (in dit geval SAP S/4HANA), en tekent de MDN. Activities pakken het rommelige werk op: certificaat-lookups, schemavalidatie, de IDoc-vertaling.
import { proxyActivities } from '@temporalio/workflow';
import type * as activities from './activities';
const { decode, dispatchToBackoffice, signAndSendMdn } = proxyActivities<typeof activities>({
startToCloseTimeout: '90 seconds',
retry: { maximumAttempts: 6, backoffCoefficient: 2 }
});
export async function inboundAs2(input: InboundInput) {
const message = await decode(input);
await dispatchToBackoffice(message);
return signAndSendMdn(message);
}Het hele workflow-bestand paste op een telefoonscherm, en dat was belangrijk. De PI Java-mapping die het verving telde 380 regels en riep vier UDF's aan. Als er om 3 uur 's nachts iets sneuvelde, moest Marleen de code kunnen lezen, niet ontcijferen.
Weken 5 en 6: shadow mode en reconciliatie
Twee weken lang verwerkten beide systemen elk bericht. De Worker accepteerde de inkomende request, schopte een Temporal-workflow af, en speelde tegelijk de request opnieuw af richting het bestaande PI-cluster. De MDN die naar de partner terugging kwam onveranderd uit PI. De nieuwe stack deed alles parallel, behalve antwoorden.
De reconciliatielogica was een Postgres-tabel en één lelijke query. Elke workflow schreef een rij in shadow_runs met zijn gedecodeerde velden, de back-office-ack en de voorgestelde MDN. Een geplande job haalde de bijbehorende PI-run uit de legacy audit log en zette ze naast elkaar.
select partner_id,
count(*) filter (where new_outcome = 'mdn_signed') as new_ok,
count(*) filter (where pi_outcome = 'MDNSent') as pi_ok,
count(*) filter (where new_outcome = 'mdn_signed'
and pi_outcome = 'MDNSent'
and new_doc_hash = pi_doc_hash) as both_ok,
count(*) filter (where new_outcome = 'mdn_signed'
and pi_outcome = 'MDNSent'
and new_doc_hash <> pi_doc_hash) as diverged
from edi_shadow_runs
where window_start >= now() - interval '24 hours'
group by partner_id
order by partner_id;We hielden twee regels aan. Eén: elke partner met meer dan nul afwijkende rijen over een venster van 24 uur blokkeerde de cutover voor die partner. Twee: elke partner waarvan new_ok onder de 99,95% van pi_ok zat, blokkeerde de cutover. Vijf partners haalden beide regels aan het einde van week vijf. Drie hadden schema-fixes in onze decode-activity nodig. Twee van de X.400-partners legden timingproblemen bloot in de OpenText-shim, die nog een week kostten om uit te trillen.
Weken 7 en 8: cutover, partner voor partner
We flipten één partner per werkdag. De Worker-config schoof die partner van 'shadow PI' naar 'antwoord vanuit Temporal'. Het PI-kanaal bleef draaien, maar verstuurde geen MDN's meer. We keken 48 uur mee voordat we de volgende partner omzetten. Twee partners hadden een korte heads-up nodig richting hun ops-team dat er een nieuw IP-adres op hun AS2-ACL zou verschijnen. Geen enkele vroeg om een certificaatonderhandeling, wat het uitgesproken ontwerpdoel was geweest.
Eén partner-cutover liep mis. Een retry storm tijdens een regionale Azure-storing aan de kant van de partner overspoelde onze Worker met dubbele Message-ID's. De idempotency key van de Temporal-workflow was het AS2 Message-ID, geen hash van body plus Message-ID, en we tekenden uiteindelijk twee MDN's voor dezelfde logische levering. De partner merkte het niet. Wij wel, hebben de activity gefikst en een regressietest toegevoegd die de exacte pcap opnieuw afspeelt.
Week 9: decommissioning en het runbook
SAP PI 7.4 offline halen is geen knop. We hebben de queues leeggetrokken, het Oracle-schema gearchiveerd, het cluster nog 30 dagen in 'read-only, geen inbound'-stand laten staan als audit-fallback, en pas daarna afgesloten. De Oracle-licentie-opzegging liep apart via finance en de besparing landde in de boeken van het volgende kwartaal.
Het runbook telde vier pagina's. Eén pagina per veelvoorkomende failure mode (certificaat verloopt, partner down, back-office down, Temporal worker queue volgelopen), elk met de exacte CLI-commando's. We pinden hem in het ops-Slack-kanaal. De zondagmiddagtest was een echte: Marleen draaide in haar eentje een gesimuleerde partnerstoring, end-to-end, terwijl wij read-only meekeken via Zoom.
Wat we opnieuw zouden doen, en wat we zouden overslaan
De twee weken verkeerscapture voor er ook maar één regel code stond, hebben zichzelf vijf keer terugverdiend. Sla die over, en in week vijf vliegen de wielen eraf in shadow mode. De Workers-ingress als de domst denkbare dunne laag bouwen was de tweede onverhandelbare keuze: alle logica die in de Worker zit, kun je niet opnieuw afspelen vanuit R2.
Wat we de volgende keer overslaan: zelf een diff-job schrijven. Er bestaat een kleine maar reële categorie EDI-bewuste reconciliatietools die ons een week hadden gescheeld. Wij bouwden de onze omdat de schema-mappings specifiek waren, maar de volgende klant krijgt op zijn minst een offerte van de off-the-shelf-optie te zien.
Wat we onderschat hadden: het politieke werk. Twee van de partnercontacten hadden in tien jaar tijd persoonlijk vertrouwen opgebouwd met de voorganger van Marleen. Zij wilden eerst een Zoom-call voordat de ACL aangepast werd, geen ticket. We hebben 'stuur een mens een mail' als expliciete stap aan de cutover-checklist toegevoegd na het derde beleefde verzoek.
De vorm die werkelijk uitmaakte
Als je één ding meeneemt uit dit draaiboek, neem dan de cadans mee. Twee weken kijken, twee weken bouwen, twee weken shadowen, twee weken flippen, één week opruimen. Druk één fase samen en de volgende absorbeert de kosten met rente. Rek ze op en het team verliest de draad. Negen weken was geen gok; het was het kleinste venster dat elke fase ruimte gaf om adem te halen.
Toen we de EDI-stack voor deze logistieke klant bouwden, struikelden we steeds over de X.400-timing-shim. We hebben het opgelost door de OpenText-gateway als een ondoorzichtige MTA te behandelen en Temporal alle retry-semantiek aan onze kant van de lijn te laten bezitten. Die beslissing is degene waar we nu als eerste naar grijpen bij elke legacy-migratie waarbij het protocol op de lijn ouder is dan het team dat het ondersteunt.
Een redelijke vijf-minuten-audit voor je eigen integratielandschap: open de outbound logs van je middleware van gisteren en tel hoeveel verschillende error-klassen er voorbijkwamen. Is dat aantal groter dan het aantal pagina's in je runbook, dan heb je geen runbook. Dan heb je een wiki.
Kern
Twee weken kijken, twee weken bouwen, twee weken shadowen, twee weken flippen, één week opruimen. Druk er één samen en de volgende betaalt de rekening.
FAQ
Waarom Temporal en niet een event-driven architectuur op Kafka?
Lang-lopende retries met volledige audit-history zitten er standaard in. Kafka had een state machine erbovenop nodig gehad, en dat is ongeveer de helft van wat Temporal je sowieso al geeft.
Kunnen Cloudflare Workers echt AS2 mutual TLS aan?
Ja. Client-certificaatvalidatie per zone termineert het partnercertificaat aan de edge. Wij leidden de partneridentiteit af uit de cert subject en wezen al het andere af voordat Temporal in beeld kwam.
Hoe hebben jullie downtime tijdens de cutover voorkomen?
Twee weken shadow-verwerking, daarna één partner per werkdag omgezet op de Worker-config-laag. Het oude PI-cluster bleef 30 dagen read-only als audit-fallback.
Welke week was de zwaarste van het project?
Week zes. Een X.400-timing-verschil door de OpenText-shim hield twee partners tegen op de reconciliatie-gate, totdat we Temporal alle retry-semantiek aan onze kant van de lijn lieten bezitten.