Usar Temporal no Node.js permite implementar workflows duráveis que sobrevivem a reinícios, falhas de rede e indisponibilidade temporária. Em vez de controlar manualmente estados, timers, retries e compensações em tabelas e cron jobs, a plataforma registra o histórico da execução e retoma o código do ponto correto.
Temporal é especialmente útil para processos longos, como onboarding, pedidos, pagamentos, provisionamento, aprovação e integrações que podem durar minutos, dias ou meses. O modelo separa Workflows determinísticos de Activities que executam efeitos externos.
Neste guia, você aprenderá Workflows, Activities, Workers, Client, Task Queues, retries, timeouts, signals, queries, cancellation, versionamento, testes, segurança e observabilidade.
O que é Temporal?
Temporal é uma plataforma de execução durável. O guia oficial do SDK TypeScript apresenta Workflows, Activities, Workers, Client, cancellation, timers, versionamento e testes. A referência da API TypeScript detalha os pacotes e tipos.
Para processos distribuídos baseados em compensações, consulte Saga Pattern no Node.js. Para eventos persistidos junto ao banco, veja Outbox Pattern no Node.js.
Pacotes principais
npm install \
@temporalio/client \
@temporalio/worker \
@temporalio/workflow \
@temporalio/activityFerramentas de teste podem exigir pacotes adicionais da versão atual.
Conceitos
- Workflow: lógica durável e determinística.
- Activity: efeito externo que pode falhar.
- Worker: processo que executa tasks.
- Task Queue: fila lógica de distribuição.
- Client: inicia e interage com workflows.
- Temporal Service: mantém histórico e coordenação.
Activity
export async function chargePayment(input: {
orderId: string;
amountCents: number;
idempotencyKey: string;
}): Promise<{ transactionId: string }> {
return paymentProvider.charge(input);
}Activities podem acessar banco, HTTP, filesystem e SDKs externos. Elas precisam ser idempotentes, pois podem executar novamente.
Workflow
import { proxyActivities } from '@temporalio/workflow';
import type * as activities from './activities';
const { chargePayment, reserveInventory, sendEmail } =
proxyActivities<typeof activities>({
startToCloseTimeout: '30 seconds',
retry: {
maximumAttempts: 5,
initialInterval: '1 second',
backoffCoefficient: 2
}
});
export async function processOrder(input: OrderInput) {
const reservation = await reserveInventory(input);
const payment = await chargePayment({
orderId: input.orderId,
amountCents: input.amountCents,
idempotencyKey: `payment:${input.orderId}`
});
await sendEmail({
orderId: input.orderId,
template: 'order-approved'
});
return {
reservationId: reservation.id,
transactionId: payment.transactionId
};
}O Workflow parece código assíncrono comum, mas é reproduzido a partir do histórico.
Determinismo
O Workflow deve produzir as mesmas decisões ao ser reexecutado. Não faça diretamente:
- fetch;
- query ao banco;
- leitura de arquivo;
- Math.random;
- new Date dependente do relógio real;
- acesso arbitrário a process.env;
- efeitos externos.
Use Activities e APIs do SDK para tempo, aleatoriedade e mensagens.
Por que o Workflow é reexecutado?
Temporal armazena eventos como ActivityScheduled, ActivityCompleted e TimerFired. O código é replayado para reconstruir o estado. Efeitos já registrados não são executados novamente durante replay.
Worker
import { Worker } from '@temporalio/worker';
import * as activities from './activities';
const worker = await Worker.create({
workflowsPath: require.resolve('./workflows'),
activities,
taskQueue: 'orders'
});
await worker.run();Vários workers podem ouvir a mesma Task Queue para escalar horizontalmente.
Client
import { Client, Connection } from '@temporalio/client';
const connection = await Connection.connect({
address: process.env.TEMPORAL_ADDRESS
});
const client = new Client({
connection,
namespace: process.env.TEMPORAL_NAMESPACE
});Iniciando Workflow
const handle = await client.workflow.start(
processOrder,
{
taskQueue: 'orders',
workflowId: `order:${orderId}`,
args: [input]
}
);Um workflowId de negócio impede iniciar acidentalmente dois processos para o mesmo pedido, conforme a política configurada.
Obtendo resultado
const result = await handle.result();Não bloqueie uma requisição HTTP por horas. Muitas APIs retornam 202 e permitem consultar o status.
Workflow Handle
const handle = client.workflow.getHandle(
`order:${orderId}`
);
const description = await handle.describe();O handle também envia signals, executa queries e solicita cancelamento.
Timers duráveis
import { sleep } from '@temporalio/workflow';
await sleep('24 hours');
await sendReminder(input);O worker não precisa ficar ativo por 24 horas. O timer é persistido pelo serviço.
Schedules versus timers
Timers pertencem à execução de um Workflow. Schedules iniciam workflows em horários recorrentes. Use schedules para tarefas periódicas e timers para espera dentro de um processo.
Activity timeouts
- Schedule-to-Start: tempo aguardando worker.
- Start-to-Close: duração de uma tentativa.
- Schedule-to-Close: duração total incluindo retries.
- Heartbeat: limite para activity longa informar progresso.
Configure pelo comportamento real.
Retries
retry: {
initialInterval: '1 second',
backoffCoefficient: 2,
maximumInterval: '1 minute',
maximumAttempts: 10,
nonRetryableErrorTypes: [
'InvalidPaymentMethod'
]
}Erros permanentes não devem consumir dez tentativas.
ApplicationFailure
Activities podem lançar falhas classificadas:
throw ApplicationFailure.nonRetryable(
'Cartão inválido',
'InvalidPaymentMethod'
);Não baseie política em mensagens livres.
Idempotência das Activities
Uma Activity pode completar no fornecedor e falhar antes de retornar ao Temporal. A tentativa seguinte precisa usar a mesma chave:
idempotencyKey: `order:${orderId}:charge`Consulte Idempotência em APIs Node.js.
Compensações
let reservationId: string | undefined;
try {
const reservation = await reserveInventory(input);
reservationId = reservation.id;
await chargePayment(paymentInput);
} catch (error) {
if (reservationId) {
await releaseInventory({ reservationId });
}
throw error;
}Compensação também precisa de retry e idempotência.
Signals
Signals enviam mensagens assíncronas para um Workflow:
import {
defineSignal,
setHandler,
condition
} from '@temporalio/workflow';
export const approveSignal = defineSignal('approve');
export async function approvalWorkflow() {
let approved = false;
setHandler(approveSignal, () => {
approved = true;
});
await condition(() => approved);
return 'approved';
}Um sistema externo pode sinalizar aprovação humana.
Queries
const statusQuery = defineQuery<WorkflowStatus>('status');
setHandler(statusQuery, () => status);Queries leem estado sem alterá-lo. Não executam Activities.
Updates
Workflow Updates permitem solicitar mudança com resposta e validação, conforme a versão do SDK. Use quando precisa de confirmação mais forte que um Signal.
Cancellation
await handle.cancel();O Workflow precisa decidir o que cancelar e compensar. Activities podem receber cancelamento e devem limpar recursos.
Cancellation scopes
Scopes permitem proteger uma compensação do cancelamento principal. Um pedido cancelado ainda pode precisar liberar reserva.
Child Workflows
const result = await executeChild(
provisionTenant,
{
workflowId: `tenant:${tenantId}`,
args: [input]
}
);Use para processos com ciclo de vida próprio, histórico separado ou necessidade de escala.
Não fragmente demais
Cada child adiciona coordenação. Activities podem ser suficientes para passos simples.
Continue-As-New
Workflows com histórico muito longo podem reiniciar a execução preservando estado lógico:
return continueAsNew({
remainingItems,
processedCount
});Isso limita o tamanho do histórico.
Versionamento de Workflow
Alterar código determinístico enquanto execuções antigas existem pode causar erro de nondeterminism. Estratégias incluem:
- Worker Versioning;
- patching APIs;
- Task Queues versionadas;
- manter código compatível;
- esperar workflows curtos terminarem.
Siga a documentação da versão atual.
Deploy seguro
Não substitua workers sem considerar execuções antigas. Blue-green ou canary precisam preservar capacidade de processar históricos existentes.
Consulte Canary Deploy no Node.js.
Payloads
Argumentos e resultados entram no histórico. Não envie:
- senhas;
- tokens;
- cartão;
- documentos completos;
- arquivos grandes;
- objetos mutáveis gigantes.
Use IDs e busque dados em Activities.
Data converters
Converters customizados podem criptografar payloads. Gerencie chaves, rotação e compatibilidade.
Namespaces
Namespaces isolam workloads, retenção e configuração. Separe produção de desenvolvimento e defina políticas de acesso.
Task Queues
Uma Task Queue representa capacidade, não necessariamente uma fila de negócio. Workers fazem long polling e o serviço distribui tasks.
Escala de workers
Dimensione por:
- workflow tasks;
- activity tasks;
- CPU;
- pool de banco;
- limites externos;
- schedule-to-start latency.
Worker tuning
O SDK oferece controles de concorrência e tuning. Aumentar concorrência sem capacidade no banco apenas aumenta timeouts.
Observabilidade
Monitore:
- workflow success e failure;
- activity retries;
- schedule-to-start;
- task queue backlog;
- timeout;
- workflow history size;
- workers ativos;
- non-determinism;
- compensações.
Search Attributes
Search Attributes permitem localizar workflows por campos controlados. Não use valores de cardinalidade ou sensibilidade inadequadas sem planejamento.
Tracing
Use interceptors e integração OpenTelemetry. O replay não deve criar spans duplicados como se fossem novas execuções.
Consulte OpenTelemetry no Node.js.
Logs em Workflow
O SDK oferece logger consciente de replay. Não use console comum para eventos críticos, pois replays podem duplicar mensagens.
Shutdown do Worker
Ao receber SIGTERM, deixe o Worker parar polling e concluir tasks dentro do prazo. Activities interrompidas serão reentregues conforme política.
Consulte Graceful Shutdown no Node.js.
Testes unitários
Teste Activities como funções comuns, usando fakes para fornecedores.
Testes de Workflow
O ambiente de teste do SDK pode avançar o relógio virtual, permitindo testar timers de dias em segundos.
const environment = await TestWorkflowEnvironment.createTimeSkipping();Confirme a API exata da versão instalada.
Teste de retry
Faça uma Activity falhar duas vezes e ter sucesso na terceira. Confirme tentativas e resultado sem esperar o backoff real em ambiente de time skipping.
Teste de signal
Inicie o Workflow, espere chegar ao estado de espera, envie signal e confirme continuação.
Teste de replay
Reproduza históricos reais contra a nova versão antes do deploy para detectar nondeterminism.
Quando usar Temporal?
- processos longos;
- múltiplas dependências;
- retries complexos;
- timers duráveis;
- aprovação humana;
- compensações;
- estado de workflow consultável;
- necessidade de retomar após falha.
Quando não usar?
Uma operação CRUD simples ou job curto pode usar fila tradicional. Temporal adiciona serviço, workers e disciplina de determinismo.
Erros comuns
- I/O no Workflow: replay deixa de ser determinístico.
- Activity sem idempotência: retry duplica efeito.
- Timeout ausente: execução fica presa.
- Payload sensível: dado entra no histórico.
- Deploy sem versionamento: ocorre nondeterminism.
- Workflow ID aleatório: duplicidade de processo.
- Histórico ilimitado: replay fica pesado.
- Concorrência excessiva: dependências saturam.
Boas práticas
- Mantenha Workflows determinísticos.
- Coloque efeitos em Activities.
- Use IDs de negócio.
- Torne Activities idempotentes.
- Defina timeouts e retries.
- Classifique erros permanentes.
- Use compensações seguras.
- Versione deploys.
- Proteja payloads.
- Teste replay e timers.
Conclusão
Usar Temporal no Node.js transforma processos distribuídos em código durável. Workflows mantêm estado e timers, enquanto Activities isolam efeitos externos e retries.
O modelo exige determinismo, idempotência e versionamento cuidadoso. Com timeouts, compensações, signals, testes de replay e observabilidade, Temporal reduz a quantidade de estado manual necessário para processos longos e críticos.



