Pular para o conteúdo principal
Página não listada
Esta página não está listada. Mecanismos de busca não irão indexá-la, e somente usuários que possuam o link direto poderão acessá-la

Correlação de Eventos — EVENT_CATCHER e EVENT_THROWER

Pense num guarda-volumes de aeroporto. Você deixa a mala e recebe uma ficha com um número. Mais tarde, qualquer pessoa que apresente aquele número retira a mala — o atendente não precisa saber quem você é, em que voo chegou, nem onde estava sentado. O número basta.

Uma EVENT_CATCHER é o balcão esperando: a instância para e diz "vou seguir quando aparecer a chave conta-criada-11122233344". Um EVENT_THROWER, em outra instância ou noutro processo, é quem chega com essa chave na mão. Onde a analogia quebra: no guarda-volumes há sempre uma mala esperando; no Kikwiflow, se ninguém está esperando a chave quando o EVENT_THROWER a apresenta, o nó falha (ver a política de v1 mais abaixo).

Uma EVENT_CATCHER modela um passo que aguarda um evento externo de negócio — um webhook, um callback assíncrono, uma mensagem Kafka/RabbitMQ — identificado por uma chave de correlação, não por um taskId interno do motor. É a resposta do Kikwiflow para o padrão Message Catch / Receive Task, e para o padrão Scatter-Gather quando é preciso aguardar várias correlações antes de seguir.

Diferente de uma EXTERNAL_TASK, que só pode ser destravada sabendo o taskId gerado pelo motor, uma EVENT_CATCHER é destravada por uma chave de negócio que o sistema que envia o evento já conhece — por exemplo "conta-criada-11122233344" — sem precisar consultar processInstanceId/taskId nenhum antes.

No fio de abertura de conta, o encaixe natural é a confirmação do core bancário: depois de Aprovar Abertura, o processo pede ao core que crie a conta de fato e para, aguardando o callback assíncrono de "conta criada" daquele CPF antes de dar a conta como ativa.


EVENT_CATCHER — aguardando uma correlação

"type": "EVENT_CATCHER". Todo campo é lido do JSON com esse nome exato — não há aliases.

CampoTipoObrigatório?Descrição
idstringSimIdentificador único do nó. Referenciado por targetNodeId de outras arestas e por attachedToRef de eventos de borda.
name / descriptionstringNãoRótulo e texto livre — sem efeito em tempo de execução.
outgoingarraySim (≥1)Exatamente uma aresta é usada (outgoing[0]) — este nó não ramifica.
catchTypeSTANDALONE | GROUPSimVer abaixo.
providerTypeSTATIC | VARIABLE | BEAN | TEMPLATESimEstratégia de resolução da(s) chave(s).
staticKeystringSó se STATICChave fixa. Só faz sentido em STANDALONE.
providerVariablestringSó se VARIABLENome da variável. Em STANDALONE, valor escalar; em GROUP, deve ser uma List em tempo de execução.
providerBeanstringSó se BEANBean @Component que implementa CorrelationKeysProvider.
correlationTemplatesarraySó se TEMPLATEEncadeamento de segmentos LITERAL/VARIABLE (ver abaixo).
keyPrefix / keySuffixstringNãoSó com VARIABLE — concatenados antes/depois do valor da variável.
displayNamePrefix / displayNameSuffixstringNãoRótulo humano opcional; sem nenhum, o rótulo é a própria chave técnica.
matchPolicyALL | ANYSim quando GROUPVer "Política de casamento".
boundaryEventIdsarrayNãoIDs de BOUNDARY_INTERRUPTIVE_TIMER/BOUNDARY_NON_INTERRUPTIVE_TIMER anexados. Não aceita BOUNDARY_INTERRUPTIVE_CATCH_EVENT nem BOUNDARY_ERROR_HANDLER.

:::info STATIC em modo GROUP não é suportado STATIC sempre resolve exatamente 1 item. Em GROUP, use VARIABLE, BEAN ou TEMPLATE. :::

Os dois modos: STANDALONE e GROUP

catchTypeComportamentoUso típico
STANDALONEAguarda uma única chave. Ao recebê-la, segue direto pelo outgoing.Confirmação de pagamento, callback único de um parceiro.
GROUPAguarda N chaves (de uma lista/bean). Gera uma tarefa-mãe coordenadora + N tarefas-filhas.Aguardar a ativação de vários produtos contratados na abertura; cotação com múltiplos fornecedores.

EVENT_CATCHER STANDALONE

{
"id": "AGUARDAR_CONTA_CRIADA",
"name": "Aguardar Confirmação do Core",
"type": "EVENT_CATCHER",
"catchType": "STANDALONE",
"providerType": "VARIABLE",
"providerVariable": "cpf",
"keyPrefix": "conta-criada-",
"outgoing": [ { "targetNodeId": "CONTA_ATIVA" } ]
}

Se cpf = "11122233344", a chave resolvida em tempo de execução é "conta-criada-11122233344".

EVENT_CATCHER GROUP

{
"id": "AGUARDAR_PRODUTOS_ATIVADOS",
"name": "Aguardar Ativação de Todos os Produtos",
"type": "EVENT_CATCHER",
"catchType": "GROUP",
"providerType": "VARIABLE",
"providerVariable": "produtos",
"keyPrefix": "produto-ativado-",
"matchPolicy": "ALL",
"boundaryEventIds": ["TIMEOUT_ATIVACAO"],
"outgoing": [ { "targetNodeId": "NOTIFICAR_CLIENTE" } ]
}

Se produtos = ["conta-corrente", "cartao-credito"], o motor cria uma tarefa-mãe aguardando ["produto-ativado-conta-corrente", "produto-ativado-cartao-credito"] e duas tarefas-filhas, uma por chave.

Estratégias de resolução da chave (providerType)

providerTypeOrigemCampos
STATICChave fixa no JSONstaticKey
VARIABLEVariável de processo. STANDALONE: escalar; GROUP: List de tamanho só conhecido em tempo de execução.providerVariable, keyPrefix, keySuffix
BEANBean Spring CorrelationKeysProviderproviderBean
TEMPLATEEncadeamento de texto fixo + variáveis, declarado no .kikwicorrelationTemplates

É o mesmo padrão de estratégias dos timers (DueDateProvider), agora aplicado a chaves de correlação.

TEMPLATE — quando você já sabe exatamente quais eventos vai aguardar

VARIABLE (list) serve para quando o tamanho da lista só é conhecido em tempo de execução. Quando você já sabe, ao desenhar o processo, quais eventos vai aguardar — só precisa combinar texto fixo com uma ou mais variáveis — use TEMPLATE:

{
"id": "AGUARDAR_CONTA_CRIADA",
"type": "EVENT_CATCHER",
"catchType": "STANDALONE",
"providerType": "TEMPLATE",
"correlationTemplates": [
{
"keySegments": [
{ "type": "LITERAL", "value": "conta-criada-" },
{ "type": "VARIABLE", "value": "cpf" }
],
"displayNameSegments": [ { "type": "LITERAL", "value": "Confirmação do core bancário" } ]
}
],
"outgoing": [ { "targetNodeId": "CONTA_ATIVA" } ]
}

Cada segmento é LITERAL (texto fixo) ou VARIABLE (nome de uma variável de processo). Em catchType: GROUP, correlationTemplates é uma lista de templates — um por evento esperado, declarada de forma fixa no .kikwi, sem depender de nenhuma variável de lista.

CorrelationKeysProvider — resolução programática

Quando a regra de geração das chaves é complexa demais para prefix/suffix:

package com.empresa.processo.correlacao;

