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

Timers e Agendamento

O Kikwiflow modela dois padrões de timer de borda (boundary events), cada um resolvido por um evaluator diferente porque respondem a perguntas distintas: "quando este timer dispara pela primeira (e única) vez?" versus "qual é a próxima ocorrência deste timer recorrente?".

DisparaEfeito sobre o nó pai
BOUNDARY_INTERRUPTIVE_TIMER (InterruptiveTimerEventDefinition)Uma vez, em um instante calculadoInterrompe o nó pai — a tarefa original é cancelada e a execução desvia para as arestas de saída do timer
BOUNDARY_NON_INTERRUPTIVE_TIMER (NonInterruptiveTimerEventDefinition)Repetidamente, segundo uma SchedulePolicyNão interrompe o nó pai — dispara em paralelo (ex.: para notificações periódicas de SLA) e se reagenda sozinho

Timer interruptivo: TimeProviderType

Um InterruptiveTimerEventDefinition precisa resolver um único instante-alvo (dueDate). Assim como os gateways de decisão, ele suporta três estratégias via providerType:

public enum TimeProviderType {
STATIC,
VARIABLE,
BEAN
}
{
"id": "interruptive-timer-EXTERNAL_72040E2C",
"type": "BOUNDARY_INTERRUPTIVE_TIMER",
"providerType": "STATIC",
"staticValue": "PT1M",
"attachedToRef": "wait-customer-registry",
"outgoing": [ { "targetNodeId": "invalidate-invite" } ]
}
providerTypeOrigem do valorCampo relevante
STATICValor fixo declarado no JSONstaticValue
VARIABLEVariável de processo já calculadaproviderVariable
BEANBean Spring implementando DueDateProviderproviderBean

DueDateProvider: prazos calculados em Java

Quando o prazo depende de regra de negócio (ex.: SLA diferenciado por tipo de cliente, dia útil seguinte, feriados), implemente DueDateProvider:

public interface DueDateProvider {
/** ISO-8601: duração ("PT1H") ou data absoluta ("2026-12-25T20:00:00Z") */
String resolve(EvaluationContext execution);
}
@Component("slaCriticoBean")
public class SlaCriticoDueDateProvider implements DueDateProvider {
@Override
public String resolve(EvaluationContext execution) {
boolean vip = Boolean.TRUE.equals(execution.getVariableValue("clienteVip").orElse(false));
return vip ? "PT30M" : "PT2H";
}
}

Parsing híbrido de tempo

O valor resolvido — seja de staticValue, de uma variável, ou do retorno de um DueDateProvider — passa por TimerDueDateEvaluator.parseDynamicTime, que tenta interpretá-lo primeiro como data absoluta ISO-8601 e, falhando, como duração relativa (somada a Instant.now()):

private Instant parseDynamicTime(String timeValue) {
try {
return Instant.parse(timeValue); // ex: "2026-12-31T23:59:59Z"
} catch (DateTimeParseException e1) {
try {
return Instant.now().plus(Duration.parse(timeValue)); // ex: "PT1M"
} catch (DateTimeParseException e2) {
throw new IllegalArgumentException("Formato de tempo inválido: '" + timeValue + "'...");
}
}
}

:::tip Developer experience Isso significa que a mesma variável de processo, ou o mesmo DueDateProvider, pode retornar tanto "PT1H" quanto "2026-08-01T09:00:00Z" sem que o autor do processo precise declarar qual formato está usando — o motor resolve isso de forma transparente e falha com uma mensagem de erro explícita se nenhum dos dois formatos bater. :::

Timer não-interruptivo: SchedulePolicy

Diferente do interruptivo, o timer não-interruptivo não calcula um único dueDate — ele calcula repetidamente a próxima ocorrência, via TimerDueDateEvaluator.calculateNextSchedule(SchedulePolicy), e o ContinuationService reagenda automaticamente um novo ciclo cada vez que o timer anterior é concluído (mais detalhes em Execução Síncrona e Assíncrona).

public record SchedulePolicy(
ScheduleType type,
String expression,
List<String> fixedDates,
Integer maxOccurrences
) {}

