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

RecursoTemporalApache AirflowAWS Step FunctionsPrefectInngestConductor
Uso principalWorkflows duraveis em microsservicosDAGs de pipeline de dadosMaquinas de estado serverlessOrquestracao de dadosFuncoes event-drivenOrquestracao de microsservicos
Modelo de execucaoDuradouro de longa duracaoExecucoes batch DAGServerless gerenciadoExecucoes de fluxoPassos serverlessMotor de workflow
LinguagemGo, Java, TS, Python, .NETDAGs PythonJSON/YAML DSLPythonTypeScriptJSON/Java
Replay/DurabilidadeEvent sourcing completoNenhumGerenciado pela AWSBaseado em checkpointsLimitadoLimitado
Signals/QueriesSim — nativosNaoApenas callbacksNaoApenas eventosSignals
Dev localDocker ComposeDocker ComposeRequer AWSServidor localServidor devDocker
Nuvem gerenciadaTemporal CloudMWAANativoPrefect CloudSimConductor Cloud
Melhor paraWorkflows complexos e de longa duracaoPipelines ETLWorkflows AWS simplesPipelines ML/dadosCadeias de eventos serverlessCoreografia 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