News

Shopware Agentur Frankfurt: Architektur-Optimierung für Shopware 6 (API-first, MVC, PWA-ready)

Von Erol Demirkoparan
9 min
Shopware Agentur Frankfurt: Architektur-Optimierung für Shopware 6 (API-first, MVC, PWA-ready) - Cloudox Software Agentur Blog

1) Architektur-Audit: Ist-Zustand messen, bevor Sie umbauen

Für eine belastbare Architektur-Optimierung in Shopware 6 starten wir mit einem Audit, das nicht „Gefühl“, sondern Systemgrenzen sichtbar macht: Latenzen, Kopplungen, Datenflüsse, Release-Risiken und Erweiterbarkeit. Ziel ist eine Architektur, die Änderungen (Sortimente, Pricing, Kampagnen, internationale Expansion) ohne Performance- oder Deployment-Schmerz mitmacht.

Checkliste: Was wir technisch erfassen

  • Runtime-Profile: PHP-FPM/Worker, MySQL-Last, Redis-Cache, Messenger-Queues, Indexer-Laufzeiten
  • API-Nutzung: Store API / Admin API Konsum, Auth-Strategien, Rate-Limits, Payload-Größen
  • Plugin-Landschaft: Event-Subscriber, Decorators, DAL-Extensions, Konfliktpotenzial
  • Frontend: Storefront vs. PWA, Rendering-Strategie, Core Web Vitals
  • Deployments: Blue/Green oder Rolling, Migrations-/Indexer-Fenster, Rollback-Fähigkeit
  • Integrationen: ERP/PIM/CRM/Payments/Versand als synchron vs. asynchron (Queue)
FeatureDetails
API-first ReadinessTrennung von Frontend & Commerce-Backbone, klare API-Verträge, versionierte Endpoints
MVC-KonformitätSaubere Verantwortlichkeiten (Controller/Service/Repository), wartungsfähige Erweiterungen
PWA-FähigkeitStore API-First, SSR/ISR-Strategie, Edge-Cache & Token-Handling
Deployment-SicherheitMigrations-Runbooks, Indexer-Strategie, Rollback-Plan, Secrets-Management
PerformanceCache-Hit-Rate, DB-Indizes, Such-Stack, Async-Verarbeitung via Messenger

2) Zielbild definieren: API-first Approach als Architekturleitlinie

Ein API-first Approach bedeutet: Sie definieren zuerst die Schnittstellenverträge (Ressourcen, Payloads, Auth, Fehlercodes) und bauen UI/Integrationen danach – nicht umgekehrt. Für Shopware 6 ist das besonders relevant, weil Sie damit Storefront, PWA und externe Systeme entkoppeln.

Warum API-first in Shopware 6 konkret hilft

  • Entkopplung: Frontend-Teams arbeiten unabhängig von Backend-Teams (Release-Zyklen werden kürzer).
  • Skalierung: Hohe Read-Last kann über Caching/CDN/Edge adressiert werden, ohne Kernlogik zu duplizieren.
  • Integrationssicherheit: ERP/PIM-Anbindung über stabile Verträge (weniger „Breaking Changes“).
  • Messbarkeit: API-Latenzen, Error-Rates und Payload-Größen sind klare Optimierungsmetriken.

API Platform: Rolle im Architekturplan

Unter API Platform verstehen wir die Betriebs- und Design-Schicht Ihrer Schnittstellen: API-Gateway/Reverse Proxy, Auth, Rate-Limiting, Observability, Versionierung und Developer Experience (Doku, Tests). In Shopware-Kontext heißt das: Admin API/Store API sauber konsumieren, ggf. BFF (Backend-for-Frontend) etablieren und Integrationen standardisieren.

3) MVC Pattern in Shopware 6: Wartbarkeit durch klare Verantwortlichkeiten

Das MVC Pattern (Model-View-Controller) sorgt dafür, dass Business-Logik nicht im Controller „klebt“ und Views nicht zu viel wissen. In Shopware 6 (Symfony-basiert) ist das entscheidend, um Plugins/Extensions konfliktarm zu halten.

So wenden wir MVC pragmatisch an

  • Controller: Nur Request/Response, Validierung/Mapping, keine Geschäftsregeln.
  • Service Layer: Business-Regeln, Orchestrierung, Transaktionsgrenzen.
  • Model/DAL: Datenzugriff über Repository/DAL, klare Entities, keine SQL-Spaghetti.
  • View: Storefront Twig oder PWA UI; Darstellung, keine Domänenlogik.

4) Referenz-Architektur: Shopware 6 + PWA + Integrationen (API-first)

Ein typisches Zielbild für wachsende Shops: Shopware 6 als Commerce-Kern, eine PWA als Frontend, Integrationen (ERP/PIM/Payments) asynchron über Queue/Events, und eine API Platform für Governance.

