TL;DR — Resumo Rápido
Domine o Temporal para orquestracao de workflows duraveis em microsservicos. Arquitetura, instalacao, SDKs, padrao saga e exemplo de pedidos completo.
Sistemas distribuidos falham de forma parcial — um servico de pagamento atinge o timeout, um registro de envio e gravado mas a confirmacao nunca chega, e agora os dados estao inconsistentes em tres bancos de dados sem como reverter. O Temporal resolve essa classe de problema tornando os workflows duraveis por padrao, usando event sourcing para sobreviver a crashes, replays para recuperar estado e politicas de retry estruturadas para lidar com falhas transitorias. Este guia cobre o stack completo do Temporal: arquitetura do servidor, opcoes de instalacao, primitivos de workflow e activity em Go, TypeScript e Python, padroes avancados como saga e fluxos com intervencao humana, e um exemplo completo de processamento de pedidos.
Pre-requisitos
- Docker e Docker Compose instalados (para Temporal Server local)
- Go 1.22+, Node.js 22+ ou Python 3.11+ dependendo do SDK escolhido
- Familiaridade basica com microsservicos e sistemas distribuidos
- Compreensao de padroes de programacao assincrona (promises, goroutines ou asyncio)
Arquitetura do Temporal
O Temporal Server e composto por quatro servicos internos que podem rodar como um unico binario ou de forma independente para escalar:
Frontend — o gateway gRPC e HTTP exposto externamente. Clientes e Workers se conectam aqui para iniciar workflows, enviar signals, executar queries e consultar task queues.
History — o nucleo do Temporal. Persiste cada evento do workflow no banco de dados e dirige a execucao do workflow reproduzindo o historico de eventos. Cada execucao de workflow e gerenciada por um unico shard de History, fornecendo garantias fortes de ordenacao.
Matching — gerencia os task queues. Quando o servico History precisa que uma tarefa de workflow ou activity seja executada, ele a empurra para o Matching, que a retém ate que um Worker a consulte. Esse modelo pull significa que os Workers nunca sao sobrecarregados.
Worker Interno — executa os proprios workflows do sistema Temporal para gerenciamento de namespaces, arquivamento e replicacao.
Workers sao seus processos de aplicacao — contem o codigo de seus workflows e activities. Os Workers consultam task queues nomeadas do Temporal Server, executam o trabalho localmente e retornam resultados. Workers sao sem estado e escaláveis horizontalmente.
Event Sourcing e Replay: cada workflow mantem um historico completo e ordenado de eventos no banco de dados. Se um Worker falhar no meio de um workflow, um novo Worker pega a tarefa, reproduz o historico de eventos para reconstruir o estado exato em memoria e continua a execucao a partir do ultimo ponto de controle duravel.
Instalacao
Docker Compose (desenvolvimento local)
git clone https://github.com/temporalio/docker-compose.git
cd docker-compose
docker compose up
Isso inicia:
temporal— Temporal Server na porta 7233 (gRPC)temporal-ui— Interface web na porta 8080temporal-admin-tools— container comtctlCLI pre-instaladopostgresql— backend de persistencia
# Acessar tctl via container admin-tools
docker exec -it temporal-admin-tools tctl namespace register --retention 7 default
# Listar workflows em execucao
docker exec -it temporal-admin-tools tctl workflow list
Kubernetes com 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 (gerenciado)
O Temporal Cloud elimina o overhead operacional. Voce recebe um endpoint de namespace, certificados mTLS e faturamento por uso:
TEMPORAL_ADDRESS=<namespace>.tmprl.cloud:7233
TEMPORAL_TLS_CERT=caminho/para/client.pem
TEMPORAL_TLS_KEY=caminho/para/client.key
Conceitos Fundamentais
Workflow — Uma funcao deterministica que orquestra activities, timers, signals e workflows filho. Deve ser deterministica: sem numeros aleatorios, sem chamadas diretas ao sistema, sem acesso a estado global mutavel.
Activity — Uma funcao que realiza efeitos colaterais nao deterministicos: chamadas HTTP, gravacoes em banco de dados, E/S de arquivos, envio de emails. As Activities rodam em Workers e possuem politicas de retry configuráveis.
Signal — Um evento externo enviado para um workflow em execucao. Signals permitem que sistemas externos enviem dados para um workflow em andamento.
Query — Uma leitura sincrona do estado atual de um workflow sem afetar sua execucao.
Task Queue — Um canal nomeado pelo qual o Temporal Server distribui trabalho para os Workers.
Namespace — Um limite de isolamento para workflows, com configuracoes de retencao, politicas de seguranca e esquemas de atributos de busca independentes.
Escrevendo Workflows em 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
}
// Aguardar signal de envio com timeout de 24 horas
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 duravel — sobrevive a reinicializacoes do 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
}
Versionamento de Workflows com 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)
}
// ... resto do workflow
}
Escrevendo Workflows em 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' };
}
Escrevendo Workflows em 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
Padroes de Activity
Heartbeat para Activities Longas
As Activities devem enviar heartbeats para informar ao Temporal que ainda estao ativas. Se um Worker falhar, o timeout de heartbeat aciona o reagendamento em outro 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 Locais
Activities Locais rodam no mesmo processo Worker que o Workflow, sem ida e volta ao Temporal Server. Use-as para operacoes rapidas (menos de um segundo) que ainda precisam de retry:
lao := workflow.LocalActivityOptions{StartToCloseTimeout: 5 * time.Second}
ctx = workflow.WithLocalActivityOptions(ctx, lao)
workflow.ExecuteLocalActivity(ctx, FormatOrderID, order).Get(ctx, &formattedID)
Padroes de Workflow
Padrao Saga para Transacoes Distribuidas
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 Agendados com CronSchedule
c.ExecuteWorkflow(ctx,
client.StartWorkflowOptions{
ID: "relatorio-diario",
TaskQueue: "relatorios",
CronSchedule: "0 9 * * MON-FRI",
},
DailyReportWorkflow,
ReportInput{ReportType: "vendas"},
)
Namespaces e Visibilidade
# Criar namespace com retencao de 30 dias
tctl namespace register \
--retention 30 \
--description "Processamento de pedidos em producao" \
pedidos-producao
# Adicionar atributos de busca personalizados
tctl admin cluster add-search-attributes \
--name StatusPedido --type Text \
--name NivelCliente --type Keyword \
--name ValorPedido --type Double
# Listar workflows com filtro avancado
tctl workflow list \
--query 'StatusPedido="pendente" AND NivelCliente="premium" ORDER BY StartTime DESC'
Interface do Temporal UI
A interface do Temporal em localhost:8080 oferece:
- Lista de Workflows — tabela pesquisavel de todas as execucoes com status, hora de inicio e task queue
- Detalhe de Execucao — historico completo de eventos com cada transicao de estado, timestamps e payloads
- Stack Trace — mostra em qual ponto do codigo o workflow esta atualmente bloqueado
- Activities Pendentes — lista activities agendadas mas ainda nao iniciadas
Comparacao de Ferramentas
| Recurso | Temporal | Apache Airflow | AWS Step Functions | Prefect | Inngest | Conductor |
|---|---|---|---|---|---|---|
| Uso principal | Workflows duraveis em microsservicos | DAGs de pipeline de dados | Maquinas de estado serverless | Orquestracao de dados | Funcoes event-driven | Orquestracao de microsservicos |
| Modelo de execucao | Duradouro de longa duracao | Execucoes batch DAG | Serverless gerenciado | Execucoes de fluxo | Passos serverless | Motor de workflow |
| Linguagem | Go, Java, TS, Python, .NET | DAGs Python | JSON/YAML DSL | Python | TypeScript | JSON/Java |
| Replay/Durabilidade | Event sourcing completo | Nenhum | Gerenciado pela AWS | Baseado em checkpoints | Limitado | Limitado |
| Signals/Queries | Sim — nativos | Nao | Apenas callbacks | Nao | Apenas eventos | Signals |
| Dev local | Docker Compose | Docker Compose | Requer AWS | Servidor local | Servidor dev | Docker |
| Nuvem gerenciada | Temporal Cloud | MWAA | Nativo | Prefect Cloud | Sim | Conductor Cloud |
| Melhor para | Workflows complexos e de longa duracao | Pipelines ETL | Workflows AWS simples | Pipelines ML/dados | Cadeias de eventos serverless | Coreografia de microsservicos |
Exemplo Pratico: Saga de Processamento de Pedidos
// 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 };
}
# Iniciar o workflow
tctl workflow start \
--taskqueue processamento-pedidos \
--workflow_type orderSagaWorkflow \
--workflow_id "pedido-12345" \
--input '{"id":"12345","items":[{"sku":"PROD-001","qty":2}]}'
# Consultar estado atual
tctl workflow query --workflow_id pedido-12345 --query_type status
# Enviar signal de confirmacao de entrega
tctl workflow signal \
--workflow_id pedido-12345 \
--name delivered \
--input '{"deliveredAt":"2026-03-23T14:00:00Z"}'
Armadilhas e Erros Comuns
Bugs de nao-determinismo sao o problema mais frequente no Temporal. Qualquer codigo que produza resultados diferentes no replay corrompe o estado do workflow. Nunca use time.Now(), rand, UUIDs ou chamadas diretas de API dentro de funcoes de workflow — use sempre workflow.Now() e workflow.GetVersion().
Falta de heartbeats em activities longas faz com que a activity seja reagendada mesmo ainda estando em execucao, criando execucoes duplicadas. Sempre envie heartbeats em loops e verifique ctx.Err() apos cada um.
Historico de eventos ilimitado se acumula quando um workflow roda indefinidamente sem checkpoint. Use Continue-As-New para loops de polling e processos de longa duracao.
Discrepancia no task queue — Workers e inicios de workflow devem usar o mesmo nome de task queue. Um erro de digitacao faz a tarefa do workflow ficar na fila para sempre sem Worker para processá-la.
Resumo
- O modelo de event sourcing do Temporal torna os workflows duraveis por padrao — crashes, deploys e interrupcoes de rede nao perdem o estado do workflow
- Workers consultam task queues — o modelo pull garante que Workers nunca sejam sobrecarregados e escalem independentemente do servidor
- Activities lidam com todos os efeitos colaterais nao deterministicos com politicas de retry configuráveis, heartbeats e controles de timeout
- Signals e Queries permitem que sistemas externos interajam com workflows em execucao sem precisar consultar seu banco de dados
- O padrao Saga com Activities compensatorias e a abordagem nativa do Temporal para transacoes distribuidas
- GetVersion habilita deploys progressivos seguros sem quebrar execucoes de workflow em andamento
- Use Temporal Cloud em producao para eliminar o overhead operacional do servidor