Vendure in Produktion: Stärken, Lücken und wann du es wählen solltest
Eine ehrliche Architektur-Bewertung von Vendure für den Produktionseinsatz. Plugin-System, EventBus, Worker Service, AdminUI, Multi-Pod-Deployment und Vergleich mit Medusa und Saleor.
Warum wir Vendure gewählt haben (und wann nicht)
Wir bauen seit den frühen Versionen auf Vendure, und wir arbeiten wirklich gern damit. Die Developer Experience ist herausragend. Du schreibst überall TypeScript, NestJS Dependency Injection hält Services sauber und testbar, das Plugin-System lässt dich alles erweitern ohne zu forken, und die GraphQL API ist von Tag eins durchdacht designed. Als Team aus Architekten und Senior Engineers ist uns DX enorm wichtig, und Vendure liefert sie besser als jede Commerce-Plattform, mit der wir bisher gearbeitet haben.
Das ist kein Marketing. Es ist das, was passiert, wenn du zwei Enterprise Plugins auf einem Framework baust und alles genau so funktioniert, wie du es erwartest. Eins ist ein vollständiges ETL-Pipeline-System (Vendure Data Hub Plugin) mit 9 Extractors, 61 Transform-Operatoren und 24 Entity Loaders. Das andere ist eine 6-Module umfassende Customer Intelligence Suite mit Wishlists, Reviews, Treueprogrammen, Warenkorbwiederherstellung, Back-in-Stock-Alerts und zuletzt angesehenen Artikeln. Wir haben Vendure dafür gewählt, weil die Plugin-Architektur uns die Flexibilität gab, komplexe Systeme zu bauen, ohne gegen das Framework zu kämpfen. Die Codebasis bleibt sauber, die Patterns bleiben konsistent, und Refactoring ist sicher, weil TypeScript alles zur Compile-Zeit abfängt.
Trotzdem ist Vendure nicht für jeden Anwendungsfall perfekt. Dieser Artikel ist eine ehrliche Bewertung aus der Perspektive der Systemarchitektur. Wenn du CTO bist und Commerce-Plattformen evaluierst, Lead Engineer und einen Build planst, oder Architekt und ein Multi-Channel-System entwirfst: das hier musst du wissen. Für den breiteren Kontext zu E-Commerce-Plattform-Entscheidungen behandelt dieser Guide die gesamte Landschaft.
Architekturüberblick
Vendure ist ein Headless Commerce Framework, gebaut auf NestJS (Node.js), TypeORM und GraphQL. Der Core bietet Produkte, Bestellungen, Kunden, Zahlungen, Versand, Aktionen und ein Plugin-System, um alles zu erweitern.
┌─────────────────────────────────────────────────────────────┐
│ CLIENT APPLICATIONS │
│ Storefront (Next.js, Nuxt, etc.) │
│ Mobile App (React Native, Flutter) │
│ Admin Dashboard (React, built-in) │
│ POS System, Marketplace, Kiosk │
└────────────────────────┬────────────────────────────────────┘
│ GraphQL (Shop API + Admin API)
▼
┌─────────────────────────────────────────────────────────────┐
│ VENDURE SERVER │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ Shop API │ │ Admin API │ │ Plugin APIs │ │
│ │ (customer) │ │ (backoffice)│ │ (extensions) │ │
│ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │
│ │ │ │ │
│ ┌──────▼─────────────────▼────────────────────▼─────────┐ │
│ │ NestJS Core │ │
│ │ Services │ Resolvers │ Guards │ Interceptors │ │
│ └──────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼────────────────────────────────┐ │
│ │ EventBus │ │
│ │ ProductEvent │ OrderEvent │ CustomerEvent │ Custom │ │
│ └──────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼────────────────────────────────┐ │
│ │ TypeORM + Database │ │
│ │ PostgreSQL (prod) / SQLite (dev) │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Worker Service (BullMQ) │ │
│ │ Job Queues │ Email │ Search Index │ Custom Jobs │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Was Vendure hervorragend macht
1. Das Plugin-System
Das ist Vendures stärkster Differenzierungsfaktor. Ein Plugin kann jede Schicht des Systems erweitern: Entities, Services, Resolver, GraphQL Schema, Admin UI, Worker und Event Handler. Alles ohne Core-Code zu modifizieren.
import { PluginCommonModule, VendurePlugin } from '@vendure/core';
import { WishlistService } from './services/wishlist.service';
import { WishlistShopResolver } from './resolvers/wishlist-shop.resolver';
import { WishlistAdminResolver } from './resolvers/wishlist-admin.resolver';
import { CiWishlist } from './entities/wishlist.entity';
import { CiWishlistItem } from './entities/wishlist-item.entity';
import { wishlistShopSchema, wishlistAdminSchema } from './schemas';
@VendurePlugin({
imports: [PluginCommonModule],
entities: [CiWishlist, CiWishlistItem],
providers: [WishlistService],
shopApiExtensions: {
schema: wishlistShopSchema,
resolvers: [WishlistShopResolver],
},
adminApiExtensions: {
schema: wishlistAdminSchema,
resolvers: [WishlistAdminResolver],
},
configuration: (config) => {
// Konfiguration bei Bedarf anpassen
return config;
},
})
export class WishlistPlugin {}
Was es wirklich gut macht:
- Entity-Erweiterung: Neue TypeORM Entities mit eigenen Tabellen, Migrationen und Relationen hinzufügen
- Schema-Erweiterung: Shop API und Admin API GraphQL Schemas unabhängig voneinander erweitern
- Resolver-Trennung: Shop API Resolver (kundenorientiert) und Admin API Resolver (Backoffice) sind immer separate Klassen. Diese Design-Einschränkung verhindert die versehentliche Freigabe von Admin-Operationen an Kunden
- Service Injection: Volle NestJS Dependency Injection. Deine Plugin Services können Vendure Core Services injizieren.
- Custom Fields: Felder zu jeder Core Entity (Product, Customer, Order) hinzufügen, ohne den Vendure Quellcode anzufassen
Wir haben zwei Enterprise Plugins mit diesem System gebaut. Das Data Hub Plugin fügt eine komplette ETL-Pipeline-Engine mit 9 Data Extractors, 61 Transform-Operatoren und 24 Entity Loaders hinzu. Das Customer Intelligence Plugin fügt 6 Customer-Engagement-Module mit eigenen Entities, GraphQL APIs, Admin Dashboards und Background Jobs hinzu. Beide laufen in Produktion, ohne eine einzige Zeile Vendure Core zu verändern.
2. Der EventBus
Vendures EventBus ist das Rückgrat der losen Kopplung zwischen Modulen. Jede bedeutende Aktion im System löst ein Event aus:
// Vendure Core löst Events automatisch aus
ProductEvent // Produkt erstellt/aktualisiert/gelöscht
OrderStateTransitionEvent // Bestellstatus-Änderungen
CustomerEvent // Kunde erstellt/aktualisiert
OrderPlacedEvent // Bestellung erfolgreich aufgegeben
RefundStateTransitionEvent // Erstattungsstatus-Änderungen
// Deine Plugins lösen benutzerdefinierte Events aus
export class CiWishlistItemAddedEvent extends VendureEvent {
constructor(
public ctx: RequestContext,
public wishlistId: string,
public productVariantId: string,
) {
super();
}
}
// Andere Module abonnieren, ohne das auslösende Modul zu importieren
@Injectable()
export class StockSubscriber {
constructor(private eventBus: EventBus) {
this.eventBus.ofType(OrderPlacedEvent).subscribe(event => {
// Bestand aktualisieren, Lager benachrichtigen, usw.
});
}
}
Die zentrale Architekturregel: Module kommunizieren nur über den EventBus. Keine modulübergreifenden Service-Imports. Das verhindert zirkuläre Abhängigkeiten und hält Module unabhängig deploybar. Als wir das Customer Intelligence Plugin mit 6 Modulen gebaut haben (Wishlist, Reviews, Loyalty, Warenkorbwiederherstellung, Back-in-Stock, Zuletzt angesehen), kannte jedes Modul nur seine eigenen Services. Modulübergreifende Koordination (z.B. Treuepunkte für eine Bewertung) läuft über Events.
3. TypeScript End-to-End (Die DX, die uns hält)
Hier glänzt Vendure als Developer Experience. Der gesamte Stack ist TypeScript: Server, Plugins, Admin UI, GraphQL Code Generation. Alles ist typisiert, alles kompiliert, alles fängt Fehler vor der Laufzeit ab.
- Type-safe API-Verträge von der Datenbank-Entity über das GraphQL Schema bis zur Frontend-Komponente
- GraphQL Schema generiert TypeScript Types automatisch mit Codegen
- Benennst du ein Feld in deiner Entity um, zeigt dir der Compiler jede Stelle, die aktualisiert werden muss
- Eine Sprache für Backend Services, Plugin-Entwicklung, Admin Dashboard und Test Suites
- NestJS Decorators (
@Injectable(),@Transaction(),@Allow()) machen die Intention im Code explizit RequestContextfließt durch jede Service-Methode und transportiert Auth, Channel und Locale-Kontext
Das ist keine Kleinigkeit. In PHP-basierten Commerce-Plattformen (Magento, Sylius, sogar Pimcores Commerce-Layer) verlierst du Type Safety an jeder Grenze. In Vendure taucht eine Breaking Change in deiner Product Entity als Compile-Fehler in deiner Storefront-Query auf. Diese Feedback-Schleife ist der Unterschied zwischen 2 Minuten für einen Type-Mismatch und 2 Stunden Debugging eines Produktionsfehlers.
Die Flexibilität ist ebenso bemerkenswert. Brauchst du einen Custom Checkout Flow? Schreib einen Service. Willst du ändern, wie Promotions angewendet werden? Override die Promotion Strategy. Brauchst du eine komplett neue Entity mit eigener GraphQL API, Admin Dashboard Seite und Background Job? Das Plugin-System handhabt das alles mit sauberen Patterns, die konsistent bleiben, während das System wächst. Wir haben uns nie vom Framework eingeschränkt gefühlt, und das ist selten bei Commerce-Plattformen.
4. Worker Service (BullMQ)
Vendure trennt lang laufende Operationen in einen dedizierten Worker Service, gestützt auf BullMQ (Redis):
// Job Queue in deinem Plugin definieren
const myJobQueue = new JobQueue<{ productId: string }>({
name: 'generate-feed',
process: async (ctx, job) => {
const product = await this.productService.findOne(ctx, job.data.productId);
await this.feedGenerator.generate(product);
},
});
// Von überall in die Queue einreihen
await this.myJobQueue.add({ productId: '123' }, { ctx });
Der Worker Service läuft als separater Prozess. In Kubernetes ist es ein separates Deployment mit unabhängiger Skalierung. Das trennt sauber die Request-Verarbeitung (Web Pods) von der Hintergrundverarbeitung (Worker Pods).
Wie wir über eventgesteuerte Patterns und Background-Job-Architekturen denken, behandelt unser Engineering Guide mit den breiteren Prinzipien.
5. GraphQL API Design
Vendure bietet zwei separate GraphQL APIs:
- Shop API: Kundenorientierte Operationen (Produkte durchsuchen, Bestellungen aufgeben, Konto verwalten)
- Admin API: Backoffice-Operationen (Produkte verwalten, Bestellungen bearbeiten, System konfigurieren)
Beide unterstützen:
- Automatische Paginierung mit
ListQueryBuilder - Eingebautes Filtern und Sortieren
- Berechtigungen auf Feld-Ebene
- Custom Fields auf jeder Entity
// ListQueryBuilder generiert effizientes SQL mit Paginierung, Sortierung, Filterung
async findByCustomer(ctx: RequestContext, options?: ListQueryOptions<CiWishlist>) {
return this.listQueryBuilder
.build(CiWishlist, options ?? {}, { ctx })
.andWhere('entity.customerId = :customerId', { customerId: ctx.activeUserId })
.getManyAndCount()
.then(([items, totalItems]) => ({ items, totalItems }));
}
6. Die React Admin UI (Vendure 3)
Vendure 3 liefert eine vollständig React-basierte Admin UI, von Grund auf für Erweiterbarkeit designed. Sie ersetzt die alte Angular Admin UI aus Vendure 1/2 und ist ein großer Schritt nach vorne.
Plugin-Entwickler können:
- Eigene Seiten und Routen zum Admin Dashboard hinzufügen
- Bestehende Views mit eigenen Komponenten erweitern
- Vollständige Dashboard-Erfahrungen mit React, TanStack Query und TanStack Router bauen
- Vendures Dashboard SDK Komponenten nutzen (Page, PageTitle, DetailFormGrid, ListPage)
- Eigene Menüpunkte, Widgets und Detail-Tabs registrieren
Wir haben unsere Plugin Dashboards mit diesem Stack gebaut. Die DX ist solide und fühlt sich für jeden React-Entwickler natürlich an.
7. Strategies und Extension Points
Vendure bietet Strategy-Interfaces, um das Commerce-Kernverhalten anzupassen, ohne Quellcode zu modifizieren:
// Benutzerdefinierte Promotion-Bedingung
class MinimumOrderAmountCondition implements PromotionCondition {
code = 'minimum_order_amount';
description = [{ languageCode: LanguageCode.en, value: 'Minimum order amount' }];
args = {
amount: { type: 'int' },
};
check(ctx: RequestContext, order: Order, args: { amount: number }) {
return order.subTotal >= args.amount;
}
}
// Benutzerdefinierter Versandrechner
class WeightBasedShippingCalculator implements ShippingCalculator {
calculate(ctx: RequestContext, order: Order, args: any) {
const totalWeight = order.lines.reduce((sum, line) =>
sum + (line.productVariant.customFields.weight || 0) * line.quantity, 0
);
return { price: totalWeight * args.pricePerKg, priceIncludesTax: false };
}
}
Steuerberechnung, Zahlungsabwicklung, Auftragserfüllung, Asset-Speicherung und Such-Indexierung haben alle Strategy-Interfaces. Das bedeutet, du kannst ändern, wie die Mehrwertsteuer für ein bestimmtes Land berechnet wird, wie Zahlungen erfasst werden oder wie Bestellungen erfüllt werden, ohne Core-Code anzufassen. Das Framework liefert die Hooks, du lieferst die Geschäftslogik.
8. Custom Fields auf Core Entities
Eines von Vendures pragmatischsten Features. Felder zu jeder Core Entity hinzufügen, ohne Migrationen oder Core-Modifikationen:
// In deiner Plugin-Konfiguration
customFields: {
Product: [
{ name: 'weight', type: 'float', defaultValue: 0, label: [{ languageCode: LanguageCode.en, value: 'Weight (kg)' }] },
{ name: 'erpId', type: 'string', unique: true, label: [{ languageCode: LanguageCode.en, value: 'ERP ID' }] },
{ name: 'countryOfOrigin', type: 'string', label: [{ languageCode: LanguageCode.en, value: 'Country of Origin' }] },
{ name: 'harmonizedCode', type: 'string', label: [{ languageCode: LanguageCode.en, value: 'HS Code' }] },
],
ProductVariant: [
{ name: 'supplierSku', type: 'string', label: [{ languageCode: LanguageCode.en, value: 'Supplier SKU' }] },
{ name: 'minOrderQty', type: 'int', defaultValue: 1, label: [{ languageCode: LanguageCode.en, value: 'Min Order Quantity' }] },
{ name: 'leadTimeDays', type: 'int', nullable: true, label: [{ languageCode: LanguageCode.en, value: 'Lead Time (days)' }] },
],
Customer: [
{ name: 'loyaltyTier', type: 'string', options: [{ value: 'bronze' }, { value: 'silver' }, { value: 'gold' }] },
{ name: 'erpCustomerId', type: 'string', unique: true, label: [{ languageCode: LanguageCode.en, value: 'ERP Customer ID' }] },
{ name: 'creditLimit', type: 'int', nullable: true, label: [{ languageCode: LanguageCode.en, value: 'Credit Limit (cents)' }] },
],
Order: [
{ name: 'erpOrderId', type: 'string', nullable: true, label: [{ languageCode: LanguageCode.en, value: 'ERP Order ID' }] },
{ name: 'exportedAt', type: 'datetime', nullable: true, label: [{ languageCode: LanguageCode.en, value: 'Exported to ERP' }] },
],
}
Custom Fields erscheinen automatisch in der Admin UI, werden in GraphQL Schemas aufgenommen und sind über die API abfragbar. Sie werden in derselben Tabelle wie die Core Entity gespeichert, Joins sind also kostenlos. Du kannst nach Custom Fields mit Vendures Standard-ListQueryBuilder filtern und sortieren. So baust du die Brücke zwischen Vendure und externen Systemen: die erpId auf Product wird der Join Key für den ERP-Sync, die erpCustomerId auf Customer verknüpft mit deinem CRM, und die erpOrderId auf Order trackt, welche Bestellungen exportiert wurden.
Custom Fields unterstützen auch Relationen zu anderen Entities, lokalisierte Strings (pro Sprache übersetzt) und Validierungs-Hooks. Sie sind der primäre Mechanismus, um Vendure an deine Domäne anzupassen, ohne separate Erweiterungstabellen.
9. Custom Pricing Strategies
Vendures Preissystem ist Strategy-basiert. Die Standardberechnung nimmt den Preis aus dem gespeicherten Produktvariantenpreis. Aber im Enterprise Commerce ist Preisgestaltung selten so einfach. Du brauchst möglicherweise Preise aus einem ERP, kundenspezifische Rabatte, Mengenrabattstaffeln oder Echtzeit-Preisberechnungen.
// Benutzerdefinierte Pricing Strategy, die Preise von einem externen ERP abruft
class ErpPricingStrategy implements OrderItemPriceCalculationStrategy {
private erpClient: ErpApiClient;
init(injector: Injector) {
this.erpClient = injector.get(ErpApiClient);
}
async calculateUnitPrice(
ctx: RequestContext,
productVariant: ProductVariant,
orderLineCustomFields: { [key: string]: any },
order: Order,
): Promise<PriceCalculationResult> {
// Prüfen, ob die Variante eine ERP-SKU hat
const erpSku = productVariant.customFields?.supplierSku;
if (!erpSku) {
// Fallback auf Vendures gespeicherten Preis
return {
price: productVariant.listPrice,
priceIncludesTax: productVariant.listPriceIncludesTax,
};
}
// Echtzeit-Preis vom ERP abrufen
const erpCustomerId = order.customer?.customFields?.erpCustomerId;
const erpPrice = await this.erpClient.getPrice({
sku: erpSku,
customerId: erpCustomerId,
quantity: 1,
currency: ctx.currencyCode,
});
return {
price: erpPrice.unitPriceCents,
priceIncludesTax: false,
};
}
}
Registriere die Strategy in deiner Vendure Config:
orderOptions: {
orderItemPriceCalculationStrategy: new ErpPricingStrategy(),
}
So handhaben Enterprise-Vendure-Installationen kundenspezifische Preise, Vertragspreise und dynamische Preisberechnungen. Die Strategy erhält den vollen Order-Kontext (Kunde, Menge, Währung), sodass du jede Preislogik implementieren kannst, die dein Geschäft erfordert. Du kannst dies auch mit Vendures eingebautem Promotion-System kombinieren, um Rabatte auf ERP-basierte Grundpreise zu stapeln.
10. Custom GraphQL Queries für die Integration externer Systeme
Eines von Vendures stärksten Patterns für Enterprise-Integration: Du kannst benutzerdefinierte GraphQL Queries und Mutations hinzufügen, die Daten aus jedem externen System abrufen und über Vendures API bereitstellen. Das macht Vendure zu einem einheitlichen Commerce API Layer, der Daten aus ERPs, PIMs, Lagersystemen und anderen Backends zusammenführt.
// Schema-Erweiterung: Query hinzufügen, die Echtzeit-Bestand vom ERP abruft
const shopApiExtensions = gql`
type ErpStockInfo {
sku: String!
warehouseCode: String!
availableQty: Int!
nextDeliveryDate: DateTime
leadTimeDays: Int
}
type ErpPriceInfo {
sku: String!
unitPrice: Int!
currency: String!
priceListName: String
validUntil: DateTime
}
extend type Query {
erpStockAvailability(sku: String!): [ErpStockInfo!]!
erpCustomerPrice(sku: String!, quantity: Int): ErpPriceInfo
}
`;
// Resolver: Daten vom ERP API abrufen
@Resolver()
export class ErpIntegrationShopResolver {
constructor(private erpService: ErpIntegrationService) {}
@Query()
@Allow(Permission.Public)
async erpStockAvailability(
@Ctx() ctx: RequestContext,
@Args() args: { sku: string },
): Promise<ErpStockInfo[]> {
return this.erpService.getStockLevels(ctx, args.sku);
}
@Query()
@Allow(Permission.Owner)
async erpCustomerPrice(
@Ctx() ctx: RequestContext,
@Args() args: { sku: string; quantity?: number },
): Promise<ErpPriceInfo | null> {
const customerId = ctx.activeUser?.customFields?.erpCustomerId;
if (!customerId) return null;
return this.erpService.getCustomerPrice(ctx, customerId, args.sku, args.quantity ?? 1);
}
}
// Service: eigentliche ERP API-Aufrufe mit Caching und Fehlerbehandlung
@Injectable()
export class ErpIntegrationService {
constructor(
private httpService: HttpService,
private cacheService: CacheService,
) {}
async getStockLevels(ctx: RequestContext, sku: string): Promise<ErpStockInfo[]> {
const cacheKey = `erp:stock:${sku}`;
const cached = await this.cacheService.get(cacheKey);
if (cached) return cached;
const response = await this.httpService.get(
`${process.env.ERP_API_URL}/stock/${sku}`,
{ headers: { 'Authorization': `Bearer ${process.env.ERP_API_TOKEN}` } },
);
const result = response.data.warehouses.map((w: any) => ({
sku,
warehouseCode: w.code,
availableQty: w.available,
nextDeliveryDate: w.nextDelivery,
leadTimeDays: w.leadTime,
}));
await this.cacheService.set(cacheKey, result, { ttl: 300 }); // 5 Min. Cache
return result;
}
async getCustomerPrice(
ctx: RequestContext, customerId: string, sku: string, quantity: number,
): Promise<ErpPriceInfo | null> {
try {
const response = await this.httpService.post(
`${process.env.ERP_API_URL}/pricing`,
{ customerId, sku, quantity, currency: ctx.currencyCode },
);
return {
sku,
unitPrice: Math.round(response.data.unitPrice * 100), // in Cent umrechnen
currency: ctx.currencyCode,
priceListName: response.data.priceList,
validUntil: response.data.validUntil,
};
} catch (error) {
// ERP nicht erreichbar: null zurückgeben, Frontend fällt auf Katalogpreis zurück
return null;
}
}
}
Das Storefront fragt dann Vendure sowohl für Katalogdaten als auch ERP-Daten über einen einzigen GraphQL Endpoint ab:
query ProductWithErpData($slug: String!, $sku: String!) {
product(slug: $slug) {
id
name
description
variants {
id
sku
price
customFields {
supplierSku
minOrderQty
leadTimeDays
}
}
}
erpStockAvailability(sku: $sku) {
warehouseCode
availableQty
nextDeliveryDate
}
erpCustomerPrice(sku: $sku, quantity: 1) {
unitPrice
priceListName
validUntil
}
}
Ein Request. Katalogdaten aus Vendures Datenbank, Bestand vom ERP, kundenspezifische Preise vom ERP. Das Storefront muss nichts vom ERP wissen. Es fragt einfach Vendure. Dieses Pattern funktioniert mit jedem externen System: REST APIs, SOAP Services, GraphQL Endpoints, gRPC Backends. Vendure wird zum Composition Layer.
Wie wir diese Integrationspipelines im großen Maßstab bauen (geplante Syncs, Webhook Listener, Massenimporte), dafür ist das Data Hub Plugin für die komplette ETL-Seite zuständig. Die Custom GraphQL Queries decken die Echtzeit-, per-Request-Seite ab.
Wo Vendure Lücken hat
1. Kein eingebautes Multi-Warehouse
Vendure hat ein Single-Stock-Location-Modell. Wenn du Multi-Warehouse-Inventar brauchst (Bestand in Lager A, anderer Bestand in Lager B, Fulfillment-Routing nach Standort), musst du es selbst bauen oder ein Drittanbieter-Plugin nutzen.
Das ist eine signifikante Lücke für jede Commerce-Operation mit mehr als einem physischen Standort. Der Workaround (Custom Fields + benutzerdefinierte Bestandszuweisungslogik) ist fragil und integriert sich nicht mit Vendures eingebautem Order-Fulfillment-Flow.
2. Begrenzte B2B-Features
Vendure ist primär für B2C Commerce designed. B2B-Anforderungen wie:
- Firmenkonten mit mehreren Einkäufern
- Genehmigungsworkflows für Bestellungen
- Kundenspezifische Preise mit komplexen Regeln
- Angebotsverwaltung
- Bestellaufträge
- Budgetkontrollen pro Einkäufer
Alle erfordern Custom-Entwicklung. Das Channel-System bietet einige Multi-Tenant-Fähigkeiten, aber es ist kein B2B-Feature-Set.
3. Suche ist rudimentär
Vendures eingebaute Suche nutzt einen datenbankgestützten Volltext-Index. Das funktioniert für kleine Kataloge, skaliert aber nicht für:
- Facettensuche mit komplexen Filtern
- Tippfehlertoleranz
- Synonymbehandlung
- Mehrsprachige Suche mit sprachspezifischen Analyzern
- Echtzeit-Index-Updates
Für die Produktionssuche brauchst du eine externe Engine (MeiliSearch, Elasticsearch, Algolia). Unser Data Hub Plugin enthält Such-Sinks für MeiliSearch, Elasticsearch, OpenSearch, Algolia und Typesense mit Echtzeit-Indexierung über Vendure Events. Für mehr zur Sucharchitektur im Commerce behandelt dieser Guide die technischen Details.
4. Migrationstooling
TypeORM-Migrationen in Vendure können fragil sein. Wenn mehrere Plugins Entities definieren, wird die Reihenfolge der Migrationsgenerierung unvorhersagbar. Wir haben erlebt:
- Migrationen, die Tabellen referenzieren, die von anderen Plugin-Migrationen noch nicht erstellt wurden
- Spaltentypkonflikte zwischen SQLite (Dev) und PostgreSQL (Prod)
- Migrationskonflikte, wenn zwei Plugins dieselbe Core Entity über Custom Fields modifizieren
Der Workaround: Migrationen pro Plugin generieren, mit PostgreSQL testen (nicht SQLite) und eine strikte Migrationsreihenfolge in deinen Deployment-Skripten einhalten. Ähnliche Migrationsherausforderungen behandeln wir in unserem Pimcore Upgrade Guide, wo Datenbankschema-Management noch komplexer ist.
Entity-Architektur-Patterns
Nach dem Bau zweier Enterprise Plugins haben sich diese Patterns als nicht verhandelbar für Produktionsqualität herausgestellt.
Entity-Präfix-Konvention
Jede Plugin Entity nutzt ein Präfix, um Namenskollisionen zu vermeiden:
// Tabellennamen in Konstanten definiert (nie hardcoded)
export const TABLE_NAMES = {
WISHLIST: 'ci_wishlist',
WISHLIST_ITEM: 'ci_wishlist_item',
REVIEW: 'ci_review',
REVIEW_VOTE: 'ci_review_vote',
LOYALTY_ACCOUNT: 'ci_loyalty_account',
LOYALTY_TRANSACTION: 'ci_loyalty_transaction',
// ...
};
// Entity nutzt die Konstante
@Entity(TABLE_NAMES.WISHLIST)
@Index(['customerId', 'channelId'])
export class CiWishlist extends VendureEntity {
constructor(input?: DeepPartial<CiWishlist>) {
super(input);
}
@Column({ type: 'varchar', length: 255 })
name!: string;
@Column()
customerId!: number;
@Column()
channelId!: number;
}
Jede Entity erweitert VendureEntity (liefert id, createdAt, updatedAt). Jede Entity enthält channelId für Multi-Channel-Isolation. Jeder Tabellenname kommt aus einer gemeinsamen Konstante, nie als hardcodierter String.
Soft Deletes
Für Entities, die Audit Trails brauchen, nutze eine deletedAt-Spalte statt harter Löschungen:
@Column({ type: 'datetime', nullable: true })
deletedAt?: Date;
Das bewahrt referenzielle Integrität und ermöglicht Wiederherstellung. Gelöschte Datensätze standardmäßig in allen Queries filtern.
Channel Scoping
Jede Query muss auf den aktiven Channel scopen. Vendures RequestContext transportiert die Channel-Information:
async findByCustomer(ctx: RequestContext, customerId: number) {
return this.connection.getRepository(ctx, CiWishlist).find({
where: {
customerId,
channelId: ctx.channelId,
},
});
}
Ohne Channel Scoping leaken Daten von einem Storefront in ein anderes. Das ist der häufigste Sicherheitsfehler in Multi-Channel Vendure Deployments. Ähnliche Multi-Tenant-Datenisolations-Patterns behandeln wir in unserem AI Governance Guide und auf unserer Trust-Seite.
Testing Patterns
Vendure unterstützt sowohl Unit- als auch E2E-Tests für Plugins. Das Testing-Setup ist eine der unterschätzten Stärken des Frameworks.
Unit Tests
Service-Methoden mit gemockten Repositories testen:
import { describe, it, expect, vi } from 'vitest';
describe('WishlistService', () => {
it('should add item to wishlist', async () => {
const mockRepo = {
save: vi.fn().mockResolvedValue({ id: '1', productVariantId: '42' }),
findOne: vi.fn().mockResolvedValue(null),
};
const service = new WishlistService(mockRepo as any);
const result = await service.addItem(mockCtx, { productVariantId: '42' });
expect(result.productVariantId).toBe('42');
expect(mockRepo.save).toHaveBeenCalledOnce();
});
});
E2E Tests
Die volle GraphQL API mit einem echten Vendure Server testen:
import { createTestEnvironment } from '@vendure/testing';
import { testConfig } from './test-config';
describe('Wishlist Shop API', () => {
const { server, adminClient, shopClient } = createTestEnvironment(testConfig);
beforeAll(async () => {
await server.init({ initialData, productsCsvPath: './test-data/products.csv' });
await shopClient.asUserWithCredentials('sara.mustermann@beispiel.de', 'test');
});
afterAll(async () => {
await server.destroy();
});
it('should create a wishlist', async () => {
const { createWishlist } = await shopClient.query(CREATE_WISHLIST, {
input: { name: 'My Favorites' },
});
expect(createWishlist.name).toBe('My Favorites');
});
it('should enforce permissions', async () => {
await shopClient.asAnonymousUser();
const result = await shopClient.query(CREATE_WISHLIST, {
input: { name: 'Should Fail' },
});
expect(result.errors?.[0]?.extensions?.code).toBe('FORBIDDEN');
});
});
Vendures createTestEnvironment startet einen echten Server mit SQLite, führt Migrationen aus, seeded Testdaten und stellt authentifizierte GraphQL Clients für sowohl Shop als auch Admin APIs bereit. Tests laufen gegen die tatsächliche API-Oberfläche, nicht gegen Mocks. Das fängt Berechtigungsprobleme, Schema-Mismatches und Datenvalidierungsfehler ab, die Unit Tests übersehen.
Wir nutzen Vitest mit SWC für schnelle Unit Tests und Vitest mit geforkten Prozessen für E2E Tests (notwendig, weil Vendures Server-Lifecycle Prozess-Isolation zwischen Test Suites erfordert). Wie wir Testing und Qualität in unserer breiteren Engineering-Praxis angehen, siehst du auf unserer Methodologie-Seite.
Multi-Pod-Koordination
Vendure über mehrere Pods laufen zu lassen, bringt Koordinationsherausforderungen mit sich, die in Single-Instance-Deployments nicht existieren.
Leader Election für Scheduler
Geplante Jobs (Warenkorberfassung alle 15 Minuten, Treuepunkte-Ablauf täglich, Preissenkungschecks stündlich) müssen auf genau einer Instanz laufen. Wenn alle Pods den Scheduler ausführen, bekommst du doppelte Verarbeitung.
// Scheduler Services nutzen Leader Election über Vendures JobQueue
// Nur eine Instanz verarbeitet den Job
@Injectable()
export class CartDetectionService {
private jobQueue: JobQueue<{}>;
async onModuleInit() {
this.jobQueue = await this.jobQueueService.createQueue({
name: 'cart-detection',
process: async (ctx) => {
await this.detectAbandonedCarts(ctx);
},
});
// Geplant: läuft alle 15 Minuten, aber nur auf einem Pod
await this.jobQueue.add({}, { ctx: RequestContext.empty() });
}
}
Business-Key-Deduplizierung für Event Consumer
Event Consumer (Preissenkungsbenachrichtigungen, Bestandsalarme, Bewertungsanfragen) laufen auf allen Pods. Jedes Pods EventBus-Subscription empfängt das Event. Deduplizierung verhindert das Senden doppelter E-Mails:
| Service-Typ | Multi-Instanz-Verhalten |
|---|---|
| Scheduler (Warenkorberfassung, Treuepunkte-Ablauf) | Leader Election via DB Lock. Nur 1 Instanz läuft. |
| Event Consumer (Preissenkungen, Bestandsalarme) | Alle Instanzen konsumieren. Idempotent via Business-Key-Dedup. |
// Business-Key-Deduplizierung für Benachrichtigungen
const dedupeKey = `${recipientEmail}:${category}:${entityRef}:${dayBucket}`;
const existing = await this.notificationRepo.findOne({ where: { dedupeKey } });
if (existing) {
return; // Heute schon gesendet
}
Der Dedupe Key enthält einen dayBucket (UTC-Datum YYYY-MM-DD), sodass dieselbe Benachrichtigung am nächsten Tag erneut gesendet werden kann, aber nie zweimal am selben Tag.
Idempotenz für API Mutations
Kundenorientierte Mutations (zur Wishlist hinzufügen, Bewertung abgeben, Treuepunkte einlösen) brauchen Idempotenz, um Retries und Netzwerkausfälle zu handhaben:
@Entity(TABLE_NAMES.IDEMPOTENCY_KEY)
export class CiIdempotencyKey extends VendureEntity {
@Column({ type: 'varchar', length: 255 })
@Index()
key!: string;
@Column({ type: 'varchar', length: 50 })
scope!: string; // 'wishlist_add', 'review_submit', usw.
@Column({ type: 'varchar', length: 64 })
requestHash!: string; // SHA-256 des normalisierten Inputs
@Column({ type: 'varchar', length: 20 })
status!: string; // PENDING, COMPLETED, FAILED
}
// Unique Constraint: UNIQUE(scope, key)
Zwei unterschiedliche Idempotenz-Modelle:
- API-Idempotenz (CiIdempotencyKey): Für benutzerinitiierte Mutations. Key vom Client bereitgestellt oder aus Input-Hash generiert. Gibt gecachte Antwort bei Duplikaten zurück.
- Job-Idempotenz (Queue-Ebene): Für Hintergrundverarbeitung. Dedupe Key im Job Payload. Vom Worker vor der Ausführung geprüft. Nutzt DB Constraints oder Completion Markers.
Diese beiden Modelle nie vermischen. Sie haben unterschiedliche Lebenszyklen und unterschiedliche Fehlersemantiken.
Wie wir ähnliche Concurrency Patterns in unseren Systemen handhaben, behandelt unser Guide zu AI Workflow Design verwandte Orchestrierungsherausforderungen.
Data Hub: ETL für Vendure
Eine der größten Lücken im Headless Commerce ist Datenintegration. Wie bekommst du Produkte aus deinem ERP in Vendure? Wie synchronisierst du Inventar über Kanäle hinweg? Wie generierst du Produkt-Feeds für Google Merchant Center?
Wir haben das Vendure Data Hub Plugin gebaut, um das zu lösen. Es ist eine vollständige ETL-Pipeline-Engine, die innerhalb von Vendure läuft:
| Komponente | Anzahl | Beispiele |
|---|---|---|
| Extractors | 9 | HTTP/REST, GraphQL, Database (SQL), File (CSV/JSON/XML), S3, FTP/SFTP, Webhook, CDC |
| Transform-Operatoren | 61 | String (12), Date (5), Numeric (9), Logic (4), JSON (4), Data (8), Enrichment (5), Aggregation (8), Validation (2) |
| Entity Loaders | 24 | Products, Variants, Customers, Collections, Orders, Promotions, Assets, Facets |
| Feed-Generatoren | 4 | Google Merchant Center, Meta Catalog, Amazon Seller Central, Custom |
| Such-Sinks | 7 | Elasticsearch, OpenSearch, MeiliSearch, Algolia, Typesense, Queue Producers, Webhooks |
| Trigger | 6 | Manual, Scheduled (cron), Webhook, Vendure Events, File Watch, Message Queue |
Das Plugin nutzt dieselben Patterns wie Vendure Core: TypeORM Entities, NestJS Services, GraphQL API, EventBus Integration. Es enthält einen visuellen Pipeline-Editor im Admin Dashboard, Echtzeit-Ausführungslogs, Checkpoint Recovery (Fortsetzen ab dem letzten erfolgreichen Datensatz) und Distributed Locks für Multi-Pod-Sicherheit.
Das ist die Art von Data Engineering-Infrastruktur, die Enterprise Commerce braucht, aber selten von der Commerce-Plattform selbst bekommt.
Vendure vs Medusa vs Saleor (2026)
Ein ehrlicher Vergleich für Architekten, die Plattformen evaluieren:
| Kriterium | Vendure | Medusa v2 | Saleor |
|---|---|---|---|
| Sprache | TypeScript (NestJS) | TypeScript (eigenes Framework) | Python (Django + GraphQL) |
| API | GraphQL (Shop + Admin) | REST + JS SDK + Admin API | GraphQL |
| Plugin-System | Exzellent (Entities, Schema, Resolver, Admin) | Gut (Module, Workflows) | Begrenzt (Apps via Webhooks) |
| Datenbank | PostgreSQL, MySQL, SQLite (TypeORM) | PostgreSQL (MikroORM) | PostgreSQL (Django ORM) |
| Admin UI | React (eingebaut, voll anpassbar) | React (eingebaut) | React (eingebaut, Dashboard) |
| Worker/Jobs | BullMQ (eingebaut) | Eingebautes Job-System | Celery (Python) |
| Multi-Channel | Channels (eingebaut) | Sales Channels | Channels |
| B2B-Features | Begrenzt (Custom-Entwicklung nötig) | Wachsend (teilweise eingebaut) | Wachsend (teilweise eingebaut) |
| Suche | Rudimentär (DB-basiert) | Rudimentär (Integration nötig) | Rudimentär (Integration nötig) |
| Multi-Warehouse | Nicht eingebaut | Eingebaut (v2) | Eingebaut |
| Community | Mittel (wachsend) | Groß (schnell wachsend) | Mittel |
| Hosting | Self-hosted | Self-hosted + Medusa Cloud | Saleor Cloud + Self-hosted |
| Reife | Stabil, produktionserprobt | v2 ist neuer, API-Änderungen möglich | Stabil, produktionserprobt |
| DX für TypeScript-Teams | Exzellent | Gut | Erfordert Python-Kenntnisse |
| Enterprise Plugins | Starkes Ökosystem | Wachsend | Webhook-basiert (begrenzt) |
Wann Vendure wählen
- Dein Team schreibt TypeScript. Der DX-Vorteil ist nicht marginal, er ist transformativ. Type Safety über den gesamten Commerce Stack verändert, wie schnell du ausliefern und wie sicher du refactoren kannst.
- Du brauchst tiefe Plugin-Erweiterbarkeit. Keine andere Plattform lässt dich Entities hinzufügen, das GraphQL Schema erweitern, Admin Dashboard Seiten bauen und Background Jobs registrieren, alles aus einer einzigen Plugin-Deklaration.
- Du baust B2C oder B2C-lastigen Hybrid Commerce mit individueller Geschäftslogik. Vendure gibt dir einen soliden Commerce Core und steht dir für den Rest nicht im Weg.
- Du willst deine Infrastruktur besitzen. Vendure läuft überall, wo Node.js läuft.
- Du brauchst enge Integration mit bestehenden NestJS Microservices oder TypeScript Backends. Vendure ist bereits NestJS, deine Services sprechen also dieselbe Sprache.
- Du schätzt saubere Architektur und langfristige Wartbarkeit. Die Patterns (EventBus, RequestContext, separate Shop/Admin Resolver) skalieren zu großen Codebasen ohne Verfall.
Wann Medusa wählen
- Du brauchst Multi-Warehouse out of the box.
- Du bevorzugst REST gegenüber GraphQL für dein Storefront.
- Du willst eine Managed-Cloud-Option mit der Open-Source-Flexibilität.
- Du startest von Null und willst die neuesten Framework-Patterns.
Wann Saleor wählen
- Dein Team arbeitet mit Python/Django.
- Du brauchst das Managed-Cloud-Angebot (Saleor Cloud).
- Du willst starke eingebaute Internationalisierung und Multi-Currency.
- Du brauchst Marketplace-Features.
Wann Shopify wählen
- Du willst keine Infrastruktur verwalten.
- Deine Commerce-Anforderungen sind Standard (Katalog, Checkout, Zahlungen).
- Du brauchst das größte App-Ökosystem.
- Du bist bereit, die Einschränkungen der Plattform für schnellere Time-to-Market zu akzeptieren.
Für unsere breitere Perspektive zu Headless Commerce Plattformen und wohin sich die Branche entwickelt, behandelt dieser Guide die gesamte Landschaft.
Produktions-Deployment-Architektur
Empfohlener Stack
| Komponente | Technologie | Zweck |
|---|---|---|
| Server | Vendure (NestJS) | Commerce API |
| Datenbank | PostgreSQL 15+ | Primärer Datenspeicher |
| Cache | Redis 7+ | Sessions, Cache, Job Queues |
| Suche | MeiliSearch oder OpenSearch | Produktsuche, Facettenfilterung |
| Speicher | S3 / Azure Blob / lokal | Asset-Speicherung |
| CDN | CloudFront / Cloudflare | Asset-Auslieferung |
| Message Broker | Redis (BullMQ) oder RabbitMQ | Job Queues, Event-Verarbeitung |
| Monitoring | OpenTelemetry + Grafana | Observability |
Kubernetes Pod-Architektur
| Pod | Zweck | Replicas |
|---|---|---|
vendure-server | Shop API + Admin API | 2-4 |
vendure-worker | BullMQ Job-Verarbeitung | 1-3 |
postgres | Datenbank (oder managed) | 1+ |
redis | Cache + Queues | 1+ |
meilisearch | Suchmaschine | 1-2 |
Umgebungskonfiguration
// vendure-config.ts (Produktion)
export const config: VendureConfig = {
apiOptions: {
port: 3000,
adminApiPath: 'admin-api',
shopApiPath: 'shop-api',
cors: {
origin: process.env.CORS_ORIGIN?.split(',') || [],
},
},
dbConnectionOptions: {
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || '5432'),
database: process.env.DB_NAME,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
ssl: process.env.DB_SSL === 'true' ? { rejectUnauthorized: false } : false,
synchronize: false, // In Produktion NIEMALS true
},
workerOptions: {
runInForkedProcess: false, // Separates Deployment in K8s
},
jobQueueOptions: {
activeQueues: process.env.WORKER === 'true'
? undefined // Alle Queues verarbeiten
: [], // Keine verarbeiten (nur Server-Pod)
},
plugins: [
AssetServerPlugin.init({
storageStrategyFactory: configureAssetStorage(),
}),
DefaultSearchPlugin.init({ bufferUpdates: true }),
EmailPlugin.init({ /* ... */ }),
// Deine Plugins
DataHubPlugin,
CustomerIntelligencePlugin.init({ /* ... */ }),
],
};
Die kritische Einstellung: jobQueueOptions.activeQueues. Auf Server Pods setzt du es auf [] (leeres Array), damit sie keine Background Jobs verarbeiten. Auf Worker Pods lässt du es undefined, damit sie alle Queues verarbeiten. Das trennt Request-Handling von Hintergrundverarbeitung.
Für mehr darüber, wie wir Cloud Deployment und Infrastruktur-Architektur angehen, behandelt diese Service-Seite unseren Ansatz.
Performance-Überlegungen
Datenbank-Indexierung
Vendure erstellt Basis-Indizes auf Core Entities. Für die Produktion füge Indizes hinzu auf:
- Custom Fields, die du häufig abfragst
- Plugin Entity Spalten, die in WHERE-Klauseln verwendet werden
- Foreign Keys auf großen Tabellen
- Composite Indizes für häufige Query-Patterns
@Entity(TABLE_NAMES.REVIEW)
@Index(['productId', 'status', 'channelId']) // Häufige Query: freigegebene Bewertungen für ein Produkt
@Index(['customerId', 'createdAt']) // Häufige Query: Bewertungshistorie eines Kunden
export class CiReview extends VendureEntity {
// ...
}
Connection Pooling
Für PostgreSQL in Produktion:
dbConnectionOptions: {
type: 'postgres',
extra: {
max: 20, // Max Verbindungen pro Pod
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
},
}
Mit 4 Server Pods und 2 Worker Pods sind das insgesamt 120 Verbindungen. Stelle sicher, dass deine PostgreSQL max_connections-Einstellung das verarbeiten kann (Standard ist 100, das ist zu niedrig).
GraphQL Query-Komplexität
Vendure limitiert GraphQL Query-Tiefe oder -Komplexität standardmäßig nicht. In Produktion füge Limits hinzu, um teure Queries daran zu hindern, die Datenbank zu überlasten:
apiOptions: {
shopApiPlayground: false, // In Produktion deaktivieren
adminApiPlayground: false,
middleware: [{
handler: depthLimit(10), // Maximale Query-Tiefe
route: '*',
}],
},
Die Zukunft von Vendure
Vendure entwickelt sich aktiv weiter. Wichtige Bereiche, die du im Auge behalten solltest:
- Dashboard-Reifung: Die React Admin UI ist bereits der Standard. Erwarte mehr eingebaute Komponenten, bessere Plugin Extension Points und reichhaltigere Out-of-the-Box Dashboard-Features.
- Verbesserte B2B-Features: Firmenkonten und Einkäufer-Features stehen auf der Roadmap.
- Bessere Suche: Flexiblere Such-Integrationspunkte.
- Vendure Cloud: Managed-Hosting-Option (angekündigt, noch nicht breit verfügbar).
Das Core-Team ist reaktionsschnell und das Projekt ist kommerziell unterstützt (Vendure Ltd). Das TypeScript-Ökosystem drumherum (NestJS, TypeORM, BullMQ) ist ausgereift und gut gewartet.
Häufige Fallstricke
-
SQLite in Produktion nutzen. SQLite ist für die Entwicklung in Ordnung. In Produktion nutze PostgreSQL. TypeORM-Verhaltensunterschiede zwischen den beiden Datenbanken verursachen Bugs, die du in der Entwicklung nie siehst.
-
Worker im Server-Prozess laufen lassen. Trenne dein Worker Deployment. Ein lang laufender Job im selben Prozess wie dein API Server blockiert die Request-Verarbeitung.
-
Kein Channel Scoping. Jede Query muss nach
ctx.channelIdfiltern. Fehlt das, leaken Daten zwischen Storefronts. -
Modulübergreifende Service-Imports. Nutze den EventBus für die Inter-Modul-Kommunikation. Direkte Imports erzeugen zirkuläre Abhängigkeiten, die brechen, wenn das System wächst.
-
Keine Idempotenz bei Mutations. Netzwerk-Retries und Webhook-Replays erzeugen doppelte Bestellungen, doppelte Bewertungen, doppelte Wishlist-Einträge. Baue Idempotenz vom ersten Tag an ein.
-
Hardcodierte Tabellennamen. Nutze Konstanten. Wenn du 30+ Entities über 3 Plugins hast, werden hardcodierte Strings zum Wartungsalptraum.
-
Synchronize: true in Produktion. TypeORMs Auto-Sync wird Spalten löschen und Daten verlieren. Nutze Migrationen. Immer.
-
Nicht mit PostgreSQL testen. Wenn du auf SQLite entwickelst und auf PostgreSQL deployst, wirst du Spaltentyp-Mismatches, Transaktionsverhaltensunterschiede und JSON-Handling-Bugs in der Produktion entdecken.
-
Den Worker Service ignorieren. E-Mail-Versand, Such-Indexierung und Asset-Verarbeitung sollten alle über die Job Queue laufen. Sie synchron in Request Handlern auszuführen, macht deine API langsam und unzuverlässig.
-
Alles selbst bauen. Prüfe zuerst das Plugin-Ökosystem. Für ETL/Datenintegration, Such-Indexierung und gängige Commerce-Features existieren Plugins. Von Grund auf bauen dauert 10x länger.
Zentrale Erkenntnisse
-
Vendures Plugin-System ist das beste im Headless Commerce. Entity-Erweiterung, Schema-Erweiterung, separate Shop/Admin Resolver, volle NestJS DI. Keine andere Plattform kommt für TypeScript-Teams auch nur in die Nähe. Die Developer Experience ist das, was uns weiter darauf bauen lässt.
-
Der EventBus ermöglicht echte lose Kopplung. Module kommunizieren nur über Events. Das skaliert auf 6+ Module in einem einzelnen Plugin ohne zirkuläre Abhängigkeiten. Es ist das Pattern, das Enterprise Plugins möglich macht.
-
Multi-Pod-Deployment erfordert explizite Koordination. Leader Election für Scheduler, Business-Key-Deduplizierung für Event Consumer, Idempotenz für API Mutations. Nichts davon passiert automatisch.
-
Die React Admin UI ist voll anpassbar. Vendure 3s React-basierte Admin UI unterstützt eigene Seiten, Routen, Komponenten und Dashboard Widgets. Plugin-Entwickler bekommen eine moderne DX mit React 18, TanStack Query und Vendures Dashboard SDK.
-
Multi-Warehouse und B2B sind echte Lücken. Wenn das Kernanforderungen sind, evaluiere Medusa v2 oder Saleor. Sie auf Vendure custom zu bauen ist teuer.
-
Produktion erfordert PostgreSQL, Redis und separates Worker Deployment. SQLite ist für die Entwicklung. BullMQ ist für Background Jobs. Trenne deine Server und Worker Pods.
Wir arbeiten wirklich gern auf Vendure. Die Developer Experience, die Architektur-Patterns und die Flexibilität des Plugin-Systems machen es zu einer Plattform, die wir TypeScript-Teams mit Überzeugung empfehlen. Die Codebasis bleibt sauber, auch wenn sie wächst, das Framework steht dir nicht im Weg, und die Community ist reaktionsschnell und wachsend. Wenn du Multi-Warehouse oder umfangreiches B2B out of the box brauchst, evaluiere Alternativen. Für alles andere im Headless Commerce ist Vendure unsere erste Wahl.
Erkunde unseren Vendure Headless Commerce Guide für mehr über unsere Vendure-Praxis, oder sieh dir reale Anwendungsfälle an, bei denen wir Vendure in Produktion eingesetzt haben. Wenn du Commerce-Plattformen für dein nächstes Projekt evaluierst, sprich mit unserem Team oder fordere ein Angebot an.
Behandelte Themen
Verwandte Guides
Enterprise Vendure Plugins entwickeln: Patterns, die Multi-Pod-Deployment überleben
Produktionserprobte Patterns für Vendure Plugin-Entwicklung. Entity-Konventionen, Resolver-Trennung, EventBus-Kommunikation, Leader Election, Benachrichtigungs-Deduplizierung und Testing mit echten Servern.
Guide lesenUnternehmenshandbuch 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 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