TL;DR — Résumé Rapide
Maitrisez Temporal pour l'orchestration de workflows durables. Architecture, installation, SDKs Go/TypeScript/Python, patron saga et exemple de commandes.
Les systemes distribues echouent de maniere partielle — un service de paiement depasse le delai, un enregistrement d’expedition est ecrit mais la confirmation n’arrive jamais, et maintenant les donnees sont incoherentes dans trois bases de donnees sans possibilite de retour arriere. Temporal resout cette classe de probleme en rendant les workflows durables par defaut, en utilisant l’event sourcing pour survivre aux pannes, le replay pour recuperer l’etat et des politiques de retry structurees pour gerer les defaillances transitoires. Ce guide couvre l’integralite du stack Temporal : architecture serveur, options d’installation, primitives de workflow et d’activity en Go, TypeScript et Python, patrons avances comme le saga et les flux avec intervention humaine, et un exemple complet de traitement de commandes.
Prerequis
- Docker et Docker Compose installes (pour le Temporal Server local)
- Go 1.22+, Node.js 22+ ou Python 3.11+ selon le SDK choisi
- Familiarite de base avec les microservices et les systemes distribues
- Comprehension des patrons de programmation asynchrone (promises, goroutines ou asyncio)
Architecture de Temporal
Le Temporal Server est compose de quatre services internes pouvant tourner comme un seul binaire ou independamment pour la mise a l’echelle :
Frontend — la passerelle gRPC et HTTP exposee a l’exterieur. Les Clients et Workers s’y connectent pour demarrer des workflows, envoyer des signals, executer des queries et interroger les task queues.
History — le noyau de Temporal. Persiste chaque evenement de workflow en base de donnees et pilote l’execution en rejouant l’historique d’evenements. Chaque execution de workflow est geree par un seul shard de History, offrant de solides garanties d’ordonnancement.
Matching — gere les task queues. Quand le service History a besoin qu’une tache de workflow ou d’activity soit executee, il la pousse vers Matching qui la retient jusqu’a ce qu’un Worker l’interroge. Ce modele pull signifie que les Workers ne sont jamais surcharges.
Worker Interne — execute les propres workflows systeme de Temporal pour la gestion des namespaces, l’archivage et la replication.
Les Workers sont vos processus applicatifs — ils contiennent votre code de workflow et d’activity. Les Workers interrogent des task queues nommees depuis le Temporal Server, executent le travail localement et retournent les resultats. Les Workers sont sans etat et horizontalement extensibles.
Event Sourcing et Replay : chaque workflow maintient un historique complet et ordonne d’evenements en base de donnees. Si un Worker tombe en panne au milieu d’un workflow, un nouveau Worker reprend la tache, rejoue l’historique pour reconstruire l’etat exact en memoire et continue l’execution depuis le dernier point de controle durable.
Installation
Docker Compose (developpement local)
git clone https://github.com/temporalio/docker-compose.git
cd docker-compose
docker compose up
Cela demarre :
temporal— Temporal Server sur le port 7233 (gRPC)temporal-ui— Interface web sur le port 8080temporal-admin-tools— conteneur avectctlCLI pre-installepostgresql— backend de persistance
# Acceder a tctl via le conteneur admin-tools
docker exec -it temporal-admin-tools tctl namespace register --retention 7 default
# Lister les workflows en cours
docker exec -it temporal-admin-tools tctl workflow list
Kubernetes avec 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 (gere)
Temporal Cloud elimine la charge operationnelle. Vous obtenez un endpoint de namespace, des certificats mTLS et une facturation a l’usage :
TEMPORAL_ADDRESS=<namespace>.tmprl.cloud:7233
TEMPORAL_TLS_CERT=chemin/vers/client.pem
TEMPORAL_TLS_KEY=chemin/vers/client.key
Concepts Fondamentaux
Workflow — Une fonction deterministe qui orchestre des activities, timers, signals et workflows enfants. Doit etre deterministe : pas de nombres aleatoires, pas d’appels systeme directs, pas d’acces a un etat global mutable.
Activity — Une fonction qui realise des effets secondaires non deterministes : appels HTTP, ecritures en base de donnees, E/S de fichiers, envoi d’emails. Les Activities s’executent dans des Workers et disposent de politiques de retry configurables.
Signal — Un evenement externe envoye a un workflow en cours d’execution. Les Signals permettent aux systemes externes d’envoyer des donnees dans un workflow en cours.
Query — Une lecture synchrone de l’etat courant d’un workflow sans affecter son execution.
Task Queue — Un canal nomme par lequel le Temporal Server distribue le travail aux Workers.
Namespace — Une frontiere d’isolation pour les workflows, avec des parametres de retention, des politiques de securite et des schemas d’attributs de recherche independants.
Ecriture de Workflows en Go
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
}
// Attendre le signal d'expedition avec timeout de 24 heures
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)
// Sleep durable — survit aux redemarrages du Worker
workflow.Sleep(ctx, 7*24*time.Hour)
workflow.ExecuteActivity(ctx, SendDeliveryConfirmation, order, shippingInfo).Get(ctx, nil)
return OrderResult{OrderID: order.ID, Status: "completed"}, nil
}
Versionnement de Workflows avec 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)
}
// ... reste du workflow
}
Ecriture de Workflows en TypeScript
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' };
}
Ecriture de Workflows en Python
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
Patrons d’Activity
Heartbeat pour les Activities Longues
Les Activities doivent envoyer des heartbeats pour signaler a Temporal qu’elles sont toujours actives. Si un Worker tombe en panne, le timeout de heartbeat declenche le replanification sur un autre Worker :
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
}
Activities Locales
Les Activities Locales s’executent dans le meme processus Worker que le Workflow, sans aller-retour vers le Temporal Server. Utilisez-les pour les operations rapides (moins d’une seconde) qui necessitent quand meme des retries :
lao := workflow.LocalActivityOptions{StartToCloseTimeout: 5 * time.Second}
ctx = workflow.WithLocalActivityOptions(ctx, lao)
workflow.ExecuteLocalActivity(ctx, FormatOrderID, order).Get(ctx, &formattedID)
Patrons de Workflow
Patron Saga pour les Transactions Distribuees
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
}
Workflows Planifies avec CronSchedule
c.ExecuteWorkflow(ctx,
client.StartWorkflowOptions{
ID: "rapport-quotidien",
TaskQueue: "rapports",
CronSchedule: "0 9 * * MON-FRI",
},
DailyReportWorkflow,
ReportInput{ReportType: "ventes"},
)
Namespaces et Visibilite
# Creer un namespace avec une retention de 30 jours
tctl namespace register \
--retention 30 \
--description "Traitement des commandes en production" \
commandes-production
# Ajouter des attributs de recherche personnalises
tctl admin cluster add-search-attributes \
--name StatutCommande --type Text \
--name NiveauClient --type Keyword \
--name MontantCommande --type Double
# Lister les workflows avec un filtre avance
tctl workflow list \
--query 'StatutCommande="en-attente" AND NiveauClient="premium" ORDER BY StartTime DESC'
Interface Temporal UI
L’interface Temporal sur localhost:8080 offre :
- Liste des Workflows — tableau consultable de toutes les executions avec statut, heure de debut et task queue
- Detail d’Execution — historique complet des evenements avec chaque transition d’etat, horodatages et charges utiles
- Stack Trace — montre sur quel point du code le workflow est actuellement bloque
- Activities en attente — liste les activities planifiees mais pas encore demarrees
Comparaison des Outils
| Fonctionnalite | Temporal | Apache Airflow | AWS Step Functions | Prefect | Inngest | Conductor |
|---|---|---|---|---|---|---|
| Usage principal | Workflows durables en microservices | DAGs de pipeline de donnees | Machines d’etat serverless | Orchestration de donnees | Fonctions event-driven | Orchestration de microservices |
| Modele d’execution | Durable longue duree | Executions batch DAG | Serverless gere | Executions de flux | Etapes serverless | Moteur de workflow |
| Langage | Go, Java, TS, Python, .NET | DAGs Python | JSON/YAML DSL | Python | TypeScript | JSON/Java |
| Replay/Durabilite | Event sourcing complet | Aucun | Gere par AWS | Bases sur des checkpoints | Limite | Limite |
| Signals/Queries | Oui — natifs | Non | Callbacks uniquement | Non | Evenements uniquement | Signals |
| Dev local | Docker Compose | Docker Compose | Necessite AWS | Serveur local | Serveur dev | Docker |
| Cloud gere | Temporal Cloud | MWAA | Natif | Prefect Cloud | Oui | Conductor Cloud |
| Ideal pour | Workflows complexes et longue duree | Pipelines ETL | Workflows AWS simples | Pipelines ML/donnees | Chaines d’evenements serverless | Choregraphie de microservices |
Exemple Pratique: Saga de Traitement de Commandes
// 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 };
}
# Demarrer le workflow
tctl workflow start \
--taskqueue traitement-commandes \
--workflow_type orderSagaWorkflow \
--workflow_id "commande-12345" \
--input '{"id":"12345","items":[{"sku":"PROD-001","qty":2}]}'
# Interroger l'etat courant
tctl workflow query --workflow_id commande-12345 --query_type status
# Envoyer le signal de confirmation de livraison
tctl workflow signal \
--workflow_id commande-12345 \
--name delivered \
--input '{"deliveredAt":"2026-03-23T14:00:00Z"}'
Pieges et Erreurs Courantes
Les bugs de non-determinisme sont le probleme le plus frequent dans Temporal. Tout code produisant des resultats differents au replay corrompt l’etat du workflow. N’utilisez jamais time.Now(), rand, UUIDs ou des appels API directs dans des fonctions de workflow — utilisez toujours workflow.Now() et workflow.GetVersion().
Les heartbeats manquants sur des activities longues provoquent la replanification de l’activity meme si elle tourne encore, creant des executions dupliquees. Envoyez toujours des heartbeats dans les boucles et verifiez ctx.Err() apres chacun.
L’historique d’evenements non borne s’accumule quand un workflow tourne indefiniment sans point de controle. Utilisez Continue-As-New pour les boucles de polling et les processus longue duree.
La discordance de task queue — les Workers et les demarrages de workflow doivent utiliser le meme nom de task queue. Une faute de frappe signifie que la tache du workflow attend indefiniment dans la file sans Worker pour la prendre.
Recapitulatif
- Le modele d’event sourcing de Temporal rend les workflows durables par defaut — les pannes, deploiements et interruptions reseau ne font pas perdre l’etat du workflow
- Les Workers interrogent les task queues — le modele pull garantit que les Workers ne sont jamais surcharges et s’echelonnent independamment du serveur
- Les Activities gerent tous les effets secondaires non deterministes avec des politiques de retry configurables, des heartbeats et des controles de timeout
- Les Signals et Queries permettent aux systemes externes d’interagir avec les workflows en cours sans interroger votre base de donnees
- Le patron Saga avec des Activities compensatoires est l’approche native de Temporal pour les transactions distribuees
- GetVersion permet des deploiements progressifs securises sans casser les executions de workflow en cours
- Utilisez Temporal Cloud en production pour eliminer la charge operationnelle du serveur
Articles Connexes
- Introduction a Kubernetes: Orchestration de Conteneurs a Grande Echelle
- Docker Compose pour le Developpement Local: Guide Complet
- Conception de Microservices: Patrons et Meilleures Pratiques
- Apache Kafka pour le Streaming d’Evenements: Guide Complet
- Ansible Playbooks et Roles: Guide d’Automatisation d’Infrastructure