TL;DR — Kurzzusammenfassung
Temporal fuer dauerhafte Workflow-Orchestrierung in Microservices. Architektur, Installation, SDKs Go/TypeScript/Python, Saga-Muster und Bestellbeispiel.
Verteilte Systeme versagen auf teilweise Weise — ein Zahlungsdienst laeuft in einen Timeout, ein Versanddatensatz wird geschrieben, aber die Bestaetigung kommt nie an, und nun sind die Daten in drei Datenbanken inkonsistent ohne Moeglichkeit der Wiederherstellung. Temporal loest diese Problemklasse, indem Workflows standardmaessig dauerhaft gemacht werden: Event Sourcing zum Ueberleben von Abstuerzen, Replay zur Zustandswiederherstellung und strukturierte Retry-Richtlinien fuer den Umgang mit temporaeren Ausfaellen. Dieser Leitfaden deckt den vollstaendigen Temporal-Stack ab: Serverarchitektur, Installationsoptionen, Workflow- und Activity-Primitive in Go, TypeScript und Python, fortgeschrittene Muster wie Saga und menschliche Genehmigungsflows sowie ein vollstaendiges Bestellverarbeitungsbeispiel.
Voraussetzungen
- Docker und Docker Compose installiert (fuer lokalen Temporal Server)
- Go 1.22+, Node.js 22+ oder Python 3.11+ je nach gewaaehltem SDK
- Grundkenntnisse in Microservices und verteilten Systemen
- Verstaendnis asynchroner Programmiermuster (Promises, Goroutines oder asyncio)
Temporal-Architektur
Der Temporal Server besteht aus vier internen Diensten, die als einzelne Binaerdatei oder unabhaengig skalierbar betrieben werden koennen:
Frontend — das extern erreichbare gRPC- und HTTP-Gateway. Clients und Workers verbinden sich hier, um Workflows zu starten, Signals zu senden, Queries auszufuehren und Task-Queues abzufragen.
History — der Kern von Temporal. Speichert jeden Workflow-Event dauerhaft in der Datenbank und steuert die Workflow-Ausfuehrung durch Replay des Ereignisverlaufs. Jede Workflow-Ausfuehrung wird von einem einzelnen History-Shard verwaltet, was starke Reihenfolgegarantien bietet.
Matching — verwaltet Task-Queues. Wenn der History-Dienst eine Workflow- oder Activity-Aufgabe ausfuehren muss, uebergibt er sie an Matching, das sie haelt, bis ein Worker sie abfragt. Dieses Pull-Modell bedeutet, dass Workers niemals ueberlastet werden.
Interner Worker — fuehrt Temporals eigene System-Workflows fuer Namespace-Verwaltung, Archivierung und Replikation aus.
Workers sind Ihre Anwendungsprozesse — sie enthalten Ihren Workflow- und Activity-Code. Workers fragen benannte Task-Queues vom Temporal Server ab, fuehren die Arbeit lokal aus und geben Ergebnisse zurueck. Workers sind zustandslos und horizontal skalierbar.
Event Sourcing und Replay: Jeder Workflow pflegt einen vollstaendigen, geordneten Ereignisverlauf in der Datenbank. Faellt ein Worker mitten in einem Workflow aus, uebernimmt ein neuer Worker die Aufgabe, repliziert den Ereignisverlauf zur Rekonstruktion des exakten In-Memory-Zustands und setzt die Ausfuehrung ab dem letzten dauerhaften Pruefpunkt fort.
Installation
Docker Compose (lokale Entwicklung)
git clone https://github.com/temporalio/docker-compose.git
cd docker-compose
docker compose up
Dies startet:
temporal— Temporal Server auf Port 7233 (gRPC)temporal-ui— Web-Oberflaeche auf Port 8080temporal-admin-tools— Container mit vorinstalliertemtctlCLIpostgresql— Persistenz-Backend
# tctl ueber den admin-tools-Container aufrufen
docker exec -it temporal-admin-tools tctl namespace register --retention 7 default
# Laufende Workflows auflisten
docker exec -it temporal-admin-tools tctl workflow list
Kubernetes mit Helm
helm repo add temporal https://go.temporal.io/helm-charts
helm repo update
helm install temporal temporal/temporal \
--set server.replicaCount=3 \
--set cassandra.config.cluster_size=3 \
--set elasticsearch.enabled=true \
--namespace temporal \
--create-namespace
Temporal Cloud (verwaltet)
Temporal Cloud beseitigt den Betriebsaufwand. Sie erhalten einen Namespace-Endpunkt, mTLS-Zertifikate und nutzungsbasierte Abrechnung:
TEMPORAL_ADDRESS=<namespace>.tmprl.cloud:7233
TEMPORAL_TLS_CERT=pfad/zu/client.pem
TEMPORAL_TLS_KEY=pfad/zu/client.key
Grundlegende Konzepte
Workflow — Eine deterministische Funktion, die Activities, Timer, Signals und Kind-Workflows orchestriert. Muss deterministisch sein: keine Zufallszahlen, keine direkten Systemaufrufe, kein Zugriff auf mutablen globalen Zustand.
Activity — Eine Funktion, die nicht-deterministische Seiteneffekte durchfuehrt: HTTP-Aufrufe, Datenbankschreibvorgaenge, Datei-E/A, E-Mail-Versand. Activities laufen in Workers und verfuegen ueber konfigurierbare Retry-Richtlinien.
Signal — Ein externes Ereignis, das an einen laufenden Workflow gesendet wird. Signals erlauben externen Systemen, Daten in einen laufenden Workflow einzuspeisen.
Query — Ein synchrones Lesen des aktuellen Workflow-Zustands ohne Beeinflussung der Ausfuehrung.
Task-Queue — Ein benannter Kanal, ueber den der Temporal Server Arbeit an Workers verteilt.
Namespace — Eine Isolierungsgrenze fuer Workflows mit unabhaengigen Retention-Einstellungen, Sicherheitsrichtlinien und Suchattribut-Schemata.
Workflows in Go schreiben
package order
import (
"time"
"go.temporal.io/sdk/workflow"
)
var defaultRetryPolicy = &temporal.RetryPolicy{
InitialInterval: time.Second,
BackoffCoefficient: 2.0,
MaximumInterval: 30 * time.Second,
MaximumAttempts: 5,
NonRetryableErrorTypes: []string{"InvalidOrderError"},
}
func OrderWorkflow(ctx workflow.Context, order OrderInput) (OrderResult, error) {
ao := workflow.ActivityOptions{
StartToCloseTimeout: 30 * time.Second,
RetryPolicy: defaultRetryPolicy,
}
ctx = workflow.WithActivityOptions(ctx, ao)
var paymentResult PaymentResult
err := workflow.ExecuteActivity(ctx, ValidatePayment, order).Get(ctx, &paymentResult)
if err != nil {
return OrderResult{}, err
}
var inventoryResult InventoryResult
err = workflow.ExecuteActivity(ctx, ReserveInventory, order).Get(ctx, &inventoryResult)
if err != nil {
workflow.ExecuteActivity(ctx, RefundPayment, paymentResult).Get(ctx, nil)
return OrderResult{}, err
}
// 24 Stunden auf Versand-Signal warten
signalChan := workflow.GetSignalChannel(ctx, "shipping-update")
var shippingInfo ShippingInfo
selector := workflow.NewSelector(ctx)
selector.AddReceive(signalChan, func(c workflow.ReceiveChannel, _ bool) {
c.Receive(ctx, &shippingInfo)
})
timerFuture := workflow.NewTimer(ctx, 24*time.Hour)
selector.AddFuture(timerFuture, func(f workflow.Future) {
shippingInfo.Status = "timeout"
})
selector.Select(ctx)
// Dauerhafter Sleep — ueberlebt Worker-Neustarts
workflow.Sleep(ctx, 7*24*time.Hour)
workflow.ExecuteActivity(ctx, SendDeliveryConfirmation, order, shippingInfo).Get(ctx, nil)
return OrderResult{OrderID: order.ID, Status: "completed"}, nil
}
Workflow-Versionierung mit GetVersion
func OrderWorkflow(ctx workflow.Context, order OrderInput) (OrderResult, error) {
v := workflow.GetVersion(ctx, "add-fraud-check", workflow.DefaultVersion, 1)
if v >= 1 {
workflow.ExecuteActivity(ctx, FraudCheck, order).Get(ctx, nil)
}
// ... Rest des Workflows
}
Workflows in TypeScript schreiben
import { proxyActivities, sleep, setHandler, defineSignal,
defineQuery, condition } from '@temporalio/workflow';
const { validatePayment, reserveInventory, refundPayment,
sendDeliveryConfirmation } = proxyActivities<Activities>({
startToCloseTimeout: '30 seconds',
retry: {
initialInterval: '1s',
backoffCoefficient: 2,
maximumInterval: '30s',
maximumAttempts: 5,
nonRetryableErrorTypes: ['InvalidOrderError'],
},
});
const shippingSignal = defineSignal<[ShippingInfo]>('shipping-update');
const orderStatusQuery = defineQuery<string>('order-status');
export async function orderWorkflow(order: OrderInput): Promise<OrderResult> {
let currentStatus = 'processing';
setHandler(shippingSignal, (info: ShippingInfo) => {
currentStatus = `shipped:${info.trackingId}`;
});
setHandler(orderStatusQuery, () => currentStatus);
const payment = await validatePayment(order);
let inventory;
try {
inventory = await reserveInventory(order);
} catch (err) {
await refundPayment(payment);
throw err;
}
const signalReceived = await condition(
() => currentStatus.startsWith('shipped:'),
'24 hours'
);
if (!signalReceived) currentStatus = 'shipping-timeout';
await sleep('7 days');
await sendDeliveryConfirmation(order, currentStatus);
return { orderId: order.id, status: 'completed' };
}
Workflows in Python schreiben
from datetime import timedelta
from temporalio import workflow, activity
from temporalio.common import RetryPolicy
@workflow.defn
class OrderWorkflow:
def __init__(self) -> None:
self._status = "processing"
self._shipping_info = None
@workflow.run
async def run(self, order: OrderInput) -> OrderResult:
retry_policy = RetryPolicy(
initial_interval=timedelta(seconds=1),
backoff_coefficient=2.0,
maximum_interval=timedelta(seconds=30),
maximum_attempts=5,
non_retryable_error_types=["InvalidOrderError"],
)
payment = await workflow.execute_activity(
validate_payment, order,
start_to_close_timeout=timedelta(seconds=30),
retry_policy=retry_policy,
)
try:
inventory = await workflow.execute_activity(
reserve_inventory, order,
start_to_close_timeout=timedelta(seconds=30),
retry_policy=retry_policy,
)
except Exception:
await workflow.execute_activity(refund_payment, payment,
start_to_close_timeout=timedelta(seconds=30))
raise
await workflow.wait_condition(
lambda: self._shipping_info is not None,
timeout=timedelta(hours=24),
)
await workflow.sleep(timedelta(days=7))
await workflow.execute_activity(send_delivery_confirmation, order,
start_to_close_timeout=timedelta(seconds=30))
return OrderResult(order_id=order.id, status="completed")
@workflow.signal
def shipping_update(self, info: ShippingInfo) -> None:
self._shipping_info = info
@workflow.query
def order_status(self) -> str:
return self._status
Activity-Muster
Heartbeating fuer lang laufende Activities
Activities muessen Heartbeats senden, um Temporal mitzuteilen, dass sie noch aktiv sind. Faellt ein Worker aus, loest der Heartbeat-Timeout die Neuplanung auf einem anderen Worker aus:
func ProcessLargeFile(ctx context.Context, fileURL string) error {
for i, chunk := range chunks {
activity.RecordHeartbeat(ctx, fmt.Sprintf("chunk %d/%d", i+1, len(chunks)))
if ctx.Err() != nil {
return ctx.Err()
}
processChunk(chunk)
}
return nil
}
Lokale Activities
Lokale Activities laufen im selben Worker-Prozess wie der Workflow, ohne Roundtrip zum Temporal Server. Verwenden Sie sie fuer schnelle Operationen (unter einer Sekunde), die trotzdem Retries benoetigen:
lao := workflow.LocalActivityOptions{StartToCloseTimeout: 5 * time.Second}
ctx = workflow.WithLocalActivityOptions(ctx, lao)
workflow.ExecuteLocalActivity(ctx, FormatOrderID, order).Get(ctx, &formattedID)
Workflow-Muster
Saga-Muster fuer verteilte Transaktionen
func OrderSagaWorkflow(ctx workflow.Context, order OrderInput) error {
var compensations []func(workflow.Context) error
ao := workflow.ActivityOptions{StartToCloseTimeout: 30 * time.Second}
ctx = workflow.WithActivityOptions(ctx, ao)
var payment PaymentResult
if err := workflow.ExecuteActivity(ctx, ChargePayment, order).Get(ctx, &payment); err != nil {
return err
}
compensations = append(compensations, func(ctx workflow.Context) error {
return workflow.ExecuteActivity(ctx, RefundPayment, payment).Get(ctx, nil)
})
var reservation InventoryReservation
if err := workflow.ExecuteActivity(ctx, ReserveInventory, order).Get(ctx, &reservation); err != nil {
for i := len(compensations) - 1; i >= 0; i-- {
compensations[i](ctx)
}
return err
}
compensations = append(compensations, func(ctx workflow.Context) error {
return workflow.ExecuteActivity(ctx, ReleaseInventory, reservation).Get(ctx, nil)
})
if err := workflow.ExecuteActivity(ctx, CreateShipment, order, reservation).Get(ctx, nil); err != nil {
for i := len(compensations) - 1; i >= 0; i-- {
compensations[i](ctx)
}
return err
}
return nil
}
Geplante Workflows mit CronSchedule
c.ExecuteWorkflow(ctx,
client.StartWorkflowOptions{
ID: "tagesbericht",
TaskQueue: "berichte",
CronSchedule: "0 9 * * MON-FRI",
},
DailyReportWorkflow,
ReportInput{ReportType: "verkauf"},
)
Namespaces und Sichtbarkeit
# Namespace mit 30-Tage-Retention erstellen
tctl namespace register \
--retention 30 \
--description "Bestellverarbeitung Produktion" \
bestellungen-produktion
# Benutzerdefinierte Suchattribute hinzufuegen
tctl admin cluster add-search-attributes \
--name BestellStatus --type Text \
--name KundenTier --type Keyword \
--name BestellBetrag --type Double
# Workflows mit erweitertem Filter auflisten
tctl workflow list \
--query 'BestellStatus="ausstehend" AND KundenTier="premium" ORDER BY StartTime DESC'
Temporal UI
Die Temporal-Oberflaeche unter localhost:8080 bietet:
- Workflow-Liste — durchsuchbare Tabelle aller Ausfuehrungen mit Status, Startzeit und Task-Queue
- Ausfuehrungsdetail — vollstaendiger Ereignisverlauf mit jeder Zustandsuebergabe, Zeitstempeln und Payloads
- Stack Trace — zeigt an, an welchem Punkt im Code der Workflow aktuell blockiert ist
- Ausstehende Activities — listet geplante, aber noch nicht gestartete Activities auf
Tool-Vergleich
| Merkmal | Temporal | Apache Airflow | AWS Step Functions | Prefect | Inngest | Conductor |
|---|---|---|---|---|---|---|
| Hauptverwendung | Dauerhafte Microservice-Workflows | Daten-Pipeline-DAGs | Serverless-Zustandsmaschinen | Daten-Workflow-Orchestrierung | Event-gesteuerte Funktionen | Microservice-Orchestrierung |
| Ausfuehrungsmodell | Langlebig dauerhaft | DAG-Batch-Laeufe | Verwaltetes Serverless | Flow-Laeufe | Serverlose Schritte | Workflow-Engine |
| Sprache | Go, Java, TS, Python, .NET | Python-DAGs | JSON/YAML DSL | Python | TypeScript | JSON/Java |
| Replay/Dauerhaftigkeit | Vollstaendiges Event Sourcing | Keine | Von AWS verwaltet | Checkpoint-basiert | Begrenzt | Begrenzt |
| Signals/Queries | Ja — nativ | Nein | Nur Callbacks | Nein | Nur Events | Signals |
| Lokale Entwicklung | Docker Compose | Docker Compose | Erfordert AWS | Lokaler Server | Dev-Server | Docker |
| Verwaltete Cloud | Temporal Cloud | MWAA | Nativ | Prefect Cloud | Ja | Conductor Cloud |
| Ideal fuer | Komplexe, langlebige Workflows | ETL-Pipelines | Einfache AWS-Workflows | ML/Daten-Pipelines | Serverlose Event-Ketten | Microservice-Choreographie |
Praxisbeispiel: Bestellverarbeitungs-Saga
// workflows/order-saga.ts
import { proxyActivities, sleep, setHandler, defineSignal,
defineQuery, condition } from '@temporalio/workflow';
const { chargePayment, refundPayment, reserveInventory, releaseInventory,
createShipment, sendConfirmationEmail } = proxyActivities<Activities>({
startToCloseTimeout: '60 seconds',
retry: { maximumAttempts: 3, initialInterval: '2s', backoffCoefficient: 2 },
});
const cancelSignal = defineSignal('cancel-order');
const statusQuery = defineQuery<string>('status');
export async function orderSagaWorkflow(order: OrderInput): Promise<OrderResult> {
let status = 'received';
let cancelled = false;
setHandler(cancelSignal, () => { cancelled = true; });
setHandler(statusQuery, () => status);
status = 'charging';
const payment = await chargePayment(order);
if (cancelled) {
await refundPayment(payment);
return { orderId: order.id, status: 'cancelled' };
}
status = 'reserving';
let inventory;
try {
inventory = await reserveInventory(order);
} catch (err) {
await refundPayment(payment);
throw err;
}
status = 'shipping';
try {
await createShipment(order, inventory);
} catch (err) {
await releaseInventory(inventory);
await refundPayment(payment);
throw err;
}
status = 'awaiting-delivery';
const delivered = await condition(() => status === 'delivered', '30 days');
if (!delivered) status = 'delivery-timeout';
await sendConfirmationEmail(order, status);
return { orderId: order.id, status };
}
# Workflow starten
tctl workflow start \
--taskqueue bestellverarbeitung \
--workflow_type orderSagaWorkflow \
--workflow_id "bestellung-12345" \
--input '{"id":"12345","items":[{"sku":"PROD-001","qty":2}]}'
# Aktuellen Status abfragen
tctl workflow query --workflow_id bestellung-12345 --query_type status
# Lieferbestaetigung-Signal senden
tctl workflow signal \
--workflow_id bestellung-12345 \
--name delivered \
--input '{"deliveredAt":"2026-03-23T14:00:00Z"}'
Fallstricke und Haeufige Fehler
Nicht-Determinismus-Bugs sind das haeufigste Problem in Temporal. Jeder Code, der beim Replay andere Ergebnisse liefert, korrumpiert den Workflow-Zustand. Verwenden Sie niemals time.Now(), rand, UUIDs oder direkte API-Aufrufe innerhalb von Workflow-Funktionen — nutzen Sie stets workflow.Now() und workflow.GetVersion().
Fehlende Heartbeats bei lang laufenden Activities fuehren dazu, dass die Activity neu geplant wird, obwohl sie noch laeuft, und erzeugen so doppelte Ausfuehrungen. Senden Sie immer Heartbeats in Schleifen und pruefen Sie ctx.Err() nach jedem Heartbeat.
Unbegrenzter Ereignisverlauf haeuft sich an, wenn ein Workflow unbegrenzt ohne Pruefpunkte laeuft. Verwenden Sie Continue-As-New fuer Polling-Schleifen und lang laufende Prozesse.
Task-Queue-Diskrepanz — Workers und Workflow-Starts muessen denselben Task-Queue-Namen verwenden. Ein Tippfehler bedeutet, dass die Workflow-Aufgabe ewig in der Queue wartet, ohne Worker der sie abholt.
Zusammenfassung
- Das Event-Sourcing-Modell von Temporal macht Workflows standardmaessig dauerhaft — Abstuerze, Deployments und Netzwerkunterbrechungen fuehren nicht zum Verlust des Workflow-Zustands
- Workers fragen Task-Queues ab — das Pull-Modell stellt sicher, dass Workers nie ueberlastet werden und sich unabhaengig vom Server skalieren lassen
- Activities behandeln alle nicht-deterministischen Seiteneffekte mit konfigurierbaren Retry-Richtlinien, Heartbeats und Timeout-Kontrollen
- Signals und Queries ermoeglichen externen Systemen die Interaktion mit laufenden Workflows ohne Datenbankabfragen
- Das Saga-Muster mit kompensierenden Activities ist Temporals nativer Ansatz fuer verteilte Transaktionen
- GetVersion ermoeglicht sichere Rolling-Deployments ohne Unterbrechung laufender Workflow-Ausfuehrungen
- Verwenden Sie Temporal Cloud in der Produktion, um den Server-Betriebsaufwand zu eliminieren
Verwandte Artikel
- Einfuehrung in Kubernetes: Container-Orchestrierung in grossem Massstab
- Docker Compose fuer die lokale Entwicklung: Vollstaendiger Leitfaden
- Microservices-Design: Muster und Best Practices
- Apache Kafka fuer Event-Streaming: Vollstaendiger Leitfaden
- Ansible Playbooks und Roles: Infrastruktur-Automatisierungsleitfaden