Activities
Las activities de Temporal ejecutan operaciones no deterministas. Registre una entrada function.lua o process.lua como activity mediante sus metadatos.
Los fragmentos son recetas de API. El ejemplo de pago es ilustrativo y requiere una entrada de entorno propiedad de la aplicación, permiso env.get para la credencial, permiso http_client.request para la URL del proveedor y un contrato con el proveedor de pagos.
Registrar Activities
Agregue meta.temporal.activity para registrar una función como activity:
- 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 Metadatos
| Campo | Requerido | Descripción |
|---|---|---|
worker |
Sí | Referencia a entrada temporal.worker |
local |
No | Ejecutar como activity local (por defecto: false) |
name |
No | Nombre personalizado del tipo de activity (por defecto: el ID de la entrada) |
Implementación
Las activities son funciones Lua normales. Mantenga las credenciales fuera de las entradas del workflow, porque Temporal las conserva en su historial. Este ejemplo lee la clave de pago del registro de entorno dentro de la activity. El proveedor de ejemplo acepta una solicitud de cobro JSON y devuelve una respuesta JSON. La correspondencia de estados es una política propiedad de la aplicación: sustituya la URL, los campos de solicitud y respuesta y la correspondencia de fallos por el contrato de su proveedor.
-- 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 }
Llamar Activities
Desde workflows, use el 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
Opciones de Activity
Configure timeouts, comportamiento de reintentos y otros parámetros de ejecución usando el constructor de 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)
El executor es inmutable y reutilizable. Constrúyalo una vez y úselo para múltiples llamadas:
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
Referencia de Opciones
| Opción | Tipo | Predeterminado | Descripción |
|---|---|---|---|
activity.start_to_close_timeout |
duration | 10m | Tiempo máximo de ejecución de la activity |
activity.schedule_to_close_timeout |
duration | - | Tiempo máximo desde la programación hasta la finalización |
activity.schedule_to_start_timeout |
duration | - | Tiempo máximo antes de que la activity inicie |
activity.heartbeat_timeout |
duration | - | Tiempo máximo entre heartbeats |
activity.id |
string | - | ID de ejecución personalizado de la activity |
activity.task_queue |
string | - | Sobreescribir cola de tareas para esta llamada |
activity.wait_for_cancellation |
boolean | false | Esperar cancelación de la activity |
activity.disable_eager_execution |
boolean | false | Deshabilitar ejecución anticipada |
activity.retry_policy |
table | - | Configuración de reintentos (ver abajo) |
activity.name |
string | - | Nombre del tipo de activity a llamar, cuando difiere del ID del registro |
activity.summary |
string | - | Resumen legible mostrado en la UI de Temporal |
activity.priority |
table | - | Prioridad de tarea: priority_key (number), fairness_key (string), fairness_weight (number) |
activity.versioning_intent |
string | - | compatible (hereda el build ID) o default (usa las reglas de asignación) |
Los valores de duración aceptan cadenas ("5s", "10m", "1h") o milisegundos como números.
Use los nombres canónicos activity.* en código nuevo. Los alias heredados temporal.activity.* siguen aceptándose por compatibilidad.
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 Reintentos
Configurar comportamiento automático de reintentos para activities fallidas:
["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 | Predeterminado | Descripción |
|---|---|---|---|
initial_interval |
number | 1000 | Milisegundos antes del primer reintento |
backoff_coefficient |
number | 2.0 | Multiplicador aplicado al intervalo en cada reintento |
maximum_interval |
number | - | Límite del intervalo de reintento (ms) |
maximum_attempts |
number | 0 | Intentos máximos (0 = ilimitado) |
non_retryable_error_types |
array | - | Tipos de error que omiten reintentos |
Relaciones entre Timeouts
|--- schedule_to_close_timeout --------------------------------|
|--- schedule_to_start_timeout ---|--- start_to_close_timeout -|
(waiting in queue) (executing)
start_to_close_timeout: Cuánto tiempo puede ejecutarse la activity. Es el timeout más comúnmente usado.schedule_to_close_timeout: Tiempo total desde que la activity se programa hasta que se completa, incluyendo tiempo de espera en cola y reintentos.schedule_to_start_timeout: Tiempo máximo que la activity puede esperar en la cola de tareas antes de que un worker la tome.heartbeat_timeout: Para activities de larga ejecución, el tiempo máximo entre reportes de heartbeat.
Activities Locales
El campo local se acepta en una activity:
- name: validate_input
kind: function.lua
source: file://validate.lua
method: validate
modules:
- json
meta:
temporal:
activity:
worker: app:worker
local: true
Actualmente local: true se parsea pero se comporta igual que una activity regular: se registra y se ejecuta a través de la ruta estándar de activities. Todavía no existe una ejecución diferenciada de activity local, por lo que no cambia la latencia, el comportamiento de la cola de tareas ni el heartbeating.
Nombrado de Activities
Las activities se registran con su ID de entrada completo como nombre:
namespace: app
entries:
- name: charge_payment
kind: function.lua
# ...
Nombre de activity: app:charge_payment
Propagación de Contexto
Los valores de contexto establecidos al hacer spawn del workflow están disponibles dentro de las activities:
-- 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
Las activities llamadas desde un workflow con funcs.new():with_context() también propagan contexto:
-- Inside workflow
local executor = funcs.new():with_context({trace_id = "abc-123"})
local result, err = executor:call("app:charge_payment", input)
Contexto de Seguridad
Una activity programada bajo un contexto de seguridad recibe la cabecera firmada wippy-security, con audiencia dirigida al ID de la activity. El worker verifica la firma y la audiencia, luego fusiona los valores de ctx propagados y el payload de seguridad sobre un frame nuevo antes de que se ejecute la función de la activity.
Esa fusión es de todo o nada y fatal para la activity si falla: la activity retorna un error antes de que su código se ejecute, por lo que nunca se ejecuta con contexto parcial o con un actor no verificado. Una fusión falla cuando la firma o la audiencia no se verifican, cuando el envelope es inconsistente (un actor sin scope, o políticas sin actor), o cuando una política nombrada en el envelope no se resuelve en el registro de seguridad local — que es la causa operativa común: al despliegue del worker le falta una entrada de política que el llamador sí tenía.
El worker toma sus claves de firma y verificación de la entrada temporal.client a la que hace referencia. Consulte Propagación del contexto de seguridad.
Manejo de Errores
Retorne errores mediante el patrón estándar de 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 Error
Los errores de activity propagados a workflows portan metadatos estructurados:
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 Fallo
| Fallo | Tipo de Error | Reintentable | Descripción |
|---|---|---|---|
| Error de aplicación | Lo que la activity haya retornado | Heredado del error retornado | Error retornado por código de activity vía return nil, err |
| Crash en tiempo de ejecución | Internal |
no | Error Lua no manejado en activity |
| Activity faltante | NotFound |
no | Activity no registrada con el worker |
| Timeout | Timeout |
no | La activity excedió el timeout configurado |
| Verificación de seguridad | Internal |
sí | Falló la comprobación de firma, audiencia o envelope en la cabecera de seguridad propagada |
| Política de seguridad faltante | Internal |
sí | Una política nombrada en el envelope de seguridad no se resuelve en este worker |
Ambos fallos de seguridad ocurren durante la fusión del contexto, antes de que se ejecute la función de la activity. No están marcados como no reintentables, por lo que la política de reintentos de la activity sigue reintentándolos; los reintentos no ayudan, porque ni una firma inválida ni una entrada de política faltante cambian entre intentos. Limite maximum_attempts en las activities que quiera que fallen rápido, y lea un fallo Internal repetido sin salida en el log de la activity como un fallo de fusión de contexto en lugar de un defecto en la 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
Activities de Proceso
Las entradas process.lua también pueden registrarse como activities para operaciones de larga ejecución:
- name: long_task
kind: process.lua
source: file://long_task.lua
method: main
modules:
- http_client
meta:
temporal:
activity:
worker: app:worker
Ver También
- Overview - Configuración
- Workflows - Implementación de workflows
- Funciones - Módulo de funciones
- Manejo de errores - Tipos de error y patrones