Einleitung
In modernen E-Commerce-Systemen ist die zuverlässige und skalierbare Verarbeitung von Aufträgen von entscheidender Bedeutung. Dieser Artikel führt durch den Aufbau eines produktionsreifen, ereignisgesteuerten Auftragsverarbeitungssystems mit Azure-Services und demonstriert Cloud-native Muster, die Zuverlässigkeit, Skalierbarkeit und Beobachtbarkeit gewährleisten.
Was wir bauen
Ein Auftragsverarbeitungssystem auf Enterprise-Niveau mit:
- Ereignisgesteuerter Architektur mit Azure Service Bus
- Serverless Backend mit Azure Functions (TypeScript)
- Modernem Frontend mit Next.js und React
- Vollständiger Beobachtbarkeit mit Application Insights
- Infrastructure as Code mit Bicep
- Automatisierter CI/CD mit GitHub Actions
Tech Stack:
- Backend: Azure Functions (Node.js), TypeScript
- Frontend: Next.js, React, TailwindCSS, shadcn/ui
- Infrastruktur: Azure Service Bus, Application Insights, Storage, Bicep
- Validierung: Zod-Schemas
- State Management: TanStack Query
Architekturübersicht
Das System implementiert das Queue-Based Load Leveling Muster mit Competing Consumers und bietet:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐│ Next.js │─────▶│ Azure Function │─────▶│ Service Bus ││ Frontend │ HTTP │ (HTTP Trigger) │ │ Queue │└─────────────────┘ └──────────────────┘ └─────────────────┘│▼┌──────────────────┐ ┌─────────────────┐│ Azure Function │◀─────│ Service Bus ││ (Queue Trigger) │ │ Queue │└──────────────────┘ └─────────────────┘│▼┌──────────────────┐│ Application ││ Insights │└──────────────────┘
Hauptvorteile
- Entkopplung: Frontend und Backend-Verarbeitung sind vollständig unabhängig
- Zuverlässigkeit: Nachrichten bleiben in Service Bus erhalten, wenn die Verarbeitung fehlschlägt
- Skalierbarkeit: Mehrere Consumer können Nachrichten parallel verarbeiten
- Resilienz: Fehlgeschlagene Nachrichten werden automatisch wiederholt oder in die Dead Letter Queue verschoben
- Performance: Schnelle Antwortzeiten für Benutzer durch asynchrone Verarbeitung
Teil 1: Infrastructure as Code mit Bicep
Die Infrastruktur wird vollständig in Bicep definiert, Azures domänenspezifischer Sprache für die Bereitstellung von Ressourcen.
Bereitgestellte Ressourcen
- Resource Group - Container für alle Ressourcen
- Service Bus Namespace (Standard-Tier) mit Queue
- Application Insights + Log Analytics Workspace
- Storage Account für Azure Functions
- Function App mit vorkonfigurierten Einstellungen
- Static Web App für das Next.js-Frontend
Projektstruktur
infrastructure/├── main.bicep # Hauptorchestrierung├── modules/│ ├── servicebus.bicep # Queue-Konfiguration│ ├── appinsights.bicep # Monitoring-Setup│ ├── storage.bicep # Storage Account│ ├── functionapp.bicep # Serverless Functions│ └── staticwebapp.bicep # Frontend-Hosting└── parameters/├── dev.bicepparam # Entwicklungskonfiguration└── prod.bicepparam # Produktionskonfiguration
Service Bus Konfiguration
Die Service Bus Queue ist mit produktionsreifen Einstellungen konfiguriert:
resource queue 'Microsoft.ServiceBus/namespaces/queues@2021-11-01' = {parent: namespacename: queueNameproperties: {maxDeliveryCount: 10lockDuration: 'PT5M' // 5 MinutendefaultMessageTimeToLive: 'P14D' // 14 TagedeadLetteringOnMessageExpiration: trueenablePartitioning: falseduplicateDetectionHistoryTimeWindow: 'PT10M'}}
Infrastruktur bereitstellen
cd infrastructure# In Entwicklung bereitstellenaz deployment sub create \--name orderproc-infra-dev \--location westeurope \--template-file main.bicep \--parameters parameters/dev.bicepparam# Bereitstellung dauert 5-7 Minuten
Kostenschätzung: ~11-23€/Monat für die Entwicklungsumgebung unter Verwendung kostenloser Tiers, wo verfügbar.
Teil 2: Backend - Azure Functions
Das Backend besteht aus vier TypeScript Azure Functions:
1. Order Submit Function (HTTP Trigger)
Empfängt Aufträge vom Frontend und veröffentlicht sie im Service Bus:
app.http('OrderSubmit', {methods: ['POST'],authLevel: 'anonymous',handler: async (request, context) => {const body = await request.json();// Mit Zod validierenconst validatedOrder = OrderSchema.parse(body);// Auftrags-ID und Zeitstempel hinzufügenconst order = {...validatedOrder,orderId: uuidv4(),orderDate: new Date().toISOString(),status: 'submitted'};// Im Service Bus veröffentlichenconst sender = serviceBusClient.createSender('orders-queue');await sender.sendMessages({body: order,messageId: order.orderId});return {status: 201,jsonBody: { orderId: order.orderId }};}});
2. Order Processor Function (Queue Trigger)
Verarbeitet Aufträge asynchron aus der Queue:
app.serviceBusQueue('OrderProcessor', {connection: 'ServiceBusConnectionString',queueName: 'orders-queue',handler: async (message, context) => {const order = message as Order;context.log(`Verarbeite Auftrag ${order.orderId}`);try {// Verarbeitung simulieren (Bestandsprüfung, Zahlung, etc.)await processOrder(order);// Erfolg verfolgenappInsights.defaultClient.trackEvent({name: 'OrderProcessed',properties: {orderId: order.orderId,totalAmount: order.totalAmount}});} catch (error) {context.error(`Fehler bei der Auftragsverarbeitung: ${error}`);throw error; // Löst Wiederholung oder Dead Letter aus}}});
3. Dead Letter Processor Function
Behandelt Nachrichten, deren Verarbeitung fehlgeschlagen ist:
app.serviceBusQueue('DeadLetterProcessor', {connection: 'ServiceBusConnectionString',queueName: 'orders-queue/$deadletterqueue',handler: async (message, context) => {context.error(`Dead Letter Nachricht: ${JSON.stringify(message)}`);// In Application Insights protokollierenappInsights.defaultClient.trackException({exception: new Error('Auftrag nach maximalen Wiederholungen fehlgeschlagen'),properties: {orderId: message.orderId,deliveryCount: message.deliveryCount}});// Könnte Alarm, E-Mail oder manuellen Überprüfungsprozess auslösen}});
4. Health Check Function
Stellt den Systemgesundheitsstatus bereit:
app.http('Health', {methods: ['GET'],authLevel: 'anonymous',handler: async (request, context) => {return {jsonBody: {status: 'healthy',timestamp: new Date().toISOString(),environment: process.env.ENVIRONMENT,checks: {serviceBus: process.env.ServiceBusConnectionString ? 'configured' : 'missing',appInsights: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING ? 'configured' : 'missing'}}};}});
Datenvalidierung mit Zod
Typsichere Validierung gewährleistet Datenintegrität:
import { z } from 'zod';const OrderItemSchema = z.object({productId: z.string().uuid(),productName: z.string().min(1),quantity: z.number().int().positive(),price: z.number().positive()});const OrderSchema = z.object({customerId: z.string().min(1),customerEmail: z.string().email(),items: z.array(OrderItemSchema).min(1),totalAmount: z.number().positive(),currency: z.string().length(3), // ISO 4217});
Teil 3: Frontend - Next.js Anwendung
Moderne React-Anwendung mit App Router und TypeScript.
Features
- Server Components für optimale Performance
- React Hook Form mit Zod-Validierung
- TanStack Query für Datenabruf
- shadcn/ui Komponenten
- Dark Mode Unterstützung mit next-themes
Order Form Component
'use client';import { useForm } from 'react-hook-form';import { zodResolver } from '@hookform/resolvers/zod';import { useMutation } from '@tanstack/react-query';import { OrderSchema } from '@/lib/schemas';export function OrderForm() {const form = useForm({resolver: zodResolver(OrderSchema),defaultValues: {customerEmail: '',items: [{ productName: '', quantity: 1, price: 0 }]}});const mutation = useMutation({mutationFn: async (data) => {const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/orders`, {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify(data)});if (!response.ok) throw new Error('Auftrag konnte nicht übermittelt werden');return response.json();},onSuccess: (data) => {toast.success(`Auftrag übermittelt! ID: ${data.orderId}`);form.reset();}});return (<form onSubmit={form.handleSubmit((data) => mutation.mutate(data))}>{/* Formularfelder */}</form>);}
API Integration Layer
// lib/api.tsexport class OrdersAPI {private baseUrl: string;constructor() {this.baseUrl = process.env.NEXT_PUBLIC_API_URL || '';}async submitOrder(order: Order) {const response = await fetch(`${this.baseUrl}/api/orders`, {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify(order)});if (!response.ok) {throw new Error(`API-Fehler: ${response.status}`);}return response.json();}async checkHealth() {const response = await fetch(`${this.baseUrl}/api/health`);return response.json();}}
Teil 4: Beobachtbarkeit mit Application Insights
Umfassendes Monitoring und Diagnose integriert.
Verfolgte Custom Events
// Auftrag übermitteltappInsights.defaultClient.trackEvent({name: 'OrderSubmitted',properties: {orderId: order.orderId,customerId: order.customerId,itemCount: order.items.length,totalAmount: order.totalAmount}});// Auftrag verarbeitetappInsights.defaultClient.trackEvent({name: 'OrderProcessed',properties: {orderId: order.orderId,processingDuration: duration}});// Verarbeitung fehlgeschlagenappInsights.defaultClient.trackException({exception: error,properties: {orderId: order.orderId,errorType: error.name}});
Key Metrics Dashboard
Verwendung von KQL (Kusto Query Language) zur Datenanalyse:
// AuftragsübermittlungsratecustomEvents| where name == "OrderSubmitted"| summarize count() by bin(timestamp, 1h)| render timechart// Durchschnittliche VerarbeitungszeitcustomEvents| where name == "OrderProcessed"| extend duration = todouble(customProperties.processingDuration)| summarize avg(duration) by bin(timestamp, 5m)// Fehlerrateexceptions| where operation_Name contains "OrderProcessor"| summarize errorCount = count() by bin(timestamp, 5m)
Alerts-Konfiguration
Proaktives Monitoring einrichten:
- Hohe Fehlerrate: Alarm bei Fehlerrate > 5%
- Langsame Verarbeitung: Alarm bei durchschnittlicher Verarbeitungszeit > 30 Sekunden
- Dead Letter Queue: Alarm bei Nachrichten in DLQ
- Queue-Tiefe: Alarm bei Queue-Tiefe > 1000 Nachrichten
Teil 5: CI/CD mit GitHub Actions
Automatisierte Deployment-Pipelines für Infrastruktur und Anwendungen.
Infrastructure Deployment Workflow
name: Deploy Infrastructureon:push:branches: [main, master]paths:- 'infrastructure/**'workflow_dispatch:inputs:environment:type: choiceoptions: [dev, staging, prod]jobs:deploy:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- name: Azure Loginuses: azure/login@v1with:creds: ${{ secrets.AZURE_CREDENTIALS }}- name: Deploy Biceprun: |az deployment sub create \--location westeurope \--template-file infrastructure/main.bicep \--parameters infrastructure/parameters/${{ inputs.environment }}.bicepparam
Backend Deployment Workflow
name: Deploy Backendon:push:branches: [main, master]paths:- 'backend/**'jobs:deploy:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- name: Setup Node.jsuses: actions/setup-node@v4with:node-version: '20'- name: Build Functionsworking-directory: backend/functionsrun: |npm cinpm run build- name: Deploy to Azure Functionsrun: |func azure functionapp publish ${{ env.FUNCTION_APP_NAME }}
Teil 6: Fehlerbehandlung & Resilienz
Retry-Strategie
Service Bus wiederholt fehlgeschlagene Nachrichten automatisch:
- Erster Versuch - Nachricht an Function zugestellt
- Wiederholung bei Fehler - Bis zu 10 Versuche mit exponentiellem Backoff
- Dead Letter Queue - Nach maximalen Wiederholungen wird Nachricht in DLQ verschoben
- DLQ-Processor - Separate Function überwacht und protokolliert Fehler
Behandelte Fehlertypen
Transiente Fehler (automatische Wiederholung):
- Netzwerk-Timeouts
- Service-Nichtverfügbarkeit
- Rate-Limiting
Permanente Fehler (Verschiebung in DLQ):
- Ungültiges Datenformat
- Verstöße gegen Geschäftsregeln
- Externe Service-Fehler
Circuit Breaker Pattern
Für externe Service-Aufrufe:
class CircuitBreaker {private failures = 0;private lastFailureTime?: Date;private state: 'closed' | 'open' | 'half-open' = 'closed';async execute<T>(fn: () => Promise<T>): Promise<T> {if (this.state === 'open') {if (this.shouldAttemptReset()) {this.state = 'half-open';} else {throw new Error('Circuit Breaker ist offen');}}try {const result = await fn();this.onSuccess();return result;} catch (error) {this.onFailure();throw error;}}private onSuccess() {this.failures = 0;this.state = 'closed';}private onFailure() {this.failures++;this.lastFailureTime = new Date();if (this.failures >= 5) {this.state = 'open';}}private shouldAttemptReset(): boolean {if (!this.lastFailureTime) return false;const elapsed = Date.now() - this.lastFailureTime.getTime();return elapsed > 60000; // 1 Minute}}
Teil 7: Test-Strategie
Unit Tests
describe('OrderProcessor', () => {it('sollte gültigen Auftrag verarbeiten', async () => {const order = {orderId: '123',items: [{ productId: 'p1', quantity: 2, price: 10 }],totalAmount: 20};await processOrder(order);expect(appInsights.trackEvent).toHaveBeenCalledWith(expect.objectContaining({name: 'OrderProcessed'}));});it('sollte bei ungültigem Auftrag Fehler werfen', async () => {const invalidOrder = { items: [] };await expect(processOrder(invalidOrder)).rejects.toThrow('Ungültiger Auftrag');});});
Integration Tests
describe('End-to-End Auftragsfluss', () => {it('sollte Auftrag von Übermittlung bis Abschluss verarbeiten', async () => {// Auftrag über HTTP übermittelnconst response = await fetch(`${API_URL}/api/orders`, {method: 'POST',body: JSON.stringify(validOrder)});expect(response.status).toBe(201);const { orderId } = await response.json();// Auf Verarbeitung wartenawait waitFor(() => {const logs = getApplicationInsightsLogs();return logs.some(log =>log.name === 'OrderProcessed' &&log.properties.orderId === orderId);}, { timeout: 30000 });});});
Teil 8: Performance-Optimierung
Function App Einstellungen
{"FUNCTIONS_WORKER_RUNTIME": "node","FUNCTIONS_WORKER_PROCESS_COUNT": "3","WEBSITE_RUN_FROM_PACKAGE": "1","WEBSITE_NODE_DEFAULT_VERSION": "~20","FUNCTIONS_EXTENSION_VERSION": "~4"}
Service Bus Batching
Nachrichten effizient verarbeiten:
app.serviceBusQueue('OrderProcessorBatch', {connection: 'ServiceBusConnectionString',queueName: 'orders-queue',cardinality: 'many',maxMessageBatchSize: 100,handler: async (messages, context) => {// Nachrichten parallel verarbeitenawait Promise.all(messages.map(message => processOrder(message)));}});
Frontend Performance
- Statische Generierung für Landing Pages
- Server Components für Datenabruf
- Optimistische Updates mit TanStack Query
- Code-Splitting mit dynamischen Imports
- Bildoptimierung mit Next.js Image
Teil 9: Security Best Practices
Authentifizierung & Autorisierung
// Function-Level Authentifizierungapp.http('OrderSubmit', {authLevel: 'function', // Erfordert Function Keyhandler: async (request, context) => {// Benutzerdefiniertes JWT-Token verifizierenconst token = request.headers.get('Authorization');const user = await verifyToken(token);// Mit Benutzerkontext verarbeiten}});
Datenvalidierung
- Eingabevalidierung mit Zod-Schemas
- Ausgabesanitierung zur Verhinderung von XSS
- SQL-Injection-Prävention (bei Datenbankverwendung)
- Rate-Limiting auf API-Endpunkten
Secrets-Management
# Im Azure Key Vault speichernaz keyvault secret set \--vault-name kv-orderproc \--name ServiceBusConnectionString \--value "<connection-string>"# In Function App referenzierenaz functionapp config appsettings set \--name func-orderproc \--settings ServiceBusConnectionString="@Microsoft.KeyVault(SecretUri=https://kv-orderproc.vault.azure.net/secrets/ServiceBusConnectionString/)"
Netzwerksicherheit
- Nur HTTPS erzwungen auf allen Services
- CORS konfiguriert für spezifische Origins
- Private Endpoints für Produktion (optional)
- Managed Identity für Service-zu-Service-Auth
Deployment
Ein-Befehl-Deployment
# Repository klonengit clone https://github.com/tatasadi/event-driven-order-processing-systemcd event-driven-order-processing-system# Alles in Entwicklung bereitstellen./scripts/deploy-local.sh dev
Das Skript wird:
- Infrastruktur bereitstellen (5-7 Minuten)
- Backend-Functions bauen und bereitstellen (2-3 Minuten)
- Frontend bereitstellen (falls konfiguriert)
- Alle URLs und Connection Strings anzeigen
Deployment verifizieren
# Health-Endpunkt prüfencurl https://func-orderproc-dev-<suffix>.azurewebsites.net/api/health# Testauftrag übermittelncurl -X POST https://func-orderproc-dev-<suffix>.azurewebsites.net/api/orders \-H "Content-Type: application/json" \-d '{"customerId": "test-123","customerEmail": "test@example.com","items": [{"productId": "prod-001","productName": "Test Product","quantity": 2,"price": 29.99}],"totalAmount": 59.98,"currency": "USD"}'
Gelernte Lektionen
Was gut funktioniert hat
- Bicep für IaC: Deklarativ, typsicher und Azure-nativ
- Service Bus: Zuverlässige Nachrichtenzustellung mit minimaler Konfiguration
- Consumption Plan: Kosteneffektiv für variable Workloads
- TypeScript: Typsicherheit über Frontend und Backend hinweg
- Application Insights: Umfangreiche Telemetrie mit minimalem Code
Herausforderungen & Lösungen
Cold Start Latenz: Vorgewärmte Instanzen, kleinere Bundle-Größen
Nachrichtenduplikaterkennung: Duplikaterkennung im Service Bus aktiviert
CORS-Konfiguration: Explizite Origin-Allowlist, dynamische Updates
Secrets-Management: Azure Key Vault Integration
Service Bus lokal testen: Azurite-Emulator für lokale Entwicklung
Kostenaufschlüsselung
Monatliche Kosten (Entwicklung):
- Service Bus (Standard): ~10€
- Function App (Consumption): ~0-5€ (1M Ausführungen kostenlos)
- Application Insights: ~0-5€ (5GB kostenlos)
- Storage Account: ~1€
- Static Web App (Free Tier): 0€
- Gesamt: ~11-21€/Monat
Skalierung für Produktion:
- Premium Functions für dedizierte Instanzen: +150€/Monat
- Service Bus Premium für höheren Durchsatz: +672€/Monat
- Static Web App Standard: +9€/Monat
Zukünftige Verbesserungen
- [ ] Cosmos DB für Auftragspersistenz hinzufügen
- [ ] SignalR für Echtzeit-Auftragsstatusaktualisierungen implementieren
- [ ] Azure API Management für Rate-Limiting und API-Gateway hinzufügen
- [ ] Saga-Pattern für komplexe mehrstufige Workflows implementieren
- [ ] Azure Front Door für globale Verteilung hinzufügen
- [ ] Feature-Flags mit Azure App Configuration implementieren
- [ ] Automatisierte Lasttests mit Azure Load Testing hinzufügen
Fazit
Dieses Projekt demonstriert, wie man ein produktionsreifes, ereignisgesteuertes System mit Azure-Services aufbaut. Die Kombination aus Serverless-Functions, Message-Queuing und modernen Frontend-Frameworks schafft eine skalierbare, wartbare Architektur, die für reale E-Commerce-Anwendungen geeignet ist.
Wichtigste Erkenntnisse:
- Ereignisgesteuerte Architektur bietet Resilienz und Skalierbarkeit
- Infrastructure as Code gewährleistet reproduzierbare Deployments
- Beobachtbarkeit von Tag eins vereinfacht Debugging und Monitoring
- TypeScript über den gesamten Stack verbessert die Entwicklererfahrung
- Serverless reduziert operativen Aufwand und Kosten
Möchten Sie einen schnellen Überblick? Schauen Sie sich dieses Projekt in meinem Portfolio an
Ressourcen
- Quellcode: GitHub Repository
- Azure Dokumentation:



