Workflows — Hangfire Batches en Job Chaining
Dit document beschrijft hoe Hangfire Batches worden gebruikt voor de uitvoering van workflow executions, hoe de graph van nodes wordt doorlopen en hoe job chaining en vertragingen werken.
Architecturale beslissing: waarom Hangfire Batches
Elke workflow execution wordt als één Hangfire Batch aangemaakt. Dit geeft drie concrete voordelen:
Zichtbaarheid — alle jobs van een execution zijn in het Hangfire Dashboard zichtbaar als één groep met de naam
Workflow Execution: {naam}.Cancelability — bij annulering kan de hele batch worden teruggezocht via
HangfireBatchId.Contextloze scheduling — individuele activity jobs kennen hun batch ID niet. Ze vragen het op uit de database en hangen zichzelf aan de batch via
BatchJob.Attach.
Aanmaken van een execution — BatchJob.StartNew
De batch wordt aangemaakt in WorkflowTriggerService, vóórdat de execution-rij in de database wordt aangelegd. De WorkflowDispatcherJob is de eerste job in de batch:
Het patroon is bewust: de HangfireBatchId wordt opgeslagen in Workflows_Executions zodat latere jobs — die al draaien en hun context niet kennen — de batch ID kunnen ophalen.
WorkflowDispatcherJob — van batch naar trigger
De WorkflowDispatcherJob is de centrale orchestrator die de workflow definition laadt, de trigger node identificeert en de eerste activity job inschrijft.
Stappen
Laad de
WorkflowExecution(bevat gelockedVersionIdenHangfireBatchId).Laad de
WorkflowVersionen deserialiseer deWorkflowDefinitionDto(nodes + edges).Zoek de trigger node (
Type == "trigger").Evalueer of de workflow moet wachten op een hire date (
GetHireDateDelay).Maak een
ExecutionJob-rij aan in de database voor de trigger node.Voeg de
WorkflowTriggerActivityJobtoe aan de batch viaBatchJob.Attach.
Hire date scheduling
Wanneer de trigger node onboardingDateType == "StartDate" heeft én de employeeHireDate in de toekomst ligt, wordt de trigger job niet direct ge-enqueued maar gepland op de hire datum:
De execution krijgt status Scheduled. Wanneer de WorkflowTriggerActivityJob na de vertraging start, zet die de status om naar Running.
Graph traversal — hoe de volgende node wordt bepaald
Na elke voltooide job bepaalt WorkflowActivityJobBase.ScheduleNextNodesAsync welke nodes als volgende worden ingepland. Dit werkt op basis van de edges in de WorkflowDefinitionDto.
Stap-voor-stap
Voor elke outgoing edge:
Zoek de target node op in
workflowDef.Nodes.Controleer of de target node meerdere inkomende edges heeft (merge point).
Als het een
delaynode is: roepHandleDelayNodeAsyncaan.Anders: roep
EnqueueActivityNodeAsyncaan.
Merge point handling
Een merge point is een node met meer dan één inkomende edge. Zonder speciale behandeling zou de node meerdere keren worden geenqueued — eén keer door elke voltooide bovenliggende job.
De check in CanScheduleMergeNodeAsync voorkomt dit: de target node wordt alleen gepland als:
alle parent nodes de status
Completedhebben, énde target node nog geen job-rij heeft in de database.
BatchJob.Attach — jobs toevoegen vanuit lopende jobs
Alle activity jobs voegen vervolgstappen toe aan de batch via BatchJob.Attach. Ze halen de HangfireBatchId op uit de database:
De lock (BatchLock) is een static object lock die thread-safe toegang tot BatchJob.Attach garandeert.
Delay nodes — vertragingen in de workflow
Een delay node wordt niet als een echte Hangfire job uitgevoerd maar vertegenwoordigt een wachttijd. De verwerking verloopt als volgt:
Bereken de vertraging via
CalculateDelay(zie hieronder).Maak een
ExecutionJob-rij aan voor de delay node met statusRunning.Zoek alle outgoing edges van de delay node (SPLIT-patroon is mogelijk).
Plan voor elke volgende node een Hangfire scheduled job met de berekende vertraging.
De isLastNode vlag zorgt ervoor dat de delay job pas als Completed wordt gemarkeerd nadat de laatste vervolgnode is gestart — dit voorkomt race conditions bij een SPLIT na een delay.
Ondersteunde delay types
De vertraging wordt berekend in CalculateDelay op basis van de delayType property in de node data:
delayType | Werking |
|---|---|
| Vaste TimeSpan, bijv. |
| Wacht tot een specifiek tijdstip ( |
| Volgende dag om een opgegeven tijd |
| Volgende werkdag (slaat za/zo over) om een opgegeven tijd |
| Eerstvolgende specifieke weekdag om een opgegeven tijd |
Fallback bij ongeldig formaat: 1 uur.
Condition nodes — conditionele vertakking
De WorkflowConditionActivityJob wijkt af van de basis job lifecycle: hij beheert zelf de scheduling van vervolgstappen (via ShouldScheduleNextNodesAutomatically() => false).
Na evaluatie van de regels wordt de juiste tak gevolgd op basis van de SourceHandle van de edges ("true" of "false"):
Nodes op de niet-genomen tak worden recursief aangemaakt als skipped jobs (IsSkipped = true). Dit is nodig zodat CheckAndCompleteExecutionAsync correct kan vaststellen of de workflow klaar is — skipped jobs worden genegeerd bij de afronding.
Execution afronding
Na elke voltooide job, als er geen outgoing edges zijn, wordt CheckAndCompleteExecutionAsync aangeroepen:
De execution wordt als Completed gemarkeerd wanneer alle niet-skipped jobs zijn voltooid. De aanwezigheid van skipped jobs (conditionele tak niet genomen) is geen reden voor een fout.
Volledige batch/job flow
Error handling
Wanneer een activity job een exception gooit:
De
ExecuteJobAsyncmethode inWorkflowActivityJobBasevangt de exception op.De job status wordt op
Failedgezet inWorkflows_ExecutionJobs(inclusiefErrorMessageenErrorStackTrace).De exception wordt opnieuw gegooid (
throw), waardoor Hangfire de job als mislukt registreert en eventueel retry-pogingen uitvoert.Hangfire's ingebouwde retry mechanisme is actief — na het maximum aantal pogingen eindigt de job in de
Failedstaat in het Hangfire Dashboard.CheckAndCompleteExecutionAsyncmarkeert de execution uiteindelijk alsFailedzodra alle actieve jobs zijn afgerond.
De WorkflowDispatcherJob heeft eigen foutafhandeling die de execution direct op Failed zet als de workflow definitie niet geladen kan worden.