Add blog posts, cleanup unused files, update components
- Add 100 blog posts covering AI, development, and tech topics - Add .env.example for environment configuration - Add accessibility and lighthouse audit scripts - Remove obsolete SEO reports and temporary files - Remove dev-dist build artifacts and backup files - Remove unused portrait images (moved/consolidated elsewhere) - Update contact form and component improvements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,522 @@
|
||||
# Workflow Orchestration mit Temporal
|
||||
|
||||
**Meta-Description:** Workflow Orchestration für komplexe Business Logic. Temporal.io, Durable Workflows, Sagas und Error Recovery.
|
||||
|
||||
**Keywords:** Workflow Orchestration, Temporal, Durable Workflows, Saga Pattern, Business Process, Error Recovery
|
||||
|
||||
---
|
||||
|
||||
## Einführung
|
||||
|
||||
Komplexe Business-Prozesse brauchen mehr als einfache Funktionen. **Workflow Orchestration** mit Temporal bietet durable Execution, automatische Retries und State Management für langlebige Prozesse.
|
||||
|
||||
---
|
||||
|
||||
## Warum Workflow Orchestration?
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ WORKFLOW ORCHESTRATION │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Probleme ohne Orchestration: │
|
||||
│ ├── Verteilte Transaktionen │
|
||||
│ ├── Timeout/Failure Handling │
|
||||
│ ├── State Management über Zeit │
|
||||
│ ├── Retries mit Exponential Backoff │
|
||||
│ └── Compensating Transactions (Rollbacks) │
|
||||
│ │
|
||||
│ Temporal Lösung: │
|
||||
│ ├── Durable Execution (überleben Crashes) │
|
||||
│ ├── Automatische Retries │
|
||||
│ ├── Timeouts auf allen Ebenen │
|
||||
│ ├── Saga Pattern Support │
|
||||
│ └── Workflow as Code (TypeScript) │
|
||||
│ │
|
||||
│ Use Cases: │
|
||||
│ ├── E-Commerce: Order Processing │
|
||||
│ ├── Finance: Payment Processing │
|
||||
│ ├── HR: Onboarding Workflows │
|
||||
│ ├── DevOps: CI/CD Pipelines │
|
||||
│ └── Data: ETL Pipelines │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Temporal Setup
|
||||
|
||||
```bash
|
||||
# Temporal Server (Docker)
|
||||
docker run --rm -p 7233:7233 temporalio/auto-setup
|
||||
|
||||
# SDK installieren
|
||||
npm install @temporalio/client @temporalio/worker @temporalio/workflow @temporalio/activity
|
||||
```
|
||||
|
||||
```typescript
|
||||
// src/activities.ts
|
||||
import { proxyActivities, defineActivity } from '@temporalio/workflow';
|
||||
|
||||
// Activities sind normale async Funktionen
|
||||
export const activities = {
|
||||
async sendEmail(to: string, subject: string, body: string): Promise<boolean> {
|
||||
console.log(`Sending email to ${to}`);
|
||||
// Resend, SendGrid, etc.
|
||||
await resend.emails.send({ to, subject, html: body });
|
||||
return true;
|
||||
},
|
||||
|
||||
async chargePayment(userId: string, amount: number): Promise<string> {
|
||||
console.log(`Charging ${amount} for user ${userId}`);
|
||||
// Stripe, PayPal, etc.
|
||||
const charge = await stripe.charges.create({
|
||||
amount: Math.round(amount * 100),
|
||||
currency: 'eur',
|
||||
customer: userId
|
||||
});
|
||||
return charge.id;
|
||||
},
|
||||
|
||||
async reserveInventory(productId: string, quantity: number): Promise<string> {
|
||||
console.log(`Reserving ${quantity} of ${productId}`);
|
||||
// Inventory Service
|
||||
const reservation = await inventoryService.reserve(productId, quantity);
|
||||
return reservation.id;
|
||||
},
|
||||
|
||||
async releaseInventory(reservationId: string): Promise<void> {
|
||||
console.log(`Releasing reservation ${reservationId}`);
|
||||
await inventoryService.release(reservationId);
|
||||
},
|
||||
|
||||
async refundPayment(chargeId: string): Promise<void> {
|
||||
console.log(`Refunding charge ${chargeId}`);
|
||||
await stripe.refunds.create({ charge: chargeId });
|
||||
},
|
||||
|
||||
async createShipment(orderId: string, address: Address): Promise<string> {
|
||||
console.log(`Creating shipment for order ${orderId}`);
|
||||
const shipment = await shippingService.create({ orderId, address });
|
||||
return shipment.trackingNumber;
|
||||
}
|
||||
};
|
||||
|
||||
export type Activities = typeof activities;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Workflow Definition
|
||||
|
||||
```typescript
|
||||
// src/workflows/order-processing.ts
|
||||
import {
|
||||
proxyActivities,
|
||||
sleep,
|
||||
defineSignal,
|
||||
defineQuery,
|
||||
setHandler,
|
||||
condition,
|
||||
ApplicationFailure
|
||||
} from '@temporalio/workflow';
|
||||
import type { Activities } from '../activities';
|
||||
|
||||
// Activity Proxies mit Retry Config
|
||||
const {
|
||||
sendEmail,
|
||||
chargePayment,
|
||||
reserveInventory,
|
||||
releaseInventory,
|
||||
refundPayment,
|
||||
createShipment
|
||||
} = proxyActivities<Activities>({
|
||||
startToCloseTimeout: '1 minute',
|
||||
retry: {
|
||||
initialInterval: '1 second',
|
||||
backoffCoefficient: 2,
|
||||
maximumAttempts: 5,
|
||||
nonRetryableErrorTypes: ['InvalidPaymentError']
|
||||
}
|
||||
});
|
||||
|
||||
// Signals für externe Events
|
||||
const cancelOrderSignal = defineSignal('cancelOrder');
|
||||
const orderShippedSignal = defineSignal<[string]>('orderShipped');
|
||||
|
||||
// Queries für Status-Abfragen
|
||||
const getStatusQuery = defineQuery<OrderStatus>('getStatus');
|
||||
|
||||
interface OrderInput {
|
||||
orderId: string;
|
||||
userId: string;
|
||||
products: Array<{ productId: string; quantity: number }>;
|
||||
totalAmount: number;
|
||||
shippingAddress: Address;
|
||||
}
|
||||
|
||||
interface OrderStatus {
|
||||
state: 'pending' | 'paid' | 'reserved' | 'shipped' | 'cancelled' | 'failed';
|
||||
chargeId?: string;
|
||||
reservationIds?: string[];
|
||||
trackingNumber?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function orderProcessingWorkflow(input: OrderInput): Promise<OrderStatus> {
|
||||
let status: OrderStatus = { state: 'pending' };
|
||||
let cancelled = false;
|
||||
|
||||
// Signal Handler
|
||||
setHandler(cancelOrderSignal, () => {
|
||||
cancelled = true;
|
||||
});
|
||||
|
||||
// Query Handler
|
||||
setHandler(getStatusQuery, () => status);
|
||||
|
||||
try {
|
||||
// 1. Payment
|
||||
status.chargeId = await chargePayment(input.userId, input.totalAmount);
|
||||
status.state = 'paid';
|
||||
|
||||
if (cancelled) {
|
||||
await compensatePayment(status.chargeId);
|
||||
status.state = 'cancelled';
|
||||
return status;
|
||||
}
|
||||
|
||||
// 2. Inventory Reservation
|
||||
status.reservationIds = [];
|
||||
for (const product of input.products) {
|
||||
const reservationId = await reserveInventory(product.productId, product.quantity);
|
||||
status.reservationIds.push(reservationId);
|
||||
}
|
||||
status.state = 'reserved';
|
||||
|
||||
if (cancelled) {
|
||||
await compensate(status);
|
||||
status.state = 'cancelled';
|
||||
return status;
|
||||
}
|
||||
|
||||
// 3. Shipment
|
||||
status.trackingNumber = await createShipment(input.orderId, input.shippingAddress);
|
||||
status.state = 'shipped';
|
||||
|
||||
// 4. Confirmation Email
|
||||
await sendEmail(
|
||||
input.userId,
|
||||
'Ihre Bestellung wurde versandt',
|
||||
`Tracking: ${status.trackingNumber}`
|
||||
);
|
||||
|
||||
return status;
|
||||
} catch (error) {
|
||||
// Compensating Transactions
|
||||
await compensate(status);
|
||||
status.state = 'failed';
|
||||
status.error = (error as Error).message;
|
||||
|
||||
// Notify Customer
|
||||
await sendEmail(
|
||||
input.userId,
|
||||
'Problem mit Ihrer Bestellung',
|
||||
'Bitte kontaktieren Sie unseren Support.'
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Saga Compensation
|
||||
async function compensate(status: OrderStatus) {
|
||||
// Reverse order: Shipment → Inventory → Payment
|
||||
|
||||
// Release Inventory
|
||||
if (status.reservationIds) {
|
||||
for (const reservationId of status.reservationIds) {
|
||||
await releaseInventory(reservationId);
|
||||
}
|
||||
}
|
||||
|
||||
// Refund Payment
|
||||
if (status.chargeId) {
|
||||
await compensatePayment(status.chargeId);
|
||||
}
|
||||
}
|
||||
|
||||
async function compensatePayment(chargeId: string) {
|
||||
await refundPayment(chargeId);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Worker Setup
|
||||
|
||||
```typescript
|
||||
// src/worker.ts
|
||||
import { Worker } from '@temporalio/worker';
|
||||
import { activities } from './activities';
|
||||
|
||||
async function run() {
|
||||
const worker = await Worker.create({
|
||||
workflowsPath: require.resolve('./workflows'),
|
||||
activities,
|
||||
taskQueue: 'order-processing',
|
||||
// Worker Options
|
||||
maxConcurrentActivityExecutions: 10,
|
||||
maxConcurrentWorkflowTaskExecutions: 5
|
||||
});
|
||||
|
||||
console.log('Worker started');
|
||||
await worker.run();
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Client Usage
|
||||
|
||||
```typescript
|
||||
// src/client.ts
|
||||
import { Client, Connection } from '@temporalio/client';
|
||||
import { orderProcessingWorkflow } from './workflows/order-processing';
|
||||
|
||||
async function main() {
|
||||
const connection = await Connection.connect({
|
||||
address: 'localhost:7233'
|
||||
});
|
||||
|
||||
const client = new Client({ connection });
|
||||
|
||||
// Workflow starten
|
||||
const handle = await client.workflow.start(orderProcessingWorkflow, {
|
||||
taskQueue: 'order-processing',
|
||||
workflowId: `order-${orderId}`,
|
||||
args: [{
|
||||
orderId: '12345',
|
||||
userId: 'user-789',
|
||||
products: [
|
||||
{ productId: 'prod-1', quantity: 2 },
|
||||
{ productId: 'prod-2', quantity: 1 }
|
||||
],
|
||||
totalAmount: 99.99,
|
||||
shippingAddress: {
|
||||
street: 'Hauptstraße 1',
|
||||
city: 'Berlin',
|
||||
zip: '10115'
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
console.log(`Started workflow: ${handle.workflowId}`);
|
||||
|
||||
// Status abfragen
|
||||
const status = await handle.query('getStatus');
|
||||
console.log('Current status:', status);
|
||||
|
||||
// Auf Completion warten
|
||||
const result = await handle.result();
|
||||
console.log('Workflow completed:', result);
|
||||
}
|
||||
|
||||
// Order canceln
|
||||
async function cancelOrder(workflowId: string) {
|
||||
const handle = client.workflow.getHandle(workflowId);
|
||||
await handle.signal('cancelOrder');
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Route Integration
|
||||
|
||||
```typescript
|
||||
// app/api/orders/route.ts
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { Client, Connection } from '@temporalio/client';
|
||||
import { orderProcessingWorkflow } from '@/workflows/order-processing';
|
||||
|
||||
let client: Client;
|
||||
|
||||
async function getClient() {
|
||||
if (!client) {
|
||||
const connection = await Connection.connect({
|
||||
address: process.env.TEMPORAL_ADDRESS || 'localhost:7233'
|
||||
});
|
||||
client = new Client({ connection });
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const body = await request.json();
|
||||
|
||||
const client = await getClient();
|
||||
|
||||
const handle = await client.workflow.start(orderProcessingWorkflow, {
|
||||
taskQueue: 'order-processing',
|
||||
workflowId: `order-${body.orderId}`,
|
||||
args: [body]
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
workflowId: handle.workflowId,
|
||||
status: 'started'
|
||||
});
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const workflowId = request.nextUrl.searchParams.get('workflowId');
|
||||
|
||||
if (!workflowId) {
|
||||
return NextResponse.json({ error: 'workflowId required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const client = await getClient();
|
||||
const handle = client.workflow.getHandle(workflowId);
|
||||
|
||||
try {
|
||||
const status = await handle.query('getStatus');
|
||||
return NextResponse.json({ workflowId, status });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Timer & Scheduling
|
||||
|
||||
```typescript
|
||||
// src/workflows/subscription-renewal.ts
|
||||
import { proxyActivities, sleep, continueAsNew } from '@temporalio/workflow';
|
||||
|
||||
export async function subscriptionRenewalWorkflow(
|
||||
userId: string,
|
||||
planId: string
|
||||
): Promise<void> {
|
||||
const { chargeSubscription, sendRenewalReminder, sendRenewalConfirmation } =
|
||||
proxyActivities<Activities>({
|
||||
startToCloseTimeout: '1 minute'
|
||||
});
|
||||
|
||||
// Reminder 7 Tage vor Ablauf
|
||||
await sleep('23 days');
|
||||
await sendRenewalReminder(userId);
|
||||
|
||||
// Warte bis Ablauf
|
||||
await sleep('7 days');
|
||||
|
||||
// Charge
|
||||
await chargeSubscription(userId, planId);
|
||||
await sendRenewalConfirmation(userId);
|
||||
|
||||
// Workflow für nächsten Monat fortsetzen
|
||||
await continueAsNew<typeof subscriptionRenewalWorkflow>(userId, planId);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling & Compensation
|
||||
|
||||
```typescript
|
||||
// Saga Pattern mit expliziter Compensation
|
||||
import { proxyActivities, ApplicationFailure } from '@temporalio/workflow';
|
||||
|
||||
interface SagaStep<T> {
|
||||
execute: () => Promise<T>;
|
||||
compensate: (result: T) => Promise<void>;
|
||||
}
|
||||
|
||||
async function runSaga<T>(steps: SagaStep<T>[]): Promise<T[]> {
|
||||
const results: T[] = [];
|
||||
const completedSteps: SagaStep<T>[] = [];
|
||||
|
||||
try {
|
||||
for (const step of steps) {
|
||||
const result = await step.execute();
|
||||
results.push(result);
|
||||
completedSteps.push(step);
|
||||
}
|
||||
return results;
|
||||
} catch (error) {
|
||||
// Compensate in reverse order
|
||||
for (let i = completedSteps.length - 1; i >= 0; i--) {
|
||||
const step = completedSteps[i];
|
||||
const result = results[i];
|
||||
try {
|
||||
await step.compensate(result);
|
||||
} catch (compensationError) {
|
||||
console.error('Compensation failed:', compensationError);
|
||||
// Log aber nicht re-throw
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Verwendung
|
||||
export async function bookingWorkflow(input: BookingInput) {
|
||||
const results = await runSaga([
|
||||
{
|
||||
execute: () => reserveHotel(input.hotelId, input.dates),
|
||||
compensate: (reservationId) => cancelHotelReservation(reservationId)
|
||||
},
|
||||
{
|
||||
execute: () => reserveFlight(input.flightId),
|
||||
compensate: (reservationId) => cancelFlightReservation(reservationId)
|
||||
},
|
||||
{
|
||||
execute: () => chargeCustomer(input.customerId, input.total),
|
||||
compensate: (chargeId) => refundCustomer(chargeId)
|
||||
}
|
||||
]);
|
||||
|
||||
return {
|
||||
hotelReservation: results[0],
|
||||
flightReservation: results[1],
|
||||
chargeId: results[2]
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fazit
|
||||
|
||||
Temporal Workflow Orchestration bietet:
|
||||
|
||||
1. **Durability**: Workflows überleben Server-Crashes
|
||||
2. **Visibility**: Vollständige History & State
|
||||
3. **Reliability**: Automatische Retries & Timeouts
|
||||
4. **Scalability**: Horizontale Worker-Skalierung
|
||||
|
||||
Für komplexe Business-Prozesse unverzichtbar.
|
||||
|
||||
---
|
||||
|
||||
## Bildprompts
|
||||
|
||||
1. "Workflow steps connected with arrows, showing state persistence"
|
||||
2. "Saga pattern with forward and backward arrows, compensation flow"
|
||||
3. "Multiple workers processing workflow tasks, distributed orchestration"
|
||||
|
||||
---
|
||||
|
||||
## Quellen
|
||||
|
||||
- [Temporal.io Documentation](https://docs.temporal.io/)
|
||||
- [Temporal TypeScript SDK](https://typescript.temporal.io/)
|
||||
- [Saga Pattern](https://microservices.io/patterns/data/saga.html)
|
||||
- [Durable Execution](https://temporal.io/blog/building-reliable-distributed-systems-in-node)
|
||||
Reference in New Issue
Block a user