How to migrate an SAP PI delivery interface with code and AI

Published

TLDR

Trace the SAP PI mapping and delivery rules, then rebuild a warehouse update with a message ledger, replay tests, and receiver acknowledgements.

Migration workflow for SAP Process Integration showing the existing workflow, replacement, output comparison and scheduled delivery

Migrate a selected SAP Process Integration (SAP PI) interface by extracting the message mapping and the runtime delivery contract together. Rebuild one warehouse delivery update in a parser, a transform, and a sender. Verify the resulting warehouse state and receipt under duplicate and out-of-order delivery.

When can a migrated SAP PI warehouse delivery update advance?

A SAP PI EOIO queue stalls after a receiver timeout. Check the affected queue's actual ordering authority and the receiver application receipt for its head message. A transport acknowledgment alone does not establish the business update.

Recovery. Reconcile the head operation by its stable key while holding later messages in that queue. Release the next eligible message after an accepted receipt or approved terminal disposition. Let independent queues progress and hold unresolved outcomes instead of replaying blindly.

Cutover check. Compare old and new message identities, receiver effects and application order per queue. In the fixture, Q-B finishes while Q-A waits, D42/2 follows D42/1, final quantities are 4 and 2, and the duplicate leaves exactly three distinct business effects.

Preserve the XI envelope, mapping and queue behavior for one delivery interface

Capture the ESR objects plus the deployed Integration Directory configuration and one observed sender-to-receiver trace. Record sender and receiver service, namespace/interface and version, message ID, QoS, queue or sequence ID where present, mapping version, adapter/channel and receiver business acknowledgement. Map the sender's actual sequencing authority into the replacement. For EOIO the queue ordering can come from the runtime or transport and the business payload may have no contiguous counter. Establish the supported handoff boundary before designing numeric sequence checks. SAP documents the separate risks of qRFC/XI restarts and queue cancellation. Parts of an XI message · SAP XI message restart

Interface layerDurable state in the successorWhat to prove
Envelope acceptancesender_system, interface_version, message_id, queue_key, source_order, payload_hash, statusAn existing ID with identical bytes is safe to acknowledge. A repeated ID with a new hash is an exception.
Mapping and validationmessage_id, mapping_revision, output_hash, normalized_delivery_id, quantity, unit, validation_statusCompare namespaces, XML leading zeros, decimal scale, empty elements and unmapped codes against the incumbent result.
Receiver transportoperation_key, queue_key, attempt, receiver_receipt, receiver_effect_stateIf a timeout follows a possible write, resolve it with the same operation key. A transport ACK is recorded separately from the application update.
Ordered releasequeue_key, committed_order, head_status, last_receiver_receiptA pending or failed head blocks its own queue. Other queues can progress. An operator-approved terminal disposition is audited before unlocking a head.

Build intake and dispatch as separate workers. The intake validates the envelope and writes one inbox row before ACK. For an ordered queue, the dispatcher leases one queue and selects the authorized head using the captured ordering contract. Persist mapping evidence before delivery. Compare receiver state, not just transport success. A confirmed application receipt lets the same transaction record the outcome and move the queue forward. Quarantine invalid messages under the route owner's approved disposition. An unrecognized receiver effect should block the affected head and surface it for reconciliation. Do not use a scheduled cleanup to delete ordered messages.

Prove that one stalled queue cannot reorder deliveries

In a synthetic interface where the producer supplies a contiguous ordinal, Q-A gets D42/1 quantity 6 then D42/2 quantity 4 and Q-B gets D99/1 quantity 2. Make D42/1's transport response disappear after acceptance. Replay its exact message ID. Q-B should finish, D42/2 must remain held and the status lookup for D42/1 must find the original receipt. Then release D42/2 and assert final quantities 4 and 2, exactly three accepted business effects, and the same per-queue order. Repeat with D42/2 arriving first. At cutover checkpoint every queue independently and reconcile unresolved or cancelled entries before redirecting any sender.

Capture the interface contract