import io.kikwiflow.execution.api.context.EvaluationContext;
import io.kikwiflow.execution.api.correlation.CorrelationItem;
import io.kikwiflow.execution.api.correlation.CorrelationKeysProvider;
import org.springframework.stereotype.Component;

import java.util.List;

@Component("produtoCorrelationResolver")
public class ProdutoCorrelationResolver implements CorrelationKeysProvider {

@Override
@SuppressWarnings("unchecked")
public List<CorrelationItem> resolveCorrelationItems(EvaluationContext context) {
List<Produto> produtos = (List<Produto>) context.getVariableValue("produtos").orElseThrow();
return produtos.stream()
.map(p -> new CorrelationItem("produto-ativado-" + p.id(), "Ativação: " + p.nome()))
.toList();
}
}

CorrelationItem(key, displayName) carrega a chave técnica e um rótulo amigável opcional. Em STANDALONE, o provider deve resolver exatamente 1 item.

Política de casamento em modo GROUP (matchPolicy)

matchPolicyComportamento
ALLO nó só conclui quando todas as chaves forem correlacionadas.
ANYO nó conclui na primeira chave correlacionada (first-response-wins); as filhas restantes são canceladas.

Correlacionando um evento

Do lado de quem recebe o webhook/mensagem, basta a chave — nada de processInstanceId/taskId:

@PostMapping("/webhooks/produtos")
public ResponseEntity<Void> onProdutoAtivado(@RequestBody ProdutoAtivadoEvent event,
IdentityContext identityContext) {
String correlationKey = "produto-ativado-" + event.produtoId();
Map<String, ProcessVariable> variaveis = Map.of(
"status_" + event.produtoId(), new ProcessVariable("status", "OK"));
kikwiflowEngine.correlateMessage(correlationKey, variaveis, identityContext);
return ResponseEntity.ok().build();
}

correlateMessage lança TaskNotFoundException sempre que a chave não corresponde a nenhuma tarefa ativa — seja porque nunca existiu, seja porque já foi consumida. Isso dá proteção natural contra reentrega duplicada de webhooks: a segunda entrega da mesma mensagem cai nesse erro, e a aplicação decide o que fazer (ignorar, mandar para uma dead letter queue).

Validações

Todas acontecem em tempo de execução, quando o nó é alcançado — não na implantação. Hoje não há validador de implantação dedicado a EVENT_CATCHER: um .kikwi com configuração inválida é implantado normalmente e só falha quando uma instância passa por aquele nó. Condições que lançam IllegalStateException: providerType/campo do modo nulo ou vazio; variável ausente/null; VARIABLE+GROUP com valor que não é List; BEAN sem bean registrado; provider que resolve lista vazia; STANDALONE com provider que resolve mais de 1 item. Chaves repetidas são deduplicadas silenciosamente (preservando a ordem), o que não é erro.


EVENT_THROWER — lançando uma correlação

EVENT_THROWER ("type": "EVENT_THROWER") é a contraparte de emissão: em vez de esperar por uma correlação externa, este nó lança uma. Um processo com um EVENT_THROWER resolve uma chave de negócio e a entrega internamente, pelo mesmo caminho que um webhook usaria — sem que quem lança conheça o processInstanceId/taskId de quem espera.

:::info Não é Executable nem WaitState Ao contrário de EXECUTABLE_TASK (que delega a um TaskHandler) ou EVENT_CATCHER/EXTERNAL_TASK (que suspendem a execução), o EVENT_THROWER tem comportamento fixo, embutido no motor — não há bean de execução para configurar, e o nó não bloqueia quem o executa esperando resposta. :::

Anatomia

{
"id": "NOTIFICAR_RECUSA",
"name": "Notificar Recusa da Abertura",
"type": "EVENT_THROWER",
"providerType": "VARIABLE",
"providerVariable": "cpf",
"keyPrefix": "abertura-recusada-",
"outgoing": [ { "targetNodeId": "FIM_RECUSADA" } ]
}