flowchart LR
  U[User] --> CDN[CDN/Edge Cache]
  CDN --> PWA[PWA Frontend (SSR/ISR)]
  PWA --> BFF[BFF / API Gateway]
  BFF --> SA[Shopware 6 Store API]
  BFF --> AA[Shopware 6 Admin API]
  SA --> SW[Shopware Core]
  SW --> DB[(MySQL)]
  SW --> R[(Redis Cache)]
  SW --> MQ[(Messenger/Queue)]
  MQ --> ERP[ERP]
  MQ --> PIM[PIM]
  MQ --> PAY[Payment/PSP]
  BFF --> OBS[Observability (Logs/Metrics/Tracing)]

5) Schritt-für-Schritt: Architektur-Optimierung (shopware_architecture_plan)

Schritt 1: Domänen & Bounded Contexts schneiden

  • Katalog (Produkte, Varianten, Medien)
  • Preis/Promotion
  • Checkout/Order
  • Kunde/Account
  • Content/Experience

Ergebnis: Sie wissen, welche Teile synchron sein müssen (Checkout) und welche asynchron werden dürfen (PIM-Import, Fulfillment-Updates).

Schritt 2: API-first Verträge festziehen (Store API / Admin API / BFF)

  • Welche Endpoints braucht die PWA wirklich (weniger Overfetching)?
  • Welche Daten können gecacht werden (Katalog) und welche nicht (Warenkorb)?
  • Wie versionieren wir (v1/v2) und wie testen wir Vertragsbrüche?

Schritt 3: Performance-Fokus: Cache, Suche, Datenbank

  • Cache-Hierarchie: HTTP Cache/CDN → App Cache → Redis → DB
  • Indexer/Async: Indexing nicht im Peak, Queues überwachen
  • DB: Slow Query Log, Indizes, Vermeidung von N+1

Schritt 4: Erweiterungen Shopware-konform (MVC + Events)

  • Business-Logik in Services
  • Controller schlank halten
  • Event Subscriber nur für klare Use-Cases (keine „God Subscriber“)
  • Decorators mit Bedacht (Konfliktpotenzial mit anderen Plugins)

Schritt 5: Deployment-Architektur & Runbooks

  • Build once, deploy many (Artefakt-Strategie)
  • Migrations/Indexer-Prozess als definierter Schritt
  • Rollback: Datenbank-Migrationsstrategie und Feature Flags

6) TypeScript: API Integration (PWA/BFF) gegen Shopware 6 Store API

Das folgende Beispiel zeigt eine realistische TypeScript-Integration, die (a) Token-basiert arbeitet, (b) saubere Fehler ausgibt und (c) typische Shopware Store-API Use-Cases abbildet: Context erstellen, Produkte suchen, Warenkorb befüllen.

TypeScript API Integration Code
type ShopwareContextToken = string;

type ShopwareError = {
    status?: number;
    code?: string;
    title?: string;
    detail?: string;
};

type ProductListingResponse = {
    elements: Array<{
        id: string;
        name: string;
        productNumber: string;
    }>;
    total: number;
};

class ShopwareStoreApiClient {
    private readonly baseUrl: string;
    private readonly accessKey: string;

    public constructor(params: { baseUrl: string; accessKey: string }) {
        this.baseUrl = params.baseUrl.replace(/\/$/, "");
        this.accessKey = params.accessKey;
    }

    private async request<T>(
        path: string,
        options: {
            method?: "GET" | "POST" | "PATCH" | "DELETE";
            contextToken?: ShopwareContextToken;
            body?: unknown;
        } = {}
    ): Promise<{ data: T; contextToken?: ShopwareContextToken } > {
        const url = `${this.baseUrl}${path}`;

        const headers: Record<string, string> = {
            "Content-Type": "application/json",
            "Accept": "application/json",
            "sw-access-key": this.accessKey
        };

        if (options.contextToken) {
            headers["sw-context-token"] = options.contextToken;
        }

        const response = await fetch(url, {
            method: options.method ?? "POST",
            headers,
            body: options.body ? JSON.stringify(options.body) : undefined
        });

        const nextContextToken = response.headers.get("sw-context-token") ?? undefined;

        if (!response.ok) {
            let errorBody: any = undefined;
            try {
                errorBody = await response.json();
            } catch {
                // ignore
            }

            const err: ShopwareError = {
                status: response.status,
                title: errorBody?.errors?.[0]?.title ?? response.statusText,
                detail: errorBody?.errors?.[0]?.detail,
                code: errorBody?.errors?.[0]?.code
            };

            throw new Error(`Shopware Store API error: ${JSON.stringify(err)}`);
        }

        const data = (await response.json()) as T;
        return { data, contextToken: nextContextToken };
    }

    public async createContext(): Promise<ShopwareContextToken> {
        const result = await this.request<{ token?: string }>("/store-api/context", {
            method: "POST",
            body: {}
        });

        // Shopware may return context token via header; we prefer that.
        if (result.contextToken) return result.contextToken;

        // Fallback (depending on setup)
        if (result.data.token) return result.data.token;

        throw new Error("No sw-context-token received from Shopware.");
    }

