Asynchroniciteit
Concept
De uitvoering van binnen de workflow gedefinieerde activiteiten kan synchroon dan wel asynchroon verlopen. We spreken van synchroon wanneer de applicatie een groep van activiteiten in één vloeiende beweging (continuous) uitvoert. We spreken van asynchroon als de applicatie diezelfde groep van activiteiten stap voor stap uitvoert, telkens met rustpunten (ook wel save points, wait states, break states of transaction boundaries genaamd) ertussenin. Een groep van activiteiten die tussen save points door in één keer wordt uitgevoerd, noemen we in technische termen een transactie.
Rollback
Save points zijn dus punten in de workflow waar het proces een stabiele status bereikt, de stand van zaken opslaat in zijn database, en dus even kan ‘uitblazen’ vooraleer verder te gaan. Het nut van deze save points wordt duidelijk wanneer het proces tegen errors aanloopt. Als dat gebeurt, voltrekt zich een rollback en keert de process token terug naar het laatst bereikte save point.
Nadat je de error hebt opgelost, kan je het proces terug opstarten en zal de workflow de betrokken transactie opnieuw uitvoeren. Bij het ‘Synchrone uitvoering’ scenario in bovenstaand diagram betekent dit dus dat de gebruikerstaak opnieuw ingediend zal worden en dat naast service taak 3 ook service taak 1 en service taak 2 opnieuw uitgevoerd zullen worden. Bij het ‘Asynchrone uitvoering’ scenario in bovenstaand diagram, zal enkel service taak 3 opnieuw uitgevoerd worden.
Effect op gebruikerservaring
In de meeste workflows is tussenkomst van een menselijke gebruiker in de frontoffice dan wel de backoffice vereist. Telkens een gebruikerstaak wordt klaargezet en toegewezen aan een gebruiker, bereikt het proces automatisch een save point. De gebruiker is nu immers aan zet om de taak uit te voeren (bijvoorbeeld een formulier invullen of een communicatie nalezen). Op het ogenblik dat de gebruiker deze taak afrondt door op de ‘indienen’ knop te klikken, gaat het proces achterliggend verder. De gebruiker krijgt evenwel pas bevestiging op het ogenblik dat het volgende save point bereikt is.
Gebeurt dit niet, bijvoorbeeld omwille van een error in een daaropvolgende service taak, dan volgt er een rollback en keert de process token terug. Het proces gaat in incident state. De taak blijft dan open en dus onafgerond. De indienknop blijft een spinner tonen. De ervaring van de gebruiker is ‘Ik kan de taak niet indienen, terwijl ik die wel correct heb uitgevoerd’, wat begrijpelijkerwijze voor frustratie zorgt. Dit komt overeen met het schema ‘Synchrone uitvoering’ onder het punt ‘Rollback’ (zie hoger).
Stel dat er bij elke tussenliggende service taak een save point is ingesteld (zie schema ‘Asynchrone uitvoering’ onder het punt ‘Rollback’), dan zal de gebruiker de taak wel kunnen indienen en brengt de rollback na error de process token terug naar het save point vlak vóór de service taak waar de error zich voordeed. De frustratie verschuift nu naar de gebruiker die de volgende gebruikerstaak ‘Formulier controleren’ moet uitvoeren. Hij of zij denkt nu ‘Het formulier is blijkbaar succesvol ingediend? Waarom krijg ik nu de taak ‘Formulier controleren’ niet voorgeschoteld?’
Activiteiten of events waaraan save points inherent zijn
Zoals hierboven geschetst, maakt een gebruikerstaak per definitie de worfklow asynchroon. Het proces moet sowieso wachten totdat de gebruiker de taak opent en afrondt. Dit betekent dat daar automatisch een save point ingebed zit. Naast gebruikerstaken zijn er nog enkele types activiteiten of events met inherente save points.
Element | Beschrijving |
---|---|
De process token wacht totdat een gebruiker de taak opent en afrondt. | |
De process token wacht op een specifieke message. | |
Service task - specifiek subtype External task | De process token wacht op een message dat aangeeft wanneer een externe service een klaargezette taak heeft uitgevoerd. |
Intermediate catch events zoals Timer, Message, Signal, Conditional, Escalation, Compensation | De process token wacht totdat een timer afloopt, een message of signal toekomt, een conditie naar |
De process token wacht totdat de eerste activiteit op één van de uitgaande flows getriggerd wordt. |
Save points expliciet instellen
Asynchronous before of after parameters
Bij elementen zonder inherente save point kan je die expliciet gaan instellen. Het instellen van een save point verloopt via de eigenschappen van de verschillende elementen (bijvoorbeeld servicetaken) in de workflow. Dit gebeurt via de checkboxes ‘asynchronous before’
(save point bevindt zich vóór uitvoering van de activiteit') en/of ‘asynchronous after’
(save point bevindt zich ná uitvoering van de activiteit).
Exclusive = true parameter
Vanaf dat je één van beide aanvinkt, krijg je een extra checkbox ‘exclusive’
te zien. Deze staat standaard aangevinkt (waarde true
). Dit betekent dat de eerstvolgende ‘transactie’ (d.w.z. een serie van synchroon uit te voeren activiteiten die reikt tot het volgende save point) onmiddellijk en door dezelfde applicatie thread als de vorige uitgevoerd wordt.
Het effect is dat de twee opeenvolgende transacties technisch gesproken asynchroon zijn (er zit een save point tussen), maar in de praktijk elkaar onmiddellijk opvolgen en dus synchroon aanvoelen. De use case hier is het uitspreiden van een aantal coherente acties over meerdere transacties heen.
Bijvoorbeeld, een personeelslid dient een verlofaanvraag in (eerste transactie), deze aanvraag wordt opgeslagen in de hr-verloftool (tweede transactie) en vervolgens ook in een gedeelde outlook-agenda geschreven (derde transactie). Vanuit technisch oogpunt zijn dit inderdaad drie aparte transacties die elk op een verschillende manier binnen een verschillend systeem uitgevoerd worden. Als de laatste mislukt, wil je ze niet per se allemaal terugrollen. Je wil dan gewoon een error op de laatste activiteit. Deze kan je dan achteraf nog remediëren. Maar vanuit ‘business’ oogpunt horen de drie transacties nauw samen. Je wil dan ook dat ze direct opeenvolgend uitgevoerd worden, vooral omdat je snel consistentie over de drie systemen heen wil bereiken: van zodra een personeelslid zijn verlofaanvraag indient, wil je de aanvraag idealiter ook direct in de verloftool en in de outlook-agenda zien.
Exclusive = false parameter
Bij uitschakelen van de checkbox ‘exclusive’
(waarde false
) wordt de eerstvolgende transactie niet onmiddellijk door dezelfde applicatie thread uitgevoerd, maar wordt deze in de wachtrij van de workflow engine geplaatst. Dit betekent dat de transactie afhankelijk van prioriteit op een ‘later’ tijdstip zal uitgevoerd worden. Dit asynchroon tijdsinterval kan variëren van enkele milliseconden tot enkele seconden, minuten of zelfs uren. Het gaat dus niet alleen om ‘technisch’ asynchroon, maar ook over ‘werkelijk’ asynchroon. Use case hier is wanneer er geen nauwe coherentie bestaat tussen de transacties.
Bijvoorbeeld, naadat het personeelslid zijn verlofaanvraag ingediend heeft, moet er ook een notificatie gestuurd worden naar de leidinggevende. Deze moet de kans krijgen om het verlof goed te keuren of te weigeren. Dit zijn transacties die niet direct samenhangen met het indienen van het verlof op zich. Het is met andere woorden niet erg als dit werkelijk asynchroon uitgevoerd wordt, en de notificatiemail pas na enkele uren in de mailbox van de leidinggevende terechtkomt.
Tips voor het definiëren van transaction boundaries
Standaard is het zo dat save points zich enkel bevinden binnen gebruikerstaken en binnen de overige, hoger opgelijste elementen. De overige stukken van de workflow zijn synchroon ingesteld. Het instellen van save points (en dus het definiëren van transaction boundaries) is een bewuste configuratie-actie. Hieronder enkele guidelines en best practices.
Tip 1: sta stil bij de ervaring van frontoffice gebruikers
Veel dienstverleningen starten met een frontoffice gebruiker die een formulier invult en indient. Wat je zeker niet wilt, is dat de gebruiker een correct ingevuld formulier niet kan indienen, omdat een stroomafwaarts gedefinieerde service taak faalt. Zorg dus voor een save point vlak na een frontoffice formuliertaak.
Tip 2: save points bij service tasks die afhangen van een externe service
Wanneer een proces afhankelijk is van een externe service (bijvoorbeeld API-call om data weg te schrijven naar een ander systeem), zorgt dit voor een bepaalde onzekerheid. Je weet niet of de service altijd beschikbaar is, je weet niet hoe lang het zal duren vooraleer de service antwoordt, je weet niet of er misschien wijzigingen zijn in de manier waarop je de service moet aanspreken, enzovoort. Dit betekent dat de taak die deze externe service aanroept, een fragiel punt in de workflow is. Overweeg daarom om dit te isoleren binnen één enkele transactie (dus met een save point er vlak vóór en een save point er vlak ná).
Tip 3: transaction boundaries en dataconsistentie
Transaction boundaries bundelen activiteiten die erg nauw samenhoren en die je misschien wel in één enkele service taak kan bundelen, ware het niet dat je ze toch apart wil definiëren en ze op die manier zichtbaar wil maken aan de business.
Het bijkomende voordeel van zo’n transactie met meerdere activiteiten is dat je ze na rollback volledig opnieuw kan uitvoeren, ook al waren bepaalde activiteiten binnen de transactie reeds uitgevoerd. Dit laatste is niet erg, op voorwaarde dat het meerdere keren uitvoeren van dezelfde activiteit tot exact hetzelfde resultaat leidt als wanneer je de activiteit slechts één keer zou uitvoeren (m.a.w. het moet gaan om zogenaamde ‘idempotente’ activiteiten). Bijvoorbeeld, het maakt niet uit hoeveel keer je een specifieke call doet naar een extern systeem, het resultaat is uiteindelijk hetzelfde.
De procedure van transaction rollbacks, troubleshooting en retry, in combinatie met het ‘at least once’-principe (elke activiteit wordt ten minste één keer uitgevoerd), leidt ertoe dat data zo consistent mogelijk blijft, ook al treden er incidenten op.
Tip 4: save points tijdens configuratie & testing
Save points inlassen na iedere stap kunnen handig zijn tijdens het configureren en testen van een workflow. Op die manier kom je veel sneller te weten waar een proces vast komt te lopen. Je kan dit ook visueel zien binnen het diagram in de dossierpagina of in de Camunda cockpit. Van zodra je de workflow op punt hebt gesteld, kan je overbodige save points opnieuw wegnemen en je dossiertype (dienstverlening) publiceren.
Tip 5: let op met bulkacties
Een bulkactie is een actie waarmee je binnen meerdere dossiers tegelijk een taak kan laten uitvoeren. Dit kan potentieel een piekbelasting in de applicatie veroorzaken.
De workflow hierboven geschetst, kan voor grote problemen zorgen. Stel dat je via een bulkactie 500 aanvragen in één keer bevestigt, dan betekent dit dat de engine 500 transacties tegelijk moet opstarten en synchroon uitvoeren. Dit betekent dat gedurende deze piekbelasting, gaande van enkele minuten tot enkele uren, er geen andere acties binnen de applicatie mogelijk zijn. Gebruikers ervaren dan bijvoorbeeld dat ze geen taken meer toegewezen krijgen of dat ze die niet kunnen indienen.
Je zou kunnen redeneren dat het helpt om de transactie op te splitsen in verschillende stukken, telkens met een save point ertussen.
Dit zal echter niet helpen, omdat standaard de engine de transacties onmiddellijk na elkaar door dezelfde applicatie thread laat uitvoeren (parameter ‘exclusive’
staat by default op true
). Dus de applicatie raakt nog steeds overbelast.
De enige oplossing is om de transactie onmiddellijk volgend op de taak ‘bevestig aanvraag in bulk’ asynchroon te maken en de parameter ‘exclusive’
op false
te plaatsen. Daarbovenop kan je de jobs (activiteiten) binnen die transactie een lage prioriteit toekennen. Het gevolg is dat alle 500 transacties met een lage prioriteit in de queue van de workflow engine belanden. Andere activiteiten met hogere prioriteit, bijvoorbeeld een gebruiker die een taak wil opstarten of afronden, krijgen dan altijd voorrang.
Tip 6: definieer BPMN-errors of escalaties en vang ze op
Bij specifieke fouten of uitzonderingen is het niet wenselijk dat het proces in incident modus gaat en teruggedraaid wordt (rollback). Overweeg om boundary catch events te definiëren op de taken waar je mogelijks dergelijke fouten of uitzonderingen verwacht. Denk bijvoorbeeld aan de API-call naar een extern systeem uit tip 2. Bij onbeschikbaarheid van de service of bij teruggave van een foutcode, kan je dit opvangen via error of escalation events en kan je de verdere afhandeling binnen de workflow zelf definiëren. Het proces hoeft dan niet in incident modus te gaan. Op die manier maak je het proces veerkrachtiger en daalt ook de nood om alles op te delen in aparte transacties met verschillende save points ertussen.
Tip 7: maak gebruik van compensation events en activities
Bij specifieke fouten of uitzonderingen wil je niet het standaard patroon van transaction rollback, troubleshooting en retry uitvoeren, maar wil je de uitgevoerde transacties annuleren en uiteindelijk terugdraaien. Hiervoor kan je gebruik maken van compensation events en activities.
Tip 8: expliciteer een transactie in de workflow via een transaction subprocess
Een transaction kan je ook expliciet visualiseren of modelleren aan de hand van een transactional subproces. Een interessante optie daarbij is een cancellation boundary event die de binnen het subproces gedefinieerde compensation events triggert.