Pular para o conteúdo principal

Processamento Paralelo

Os capítulos anteriores construíram o processo abertura-de-conta como uma linha: cada passo espera o anterior terminar. A partir daqui a trilha entra na fase de tópicos avançados — coisas que um processo em produção acaba precisando, mas que não são o primeiro que você modela. Este é o primeiro deles.

Pense em fazer o jantar

Você não frita o bife, depois espera esfriar, depois começa a salada, depois espera, depois põe água para o arroz. Você põe a água no fogo, e enquanto ela esquenta pica a salada e tempera a carne. Três tarefas independentes correndo ao mesmo tempo, e o jantar só está pronto quando a última termina. A analogia para de funcionar no ponto em que uma tarefa depende da outra — não adianta paralelizar "escorrer o macarrão" com "cozinhar o macarrão".

"Consulta ao bureau e coleta de documentos são independentes — posso rodar as duas ao mesmo tempo?"

No abertura-de-conta, antes de calcular o score, duas coisas precisam acontecer: uma consulta ao bureau de crédito (uma chamada de API, automática) e a coleta dos documentos do solicitante (uma tarefa externa, o solicitante anexa RG e comprovante). Uma não depende da outra. Modeladas em sequência, o tempo de uma soma no tempo da outra — e a consulta ao bureau fica parada esperando o solicitante achar o comprovante de residência.

Um PARALLEL_GATEWAY abre as duas como ramos independentes; um JOIN_GATEWAY espera as duas terminarem antes de seguir para Calcular Score.

Modelar no Craft

Arraste um Parallel (split) logo depois do Start e um Join antes de Calcular Score. Ligue o split aos dois ramos (Consulta Bureau, um Executable Task; Coletar Documentos, um External Task) e ligue os dois ramos ao Join. O Craft associa o Join ao split automaticamente. Veja Construindo o Fluxo no Canvas.

O .kikwi resultante tem quatro nós novos. O PARALLEL_GATEWAY declara para qual join seus ramos convergem (targetJoinId); o JOIN_GATEWAY declara de qual split ele fecha (sourceSplitId):

{
"PARALLEL_SPLIT": {
"id": "PARALLEL_SPLIT",
"name": "Divisão de Esteiras",
"type": "PARALLEL_GATEWAY",
"targetJoinId": "JOIN_VERIFICACOES",
"outgoing": [
{ "id": "flow-split-bureau", "targetNodeId": "CONSULTA_BUREAU" },
{ "id": "flow-split-docs", "targetNodeId": "COLETAR_DOCUMENTOS" }
]
},
"CONSULTA_BUREAU": {
"id": "CONSULTA_BUREAU",
"name": "Consulta Bureau",
"type": "EXECUTABLE_TASK",
"executor": "consultaBureauTaskHandler",
"commitBefore": true,
"outgoing": [ { "id": "flow-bureau-join", "targetNodeId": "JOIN_VERIFICACOES" } ]
},
"COLETAR_DOCUMENTOS": {
"id": "COLETAR_DOCUMENTOS",
"name": "Coletar Documentos",
"type": "EXTERNAL_TASK",
"extensionProperties": { "actor": "solicitante" },
"outgoing": [ { "id": "flow-docs-join", "targetNodeId": "JOIN_VERIFICACOES" } ]
},
"JOIN_VERIFICACOES": {
"id": "JOIN_VERIFICACOES",
"name": "Sincronização",
"type": "JOIN_GATEWAY",
"sourceSplitId": "PARALLEL_SPLIT",
"outgoing": [ { "id": "flow-join-score", "targetNodeId": "CALCULAR_SCORE" } ]
}
}

O JOIN_GATEWAY não decide nada — ele apenas segue sua única saída assim que todos os ramos abertos por aquele split tiverem chegado nele. Um split com dois ramos exige que os dois cheguem; se um ramo terminar em um DEFAULT_END_EVENT no meio do caminho (em vez de chegar no join), o join espera para sempre — modele os ramos para sempre convergirem.