In the Enterprise Services Repository (ESR), collect the relevant service interfaces, schemas, and message or operation mappings. In the Integration Directory or the configuration views for your release, record the sender and receiver channels, adapter settings, routing rules, and configured quality of service. Capture source and receiver examples with a stable business correlation key. SAP explains the split between design objects and runtime configuration and describes the integration engine's delivery modes. Integration Directory · Quality of Service

Verify actual runtime behavior for a mapping rejection, connection timeout, and duplicate delivery. Write down whether sequence is per delivery, per sender, or per configured queue. Check the meaning of acknowledgements at each hop. Receipt by a broker can be earlier than application of the update by the warehouse.

Rebuild the delivery path

  1. Parse XML against the agreed schema and preserve message identifiers, namespaces, and source version. Translate the original mapping into small functions or XSLT with fixtures for absent fields, quantity units, and repeated line items.
  2. Stage messages in a ledger with correlation ID, delivery sequence, and separate mapping and delivery states. Lock by the confirmed ordering key. If the receiver requires ordered changes, hold a later update while a required earlier update is unresolved.
  3. Send with a stable idempotency key or an agreed receiver lookup. Record the warehouse acknowledgement and confirm the applied version by a receipt or read-back. For ambiguous timeouts, reconcile first and resend only when the delivery rules allow it.

Implementation runbook

Export design and Integration Directory configuration

For one SAP PI interface, use Enterprise Services Builder to export ESR objects in the related software component version and namespace. Include source/target message and service interfaces, schemas, mapping programs, functions/value maps and imported Java/XSLT assets. Separately use Integration Builder's Tools → Export Configuration Objects or the object context menu for the Integration Directory objects. The client export writes a TPZ; SAP recommends using server directories or transport management for large exports. Capture the actual integrated configuration or receiver/interface determinations, agreements, channels/modules and identifiers in use. Release and topology affect which objects exist. SAP ESR transport example · SAP Integration Directory client export

From the appropriate message monitor, record one accepted input, transformed output, receiver receipt, duplicate/retry and fault. Note adapter type, sender/receiver party/service/interface, message ID, queue identifier and Best Effort/EO/EOIO settings. An EOIO queue's sequencing contract applies within its queue. Treat a failed head message and the subsequent parked messages as related, and inspect in-flight state before any replay. SAP PI quality of service · SAP XI restart and qRFC caveats

Recreate the queue contract before changing the route

Accept the original sender contract, validate XML schema and preserve header IDs. Commit a unique inbox entry for source system plus source message ID before acknowledging async input. Translate mappings with controlled fixtures for null vs empty XML, namespaces, repeated elements, dates and leading zeros. For EOIO, store ordering key and sequence and assign one in-flight delivery per key. Keep later messages blocked while the key's earlier operation is pending. A transport retry uses the same idempotency key, and an ambiguous receipt is resolved against the destination or an operator. A durable receipt and recorded cursor release the next message. For synchronous interfaces return the required result or fault in the existing reply window. A polling scheduler can inspect DLQ and parked queues but may not release out-of-order business messages on its own.

Concrete fixture. Queue P1 contains orders A1 (sequence 1) and A2 (2), and P2 contains B1 (1). Force a destination timeout on A1 and inject a duplicate of A1. B1 should publish while A2 remains parked. A destination receipt for A1 permits A2. Assert one business effect per distinct message, correct per-queue order and unchanged ID. Test an invalid message at the head separately with the approved terminal disposition. At cutover stop sending new work to the old interface, record pending queue IDs and receipts, drain each key or hand it over explicitly before enabling the replacement.

Run a local failure and recovery fixture

This example uses Python 3 and its standard library to exercise a failed ordered delivery, a blocked next item, and receipt-aware replay. It uses synthetic data and does not call the incumbent service.

"""Python 3. Run with python3 sap_pi_queue.py."""
import sqlite3

db = sqlite3.connect(":memory:")
db.executescript("""
CREATE TABLE cursor (queue TEXT PRIMARY KEY, next_sequence INTEGER NOT NULL);
CREATE TABLE inbox (
    message_id TEXT PRIMARY KEY,
    queue TEXT NOT NULL,
    sequence INTEGER NOT NULL,
    payload_hash TEXT NOT NULL,
    receipt TEXT,
    UNIQUE(queue, sequence)
);
""")