Com cpf = "11122233344", lança a chave abertura-recusada-11122233344 — o mesmo formato que um EVENT_CATCHER configurado com providerVariable: cpf, keyPrefix: abertura-recusada- estaria aguardando. Os quatro providerType são os mesmos do EVENT_CATCHER (STATIC/VARIABLE/BEAN/ TEMPLATE), resolvidos pelo mesmo mecanismo. O EVENT_THROWER sempre resolve exatamente 1 chave — não há catchType/matchPolicy. Se o provider devolver mais de um item, o throw usa apenas o primeiro.

Política v1 para "ninguém está esperando essa chave": FAIL

:::danger Sem catcher ativo, o throw falha — não é engolido silenciosamente Se não há nenhuma tarefa ativa com a chave lançada, TaskNotFoundException propaga como falha do próprio nó EVENT_THROWER — a mesma trilha de incidente/retentativa de qualquer outro nó. É uma decisão de design explícita: a v1 não tem um modo "dispara e esquece" tolerante à ausência de catcher. Se o seu processo pode legitimamente lançar um evento sem ninguém esperando (uma notificação de melhor esforço), trate a falha via retryPolicy com maxRetries: 0 para virar incidente na hora, em vez de reprocessar. :::

commitBefore: o throw pode ser síncrono ou assíncrono

commitBefore no EVENT_THROWERComportamento
false (padrão)O throw dispara de forma síncrona, na mesma chamada que iniciou/retomou o processo lançador.
trueO throw não dispara imediatamente — vira uma ExecutableTask do tipo EVENT_THROW, retomada depois por um worker.

Simetricamente, o commitAfter do catcher decide se a continuação do lado de quem esperava roda de forma síncrona durante o throw ou fica pendente para retomada assíncrona. Ver Execução Síncrona e Assíncrona.

Validação no momento da implantação

DeployValidator valida EVENT_THROWER com a mesma checagem dos nós que resolvem chave de correlação: providerType não pode ser nulo, e o campo do modo escolhido precisa estar preenchido — incluindo, para providerType: BEAN, a existência real do bean CorrelationKeysProvider no contexto Spring. Um providerBean inexistente é rejeitado no momento da implantação, não descoberto só em produção.

:::tip No fio de abertura de conta Um uso natural: quando Aprovar Abertura é recusada, um EVENT_THROWER "Notificar Recusa" (providerType: VARIABLE, chave abertura-recusada-<cpf>) avisa um processo separado de comunicação com o cliente — que já espera essa chave num EVENT_CATCHER — sem que os dois processos precisem conhecer o processInstanceId um do outro. :::


Quando usar / quando não usar

SituaçãoUse
Quem completa a tarefa já tem (ou pode consultar) o taskId do motor — fila interna, tela operada por usuário autenticadoEXTERNAL_TASK
Quem sinaliza a conclusão é um sistema externo que só conhece uma chave de negócio própria (CPF, protocolo, número da solicitação)EVENT_CATCHER STANDALONE
O sinal precisa ser agregado a partir de várias correlações antes do fluxo prosseguirEVENT_CATCHER GROUP
Você precisa iniciar outro processo completo e esperar ele concluir (não apenas aguardar um sinal)CALL_ACTIVITY_COORDINATOR
Um passo do seu processo precisa avisar outra instância que já está esperando uma chaveEVENT_THROWER
Cancelar uma tarefa em andamento por uma chave externa (não como próximo passo)BOUNDARY_INTERRUPTIVE_CATCH_EVENT

Não use EVENT_THROWER para notificações que podem não ter destinatário — na v1 isso vira incidente. Não use EVENT_CATCHER quando o taskId já está disponível: EXTERNAL_TASK é mais simples e não exige combinar formato de chave entre dois lados.

Próximo passo

Continue para BOUNDARY_INTERRUPTIVE_CATCH_EVENT — Cancelamento por Correlação.