Um 9:00 Uhr startet der Sale. Das CDN hält die Produktseiten online, aber der Checkout versagt: Zwei Kunden kaufen das letzte Exemplar derselben Jacke, ein Retry nach einem Timeout hinterlässt zwei Zahlungsreservierungen auf einer Karte, und das Lager erfährt eine Stunde zu spät von einer Bestellung, weil ein Event verloren ging.
Keines davon ist ein Kapazitätsproblem. Es sind Grenzprobleme: Wem gehören welche Daten, und welche Schritte müssen abgeschlossen sein, während der Kunde wartet?
Dieser Beitrag führt durch eine realistische E-Commerce-Architektur für einen mittelgroßen Shop: Komponenten und Datenhoheit, der Checkout-Pfad Schritt für Schritt, die Bestell-Pipeline danach, Lastspitzen und ein Diagramm der Plattform.
Ausgehen von dem, was Kunden tun
Fünf Abläufe decken das meiste ab, was ein Shop tut, mit sehr unterschiedlichen Anforderungen:
| Ablauf | Komponenten auf dem Pfad | Was veraltet sein darf |
|---|---|---|
| Stöbern | CDN, Gateway, Catalog | Preise und Bestand auf einer gecachten Seite, für Minuten |
| Suche | Search-Index | Geänderte Produkte, für Sekunden |
| Warenkorb | Cart | Nichts, was der Kunde gerade geändert hat |
| Checkout | Checkout, Inventory, Zahlungsanbieter, Orders | Nichts |
| Bestellung verfolgen | Orders, Fulfillment | Versandstatus, für Minuten |
Stöbern und Suche tragen die meisten Requests und tolerieren veraltete Daten; der Checkout trägt wenige und toleriert keine. Der Großteil der Architektur folgt aus dieser Trennung.
Die Komponenten und die Daten, die jede besitzt
Am Rand cacht ein CDN Seiten und Bilder, und ein API-Gateway (oder ein Backend for Frontend pro Client-Typ) authentifiziert, begrenzt die Rate und routet den Rest. Jeder Service dahinter besitzt seine Daten:
| Service | Besitzt | Typischer Speicher | Konsistenzbedarf |
|---|---|---|---|
| Catalog | Produkte, Varianten, Preise | Postgres, Lesezugriff über Caches | Eventual; Checkout liest Preise neu |
| Search | Produktindex mit Facetten | OpenSearch oder Elasticsearch | Eventual, aktualisiert aus Catalog-Änderungen |
| Cart | Warenkorbpositionen pro Kunde | Redis mit TTL | Read-your-writes pro Kunde |
| Checkout | Sessions: Summen, Reservierungs- und Zahlungs-IDs | Postgres | Strikt innerhalb einer Session |
| Inventory | Bestand pro SKU, Reservierungen | Postgres | Strikt: nie überverkaufen |
| Orders | Bestellungen, Statusverlauf, Outbox | Postgres | Strikt: ein Writer pro Bestellung |
| Zahlungsdienstleister (PSP) | Autorisierungen, Einzüge, Erstattungen | Der PSP | Der Datensatz des PSP gilt |
| Fulfillment | Sendungen, Trackingnummern | WMS oder 3PL | Eventual, durch Events gesteuert |
Andere Services nutzen die API oder die Events des Besitzers, nie dessen Tabellen. Bestand und Bestellungen brauchen Transaktionen; Warenkörbe und der Search-Index dürfen hinterherhinken oder sogar einen Schreibvorgang verlieren, ohne echten Schaden.
Der Checkout-Pfad, Schritt für Schritt
Der Checkout ist der eine Ablauf, bei dem der Kunde auf mehrere Services gleichzeitig wartet:
Den Warenkorb neu bepreisen. Checkout liest den Warenkorb, holt aktuelle Preise aus dem Catalog und berechnet Steuer, Versand und Rabatte serverseitig. Vertraue nie der Summe eines Browsers.
Bestand mit Ablaufzeit reservieren, hier 15 Minuten:
-- One transaction: take the stock, then record the hold UPDATE stock SET reserved = reserved + 2 WHERE sku = 'JKT-042-M' AND on_hand - reserved >= 2; -- 0 rows: out of stock, stop INSERT INTO reservations (checkout_id, sku, qty, expires_at) VALUES ('chk_41a', 'JKT-042-M', 2, now() + interval '15 minutes');Wenn zwei Kunden um die letzte Jacke konkurrieren, lässt die Standard-Isolationsstufe Read Committed von PostgreSQL das zweite Update warten und prüft dessen WHERE-Klausel erneut gegen die neue Zeile, sodass einer null Zeilen erhält. Ein Sweeper gibt abgelaufene Reservierungen an den Bestand zurück. Bei einer gehosteten Zahlungsseite solltest du die Uhren angleichen: Stripe-Checkout-Sessions laufen 24 Stunden, sofern du keinen kürzeren Ablauf (mindestens 30 Minuten) setzt oder sie selbst beendest;
checkout.session.expiredsagt dir, wann du die Artikel freigeben musst.Die Zahlung mit einem Idempotency-Key anlegen. Checkout fordert beim PSP eine Zahlung über die serverseitig berechnete Summe an, mit manuellem Einzug und einem Idempotency-Key, der vor dem Aufruf gespeichert wird. Nach einem Timeout wiederholst du mit demselben Key: Stripe liefert die gespeicherte Antwort zurück, statt eine zweite Zahlung anzulegen. Die Keys sind bis zu 255 Zeichen lang und können nach 24 Stunden entfernt werden.
Autorisieren. Verlängere zuerst die Bestandsreservierung, damit sie nicht mitten in der Zahlung abläuft. Der Kunde gibt die Kartendaten in die Felder des PSP ein, sodass Kartennummern nie deine Server erreichen, und der Aussteller kann 3-D Secure verlangen. Bei Erfolg wird der Betrag reserviert: Eine Stripe-Zahlung mit manuellem Einzug wechselt zu
requires_captureund sendetpayment_intent.amount_capturable_updated. Vertraue diesem Webhook, nicht dem Redirect des Browsers; siehe das Diagramm zur Payment-Gateway-Integration.Die Bestellung anlegen. Orders legt die Bestellung als
placedan und schreibtOrderPlacedin derselben Transaktion in eine Outbox-Tabelle. Danach weist Checkout Inventory an, die Reservierung in eine Zuteilung umzuwandeln.Beim Versand einziehen. Wenn Fulfillment versendet, zieht Orders ein. Zum Zeitpunkt des Schreibens (November 2026) dokumentiert Stripe, dass Online-Kartenautorisierungen meist 7 Tage gelten, bei von Händlern initiierten Visa-Transaktionen 5 Tage (genau 4 Tage und 18 Stunden); danach wird der Betrag freigegeben und die Zahlung storniert. Die meisten Kartenzahlungen erlauben einen Einzug, und ein Teileinzug gibt den Rest frei.
Schlägt ein Schritt fehl, bevor die Bestellung existiert, mache die früheren in umgekehrter Reihenfolge rückgängig: Autorisierung stornieren, Reservierung freigeben. Das ist eine Saga mit dem Checkout als Orchestrator.
Wo sich Geld, Bestand und Bestellstatus ändern
| Bestellstatus | Gesetzt, wenn | Zahlung | Bestand |
|---|---|---|---|
| Checkout offen | Bestand reserviert ist | Angelegt, nicht autorisiert | 15 Minuten reserviert |
| Placed | Der PSP die Autorisierung meldet | Betrag reserviert | Der Bestellung zugeteilt |
| Shipped | Fulfillment versendet | Eingezogen | Vom Lagerbestand abgebucht |
| Canceled | Vor dem Versand storniert | Autorisierung storniert | Freigegeben |
| Returned | Retoure eingegangen | Erstattet | Nach Prüfung wieder eingelagert |
Die schwierigen Fälle liegen zwischen den Zeilen. Trifft eine Autorisierung ein, nachdem die Reservierung abgelaufen ist, reserviere neu, falls Bestand übrig ist; andernfalls storniere die Autorisierung und informiere den Kunden. Dauert ein Backorder länger als das 7-Tage-Fenster, ziehe beim ersten Versand den vollen Betrag ein, nutze eine verlängerte Autorisierung, wo dein PSP eine anbietet (laut Stripe bei manchen Kartennetzwerken bis zu 30 Tage), oder buche im Voraus ab und erstatte, was du nicht liefern kannst. Manche Zahlungsarten wie ACH und iDEAL können gar nicht separat einziehen.
Nachdem die Bestellung angelegt ist
Alles nach placed läuft asynchron. Ein Relay veröffentlicht OrderPlaced aus der Outbox in das order-events-Topic, sodass zwischen Commit und Broker kein Event verloren geht (siehe das Transactional-Outbox-Pattern). Fulfillment kommissioniert, verpackt und versendet und veröffentlicht dann OrderShipped, was den Einzug auslöst. Notifications versendet E-Mails; Analytics lädt jedes Event ins Warehouse.
Die Zustellung erfolgt mindestens einmal, daher deduplizieren Consumer anhand der Event-ID. Wird das Topic nach der Bestell-ID partitioniert, bleiben die Events jeder Bestellung in Reihenfolge.
Das Microservices-Architektur-Diagramm belastet die Karte stattdessen nach OrderPlaced. Das passt zu Zahlungen ohne Kundeninteraktion; 3-D Secure braucht den anwesenden Kunden, deshalb autorisiert dieses Design im Checkout.
Lastspitzen überstehen
Nimm eine Sale-Stunde mit 7.200 Bestellungen, 2 % Conversion-Rate und 10 Seitenaufrufen pro Session an: 360.000 Sessions und 3,6 Millionen Seitenaufrufe, also etwa 1.000 Seitenaufrufe pro Sekunde gegenüber 2 Bestellungen pro Sekunde. Jede Hälfte braucht ihren eigenen Schutz:
- Den Katalog am Edge cachen. Mit
Cache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=60liefert das CDN eine Produktseite fünf Minuten lang aus, danach bis zu eine Minute länger, während es sie im Hintergrund neu lädt, wie RFC 5861 es definiert. Bei 90 % Trefferquote sieht der Origin 100 Seitenaufrufe pro Sekunde statt 1.000. Purge bei Preisänderungen; der Checkout prüft ohnehin neu. - Eine Warteschlange vor den Checkout setzen. Ein virtueller Warteraum lässt Kunden in einer Rate ein, die Inventory und der PSP verkraften, wie im Systemdesign eines Ticketbuchungssystems.
- Hot SKUs schützen. Reservierungen für eine SKU aktualisieren eine Zeile, eine nach der anderen. Bei 2 ms Sperrzeit pro Transaktion erreicht diese Zeile etwa 500 Reservierungen pro Sekunde. Halte die Transaktion kurz und frei von PSP-Aufrufen, verteile den Bestand eines Doorbusters auf mehrere Zeilen und begrenze die Menge pro Bestellung.
- Die Rate Limits deines PSP kennen, bevor der Sale beginnt.
Fang kleiner an als hier
Viele Shops sollten nicht alles davon bauen. Eine gehostete Plattform wie Shopify oder BigCommerce betreibt Katalog, Warenkorb und Checkout für dich; dein Diagramm zeigt dann die Apps und Webhooks drumherum.
Ein Eigenbau startet oft am besten als modularer Monolith: dieselben Grenzen als Module in einem einzigen Deployable. 2019 beschrieben Shopifys Entwickler, wie sie eine der größten Ruby-on-Rails-Codebasen rund um Bestellungen, Versand, Bestand und Abrechnung neu organisierten, statt sie in Microservices aufzuteilen. Löse einen Service heraus, wenn ein Modul eigene Skalierung oder eigene Releases braucht; das Diagramm gilt weiterhin.
Das E-Commerce-Plattform-Diagramm
Diagramminhalt
- Shopper
- CDN
- API gateway / BFF
- Catalog
- Search
- Cart
- Checkout
- Orders
- Inventory
- Payment provider (PSP)
- order-events
- Fulfillment
- Notifications
- Analytics warehouse
- HTTPS
- misses, API
- GET /products
- GET /search
- POST /cart/items
- POST /checkout
- GET /orders/{id}
- product changes
- read cart
- reserve, 15 min
- authorize + idempotency key
- place order
- capture on OrderShipped
- OrderPlaced
- placed, shipped
- all events
So liest du es:
- Requests treten oben links ein: Kunde, CDN, dann das Gateway, das jeden Ablauf zu einem Service routet.
- Blau ist der Browse-Pfad. Die gestrichelte Kante ist der Search-Index, der Produktänderungen nachzieht.
- Bernstein ist der Checkout-Pfad, auf dem der Kunde wartet: Warenkorb lesen, Bestand reservieren, autorisieren und die Bestellung anlegen.
- Der Zahlungsanbieter bekommt zwei Aufrufe: Autorisierung im Checkout und Einzug bei
OrderShipped(gestrichelt, türkis). - Violette gestrichelte Kanten sind Events, von der Outbox über order-events zu Fulfillment, Notifications und Analytics.
- Jeder Service nennt seinen Speicher, und der Markdown-Block listet die Zahlen.
E-Commerce-Architekturdiagramme klar zeichnen
- Beschrifte Gateway-Kanten mit Routen wie
GET /products, damit Leser Seiten den Services zuordnen können. - Nenne den Speicher an jedem Service. Eine gemeinsame Datenbank sollte nicht zu übersehen sein.
- Durchgezogen fürs Warten, gestrichelt für später. Aufrufe, auf die der Kunde wartet, sollten anders aussehen als Events.
- Schreibe Zeiten an die Kanten. Die Reservierungsdauer und der Auslöser des Einzugs brauchen das meiste Review.
- Zeichne Autorisierung und Einzug als zwei Kanten. Ein Pfeil zum PSP verbirgt das entscheidende Timing.
- Lass weg: Pods, Regionen, Steuer- und Betrugsanbieter sowie das Backoffice.
In Archboard nachbauen
- Melde dich mit Google oder GitHub an und erstelle ein Diagramm.
- Füge eine Person aus dem Pack C4 Model hinzu, darunter ein CDN und ein API-Gateway aus dem Pack Cloud.
- Füge fünf Container-Nodes aus dem Pack C4 Model in einer Spalte hinzu: Catalog, Search, Cart, Checkout und Orders, jeweils mit dem Speicher in der Beschreibung.
- Füge einen Container für Inventory, ein Externes System für den Zahlungsanbieter und ein Thema aus dem Pack Daten für order-events hinzu. Rechts davon ergänzt du Container-Nodes für Fulfillment und Notifications sowie ein Datenlager aus dem Pack Daten.
- Verbinde das Gateway mit den fünf Services und beschrifte die Kanten mit Routen. Zeichne die Checkout-Aufrufe durchgezogen, die Produktänderungen, den Einzug und die Events gestrichelt.
- Füge einen Markdown-Block aus dem Pack Basis mit den Zahlen hinzu, dazu Text-Notizen für die Legende und die wichtigsten Regeln.
Wenn deine Services aus einer Compose-Datei laufen, entwirft das Tool Docker Compose zu Diagramm die Boxen; die Microservices-Architektur-Vorlage ist ein weiterer Startpunkt. Das Beispiel nutzt 15 Diagramm-Nodes und bleibt damit im Free-Plan.
FAQ
Was sind die Hauptkomponenten einer E-Commerce-Architektur? Ein CDN, ein API-Gateway, Services für Catalog, Search, Cart, Checkout, Inventory und Orders, ein Zahlungsanbieter sowie Fulfillment, Notifications und Analytics. Gehostete Plattformen bündeln die meisten davon.
Sollte ein Onlineshop Microservices nutzen? Nicht standardmäßig. Ein modularer Monolith mit denselben Grenzen ist einfacher zu betreiben, und Shopify hat sich für seinen Kern dafür entschieden. Löse einen Service heraus, wenn ein Modul unabhängige Skalierung oder Releases braucht.
Wie verhindert man das Überverkaufen von Bestand? Reserviere Bestand mit einem atomaren, bedingten Update pro SKU, gib jeder Reservierung eine Ablaufzeit und teile sie erst zu, wenn die Bestellung angelegt ist. Entscheide die Verfügbarkeit nie anhand einer gecachten Seite.
Wann sollte ein Onlineshop die Zahlung einziehen? Bei physischen Waren meist beim Versand, damit du nur für das berechnest, was versendet wird. Online-Kartenautorisierungen gelten meist 7 Tage. Bei Stripe erlaubt Klarna 28 Tage und PayPal 10, was Stripe auf 20 zu verlängern versucht.
Wie bewältigen E-Commerce-Seiten Traffic-Spitzen? Sie liefern den Katalog aus dem CDN, stellen Kunden vor dem Checkout in eine Warteschlange und schützen Hot SKUs und den Zahlungsanbieter. Plane die Checkout-Kapazität getrennt von den Seitenaufrufen.