Logo
Aufbau eines ereignisgesteuerten Auftragsverarbeitungssystems mit Azure Services

Aufbau eines ereignisgesteuerten Auftragsverarbeitungssystems mit Azure Services

Veröffentlicht am 13.11.2025

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

  1. Entkopplung: Frontend und Backend-Verarbeitung sind vollständig unabhängig
  2. Zuverlässigkeit: Nachrichten bleiben in Service Bus erhalten, wenn die Verarbeitung fehlschlägt
  3. Skalierbarkeit: Mehrere Consumer können Nachrichten parallel verarbeiten
  4. Resilienz: Fehlgeschlagene Nachrichten werden automatisch wiederholt oder in die Dead Letter Queue verschoben
  5. 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

  1. Resource Group - Container für alle Ressourcen
  2. Service Bus Namespace (Standard-Tier) mit Queue
  3. Application Insights + Log Analytics Workspace
  4. Storage Account für Azure Functions
  5. Function App mit vorkonfigurierten Einstellungen
  6. 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: namespace
name: queueName
properties: {
maxDeliveryCount: 10
lockDuration: 'PT5M' // 5 Minuten
defaultMessageTimeToLive: 'P14D' // 14 Tage
deadLetteringOnMessageExpiration: true
enablePartitioning: false
duplicateDetectionHistoryTimeWindow: 'PT10M'
}
}

Infrastruktur bereitstellen

cd infrastructure
# In Entwicklung bereitstellen
az 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 validieren
const validatedOrder = OrderSchema.parse(body);
// Auftrags-ID und Zeitstempel hinzufügen
const order = {
...validatedOrder,
orderId: uuidv4(),
orderDate: new Date().toISOString(),
status: 'submitted'
};
// Im Service Bus veröffentlichen
const 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 verfolgen
appInsights.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 protokollieren
appInsights.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.ts
export 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 übermittelt
appInsights.defaultClient.trackEvent({
name: 'OrderSubmitted',
properties: {
orderId: order.orderId,
customerId: order.customerId,
itemCount: order.items.length,
totalAmount: order.totalAmount
}
});
// Auftrag verarbeitet
appInsights.defaultClient.trackEvent({
name: 'OrderProcessed',
properties: {
orderId: order.orderId,
processingDuration: duration
}
});
// Verarbeitung fehlgeschlagen
appInsights.defaultClient.trackException({
exception: error,
properties: {
orderId: order.orderId,
errorType: error.name
}
});

Key Metrics Dashboard

Verwendung von KQL (Kusto Query Language) zur Datenanalyse:

// Auftragsübermittlungsrate
customEvents
| where name == "OrderSubmitted"
| summarize count() by bin(timestamp, 1h)
| render timechart
// Durchschnittliche Verarbeitungszeit
customEvents
| where name == "OrderProcessed"
| extend duration = todouble(customProperties.processingDuration)
| summarize avg(duration) by bin(timestamp, 5m)
// Fehlerrate
exceptions
| 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 Infrastructure
on:
push:
branches: [main, master]
paths:
- 'infrastructure/**'
workflow_dispatch:
inputs:
environment:
type: choice
options: [dev, staging, prod]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy Bicep
run: |
az deployment sub create \
--location westeurope \
--template-file infrastructure/main.bicep \
--parameters infrastructure/parameters/${{ inputs.environment }}.bicepparam

Backend Deployment Workflow

name: Deploy Backend
on:
push:
branches: [main, master]
paths:
- 'backend/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Build Functions
working-directory: backend/functions
run: |
npm ci
npm run build
- name: Deploy to Azure Functions
run: |
func azure functionapp publish ${{ env.FUNCTION_APP_NAME }}

Teil 6: Fehlerbehandlung & Resilienz

Retry-Strategie

Service Bus wiederholt fehlgeschlagene Nachrichten automatisch:

  1. Erster Versuch - Nachricht an Function zugestellt
  2. Wiederholung bei Fehler - Bis zu 10 Versuche mit exponentiellem Backoff
  3. Dead Letter Queue - Nach maximalen Wiederholungen wird Nachricht in DLQ verschoben
  4. 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 übermitteln
const 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 warten
await 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 verarbeiten
await 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 Authentifizierung
app.http('OrderSubmit', {
authLevel: 'function', // Erfordert Function Key
handler: async (request, context) => {
// Benutzerdefiniertes JWT-Token verifizieren
const 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 speichern
az keyvault secret set \
--vault-name kv-orderproc \
--name ServiceBusConnectionString \
--value "<connection-string>"
# In Function App referenzieren
az 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 klonen
git clone https://github.com/tatasadi/event-driven-order-processing-system
cd event-driven-order-processing-system
# Alles in Entwicklung bereitstellen
./scripts/deploy-local.sh dev

Das Skript wird:

  1. Infrastruktur bereitstellen (5-7 Minuten)
  2. Backend-Functions bauen und bereitstellen (2-3 Minuten)
  3. Frontend bereitstellen (falls konfiguriert)
  4. Alle URLs und Connection Strings anzeigen

Deployment verifizieren

# Health-Endpunkt prüfen
curl https://func-orderproc-dev-<suffix>.azurewebsites.net/api/health
# Testauftrag übermitteln
curl -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

  1. Bicep für IaC: Deklarativ, typsicher und Azure-nativ
  2. Service Bus: Zuverlässige Nachrichtenzustellung mit minimaler Konfiguration
  3. Consumption Plan: Kosteneffektiv für variable Workloads
  4. TypeScript: Typsicherheit über Frontend und Backend hinweg
  5. 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