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.
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-contacom oPARALLEL_GATEWAYaberto: 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.