CALL_ACTIVITY_COORDINATOR — Subprocessos
Pense num gerente que delega. Ele recebe "processar 30 contratos", passa cada contrato para um analista diferente, e só reporta "concluído" para cima quando todos os 30 devolverem o resultado. Cada analista trabalha sozinho, com sua própria mesa e seus próprios problemas — se um deles trava, o gerente continua esperando aquele, mas os outros 29 seguem. Onde a analogia quebra: o gerente humano pode decidir "esse contrato fica para depois, sigo com o resto"; a v1 do Kikwiflow ainda não tem essa política de "pular o item com falha".
Um CALL_ACTIVITY_COORDINATOR inicia uma ou mais instâncias de outra ProcessDefinition —
referenciada por chave (calledElement), não por um handler Java — e só segue adiante quando todas
concluem. Cada filho roda na sua própria ProcessInstance: ciclo de vida, incidentes e histórico
isolados do pai. É o subprocesso / call activity do Kikwiflow.
No fio de abertura de conta, o encaixe é a ativação de produtos: depois que a
conta está ativa, cada produto que o cliente contratou (produtos) é ativado por um subprocesso
próprio, isolado — se a ativação de um trava, os outros seguem.
Referência de campos
| Campo | Obrigatório? | Descrição |
|---|---|---|
id | Sim | Identificador único do nó. |
name / description | Não | Rótulo e texto livre. |
calledElement | Sim | key da ProcessDefinition a iniciar. Não validada contra processos já implantados no momento da implantação. |
collectionVariable | Não | Ausente → 1 filho. Presente → 1 por elemento da lista (precisa resolver para uma List em tempo de execução). |
elementVariable | Só se collectionVariable presente | Nome sob o qual o elemento da vez entra nas variáveis iniciais de cada filho. |
iterationMode | Não (default PARALLEL) | PARALLEL: todas as N filhas disparam de uma vez. SEQUENTIAL: uma de cada vez — a próxima só inicia depois que a anterior concluir. |
boundaryEventIds | Não | IDs de BOUNDARY_INTERRUPTIVE_TIMER/BOUNDARY_NON_INTERRUPTIVE_TIMER. Não aceita BOUNDARY_INTERRUPTIVE_CATCH_EVENT nem BOUNDARY_ERROR_HANDLER. |
outgoing | Sim | Para onde seguir quando todas as filhas concluírem. |
commitBefore/commitAfter não têm efeito — este nó é sempre tratado como assíncrono.
Um único filho (sem collectionVariable)
{
"id": "CHAMAR_VERIFICACAO_KYC",
"name": "Verificar KYC do Cliente",
"type": "CALL_ACTIVITY_COORDINATOR",
"calledElement": "verificacao-kyc",
"outgoing": [ { "targetNodeId": "CALCULAR_SCORE" } ]
}
O motor inicia uma instância de verificacao-kyc com todas as variáveis do processo pai como
variáveis iniciais do filho, e aguarda ela chegar em COMPLETED antes de seguir.
N filhos em paralelo (collectionVariable + elementVariable)
{
"id": "PROCESSAR_DOCUMENTOS",
"name": "Processar Documentos Anexados",
"type": "CALL_ACTIVITY_COORDINATOR",
"calledElement": "processamento-documento",
"collectionVariable": "documentos",
"elementVariable": "documento",
"outgoing": [ { "targetNodeId": "CALCULAR_SCORE" } ]
}
Se documentos = ["rg", "comprovante-residencia", "selfie"], o motor inicia três instâncias de
processamento-documento em paralelo — cada uma recebendo todas as variáveis do pai mais documento
= o elemento daquela vez. O fluxo só segue quando os três concluírem, em qualquer ordem.
A lista é lida uma única vez, ao alcançar o coordenador — mudar a variável depois não afeta os filhos já iniciados. Lista vazia: o fluxo segue direto, sem iniciar nenhum filho.
N filhos em sequência (iterationMode: SEQUENTIAL)
{
"id": "ATIVAR_PRODUTOS",
"type": "CALL_ACTIVITY_COORDINATOR",
"calledElement": "ativacao-produto",
"collectionVariable": "produtos",
"elementVariable": "produto",
"iterationMode": "SEQUENTIAL",
"outgoing": [ { "targetNodeId": "CONFIRMAR_PACOTE" } ]
}
Com produtos = ["conta-corrente", "cartao-credito", "seguro-vida"], o motor inicia uma
instância por vez — cartao-credito só começa depois que conta-corrente chegar em COMPLETED.
Use SEQUENTIAL quando os itens têm dependência de ordem real (um cartão vinculado a uma
conta-corrente que precisa existir primeiro; uma etapa regulatória que só pode começar depois da
anterior ser auditada). Se são independentes, prefira PARALLEL (padrão) — é estritamente mais
rápido.
Uma falha num elemento atrasa os seguintes, não os pula. Se o filho do elemento 2 fica preso num incidente, o elemento 3 nunca inicia — a sequência trava esperando o 2 se resolver. Não há (ainda) política de "pular o item com falha".
O processo filho (ativacao-produto)
O calledElement aponta para uma ProcessDefinition normal, com seu próprio .kikwi e seus
próprios beans — nada a mais. O ativacao-produto do exemplo é minúsculo:
DEFAULT_START_EVENT
→ EXECUTABLE_TASK "Criar Produto" (executor: criarProdutoTaskHandler,
commitBefore: true, retryPolicy LINEAR)
→ EXECUTABLE_TASK "Notificar Cliente" (executor: notificarClienteTaskHandler)
→ DEFAULT_END_EVENT "Produto Ativado"
Cada instância filha recebe todas as variáveis do pai mais produto (o item da vez, no nome
declarado em elementVariable). O commitBefore: true em Criar Produto garante que, se a
aplicação reiniciar no meio da criação, aquele filho é retomado do começo — sem afetar os irmãos.
Timeout (boundary event)
Um timer de borda anexado ao coordenador cancela a espera inteira:
{
"id": "TIMEOUT_ATIVACAO",
"type": "BOUNDARY_INTERRUPTIVE_TIMER",
"attachedToRef": "ATIVAR_PRODUTOS",
"providerType": "STATIC",
"staticValue": "PT2M",
"outgoing": [ { "targetNodeId": "NOTIFICAR_ATIVACAO_PARCIAL" } ]
}
Ao disparar, a coordenadora e qualquer iniciadora ainda pendente são apagadas, e o fluxo do pai
segue por NOTIFICAR_ATIVACAO_PARCIAL. Um filho já iniciado é cancelado junto — o motor apaga a
ProcessInstance do filho e suas tarefas na mesma transação, gravando um evento crítico
PROCESS_INSTANCE_FINISHED com status: CANCELLED no outbox para rastreabilidade (ver
Eventos e Observabilidade). Isso é recursivo: se o
filho também tem uma call activity que gerou um neto, o neto é cancelado junto — a subárvore inteira
some. Ver Timers e Prazos para as opções de providerType do timer.
Validações
No momento da implantação (DeployValidator): calledElement nulo/vazio →
InvalidProcessDefinitionException; elementVariable presente sem collectionVariable →
InvalidProcessDefinitionException. calledElement não é validado contra processos já
implantados (cada processo é implantado de forma independente e podem chegar em qualquer ordem).
Em tempo de execução, ao alcançar o coordenador: collectionVariable que aponta para variável ausente,
nula ou que não é lista → falha isolada à tarefa que disparou a chegada (retentativa/incidente normal);
calledElement que não resolve → falha isolada àquele filho especificamente (abre um incidente
vinculado só a ele, retentável via PUT /incidents/{id}/retry).
:::info Por que a validação de lista não acontece na implantação
O motor não tem declaração de tipo para variáveis — não existe "schema" de variáveis por
ProcessDefinition. O tipo de uma variável só existe quando ela tem um valor, em tempo de execução.
Por isso "collectionVariable precisa ser uma lista" só pode ser checado ao alcançar o coordenador.
:::
Isolamento de falha
Falha dentro do processo filho (um incidente aberto lá, o filho nunca chega a COMPLETED) deixa
o coordenador esperando indefinidamente — a não ser que haja um timeout configurado. Não existe
(ainda) um canal de erro dedicado para propagar a falha de volta ao pai automaticamente. Trate o
incidente diretamente no filho (ele é uma ProcessInstance normal, com parentInstanceId apontando
para o pai) ou configure um boundaryEventIds.
Forma em tempo de execução (resumo)
Alcançar o coordenador cria, na mesma transação e sem iniciar nenhum filho ainda:
- 1
ExecutableTaskdo tipoCALL_ACTIVITY_COORDINATOR— a espera em si;pendingBranchIdsencolhe a cada filho que conclui. - iniciadoras (
CALL_ACTIVITY_STARTER), uma por filho: emPARALLEL, N de uma vez; emSEQUENTIAL, só a do elemento corrente (as demais são criadas sob demanda). Cada iniciadora chamastartProcess()para criar a instância filha e então se auto-apaga.
Cada filho conclui liberando automaticamente sua "vaga" no coordenador — pelo mesmo mecanismo que
sincroniza ramos de um PARALLEL_GATEWAY, atravessando a fronteira entre instâncias.
Quando usar / quando não usar
| Situação | Use |
|---|---|
| O passo seguinte é executar outro processo completo (ciclo de vida, incidentes e histórico próprios); ou abrir vários ramos a partir de uma lista de tamanho só conhecido em tempo de execução | CALL_ACTIVITY_COORDINATOR (PARALLEL se itens independentes; SEQUENTIAL se há ordem real) |
| O número de ramos é fixo e conhecido ao desenhar o processo (sempre 2, sempre 3) | PARALLEL_GATEWAY |
| O que se espera são eventos externos (webhooks, callbacks), não subprocessos | EVENT_CATCHER modo GROUP |
Não use para lista com dependência de ordem em modo PARALLEL — um item pode começar antes do
seu pré-requisito. Não conte com propagação automática de erro filho→pai: configure timeout ou
monitore os incidentes dos filhos.
Fora de escopo desta versão
- Mapeamento explícito de variáveis de entrada/saída — hoje todas as variáveis do pai são propagadas para cada filho; não há como enviar só um subconjunto nem escolher o que volta.
- Canal de erro dedicado filho → pai — um incidente no filho não é refletido no pai além de deixá-lo esperando.
- Limite de tamanho de lista (circuit breaker) — não há teto configurável para
collectionVariable.
Próximo passo
Continue para Documentação Markdown no Diagrama.