Atividades
Atividades Temporal executam operações não determinísticas. Registre uma entrada function.lua ou process.lua como atividade por meio de seus metadados.
Os trechos são receitas de API. O exemplo de pagamento é ilustrativo e exige uma entrada de ambiente pertencente à aplicação, permissão env.get para a credencial, permissão http_client.request para a URL do provedor e um contrato com o provedor de pagamentos.
Registrando atividades
Adicione meta.temporal.activity para registrar uma função como atividade:
- name: charge_payment
kind: function.lua
source: file://payment.lua
method: charge
modules:
- env
- errors
- http_client
- json
meta:
temporal:
activity:
worker: app:worker
Campos de metadados
| Campo | Obrigatório | Descrição |
|---|---|---|
worker |
Sim | Referência à entrada temporal.worker |
local |
Não | Executa como activity local (padrão: false) |
name |
Não | Nome customizado do tipo de activity (padrão: ID da entrada) |
Implementação
Atividades são funções Lua comuns. Mantenha credenciais fora das entradas do workflow porque o Temporal persiste essas entradas no histórico do workflow. Este exemplo lê a chave de pagamento do registro de ambiente dentro da atividade. Seu provedor de placeholder aceita uma requisição de cobrança JSON e retorna uma resposta JSON. O mapeamento de status pertence à aplicação: substitua a URL, os campos da requisição e da resposta e o mapeamento de falhas pelo contrato do seu provedor.
-- payment.lua
local http = require("http_client")
local json = require("json")
local env = require("env")
local errors = require("errors")
local function payment_error(status)
if status == 408 then
return errors.new({kind = errors.TIMEOUT, message = "payment provider timed out", retryable = true})
elseif status == 429 then
return errors.new({kind = errors.RATE_LIMITED, message = "payment provider rate limited the request", retryable = true})
elseif status >= 500 then
return errors.new({kind = errors.UNAVAILABLE, message = "payment provider is unavailable", retryable = true})
end
return errors.new({kind = errors.INVALID, message = "payment request was rejected", retryable = false})
end
local function charge(input)
local api_key, env_err = env.get("PAYMENTS_API_KEY")
if env_err then return nil, env_err end
local body, encode_err = json.encode({
amount = input.amount,
currency = input.currency,
payment_token = input.payment_token
})
if encode_err then
return nil, encode_err
end
local response, err = http.post("https://payments.example.com/v1/charges", {
headers = {
["Authorization"] = "Bearer " .. api_key,
["Content-Type"] = "application/json"
},
body = body
})
if err then
return nil, err
end
if response.status_code >= 400 then
return nil, payment_error(response.status_code)
end
return json.decode(response.body)
end
return { charge = charge }
Chamando atividades
Em workflows, use o módulo funcs:
local funcs = require("funcs")
local result, err = funcs.call("app:charge_payment", {
amount = 5000,
currency = "usd",
payment_token = "payment-token-123"
})
if err then
return nil, err
end
Opções de atividade
Configure timeouts, comportamento de retry e outros parâmetros de execução com o builder do executor:
local funcs = require("funcs")
local executor = funcs.new():with_options({
["activity.start_to_close_timeout"] = "30s",
["activity.schedule_to_close_timeout"] = "5m",
["activity.heartbeat_timeout"] = "10s",
["activity.retry_policy"] = {
maximum_attempts = 3,
initial_interval = 1000,
backoff_coefficient = 2.0,
maximum_interval = 60000,
}
})
local result, err = executor:call("app:charge_payment", input)
O executor é imutável e reutilizável. Construa-o uma vez e use-o em várias chamadas:
local reliable = funcs.new():with_options({
["activity.start_to_close_timeout"] = "60s",
["activity.retry_policy"] = {
maximum_attempts = 5,
initial_interval = 2000,
backoff_coefficient = 2.0,
maximum_interval = 120000,
}
})
local a, err = reliable:call("app:step_one", input)
if err then
return nil, err
end
local b, err = reliable:call("app:step_two", a)
if err then
return nil, err
end
Referência de opções
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
activity.start_to_close_timeout |
duration | 10m | Tempo máximo de execução da atividade |
activity.schedule_to_close_timeout |
duration | - | Tempo máximo do agendamento à conclusão |
activity.schedule_to_start_timeout |
duration | - | Tempo máximo antes de a atividade iniciar |
activity.heartbeat_timeout |
duration | - | Tempo máximo entre heartbeats |
activity.id |
string | - | ID personalizado de execução da activity |
activity.task_queue |
string | - | Sobrescreve a task queue para esta chamada |
activity.wait_for_cancellation |
boolean | false | Aguarda cancelamento da activity |
activity.disable_eager_execution |
boolean | false | Desabilita execução eager |
activity.retry_policy |
table | - | Configuração de retry (veja abaixo) |
activity.name |
string | - | Nome do tipo de activity a chamar, quando difere do ID no registro |
activity.summary |
string | - | Resumo legível exibido na UI do Temporal |
activity.priority |
table | - | Prioridade da tarefa: priority_key (number), fairness_key (string), fairness_weight (number) |
activity.versioning_intent |
string | - | compatible (herda o build ID) ou default (usa regras de atribuição) |
Valores de duração aceitam strings, como "5s", "10m" e "1h", ou números em milissegundos.
Use os nomes canônicos activity.* em código novo. Os aliases legados temporal.activity.* continuam aceitos por compatibilidade.
local executor = funcs.new():with_options({
["activity.summary"] = "Charge the order payment",
["activity.priority"] = {
priority_key = 10,
fairness_key = "customer-123",
fairness_weight = 1.0,
},
["activity.name"] = "charge-payment",
["activity.versioning_intent"] = "use_assignment_rules",
})
Política de retry
Configure o retry automático de atividades com falha:
["activity.retry_policy"] = {
initial_interval = 1000, -- ms before first retry
backoff_coefficient = 2.0, -- multiplier for each retry
maximum_interval = 300000, -- max interval between retries (ms)
maximum_attempts = 10, -- max retry attempts (0 = unlimited)
non_retryable_error_types = { -- errors that skip retries
"Invalid",
"PermissionDenied"
}
}
| Campo | Tipo | Padrão | Descrição |
|---|---|---|---|
initial_interval |
number | 1000 | Milissegundos antes do primeiro retry |
backoff_coefficient |
number | 2.0 | Multiplicador aplicado ao intervalo a cada retry |
maximum_interval |
number | - | Limite do intervalo de retry em milissegundos |
maximum_attempts |
number | 0 | Máximo de tentativas; 0 significa ilimitado |
non_retryable_error_types |
array | - | Tipos de erro que ignoram retries |
Relações entre timeouts
|--- schedule_to_close_timeout --------------------------------|
|--- schedule_to_start_timeout ---|--- start_to_close_timeout -|
(waiting in queue) (executing)
start_to_close_timeout: tempo que a própria atividade pode executar; é o timeout usado com mais frequência.schedule_to_close_timeout: tempo total do agendamento à conclusão, incluindo espera na fila e retries.schedule_to_start_timeout: tempo máximo que a atividade pode aguardar na task queue antes de um worker recebê-la.heartbeat_timeout: em atividades de longa duração, tempo máximo entre relatórios de heartbeat.
Atividades locais
O campo local é aceito em uma atividade:
- name: validate_input
kind: function.lua
source: file://validate.lua
method: validate
modules:
- json
meta:
temporal:
activity:
worker: app:worker
local: true
Atualmente local: true é interpretado, mas se comporta de forma idêntica a uma activity regular: é registrada e executada pelo caminho padrão de activity. Ainda não existe uma execução distinta de activity local, portanto isso não altera latência, comportamento de task queue nem heartbeating.
As atividades são registradas usando como nome o ID completo da entrada:
namespace: app
entries:
- name: charge_payment
kind: function.lua
# ...
Nome da atividade: app:charge_payment
Propagação de contexto
Valores de contexto definidos ao iniciar o workflow ficam disponíveis dentro das atividades:
-- Spawner sets context
local spawner = process.with_context({
user_id = "user-1",
tenant = "tenant-1",
})
local pid, err = spawner:spawn("app:order_workflow", "app:worker", order)
if err then
return nil, err
end
-- Activity reads context
local ctx = require("ctx")
local function process_order(input)
local user_id, user_err = ctx.get("user_id") -- "user-1"
if user_err then return nil, user_err end
local tenant, tenant_err = ctx.get("tenant") -- "tenant-1"
if tenant_err then return nil, tenant_err end
-- use context for authorization, logging, etc.
end
Atividades chamadas de um workflow com funcs.new():with_context() também propagam o contexto:
-- Inside workflow
local executor = funcs.new():with_context({trace_id = "abc-123"})
local result, err = executor:call("app:charge_payment", input)
Contexto de Segurança
Uma activity agendada sob um contexto de segurança recebe o header wippy-security assinado, com audience para o ID da activity. O worker verifica a assinatura e o audience, depois mescla os valores de ctx propagados e o payload de segurança em um frame novo antes que a função da activity execute.
Essa mesclagem é tudo-ou-nada e fatal para a activity se falhar: a activity retorna um erro antes que seu código execute, portanto ela nunca roda com contexto parcial ou com um ator não verificado. A mesclagem falha quando a assinatura ou o audience não verificam, quando o envelope é inconsistente (um ator sem escopo, ou políticas sem ator), ou quando uma política nomeada no envelope não resolve no registro de segurança local — que é a causa operacional mais comum: falta ao deployment do worker uma entrada de política que o chamador tinha.
O worker obtém suas chaves de assinatura e verificação da entrada temporal.client que referencia. Veja Propagação de contexto de segurança.
Tratamento de Erros
Retorne erros pelo padrão Lua:
local errors = require("errors")
-- Replace this mapping with the payment provider's documented error contract.
local function payment_error(status)
if status == 408 then
return errors.new({kind = errors.TIMEOUT, message = "payment provider timed out", retryable = true})
elseif status == 429 then
return errors.new({kind = errors.RATE_LIMITED, message = "payment provider rate limited the request", retryable = true})
elseif status >= 500 then
return errors.new({kind = errors.UNAVAILABLE, message = "payment provider is unavailable", retryable = true})
end
return errors.new({kind = errors.INVALID, message = "payment request was rejected", retryable = false})
end
local function charge(input)
if not input.amount or input.amount <= 0 then
return nil, errors.new({ kind = errors.INVALID, message = "amount must be positive" })
end
local response, err = http.post(url, options)
if err then
return nil, errors.wrap(err, "payment API failed")
end
if response:status() >= 400 then
return nil, errors.new({ kind = errors.INVALID, message = "payment declined" })
end
return json.decode(response.body)
end
Objetos de erro
Erros de atividade propagados para workflows carregam metadados estruturados:
local result, err = funcs.call("app:charge_payment", input)
if err then
err:kind() -- error classification string
err:retryable() -- boolean, whether retry makes sense
err:message() -- human-readable error message
end
Modos de falha
| Falha | Tipo de Erro | Permite Retry | Descrição |
|---|---|---|---|
| Erro de aplicação | O que a activity retornou | Herdado do erro retornado | Erro retornado pelo código da activity via return nil, err |
| Crash de runtime | Internal |
false | Erro Lua não tratado na activity |
| Activity ausente | NotFound |
false | Activity não registrada no worker |
| Timeout | Timeout |
false | Activity excedeu o timeout configurado |
| Verificação de segurança | Internal |
true | Falha na checagem de assinatura, audience ou envelope no header de segurança propagado |
| Política de segurança ausente | Internal |
true | Uma política nomeada no envelope de segurança não resolve neste worker |
Ambas as falhas de segurança acontecem durante a mesclagem de contexto, antes da função da activity executar. Elas não são marcadas como não-retentáveis, então a política de retry da activity continua tentando novamente; retries não ajudam, porque nem uma assinatura inválida nem uma entrada de política ausente mudam entre tentativas. Limite maximum_attempts nas activities que você quer que falhem rápido, e leia uma falha Internal repetida sem saída de log da activity como uma falha de mesclagem de contexto, e não como um defeito na activity.
local executor = funcs.new():with_options({
["activity.retry_policy"] = {maximum_attempts = 1}
})
local result, err = executor:call("app:missing_activity", input)
if err then
print(err:kind()) -- "NotFound"
print(err:retryable()) -- false
end
Atividades de processo
Entradas process.lua também podem ser registradas como atividades para operações de longa duração:
- name: long_task
kind: process.lua
source: file://long_task.lua
method: main
modules:
- http_client
meta:
temporal:
activity:
worker: app:worker
Veja também
- Visão geral - Configuração
- Workflows - Implementação de workflows
- Funções - Módulo de funções
- Tratamento de erros - Tipos e padrões de erro