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.

TEMPORAL — ORQUESTRACAO DE WORKFLOWS DURAVEIS Cliente SDK Start / Signal Query Temporal Server Frontend gRPC / HTTP History Event sourcing Matching Task queues Persistencia MySQL / Postgres Isolamento por Namespace Processo Worker Executor Workflow Executor Activity Processo Worker Escala independente Servicos Externos APIs / BDs / Filas Temporal UI localhost:8080 Historico de eventos Workers consultam task queues — Temporal Server nunca empurra trabalho

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 8080
  • temporal-admin-tools — container com tctl CLI pre-instalado
  • postgresql — 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

Artigos Relacionados