    public async searchProducts(params: {
        contextToken: ShopwareContextToken;
        term: string;
        limit?: number;
    }): Promise<ProductListingResponse> {
        const result = await this.request<ProductListingResponse>("/store-api/search", {
            method: "POST",
            contextToken: params.contextToken,
            body: {
                search: params.term,
                limit: params.limit ?? 10
            }
        });

        return result.data;
    }

    public async addToCart(params: {
        contextToken: ShopwareContextToken;
        productId: string;
        quantity: number;
    }): Promise<unknown> {
        const result = await this.request<unknown>("/store-api/checkout/cart/line-item", {
            method: "POST",
            contextToken: params.contextToken,
            body: {
                items: [
                    {
                        type: "product",
                        referencedId: params.productId,
                        quantity: params.quantity
                    }
                ]
            }
        });

        return result.data;
    }
}

async function demo() {
    const client = new ShopwareStoreApiClient({
        baseUrl: "https://example.com",
        accessKey: "YOUR_STOREFRONT_ACCESS_KEY"
    });

    const contextToken = await client.createContext();
    const listing = await client.searchProducts({ contextToken, term: "Sneaker", limit: 5 });

    if (listing.elements.length > 0) {
        await client.addToCart({
            contextToken,
            productId: listing.elements[0].id,
            quantity: 1
        });
    }

    return { contextToken, found: listing.total };
}

7) YAML: Deployment-Konfiguration (Shopware 6) mit klaren Build/Deploy-Schritten

Eine wiederholbare Deployment-Pipeline ist ein Architektur-Baustein. Unten ein Beispiel, wie Sie Shopware 6 in einer CI/CD-Pipeline strukturieren: getrennte Schritte für Build, Cache-Warmup, DB-Migrationen und indexer/async Tasks. Passen Sie Kommandos/Container/Secrets an Ihre Infrastruktur an.

YAML Config for Shopware Deployment
name: Deploy Shopware 6

on:
  push:
    branches: [ "main" ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    env:
      APP_ENV: prod
      APP_DEBUG: "0"

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: "8.2"
          tools: composer:v2
          extensions: mbstring, intl, pdo_mysql

      - name: Install dependencies
        run: |
          composer install \
            --no-dev \
            --prefer-dist \
            --no-interaction \
            --optimize-autoloader

      - name: Build storefront assets
        run: |
          bin/console theme:compile

      - name: Warm up cache
        run: |
          bin/console cache:clear
          bin/console cache:warmup

      - name: Run database migrations
        run: |
          bin/console doctrine:migrations:migrate --no-interaction

      - name: Refresh DAL indices (safe default)
        run: |
          bin/console dal:refresh:index --no-interaction

      - name: Deploy artifact to server
        env:
          SSH_HOST: ${{ secrets.SSH_HOST }}
          SSH_USER: ${{ secrets.SSH_USER }}
          SSH_KEY: ${{ secrets.SSH_KEY }}
        run: |
          mkdir -p ~/.ssh
          echo "$SSH_KEY" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SSH_HOST} "mkdir -p /var/www/shopware"
          rsync -az --delete ./ ${SSH_USER}@${SSH_HOST}:/var/www/shopware/
          ssh ${SSH_USER}@${SSH_HOST} "cd /var/www/shopware && bin/console messenger:stop-workers || true"
          ssh ${SSH_USER}@${SSH_HOST} "cd /var/www/shopware && bin/console cache:clear"
          ssh ${SSH_USER}@${SSH_HOST} "cd /var/www/shopware && bin/console messenger:consume -vv --time-limit=300 --memory-limit=256M --limit=200 &"

8) Architektur-Deliverables, die eine Shopware Agentur in Frankfurt liefern sollte

  • Systemdiagramm (Ist/Ziel) inkl. Datenflüsse und Latenz-Budgets
  • API Contracts (Store API/BFF) inkl. Fehler- und Versionierungsstrategie
  • MVC-konforme Customizations (Services/Subscriber/Decorator-Plan)
  • Deployment-Runbooks (Migrationsfenster, Indexer, Rollback)
  • Observability: Dashboards für API-Latenz, Error-Rates, Queue-Backlog, DB-Slow Queries

Wenn Sie dafür einen Partner suchen: Shopware Agentur.

Häufig gestellte Fragen

Autor

Erol Demirkoparan

Erol Demirkoparan

Senior Software Architect

Full-Stack & Cloud-Native Systems Expert. Spezialisiert auf AWS, Next.js und skalierbare SaaS-Architekturen. Building the future of automated SEO.

AWSNext.jsScalable SaaSSystem Architecture

Veröffentlicht am

10. Januar 2026

Das könnte Sie auch interessieren