public enum ScheduleType {
RATE_DURATION,
FIXED_DATES
}
typeexpression / fixedDatesComportamento
RATE_DURATIONexpression = duração ISO-8601 (ex.: "PT6H")Próxima ocorrência = now + expression, indefinidamente (sujeito a maxOccurrences, ver abaixo).
FIXED_DATESfixedDates = lista de instantes ISO-8601Próxima ocorrência = a primeira data da lista que ainda está no futuro. Quando todas as datas já passaram, calculateNextSchedule retorna null e o motor não reagenda — o ciclo de recorrência termina naturalmente.

maxOccurrences: limitando a quantidade de ciclos

maxOccurrences é opcional e nullable — null preserva o comportamento legado (recorrência indefinida, bounded só pelo nó pai completar). Quando declarado, TimerDueDateEvaluator.calculateNextSchedule passa a receber também o número (1-based) do ciclo que está prestes a ser agendado e retorna null — o mesmo sinal de "laço esgotado" que FIXED_DATES já usa — assim que esse número ultrapassa maxOccurrences:

public Instant calculateNextSchedule(SchedulePolicy policy, int occurrenceAboutToFire) {
if (policy == null) return null;
if (policy.maxOccurrences() != null && occurrenceAboutToFire > policy.maxOccurrences()) {
return null;
}
return switch (policy.type()) { /* ... */ };
}

O número do ciclo em voo é carregado no próprio runtime, no campo ExecutableTask.occurrence (1 no primeiro ciclo, incrementado a cada reagendamento em ContinuationService) — não em SchedulePolicy, que é estático (parte da definição do processo, compartilhado por toda instância). Com maxOccurrences: 3, o timer dispara nos ciclos 1, 2 e 3; o 4º nunca chega a ser criado. maxOccurrences: 0 (ou negativo) esgota já no 1º ciclo — o boundary event nem chega a existir em runtime, mesmo efeito de schedulePolicy nulo.

{
"id": "regua-cobranca-timer",
"type": "BOUNDARY_NON_INTERRUPTIVE_TIMER",
"attachedToRef": "aguardar-pagamento",
"schedulePolicy": { "type": "RATE_DURATION", "expression": "P1D", "maxOccurrences": 3 }
}

:::info ScheduleType.CRON foi removido do vocabulário do motor Uma versão anterior deste enum tinha um terceiro valor, CRON, implementado apenas como alias de RATE_DURATION (nunca avaliava expressão cron de verdade — o parsing real ficou como código morto/comentado no motor). Em vez de terminar essa implementação, o valor foi removido: dar suporte a cron de verdade exigiria um SPI vendor-specific (ex.: integrar com o parser de cron do Spring), o que contraria o princípio de a engine ser vendor-neutral. DeployValidator/o próprio compilador Java já impedem "type": "CRON" — o JSON Schema (schema/kikwi-process.schema.json) também não lista mais esse valor. Use RATE_DURATION ou FIXED_DATES. :::

{
"id": "sla-warning-timer",
"type": "BOUNDARY_NON_INTERRUPTIVE_TIMER",
"attachedToRef": "wait-customer-registry",
"schedulePolicy": { "type": "RATE_DURATION", "expression": "PT30M" }
}

Escolhendo entre interruptivo e não-interruptivo

  • Use interruptivo para SLAs que, ao vencer, devem abortar a espera atual e seguir um caminho alternativo do processo — ex.: expirar um convite não confirmado (wait-customer-registryinvalidate-invite, ver customer-invite-activation.json no sample-onboarding-process).
  • Use não-interruptivo quando o timer segue uma regra de recorrência (SchedulePolicy) e apenas produz um sinal em paralelo, sem afetar o nó pai — ex.: disparar um alerta de SLA a cada 30 minutos enquanto uma tarefa externa continua aberta.
  • Use TIMER_TASK quando o prazo não é um evento de borda de outro nó, mas o próprio próximo passo do caminho feliz — ex.: "aguardar 24h antes de enviar o lembrete seguinte". Reaproveita o mesmo TimeProviderType/staticValue/providerVariable/providerBean do timer interruptivo, mas como nó de fluxo principal em vez de evento de borda.