Introdução ao Temporal.io para workflows de longa duração em produção
1. O que é o Temporal.io e por que ele existe?
1.1. Problemas clássicos de workflows de longa duração
Workflows de longa duração são comuns em sistemas modernos: onboarding de usuários, processamento de pedidos, pipelines de dados, orquestração de microsserviços. Esses workflows enfrentam desafios clássicos:
- Timeouts: operações que demoram horas ou dias podem expirar antes da conclusão
- Retentativas: falhas temporárias exigem repetição, mas sem lógica complexa manual
- Estado distribuído: manter o estado consistente entre múltiplos serviços é difícil
- Coordenação: sincronizar etapas assíncronas sem deadlocks ou perda de dados
Soluções tradicionais como filas simples (RabbitMQ, SQS) ou orquestradores manuais (código com estados em banco) resolvem partes do problema, mas introduzem complexidade adicional.
1.2. Conceitos fundamentais
Temporal.io resolve esses problemas com três conceitos principais:
- Workflows: código que define a lógica de orquestração, escrito como uma função que pode ser pausada e retomada
- Activities: funções que executam a lógica de negócio real (chamadas HTTP, acesso a banco, envio de emails)
- Workers: processos que executam Workflows e Activities, mantendo o estado no Temporal Server
1.3. Diferença para soluções tradicionais
Diferente de filas (que são fire-and-forget) ou sagas manuais (que exigem código de compensação em cada serviço), Temporal oferece:
- Execução reentrante: workflows podem ser pausados e retomados exatamente de onde pararam
- Garantia de execução: mesmo que o worker morra, o workflow continua em outro worker
- Estado centralizado: o Temporal Server mantém o estado completo do workflow
2. Arquitetura e componentes principais
2.1. Temporal Server e armazenamento persistente
O Temporal Server é o cérebro do sistema. Ele armazena:
- Histórico completo de eventos de cada workflow
- Estado atual de execução
- Filas de tasks para Workers
O armazenamento pode usar Cassandra, PostgreSQL ou MySQL. Para produção, recomenda-se Cassandra ou PostgreSQL com replicação.
2.2. Workers: execução reentrante e replay
Workers são processos que executam Workflows e Activities. A mágica está no replay: quando um workflow é retomado, o Worker reexecuta o código do workflow a partir do histórico de eventos, não da lógica real. Isso garante:
- Determinismo: o mesmo histórico sempre produz o mesmo resultado
- Resiliência: falhas de worker não perdem progresso
- Escalabilidade: múltiplos workers podem processar workflows simultaneamente
2.3. Client SDK e comunicação assíncrona
O Client SDK (disponível para Go, Java, Python, TypeScript, .NET) permite iniciar workflows, enviar sinais e consultar estado. A comunicação com o Server é assíncrona e baseada em gRPC.
3. Modelando um workflow de longa duração na prática
3.1. Estrutura básica de um Workflow
Um workflow em TypeScript se parece com:
import { proxyActivities, sleep, defineQuery, setHandler } from '@temporalio/workflow';
import type * as activities from './activities';
const { sendEmail, createUserInDB, validateDocument } = proxyActivities<typeof activities>({
startToCloseTimeout: '30 seconds',
retry: { maximumAttempts: 3 },
});
export async function onboardingWorkflow(userId: string, email: string): Promise<string> {
// Define query para consultar estado
const statusQuery = defineQuery<string>('getStatus');
let status = 'INICIADO';
setHandler(statusQuery, () => status);
// Etapa 1: Validação de documento
status = 'VALIDANDO_DOCUMENTO';
const documentValid = await validateDocument(userId);
if (!documentValid) {
status = 'DOCUMENTO_INVALIDO';
await sendEmail(email, 'Documento inválido. Por favor, reenvie.');
return 'REJEITADO';
}
// Etapa 2: Criação no banco
status = 'CRIANDO_USUARIO';
await createUserInDB(userId);
// Etapa 3: Aguarda aprovação manual (pode levar horas)
status = 'AGUARDANDO_APROVACAO';
await sleep('24h'); // Temporizador de longa duração
status = 'APROVADO';
await sendEmail(email, 'Seu cadastro foi aprovado!');
return 'APROVADO';
}
3.2. Activities: lógica de negócio com retentativas
Activities são funções que executam trabalho real:
// activities.ts
export async function validateDocument(userId: string): Promise<boolean> {
const response = await fetch(`https://api.documents.com/validate/${userId}`);
if (!response.ok) throw new Error('Falha na validação');
return response.json();
}
export async function createUserInDB(userId: string): Promise<void> {
await db.users.create({ id: userId, status: 'ACTIVE' });
}
export async function sendEmail(to: string, body: string): Promise<void> {
await emailService.send({ to, subject: 'Atualização de cadastro', body });
}
3.3. Exemplo completo: workflow de onboarding
Iniciando o workflow:
import { WorkflowClient } from '@temporalio/client';
import { onboardingWorkflow } from './workflows';
const client = new WorkflowClient();
async function startOnboarding(userId: string, email: string) {
const handle = await client.start(onboardingWorkflow, {
args: [userId, email],
taskQueue: 'onboarding-tasks',
workflowId: `onboarding-${userId}`,
});
console.log(`Workflow iniciado: ${handle.workflowId}`);
return handle;
}
4. Lidando com falhas e resiliência em produção
4.1. Retentativas automáticas com backoff exponencial
Temporal gerencia retentativas automaticamente. Você configura:
const { processPayment } = proxyActivities<typeof activities>({
startToCloseTimeout: '1 minute',
retry: {
initialInterval: '1 second',
maximumInterval: '1 minute',
backoffCoefficient: 2,
maximumAttempts: 5,
nonRetryableErrorTypes: ['PermanentError'],
},
});
4.2. Garantia de execução exactly-once
Temporal garante que cada Activity execute pelo menos uma vez, mas com side effects idempotentes. O workflow só avança após confirmação do servidor. Se um worker morre durante uma Activity, outro worker retoma a partir do último evento confirmado.
4.3. Estratégias de compensação
Para workflows que precisam de rollback, use um padrão de Saga:
export async function orderWorkflow(orderId: string) {
const steps = [
{ name: 'reserveInventory', compensate: 'releaseInventory' },
{ name: 'chargePayment', compensate: 'refundPayment' },
{ name: 'shipOrder', compensate: 'cancelShipment' },
];
const executedSteps = [];
try {
for (const step of steps) {
await activities[step.name](orderId);
executedSteps.push(step);
}
} catch (error) {
// Compensar na ordem reversa
for (const step of executedSteps.reverse()) {
await activities[step.compensate](orderId);
}
throw error;
}
}
5. Técnicas avançadas: sinais, queries e cron jobs
5.1. Sinais: comunicação externa
Sinais permitem modificar workflows em execução:
// Workflow
export async function approvalWorkflow(userId: string) {
let approved = false;
setHandler(defineSignal('approve'), () => { approved = true; });
setHandler(defineSignal('reject'), () => { approved = false; });
await condition(() => approved !== undefined, '48h');
if (approved) {
await sendEmail(userId, 'Aprovado!');
}
}
// Client
await handle.signal('approve');
5.2. Queries: consulta de estado
Queries não modificam o workflow:
// Client
const status = await handle.query('getStatus');
console.log(`Status atual: ${status}`);
5.3. Temporizadores e cron schedules
export async function dailyReportWorkflow() {
while (true) {
await generateReport();
await sleep('24h'); // Executa todo dia
}
}
// Ou usando cron nativo
await client.start(dailyReportWorkflow, {
cronSchedule: '0 8 * * *', // Todo dia às 8h
});
6. Boas práticas de implantação e operação
6.1. Configuração de Workers
// worker.ts
import { Worker } from '@temporalio/worker';
async function run() {
const worker = await Worker.create({
workflowsPath: require.resolve('./workflows'),
activitiesPath: require.resolve('./activities'),
taskQueue: 'onboarding-tasks',
maxConcurrentActivityTaskExecutions: 100,
maxConcurrentWorkflowTaskExecutions: 50,
});
await worker.run();
}
6.2. Versionamento de Workflows
Use versionamento para mudanças sem quebrar execuções em andamento:
// Versão 1
export async function onboardingWorkflowV1(userId: string) { ... }
// Versão 2
export async function onboardingWorkflowV2(userId: string) { ... }
// No worker
const worker = await Worker.create({
workflowsPath: require.resolve('./workflows'),
// Registra ambas versões
});
6.3. Monitoramento com OpenTelemetry
import { OpenTelemetrySdk } from '@temporalio/interceptors-opentelemetry';
const otel = new OpenTelemetrySdk({
exporter: new OTLPTraceExporter(),
});
const worker = await Worker.create({
interceptors: [otel.createWorkerInterceptor()],
});
7. Comparação com alternativas e quando usar Temporal
7.1. Temporal vs Bull/BullMQ
Bull/BullMQ é excelente para filas simples com jobs independentes. Temporal é superior quando:
- Workflows têm múltiplas etapas com dependências complexas
- É necessário estado compartilhado entre etapas
- Workflows podem durar dias ou meses
7.2. Temporal vs Sagas manuais
Sagas manuais exigem lógica de compensação em cada serviço. Temporal centraliza a orquestração, reduzindo complexidade e garantindo consistência.
7.3. Critérios de decisão
Use Temporal quando:
- Workflows têm múltiplas etapas assíncronas com dependências
- É necessário resiliência a falhas em qualquer ponto
- Workflows podem durar horas, dias ou meses
Evite Temporal quando:
- Apenas filas simples são necessárias (use Bull/BullMQ)
- O sistema é extremamente simples com poucas etapas
- A latência de sub-milissegundo é crítica (Temporal adiciona alguma sobrecarga)
Referências
- Documentação oficial do Temporal.io — Guia completo de conceitos, SDKs e boas práticas
- Temporal TypeScript SDK — Exemplos práticos de workflows, activities e workers em TypeScript
- Temporal vs BullMQ: Choosing the Right Workflow Engine — Comparação detalhada entre Temporal e BullMQ para diferentes cenários
- Building Resilient Microservices with Temporal — Artigo técnico sobre resiliência e padrões de workflow em microsserviços
- OpenTelemetry Integration with Temporal — Guia de monitoramento e tracing com OpenTelemetry no Temporal
- Temporal GitHub Repository — Código-fonte do Temporal Server e exemplos de uso
- Workflow Patterns with Temporal — Tutoriais interativos e cursos gratuitos sobre padrões de workflow