Kontext
Der Kunde stellt langlebige Industrieanlagen her, die weltweit verkauft und gewartet werden. Jede Einheit ist ein Investitionsgut mit einer Lebensdauer von oft mehreren Jahrzehnten und trägt Tausende von Teilenummern, Verbrauchsmaterialien und Konfigurationsoptionen, die der Betreiber nachschlagen, bepreisen und bestellen können muss — ohne den Umweg über einen Vertriebsmitarbeiter.
Die Aufgabe war eine B2B-Plattform, die diesen Katalog tragen kann: Hunderttausende SKUs, Multi-Organization-Zugriff (ein Kunde mit mehreren Tochtergesellschaften, jede mit eigenen Rollen und Berechtigungen), Preislisten je nach Kunde / Region / Währung, ein RFQ-Flow für Artikel außerhalb des Standardkatalogs sowie ein Konfigurator, mit dem ein Betreiber angeben kann, für welche Einheit er Teile bestellt, woraufhin der Katalog auf kompatible Artikel filtert.
Die Plattform basiert auf OroCommerce Enterprise Edition 6.1 — Oros B2B-orientierter E-Commerce-Applikation auf Symfony 6.4 / PHP 8.4. Ich kam als Lead Developer hinzu und verantworte Architektur und Umsetzung der Oro-Erweiterungen, die die Standardplattform nicht von Haus aus abdeckt.
Ansatz
OroCommerce EE ist unter den E-Commerce-Plattformen ungewöhnlich, weil es explizit um B2B-Anforderungen herum gebaut ist: native Multi-Organization, kundenspezifische Preise und Sichtbarkeit, RFQ-/Angebots-Flows und ein auf Symfony-Bundles basierendes Erweiterungsmodell. Das machte es zu einer starken Basis — der Auftrag erforderte dennoch Anpassungen über die meisten Subsysteme hinweg.
Die Arbeit wurde als Portfolio von 20+ eigenen Symfony-Bundles strukturiert, von denen jedes einen zusammenhängenden Verhaltensbereich besitzt:
| Bereich | Bundles |
|---|---|
| Katalog & Suche | Catalog API, WebCatalog, Equipment-Konfigurator |
| Commerce | Pricing, Order, Checkout, Customer |
| Vertriebsprozess | RFQ |
| Identität | Multi-Organization, eigene Auth-Schicht |
| Fulfillment | Warehouse, Inventory, Shipping |
| Dokumente | Document |
| Analytics | Matomo-Integration |
Jedes Bundle dockt an Oros Erweiterungspunkte an — Symfony-Events, Doctrine-Listener, Twig- und YAML-Layout-Updates — statt die Plattform zu forken. So bleibt der Weg für Upstream-Updates von Oro offen, ohne Drei-Wege-Merge-Albträume.
Umsetzungs-Highlights
JSON:API durch Erweiterung der ApiBundle-Processors von Oro.
Die Plattform brauchte Headless-Integrationen — Partnersysteme, die Produktdaten ziehen, Order-Events pushen, Bestände synchronisieren. Statt eine parallele API-Oberfläche zu bauen, habe ich die Processor-Pipeline von Oro\Bundle\ApiBundle erweitert, sodass dieselben Domänenmodelle Storefront und JSON:API-Consumer bedienen — mit feldgenauer Zugriffskontrolle über Oros bestehende ACL-Schicht. Ein neuer Endpunkt wird zur Registrierung eines Processors, nicht zum Schreiben von Controllern.
Asynchrone Workflows auf RabbitMQ über die Oro MessageQueue. Drei Pipelines laufen asynchron über RabbitMQ:
- Katalog-Import — Lieferanten-Feeds landen als Dateien, werden geparst, normalisiert und über idempotente Batches angewendet, sodass ein erneuter Lauf sicher ist. Dieselbe Pipeline verarbeitet neben den Teiledaten auch technische Maschinen-Skizzen und Maschine-zu-Teil-Zuordnungen, gestützt auf eigene Entitäten.
- Order-Processing — sobald ein Checkout abgeschlossen ist, laufen nachgelagerte Effekte (ERP-Push, Fulfillment-Benachrichtigung, Audit-Log) als separate Nachrichten, damit ein langsames ERP die Checkout-Antwort nicht blockiert.
- Inventory-Sync — inkrementelle Bestandsänderungen aus dem Warehouse-System strömen kontinuierlich ein und werden mit optimistischem Locking auf der Produkt-Entität angewendet.
Alle drei laufen über die MessageQueue-Komponenten von Oro statt über ein paralleles Symfony-Messenger-Setup, was die Observability im selben Admin-Tooling hält, das Oro ohnehin bereitstellt.
Kundenspezifische Preise. B2B-Preisfindung ist selten „ein Preis pro SKU". Kunden sehen ausgehandelte Preise, regionale Anpassungen und Mengenstaffeln. Ich habe Oros Pricing-Engine über Doctrine-Listener und Symfony-Events erweitert, sodass die Preislisten-Auflösung innerhalb von Oros Caching- und Indexing-Schicht bleibt.
Produktsichtbarkeit pro Kunde / pro Katalog — ein eigenes Drei-Schichten-Modell. Die Sichtbarkeit war der schwierigste Teil des Auftrags: welcher Kunde welches Produkt sehen darf, je Katalog skaliert, im Produktivumfang. Der Standard-Customer-Group-Resolver von Oro konnte das nicht abbilden, daher habe ich ihn durch ein Drei-Schichten-Modell ersetzt — Katalog-Metadaten → abgeleitete Basis-Katalog-Berechtigungen → aufgelöste Produkt-/Scope-Tabelle — durchgesetzt sowohl auf Elasticsearch- als auch auf ORM-Query-Ebene. Ein Kunde sieht nie ein Teil, für das er nicht berechtigt ist — ob er es über die Suche erreicht oder direkt navigiert — und die Auflösung bleibt in Oros Indexing-Schicht, statt nachträglich in PHP zu filtern.
Hierarchisches Anlagen-Domänenmodell. Der Katalog wird nicht als flache SKU-Liste navigiert, sondern über eine Machine- / Assembly- / Part- / CustomerMachine-Hierarchie. Ein Betreiber startet von der konkreten Einheit, die er besitzt (CustomerMachine), steigt in deren Baugruppen ab und gelangt zu kompatiblen Teilen — der Katalog filtert sich also auf das, was zu seiner Anlage passt, statt die volle Liste von ~140.000 Produkten zu zeigen. Eigene Doctrine-Entitäten tragen die Hierarchie, befüllt durch die Import-Pipeline.
SAP-Middleware-Integration. Bestände und Order-History liegen in der SAP-Landschaft des Kunden und werden über eine Middleware-Schicht erreicht. Die Integration betreibt eine Live-Bestandsabfrage mit lokalem Fallback — ein langsamer oder nicht verfügbarer SAP-Aufruf degradiert auf zwischengespeicherte Bestände, statt die Seite zu blockieren — dazu einen Order-History-Merge, der SAP-Auftragsdaten mit shopseitigen Bestellungen abgleicht, und asynchrone PDF-Auftragsbestätigungen auf dedizierten MessageQueue-Topics.
Multi-Organization mit eigener Authentifizierungsschicht. Ein einzelner Unternehmenskunde kann ein Dutzend Tochtergesellschaften haben, jede mit eigenen Nutzern, eigenem Katalog-Scope und eigenen Freigaberegeln. Die eigene Auth-Schicht erweitert Oros Organization-/Business-Unit-Modell und übersetzt die externe Identität des Kunden bei jeder Anfrage in die richtige Oro-Rolle und -Organisation, ohne eine Synchronisation zu persistieren.
Storefront-Maschinen-Viewer (Vue 3). Eine in die Storefront eingebettete Vue-3-App bildet Produkte auf interaktive Maschinen-Skizzen ab. Die Seitenleiste listet Teile nach Materialnummer und Bezeichnung; die Auswahl eines Eintrags hebt die zugehörige Komponente in der Skizze hervor — und macht aus „welches Teil brauche ich?" eine visuelle statt einer Teilenummern-Suche.
Suche und Speicherung.
- Elasticsearch 8 für Produktsuche und facettierte Katalog-Navigation.
- PostgreSQL 16 als primärer OLTP-Speicher.
- Redis für Cache und Session-Storage.
- MongoDB für die Dokumentenablage (technische Zeichnungen, Datenblätter, Handbücher).
CI/CD und Qualität. PHPUnit und Behat laufen in Jenkins bei jedem Push; PHPCS, PHP-CS-Fixer und PHPMD werden als Gate-Checks erzwungen. Dockerisierte lokale Umgebungen spiegeln das Staging-Deployment, sodass das Onboarding eines neuen Entwicklers Stunden statt Tage dauert.
Ergebnis
Ausgewählte Produktivzahlen:
- Katalog-Umfang — ~140.000 Produkte
- Konfigurator-Reichweite — 2.000+ Kataloge und 800+ registrierte Einheiten
- Aktive Organisationen — ~1.000 Kundenorganisationen
- Build & Deploy — Jenkins-Pipeline mit PHPUnit + Behat als Gate für jedes Release
Architekturentscheidungen, die sich ausgezahlt haben:
- Der Bundle-pro-Bereich-Schnitt hielt die Feature-Arbeit parallelisierbar; mehrere Bundles können im selben Release ausgeliefert werden, ohne sich gegenseitig zu koppeln.
- Oros ApiBundle zu erweitern, statt eine parallele API-Oberfläche zu bauen, sparte eine komplette Autorisierungsschicht.
- Asynchrone Workflows auf Oros MessageQueue laufen zu lassen bedeutete, dass Ops und Entwickler dieselben Dashboards nutzen statt zweier Stacks.
Stack
Backend — PHP 8.4, Symfony 6.4, OroCommerce EE 6.1, Doctrine ORM.
Speicher & Suche — PostgreSQL 16, Elasticsearch 8, Redis, MongoDB.
Async — RabbitMQ über die Oro MessageQueue.
Frontend — Vue 3, webpack 5.
Build & QA — Docker, Jenkins, PHPUnit, Behat, PHPCS, PHP-CS-Fixer, PHPMD.
API — JSON:API auf Basis von Oro\Bundle\ApiBundle.