def enqueue(message_id, queue, sequence, payload_hash):
    with db:
        existing = db.execute(
            "SELECT queue, sequence, payload_hash FROM inbox WHERE message_id = ?",
            (message_id,),
        ).fetchone()
        if existing:
            if existing != (queue, sequence, payload_hash):
                raise ValueError("Conflicting message ID")
            return
        db.execute(
            "INSERT INTO inbox VALUES (?, ?, ?, ?, NULL)",
            (message_id, queue, sequence, payload_hash),
        )

# One worker per queue in this fixture. Production workers need an atomic
# lease per queue with expiry. Initialize each cursor at the verified boundary.
def head(queue):
    row = db.execute("""
        SELECT inbox.message_id FROM inbox JOIN cursor USING (queue)
        WHERE queue = ? AND sequence = next_sequence AND receipt IS NULL
    """, (queue,)).fetchone()
    return row[0] if row else None

# Separate fake destination. Same operation ID returns the same receipt.
remote = {}
def deliver(message_id, lose_response=False):
    receipt = remote.setdefault(message_id, "receipt-" + message_id)
    if lose_response:
        raise TimeoutError("Destination accepted but response was lost")
    return receipt

def record_receipt(message_id, receipt):
    with db:
        row = db.execute("""
            SELECT inbox.queue FROM inbox JOIN cursor USING (queue)
            WHERE message_id = ? AND sequence = next_sequence AND receipt IS NULL
        """, (message_id,)).fetchone()
        if row is None:
            raise ValueError("Message is not the expected head")
        db.execute("UPDATE inbox SET receipt = ? WHERE message_id = ?", (receipt, message_id))
        db.execute("UPDATE cursor SET next_sequence = next_sequence + 1 WHERE queue = ?", row)

with db:
    db.executemany("INSERT INTO cursor VALUES (?, ?)", [("P1", 1), ("P2", 1)])
enqueue("A2", "P1", 2, "digest-a2")
assert head("P1") is None  # The earlier sequence has not arrived.
enqueue("A1", "P1", 1, "digest-a1")
enqueue("B1", "P2", 1, "digest-b1")
try:
    deliver(head("P1"), lose_response=True)
except TimeoutError:
    pass
else:
    raise AssertionError("The test must lose the first response")

enqueue("A1", "P1", 1, "digest-a1")
assert head("P1") == "A1"  # A2 cannot run while A1 lacks a receipt.
assert head("P2") == "B1"
record_receipt("B1", deliver("B1"))
record_receipt("A1", deliver("A1"))
assert head("P1") == "A2"
record_receipt("A2", deliver("A2"))
assert head("P1") is None and head("P2") is None
assert set(remote) == {"A1", "A2", "B1"}
print("3 unique effects, A1 before A2 in P1, P2 progressed independently")

Replay a delivery fixture

For delivery D42, prepare sequence 1 with quantity 6 and sequence 2 with quantity 4. Apply sequence 1, repeat it, and send sequence 2. The fixture should yield two applied versions and a final quantity of 4. Start a fresh test with sequence 2 and confirm it waits for sequence 1 if the selected interface requires order. Add an unsupported unit and verify a mapping reject before any outbound call. The receiver stub should distinguish accepting a request from applying a delivery update.

Prompts you can copy

Compare these sanitized SAP PI source and destination messages with the mapping excerpt. Produce a field mapping and list the rules for repeated delivery lines, units, and missing values. Cite mapping identifiers and leave unclear rules open.
Draft parser and ledger tests for D42 sequence 1 and 2, a replay, and an ambiguous acknowledgement. Assume the receiver has a read-only status endpoint. Keep all endpoints mocked.

Schedule recovery and cut over

Use the approved event intake for the primary flow. Schedule a separate reconciliation task for pending receipts, with a lock per ordering key and bounded retries. Monitor queue age, rejected mappings, sequence gaps, and applied-versus-received counts. Before switching, pause intake at a known message boundary and drain or account for the old queue. Validate messages in a shadow receiver. Enable only one production sender and retain the original route and correlation ledger for recovery.

Need help with the migration?

Explore SAP Process Integration migration services or browse all DIY migration guides.

AuthorsMage Team