Implementar

O ramo de tarefa externa (Coletar Documentos) não tem bean — é completado de fora, como qualquer tarefa externa. O ramo automático precisa do handler:

package com.empresa.processo.executors;

import io.kikwiflow.execution.api.context.ExecutionContext;
import io.kikwiflow.execution.api.handler.TaskHandler;
import io.kikwiflow.model.execution.ProcessVariable;
import org.springframework.stereotype.Component;

@Component("consultaBureauTaskHandler")
public class ConsultaBureauTaskHandler implements TaskHandler {

private final BureauClient bureauClient;

public ConsultaBureauTaskHandler(BureauClient bureauClient) {
this.bureauClient = bureauClient;
}

@Override
public void handle(ExecutionContext execution) {
String cpf = execution.getVariable("cpf").value().toString();
int scoreBureau = bureauClient.consultarScore(cpf);
execution.setVariable("scoreBureau", new ProcessVariable("scoreBureau", scoreBureau));
}
}

commitBefore nos ramos: por que ele aparece aqui

Um PARALLEL_GATEWAY sempre cruza uma fronteira assíncrona internamente — cada ramo é retomado por um worker de segundo plano, não pela thread que iniciou o processo. Isso é o motor sendo correto: os ramos precisam poder rodar de fato em paralelo, e um deles (a tarefa externa) vai ficar parado esperando um humano por tempo indeterminado.

Na prática, para você que modela: declarar commitBefore: true no nó executável de um ramo (CONSULTA_BUREAU, acima) garante que o estado é persistido antes dele rodar, então se a aplicação reiniciar no meio da consulta ao bureau, o ramo é retomado do começo em vez de se perder. O que "assíncrono" significa para você ao modelar está na seção commitBefore/commitAfter de Anatomia de um Processo; o mecanismo interno (filas de trabalho, UnitOfWork, o poller) está em Execução Síncrona e Assíncrona.

Operar no Monitor

Inicie uma instância de abertura-de-conta pelo Monitor (botão de play, com cpf e rendaDeclarada no payload). No canvas ao vivo, os dois ramos acendem ao mesmo tempo: em um a Consulta Bureau roda sozinha em segundo plano (o poller a executa em ~1s) e a conexão dela pisca; no outro, Coletar Documentos fica parada como tarefa externa. Clique nela e complete pelo Monitor. Só quando os dois ramos chegam no Sincronização é que Calcular Score dispara.

📸 [ASSET NECESSÁRIO] Print do canvas ao vivo do abertura-de-conta com o PARALLEL_GATEWAY aberto: os dois ramos ("Consulta Bureau" e "Coletar Documentos") ativos simultaneamente, um deles com a conexão piscando (token em trânsito) e o outro com o card de tarefa externa pendente. Incluir no documento de assets: monitor-parallel-dois-ramos-ativos.png

Quando usar / quando não usar

Use quando dois ou mais passos são genuinamente independentes e podem (ou devem) rodar ao mesmo tempo — duas verificações que não leem o resultado uma da outra, uma chamada de API lenta ao lado de uma espera humana. O ganho é de tempo de ciclo: o processo anda no tempo do ramo mais lento, não na soma.

Não use quando:

  • Um passo depende do resultado do outro — aí é sequência, não paralelo.
  • Você quer escolher um de vários caminhos com base numa condição — isso é um EXCLUSIVE_GATEWAY, não um paralelo.
  • Você precisa disparar N cópias do mesmo subprocesso (uma por item de uma lista) — isso é CALL_ACTIVITY_COORDINATOR, não um split com ramos fixos.

Próximo passo

Com o processo modelado, testado e agora paralelizado, o que falta é a superfície de operação: como suas definições chegam à aplicação e como você intervém quando o Monitor não basta. Veja Implantando Processos.