Tarefas Executáveis
Uma EXECUTABLE_TASK executa lógica de negócio síncrona escrita em Java comum — sem expression
language, sem scripting embutido no JSON.
Uma tarefa executável é como a estação de uma linha de montagem que aperta um parafuso assim que a peça chega: não espera ninguém, não fica esperando aprovação, faz o trabalho e passa a peça adiante no mesmo instante. A analogia para de funcionar quando o parafuso "trava" — uma falha durante a execução não é silenciosa como uma máquina emperrada; ela vira uma decisão explícita do motor entre tentar de novo ou soar o alarme (veja Retentativas abaixo).
No processo de abertura de conta, Calcular
Score é a tarefa executável: assim que a solicitação entra, ela lê cpf e rendaDeclarada e
grava um score, sem esperar mais nada.
O contrato TaskHandler
public interface TaskHandler {
void handle(ExecutionContext execution);
}
Uma única responsabilidade: receber o contexto de execução e agir sobre ele. Não há valor de
retorno — o resultado da tarefa é expresso através de variáveis de processo escritas em execution,
ou de uma ProcessErrorException lançada para sinalizar um erro de negócio (ver
Tratando Erros de Negócio).
A API de ExecutionContext
public interface ExecutionContext {
void setVariable(String variableName, ProcessVariable value);
void removeVariable(String variableName);
ProcessVariable getVariable(String variableName);
boolean hasVariable(String variableName);
String getProcessInstanceId();
FlowNodeDefinition getFlowNode();
}
getVariable/setVariable/hasVariable/removeVariable— leitura e escrita de variáveis de processo (ver Variáveis de Processo).getProcessInstanceId()— útil para logging/correlação.getFlowNode()— a própria definição do nó atual, caso o handler precise inspecionarextensionPropertiesou outros metadados declarados no JSON.
Modelar no Craft
No canvas, o nó Calcular Score é um Executable Task com o campo Executor preenchido — esse valor é o nome do bean Spring que o motor invoca quando o fluxo chega ali:
{
"id": "EXECUTABLE_A1B2C3D4",
"name": "Calcular Score",
"type": "EXECUTABLE_TASK",
"executor": "calcularScoreTaskHandler",
"outgoing": [ { "targetNodeId": "EXTERNAL_E5F3809A" } ]
}
Implementar
Registre a implementação como um bean Spring nomeado, com o nome batendo com executor:
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;
import java.math.BigDecimal;
@Component("calcularScoreTaskHandler")
public class CalcularScoreTaskHandler implements TaskHandler {
private final BureauClient bureauClient;
public CalcularScoreTaskHandler(BureauClient bureauClient) {
this.bureauClient = bureauClient;
}
@Override
public void handle(ExecutionContext execution) {
String cpf = execution.getVariable("cpf").value().toString();
BigDecimal renda = new BigDecimal(execution.getVariable("rendaDeclarada").value().toString());
int scoreBureau = bureauClient.consultarScore(cpf);
int score = Math.min(scoreBureau + renda.divide(BigDecimal.TEN).intValue(), 1000);
execution.setVariable("score", new ProcessVariable("score", score));
}
}
O nome no @Component("...") precisa bater exatamente com executor — SpringTaskHandlerResolver
resolve o handler por nome de bean em tempo de execução, e falha se o bean não existir ou não
implementar TaskHandler. Como qualquer bean Spring comum, o handler injeta as dependências da sua
aplicação (repositórios, clients HTTP, outros serviços) normalmente — aqui, um BureauClient.
Assim como qualquer outro código Spring, prefira um TaskHandler por responsabilidade de negócio,
em vez de um handler genérico que ramifica internamente por if/switch conforme o nó. Isso mantém
cada handler trivialmente testável isoladamente (ver Testando Seus Handlers).
Retentativas (retry)
"E se a consulta ao bureau falhar" — uma instabilidade de rede, um timeout momentâneo? Uma
EXECUTABLE_TASK pode declarar uma retryPolicy própria; se ausente, o motor recorre ao fallback
global (kikwiflow.retry.default-retry-interval). Duas estratégias:
{
"id": "EXECUTABLE_A1B2C3D4",
"name": "Calcular Score",
"type": "EXECUTABLE_TASK",
"executor": "calcularScoreTaskHandler",
"retryPolicy": {
"strategy": "EXPONENTIAL_BACKOFF",
"maxRetries": 5,
"initialInterval": "PT10S",
"multiplier": 2.0,
"maxInterval": "PT10M"
},
"outgoing": [ { "targetNodeId": "EXTERNAL_E5F3809A" } ]
}
strategy | Comportamento |
|---|---|
EXPONENTIAL_BACKOFF | Intervalo cresce geometricamente a cada tentativa (initialInterval × multiplier^tentativa, limitado por maxInterval). Com o exemplo acima: 10s, 20s, 40s, 80s, 160s — nunca passando de 10 minutos. Bom para falhas transitórias de infraestrutura, onde dar mais tempo entre tentativas aumenta a chance de sucesso. |
LINEAR | Lista explícita de intervalos ("intervals": ["PT5S", "PT30S", "PT2M"]), indexada pela tentativa; esgotada a lista, o último intervalo se repete. Bom quando você quer um comportamento previsível e não crescente. |
Esgotadas as maxRetries tentativas, o motor abre um incidente em vez de continuar tentando —
visível e retentável manualmente pelo Monitor ou via
API REST, sem reiniciar a instância do zero. Exceções
que nunca se resolvem sozinhas com uma nova tentativa (erros de programação, dados malformados)
podem pular retry inteiramente via kikwiflow.retry.fatal-exceptions — veja
Retry e Resiliência para o mecanismo completo, incluindo como
o motor distingue uma falha técnica de um erro de negócio esperado (este último nunca é retentado —
ver Tratando Erros de Negócio).
Operar no Monitor
Inicie uma instância de abertura-de-conta pelo Monitor
(botão de play, com cpf e rendaDeclarada no payload). Como Calcular Score é síncrona e não
declara commitBefore, ela roda dentro da própria chamada de início: quando a instância aparece no
canvas, ela já está parada na etapa seguinte, e o painel de contexto mostra a variável score
gravada pelo handler. Não há nada assíncrono para esperar.
Quando usar / quando não usar
Use EXECUTABLE_TASK quando o passo é lógica síncrona que roda inteiramente dentro da sua
aplicação e retorna na hora: um cálculo, uma validação, uma chamada a outro serviço com resposta
imediata.
Não use quando o próximo passo depende de algo que chega em um momento indeterminado — uma
aprovação humana, um callback assíncrono, um evento horas depois. Aí o nó é
EXTERNAL_TASK, não EXECUTABLE_TASK — bloquear uma thread
esperando não é uma opção.
Próximo passo
Quando o próximo passo depende de alguém de fora — o analista de crédito aprovando a abertura —
o nó correto é EXTERNAL_TASK. Veja
Tarefas Externas e Workers.