Saltar a contenido

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"):

  1. El broker crea el job: cuando un procesador BPMN (ServiceTaskProcessor, SendTaskProcessor, ScriptTaskProcessor, etc.) ejecuta finalizeActivation, invoca BpmnJobBehavior.createNewJob() que escribe un record JobIntent.CREATED.
  2. El worker solicita jobs: el worker llama ActivateJobs (polling) o StreamActivatedJobs (streaming) indicando el tipo de job que puede procesar.
  3. 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.
  4. El worker ejecuta y reporta: tras ejecutar la lógica de negocio, el worker llama CompleteJob, FailJob, o ThrowError segú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:

  1. Al activar un job, el engine calcula deadline = now + timeout y lo almacena en el job record.
  2. Un timer scheduler periódicamente revisa los jobs cuyo deadline ha expirado.
  3. Si un job activado supera su deadline sin ser completado/fallado: el engine lo marca como TIMED_OUT y lo retorna al pool de jobs activables.
  4. 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:

  1. El worker especifica retries (retries restantes) y opcionalmente retryBackOff (ms antes del próximo retry).
  2. Si retries > 0: el engine programa un timer con el backoff especificado. Al expirar, el job vuelve a ser activable.
  3. 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).
  4. Opcionalmente, el worker puede incluir variables en el FailJob para 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.