Job Worker Pattern
El job worker pattern es el mecanismo central de Camunda para ejecutar lógica de negocio externa al engine. El broker (Zeebe) crea jobs cuando un elemento BPMN los requiere, y los workers externos los activan (poll o stream), ejecutan, y reportan el resultado. Este patrón desacopla completamente la ejecución del proceso de la lógica de negocio, habilitando escalado independiente, polyglot workers, y resiliencia ante fallos de workers.
Patrón External Task¶
El modelo es un external task pattern (también llamado "pull-based" o "competing consumers"):
- El broker crea el job: cuando un procesador BPMN (ServiceTaskProcessor, SendTaskProcessor, ScriptTaskProcessor, etc.) ejecuta
finalizeActivation, invocaBpmnJobBehavior.createNewJob()que escribe un recordJobIntent.CREATED. - El worker solicita jobs: el worker llama
ActivateJobs(polling) oStreamActivatedJobs(streaming) indicando el tipo de job que puede procesar. - El broker asigna jobs: el engine marca los jobs como ACTIVATED y los entrega al worker. Un job activado solo es visible para el worker que lo activó — competing consumers a nivel de activación.
- El worker ejecuta y reporta: tras ejecutar la lógica de negocio, el worker llama
CompleteJob,FailJob, oThrowErrorsegún el resultado.
Activación de jobs¶
ActivateJobsRequest (polling)¶
| Campo | Tipo | Descripción |
|---|---|---|
type |
string | Tipo de job a activar. Matchea con el type definido en la task definition del elemento BPMN. |
worker |
string | Nombre identificador del worker. Se registra en el job para trazabilidad. |
timeout |
int64 (ms) | Tiempo máximo que el worker tiene para completar el job. Si el timeout expira, el job vuelve a ser activable por otro worker. |
maxJobsToActivate |
int32 | Máximo número de jobs a activar en esta request. Permite al worker controlar su carga. |
fetchVariable |
string[] | Lista de nombres de variables a incluir en el job activado. Si está vacía, incluye todas las variables del scope. Optimización de red importante. |
requestTimeout |
int64 (ms) | Long polling: si no hay jobs disponibles, el server mantiene la conexión abierta hasta este timeout esperando que aparezcan jobs. Reduce la latencia y el tráfico de polling vacío. |
tenantIds |
string[] | Filtro de multi-tenancy: solo activar jobs de estos tenants. |
StreamActivatedJobsRequest (streaming)¶
| Campo | Tipo | Descripción |
|---|---|---|
type |
string | Tipo de job |
worker |
string | Nombre del worker |
timeout |
int64 (ms) | Timeout del job |
fetchVariable |
string[] | Variables a incluir |
tenantIds |
string[] | Filtro multi-tenancy |
Diferencia fundamental: StreamActivatedJobs abre una conexión gRPC streaming bidireccional persistente. El server pushea jobs al worker inmediatamente cuando se crean, sin esperar a que el worker haga polling. Esto reduce la latencia de activación de ~polling_interval/2 a ~0.
Trade-off: streaming mantiene una conexión abierta permanente por worker type, lo que consume recursos del gateway. Polling es más eficiente en recursos cuando el volumen de jobs es bajo o esporádico.
Job timeout y reactivación¶
El timeout es el mecanismo central de resiliencia ante fallos de workers:
- Al activar un job, el engine calcula
deadline = now + timeouty lo almacena en el job record. - Un timer scheduler periódicamente revisa los jobs cuyo deadline ha expirado.
- Si un job activado supera su deadline sin ser completado/fallado: el engine lo marca como
TIMED_OUTy lo retorna al pool de jobs activables. - Otro worker (o el mismo) puede activarlo de nuevo.
Implicación: la lógica del worker DEBE ser idempotente o tolerar re-ejecución, porque un job puede ejecutarse más de una vez si el worker se cae justo después de hacer el trabajo pero antes de llamar CompleteJob.
Recomendación de timeout: debe ser mayor que el tiempo máximo esperado de ejecución, incluyendo reintentos de red. Un timeout muy corto causa re-ejecuciones innecesarias; uno muy largo retrasa la recuperación ante fallos.
Mecanismo de retry¶
Cuando un worker falla un job via FailJob:
- El worker especifica
retries(retries restantes) y opcionalmenteretryBackOff(ms antes del próximo retry). - Si
retries > 0: el engine programa un timer con el backoff especificado. Al expirar, el job vuelve a ser activable. - Si
retries == 0: el engine crea un incidente (IncidentIntent.CREATED) asociado al element instance. La instancia de proceso queda bloqueada en este elemento hasta que un operador resuelva el incidente (vía Operate o API). - Opcionalmente, el worker puede incluir
variablesen elFailJobpara actualizar el contexto antes del retry (por ejemplo, agregar metadata del error).
Patrón típico de retries:
Intento 1: falla → retries=2, backOff=1s
Intento 2: falla → retries=1, backOff=5s
Intento 3: falla → retries=0 → INCIDENTE
El worker es responsable de decrementar los retries y definir la estrategia de backoff. El engine solo almacena y respeta estos valores.
Ciclo de vida del job¶
stateDiagram-v2
[*] --> CREATED
CREATED --> ACTIVATED
ACTIVATED --> COMPLETED
ACTIVATED --> FAILED
ACTIVATED --> ERROR_THROWN
ACTIVATED --> TIMED_OUT
FAILED --> CREATED: retry (re-activable)
FAILED --> INCIDENT: no retries
ERROR_THROWN --> [*]: boundary event o escalación
TIMED_OUT --> CREATED: re-activable
COMPLETED --> [*]
INCIDENT --> [*]
| Estado | Descripción |
|---|---|
CREATED |
El job existe y está disponible para activación. Lo crea el procesador BPMN del elemento. |
ACTIVATED |
Un worker tiene el lock sobre este job. Tiene un deadline asociado. |
COMPLETED |
El worker completó exitosamente. Las variables de resultado se propagan al scope del elemento BPMN. El elemento continúa su ejecución. |
FAILED |
El worker reportó un fallo. Según retries, se programa retry o se crea incidente. |
ERROR_THROWN |
El worker lanzó un error BPMN (ThrowError). El engine busca un error boundary event que matchee el errorCode. Si lo encuentra, activa el boundary event. Si no, escala al scope padre. |
TIMED_OUT |
El deadline expiró sin respuesta del worker. El job retorna a CREATED. Los retries NO se decrementan en timeout — se asume que el worker se cayó. |
Estructura de ActivatedJob¶
Cuando un worker activa un job, recibe toda la información necesaria para ejecutar la lógica:
| Campo | Uso por el worker |
|---|---|
key |
Identificador único para reportar resultado (CompleteJob, FailJob, ThrowError). |
type |
Tipo de job. El worker lo usa para despachar a la lógica correcta si maneja múltiples tipos. |
processInstanceKey |
Trazabilidad: a qué instancia de proceso pertenece. |
bpmnProcessId |
ID del proceso BPMN (el definido en el XML). |
processDefinitionVersion |
Versión de la definición. Útil para backwards compatibility. |
processDefinitionKey |
Key técnico de la definición. |
elementId |
ID del elemento BPMN que creó el job (el id del service task en el XML). |
elementInstanceKey |
Key de la instancia específica del elemento (para multi-instance, cada iteración tiene un key distinto). |
customHeaders |
JSON con headers definidos en la task definition del BPMN. Configuración estática del task (URLs, templates, etc.). |
worker |
Nombre del worker que activó el job (echo-back). |
retries |
Retries restantes. El worker lo usa para calcular el siguiente valor en FailJob. |
deadline |
Unix timestamp (ms) del deadline. El worker puede usarlo para self-timeout si su ejecución es larga. |
variables |
JSON con las variables del scope. Solo las variables solicitadas en fetchVariable. |
tenantId |
Tenant del proceso. Para lógica multi-tenant en el worker. |
JobKind enum¶
El campo jobKind diferencia el origen y propósito del job:
| Valor | Origen | Comportamiento |
|---|---|---|
BPMN_ELEMENT |
Creado por un elemento BPMN estándar (service task, send task, script task, business rule task con job worker mode). | Comportamiento normal: completar el job continúa la ejecución del proceso. |
EXECUTION_LISTENER |
Creado por un execution listener configurado en un elemento. | Se ejecuta antes o después del elemento principal. No afecta el resultado del elemento, pero puede modificar variables. |
TASK_LISTENER |
Creado por un task listener en un user task. | Se ejecuta en eventos del lifecycle del user task (creating, assigning, completing, updating, canceling). |
AD_HOC_SUB_PROCESS |
Creado por actividades dentro de un ad-hoc sub-process. | Las actividades dentro del ad-hoc sub-process pueden crear jobs que se ejecutan sin un orden predeterminado. |
Conexión con elementos BPMN¶
Los siguientes elementos BPMN crean jobs via BpmnJobBehavior:
| Elemento BPMN | ¿Cuándo crea job? | Type del job |
|---|---|---|
| Service Task | Siempre en finalizeActivation |
taskDefinition.type del BPMN |
| Send Task | Siempre en finalizeActivation |
taskDefinition.type del BPMN |
| Script Task | Si tiene taskDefinition (no FEEL inline) |
taskDefinition.type del BPMN |
| Business Rule Task | Solo en modo job worker (sin calledDecisionId) |
taskDefinition.type del BPMN |
| User Task | Para task listeners (no para el task principal) | Internamente generado |
| Cualquier elemento con execution listeners | Para cada listener configurado | Tipo del listener |
Elementos que NO crean jobs: exclusive gateway, parallel gateway, sub-process, call activity (estos se ejecutan internamente en el engine). Manual tasks tampoco crean jobs — son pass-through inmediatos.