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.
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:
| Aspekt | Lieferant A | Lieferant B | Lieferant C |
|---|---|---|---|
| API-Stil | REST v2 | GraphQL | SOAP/XML |
| Preisgestaltung | Pro Person, dynamisch | Festpreis, Staffelung | Pro Gruppe, verhandelt |
| Verfügbarkeit | Echtzeit-API | Tägliche Sync-Datei | Webhook bei Änderung |
| Buchung | Zwei-Schritt (Reservieren + Bestätigen) | Ein-Schritt (Sofortbuchung) | Drei-Schritt (Angebot + Reservieren + Bestätigen) |
| Stornierung | Kostenlos bis 24h | Nicht erstattungsfähig | Teilweise Rückerstattung |
| ID-Format | UUID | Numerisch | Alphanumerisches 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:
| Konzept | Frage | Beispiel |
|---|---|---|
| Sichtbarkeit | Welche Lieferanten kann dieser Channel sehen? | Deutsche Website sieht Lieferanten A, B, C. Partner-API sieht nur Lieferant A. |
| Veröffentlichung | Ist dieses Produkt veröffentlicht und aktiv? | Produkt existiert, ist aber unveröffentlicht (Entwurf). |
| Verfügbarkeit | Kann 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:
| Strategie | Verhalten | Am besten für |
|---|---|---|
| Alles oder nichts | Lieferant A stornieren, wenn B fehlschlägt | Hochwertige Bestellungen, Events |
| Teillieferung | A bestätigen, Kunden über B informieren | Austauschbare Produkte |
| Retry dann stornieren | B 3x wiederholen, A stornieren wenn B endgültig fehlschlägt | Balance 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
| Fehlerzeitpunkt | Was ist passiert | Reaktion |
|---|---|---|
| Während der Suche | Supplier-API-Timeout | Ergebnisse anderer Lieferanten anzeigen |
| Während der Verfügbarkeitsprüfung | Veralteter Preis | Gecachten Preis mit Warnung anzeigen |
| Während der Reservierung | Inventar erschöpft | Artikel aus Warenkorb entfernen, Alternativen vorschlagen |
| Während der Bestätigung | Zahlung vom Lieferanten abgelehnt | Reservierung stornieren, Zahlung wiederholen oder erstatten |
| Nach der Bestätigung | Lieferant sendet Stornierung | Kunden informieren, Umbuchung oder Erstattung anbieten |
| Während der Stornierung | Supplier-API nicht erreichbar | Stornierung 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
-
Annahme, dass alle Lieferanten den gleichen Buchungsablauf haben. Zwei-Schritt-, Ein-Schritt- und Drei-Schritt-Abläufe existieren alle. Der Adapter muss sie normalisieren.
-
Preisänderungen zwischen Suche und Checkout ignorieren. Dynamic Pricing bedeutet, dass der Checkout-Preis vom Suchpreis abweichen kann. Zum Checkout-Zeitpunkt validieren.
-
Kein Source-Tracking bei bidirektionalem Sync. Ohne Source-Tracking loopt jedes Update endlos zwischen den Systemen.
-
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.
-
Keine Partial-Failure-Strategie. Multi-Supplier-Bestellungen werden teilweise fehlschlagen. Die Business-Policy festlegen, bevor es in Produktion passiert.
-
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
Verwandte Guides
Unternehmenshandbuch zu Agentischen KI-Systemen
Technischer Leitfaden zu agentischen KI-Systemen in Unternehmen. Erfahre mehr ueber Architektur, Faehigkeiten und Anwendungen autonomer KI-Agenten.
Guide lesenAgentic Commerce: Wie du KI-Agenten sicher einkaufen lässt
Wie du gesteuerten, KI-initiierten Handel designst. Policy Engines, HITL-Freigabe-Gates, HMAC-Quittungen, Idempotenz, Tenant-Scoping und das vollständige Agentic Checkout Protocol.
Guide lesenDie 9 Stellen, an denen dein KI-System Daten verliert (und wie du jede einzelne abdichtest)
Eine systematische Übersicht aller Stellen, an denen KI-Systeme Daten preisgeben. Prompts, Embeddings, Logs, Tool Calls, Agent Memory, Fehlermeldungen, Cache, Fine-Tuning-Daten und Agent Handoffs.
Guide lesenBereit, 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