Technischer Leitfaden

Multi-Channel Commerce: Die Architektur eines einheitlichen Checkouts über fünf Lieferanten

Wie du einen einheitlichen Checkout über mehrere Lieferanten baust. Bidirektionaler Sync, Echtzeit-Verfügbarkeits-Proxy, Channel-Scoping, Order-Routing und Fehlerbehandlung wenn Lieferanten mitten im Checkout ausfallen.

16. März 202614 Min. LesezeitOronts Engineering Team

Multi-Channel ist nicht "Anbindung an Amazon"

Die meisten Multi-Channel-Commerce-Artikel beschreiben, wie du deinen Shop an Marktplatz-APIs anbindest. Das ist Syndication, nicht Architektur. Echtes Multi-Channel Commerce bedeutet: mehrere Lieferanten mit verschiedenen APIs, verschiedenen Preismodellen, verschiedenen Verfügbarkeitsformaten und verschiedenen Buchungsabläufen, die dem Kunden als ein nahtloser Checkout präsentiert werden.

Wir haben eine Multi-Supplier-Aggregationsplattform gebaut, die den Checkout über 5 Lieferanten-APIs vereinheitlicht. Ein Warenkorb, fünf Lieferanten, eine Bestellung. Dieser Artikel behandelt die Architektur, die das möglich macht. Für die breitere Commerce-Plattform-Bewertung, schau dir unseren Vendure-Produktions-Guide und den E-Commerce-Plattformen-Guide an.

Das Problem des einheitlichen Checkouts

Jeder Lieferant hat seine eigene API, sein eigenes Datenformat, sein eigenes Verfügbarkeitsmodell und seinen eigenen Buchungsablauf:

AspektLieferant ALieferant BLieferant C
API-StilREST v2GraphQLSOAP/XML
PreisgestaltungPro Person, dynamischFestpreis, StaffelungPro Gruppe, verhandelt
VerfügbarkeitEchtzeit-APITägliche Sync-DateiWebhook bei Änderung
BuchungZwei-Schritt (Reservieren + Bestätigen)Ein-Schritt (Sofortbuchung)Drei-Schritt (Angebot + Reservieren + Bestätigen)
StornierungKostenlos bis 24hNicht erstattungsfähigTeilweise Rückerstattung
ID-FormatUUIDNumerischAlphanumerisches Präfix

Ein einheitlicher Checkout muss all das hinter einem Interface normalisieren. Der Kunde legt Artikel von verschiedenen Lieferanten in einen Warenkorb und bezahlt einmal. Das System routet jeden Artikel zum richtigen Lieferanten, verarbeitet den jeweiligen Buchungsablauf und zeigt dem Kunden eine einzige Bestätigung.

Supplier-Adapter-Pattern

Jeder Lieferant bekommt einen Adapter, der ein gemeinsames Interface implementiert:

interface SupplierAdapter {
    search(query: SearchQuery): Promise<Product[]>;
    checkAvailability(productId: string, date: string, persons: PersonConfig[]): Promise<AvailabilityResult>;
    reserve(params: ReservationParams): Promise<Reservation>;
    confirm(reservationId: string, bookerInfo: BookerInfo): Promise<BookingConfirmation>;
    cancel(bookingId: string): Promise<CancellationResult>;
}

// Jeder Lieferant implementiert das Interface anders
class SupplierAAdapter implements SupplierAdapter {
    async checkAvailability(productId: string, date: string, persons: PersonConfig[]) {
        // Lieferant A: Echtzeit-API-Aufruf
        const response = await this.httpClient.get(`/v2/availability/${productId}`, {
            params: { date, adults: persons.filter(p => p.type === 'adult').length },
        });
        return this.normalizeAvailability(response.data);
    }
}

class SupplierBAdapter implements SupplierAdapter {
    async checkAvailability(productId: string, date: string, persons: PersonConfig[]) {
        // Lieferant B: GraphQL-Abfrage
        const { data } = await this.graphqlClient.query({
            query: AVAILABILITY_QUERY,
            variables: { productId, date },
        });
        return this.normalizeAvailability(data.availability);
    }
}

Der Adapter normalisiert die Antwort jedes Lieferanten in ein gemeinsames Format. Der Checkout-Service arbeitet mit dem gemeinsamen Format, nie mit lieferantenspezifischen Datenstrukturen.

Echtzeit-Verfügbarkeits-Proxy

Manche Lieferanten liefern Echtzeit-Preise, die sich minütlich ändern (Dynamic Pricing, begrenztes Inventar). Die Suchergebnisse zeigen einen gecachten Preis, aber zum Checkout-Zeitpunkt muss das System den aktuellen Preis verifizieren.

