Pular para o conteúdo principal

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.

Pense em uma estação de linha de montagem

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).

O exemplo desta página é uma etapa Calcular Score num processo de abertura de conta: 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 inspecionar extensionProperties ou outros metadados declarados no JSON.

Declarar no .kikwi​

No .kikwi (ou no Craft, como Executable Task), o nó declara o campo executor — o nome do bean Spring que o motor invoca quando o fluxo chega ali:

{
"id": "CALCULAR_SCORE",
"name": "Calcular Score",
"type": "EXECUTABLE_TASK",
"executor": "calcularScoreTaskHandler",
"outgoing": [ { "targetNodeId": "APROVAR_ABERTURA" } ]
}

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.

Uma classe, uma responsabilidade

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": "CALCULAR_SCORE",
"name": "Calcular Score",
"type": "EXECUTABLE_TASK",
"executor": "calcularScoreTaskHandler",
"retryPolicy": {
"strategy": "EXPONENTIAL_BACKOFF",
"maxRetries": 5,
"initialInterval": "PT10S",
"multiplier": 2.0,
"maxInterval": "PT10M"
},
"outgoing": [ { "targetNodeId": "APROVAR_ABERTURA" } ]
}
strategyComportamento
EXPONENTIAL_BACKOFFIntervalo 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.
LINEARLista 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 via API REST ou pelo KikwiflowEngine (ver Operando 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).

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 passo depende de alguém de fora — uma aprovação humana, um sistema que responde depois — veja Tarefas Externas.
  • Para cobrir o handler com teste unitário e de integração, veja Testando Seus Handlers.