async function getAvailabilityWithFallback(
    productId: string, date: string, persons: PersonConfig[]
): Promise<AvailabilityResult> {
    const supplier = getSupplierForProduct(productId);

    if (supplier.supportsRealTimeAvailability) {
        try {
            // Live-Preis von der Lieferanten-API
            const live = await supplier.adapter.checkAvailability(productId, date, persons);
            await cache.set(`avail:${productId}:${date}`, live, { ttl: 300 });
            return live;
        } catch (error) {
            // Lieferanten-API nicht erreichbar: gecachten Preis mit Warnung zurückgeben
            const cached = await cache.get(`avail:${productId}:${date}`);
            if (cached) {
                return { ...cached, stale: true, warning: 'Price may have changed' };
            }
            throw new AvailabilityError('Cannot determine availability');
        }
    }

    // Lieferant mit Batch-Verfügbarkeit: aus dem Index zurückgeben
    return searchIndex.getAvailability(productId, date);
}

Das Proxy-Pattern: zuerst live versuchen, dann auf Cache zurückfallen, dann auf den Index. Dem Kunden immer mitteilen, wenn der Preis veraltet sein könnte.

Bidirektionaler Sync: Endlosschleifen verhindern

Wenn zwei Systeme Daten bidirektional synchronisieren (Commerce-System und Operations-System), löst jedes Update von System A einen Sync zu System B aus, der wiederum einen Sync zurück zu A auslöst.

// Source-Tracking verhindert Endlosschleifen
interface SyncMessage {
    entityId: string;
    entityType: string;
    data: any;
    source: string;           // "commerce" | "operations" | "import"
    correlationId: string;
}

async function handleSync(message: SyncMessage) {
    // Nachrichten ignorieren, die von diesem System stammen
    if (message.source === THIS_SYSTEM_ID) {
        return; // ACK und überspringen
    }

    // Sync verarbeiten
    await updateEntity(message.entityId, message.data);

    // Update mit UNSEREM Source-Tag veröffentlichen
    await publishSync({
        ...message,
        source: THIS_SYSTEM_ID,  // Taggen, damit das andere System es ignoriert
    });
}

Jede Nachricht trägt ein source-Feld. Wenn ein System eine Nachricht von sich selbst empfängt (über das andere System), ignoriert es sie. Die Schleife wird beim ersten Roundtrip durchbrochen.

Für weitere Event-Driven-Patterns einschliesslich Deduplizierung und Dead-Letter-Handling, schau dir unseren Event-Driven-Architecture-Guide an.

Channel-Scoping: Sichtbarkeit vs. Veröffentlichung vs. Verfügbarkeit

Drei Konzepte, die getrennt bleiben müssen:

KonzeptFrageBeispiel
SichtbarkeitWelche Lieferanten kann dieser Channel sehen?Deutsche Website sieht Lieferanten A, B, C. Partner-API sieht nur Lieferant A.
VeröffentlichungIst dieses Produkt veröffentlicht und aktiv?Produkt existiert, ist aber unveröffentlicht (Entwurf).
VerfügbarkeitKann dieses Produkt jetzt gebucht werden?Produkt ist veröffentlicht, aber für Samstag ausverkauft.
// Channel bestimmt die Sichtbarkeit
const channel = await channelStore.get(tenantId, channelId);
const visibleSupplierIds = channel.supplierIds;

// Produkte nach Channel-Sichtbarkeit filtern
const products = await searchIndex.search({
    query: userQuery,
    filters: {
        supplier_id: { $in: visibleSupplierIds },  // Channel-Scoping
        status: 'active',                           // Veröffentlichung
    },
});

// Verfügbarkeit wird zum Checkout-Zeitpunkt geprüft (separates Concern)

Ein Produkt kann sichtbar sein (in der Lieferantenliste des Channels), veröffentlicht (Status = aktiv), aber nicht verfügbar (ausverkauft). Jedes ist ein anderer Filter auf einer anderen Stufe des Flows.

Order Routing

Wenn eine Bestellung Artikel von mehreren Lieferanten enthält, muss jeder Artikel zum richtigen Lieferanten für die Fulfillment geroutet werden:

async function processMultiSupplierOrder(order: Order): Promise<OrderResult> {
    // Artikel nach Lieferant gruppieren
    const itemsBySupplier = groupBy(order.items, item => item.supplierId);

    // Artikel jedes Lieferanten unabhängig verarbeiten
    const results = await Promise.allSettled(
        Object.entries(itemsBySupplier).map(async ([supplierId, items]) => {
            const adapter = getAdapter(supplierId);

            // Reservieren
            const reservation = await adapter.reserve({
                items,
                booker: order.bookerInfo,
                expiresIn: 900, // 15 Min. Hold
            });

            // Bestätigen
            return adapter.confirm(reservation.id, order.bookerInfo);
        })
    );

    // Gemischte Ergebnisse verarbeiten
    const successful = results.filter(r => r.status === 'fulfilled');
    const failed = results.filter(r => r.status === 'rejected');

    if (failed.length > 0 && successful.length > 0) {
        // Teilerfolg: einige Lieferanten gebucht, andere fehlgeschlagen
        // Erfolgreiche Buchungen stornieren? Oder Teilbestellung bestätigen?
        // Das ist eine Business-Entscheidung, keine technische.
        return handlePartialSuccess(order, successful, failed);
    }

    return { status: failed.length === 0 ? 'COMPLETED' : 'FAILED', results };
}

Das Partial-Success-Problem

Was passiert, wenn Lieferant A bestätigt, aber Lieferant B fehlschlägt? Optionen:

StrategieVerhaltenAm besten für
Alles oder nichtsLieferant A stornieren, wenn B fehlschlägtHochwertige Bestellungen, Events
TeillieferungA bestätigen, Kunden über B informierenAustauschbare Produkte
Retry dann stornierenB 3x wiederholen, A stornieren wenn B endgültig fehlschlägtBalance aus UX und Zuverlässigkeit

Die richtige Strategie hängt vom Business ab. Bei Event-Tickets (nicht austauschbar) ist Alles-oder-nichts normalerweise richtig. Bei austauschbaren Produkten ist Teillieferung besser.

Fehlerbehandlung: Wenn Lieferanten mitten im Checkout ausfallen

FehlerzeitpunktWas ist passiertReaktion
Während der SucheSupplier-API-TimeoutErgebnisse anderer Lieferanten anzeigen
Während der VerfügbarkeitsprüfungVeralteter PreisGecachten Preis mit Warnung anzeigen
Während der ReservierungInventar erschöpftArtikel aus Warenkorb entfernen, Alternativen vorschlagen
Während der BestätigungZahlung vom Lieferanten abgelehntReservierung stornieren, Zahlung wiederholen oder erstatten
Nach der BestätigungLieferant sendet StornierungKunden informieren, Umbuchung oder Erstattung anbieten
Während der StornierungSupplier-API nicht erreichbarStornierung für Retry in die Queue stellen

Jeder Fehler muss explizit behandelt werden. "Etwas ist schiefgelaufen" ist niemals eine akzeptable Fehlermeldung für eine Commerce-Transaktion.

Häufige Fehler

  1. Annahme, dass alle Lieferanten den gleichen Buchungsablauf haben. Zwei-Schritt-, Ein-Schritt- und Drei-Schritt-Abläufe existieren alle. Der Adapter muss sie normalisieren.

  2. Preisänderungen zwischen Suche und Checkout ignorieren. Dynamic Pricing bedeutet, dass der Checkout-Preis vom Suchpreis abweichen kann. Zum Checkout-Zeitpunkt validieren.

  3. Kein Source-Tracking bei bidirektionalem Sync. Ohne Source-Tracking loopt jedes Update endlos zwischen den Systemen.

  4. Channel-Sichtbarkeit als UI-Concern behandeln. Channel-Scoping muss auf der Query-Ebene durchgesetzt werden, nicht nur im Frontend. API-Consumer sehen die gleichen Filter.

  5. Keine Partial-Failure-Strategie. Multi-Supplier-Bestellungen werden teilweise fehlschlagen. Die Business-Policy festlegen, bevor es in Produktion passiert.

  6. Synchrone Supplier-Aufrufe während des Checkouts. Wenn ein Lieferant langsam ist, wartet der gesamte Checkout. Lieferanten parallel mit individuellen Timeouts verarbeiten.

Zusammenfassung

  • Supplier-Adapter normalisieren Diversität. Jeder Lieferant bekommt einen Adapter, der ein gemeinsames Interface implementiert. Business-Logik berührt nie lieferantenspezifische Formate.

  • Echtzeit-Verfügbarkeit ist ein Proxy-Pattern. Zuerst live versuchen, dann auf Cache zurückfallen, dann auf Index. Dem Kunden immer die Aktualität der Daten kommunizieren.

  • Source-Tracking verhindert Sync-Schleifen. Jede Nachricht trägt ihren Ursprung. Systeme ignorieren Nachrichten von sich selbst.

  • Sichtbarkeit, Veröffentlichung und Verfügbarkeit sind drei separate Concerns. Channel bestimmt die Sichtbarkeit. Produktstatus bestimmt die Veröffentlichung. Inventar bestimmt die Verfügbarkeit. Nicht vermischen.

  • Partial Failure ist eine Business-Entscheidung. Alles-oder-nichts vs. Teillieferung hängt vom Produkttyp ab. Die Policy definieren, bevor du den Code baust.

Wir bauen Multi-Channel-Commerce-Systeme als Teil unserer E-Commerce- und Custom-Software-Praxis. Wenn du Hilfe mit Supplier-Integration-Architektur brauchst, sprich mit unserem Team oder fordere ein Angebot an.

Behandelte Themen

Multi-Channel CommerceOmnichannel-ArchitekturMarktplatz-IntegrationCommerce-Synchronisationeinheitlicher CheckoutLieferanten-Aggregationbidirektionaler Sync

Bereit, produktionsreife KI-Systeme zu bauen?

Unser Team ist spezialisiert auf produktionsreife KI-Systeme. Lass uns besprechen, wie wir deinem Unternehmen helfen können.

Gespräch starten