Ed eccomi qui dopo molto tempo nuovamente a scrivere, e questa volta voglio condividere con voi un'esperienza avuta di recente sulle Durable Function durante l'attività lavorativa.
Iniziamo con il dare la definizione tecnica ufficiale delle Durable Function : "Le Durable Function sono un'estensione delle Azure Function che ti permettono di creare funzioni stateful in ambienti serverless. L'estensione gestisce lo stato, i checkpoint ed i riavvii al posto tuo."
Leggendo la definizione ho un déjà vu di quando ho avuto i primi approcci con le orchestrazioni (WF o biztalk ed anche LogicApp), ed effettivamente, come vedremo più avanti, ci sono molte assonanze. Ovviamente il confronto con tutti i prodotti elencati è solo dal punto di vista funzionale in quanto tutti i sistemi elencati ti permettono di disegnare le orchestrazioni con un'interfaccia visuale mentre le Durable Function vengono disegnate via codice.
Ma entriamo subito nel vivo delle nostre Durable Function!
Per poter implementare le nostre orchestrazioni possiamo definire diverse tipologie di Funzioni, che sono :
Activity Function
Le Activity Function sono l'unità base di elaborazione all'interno della nostra orchestrazione. Possiamo identificare un' Activity Function come la singola operazione che viene eseguita all'interno della nostra orchestrazione, la quale può anche restituire grandi quantità di dati; per esempio in un'orchestrazione per l'invio di un ordine presente nel carrello, possiamo identificare come Activity Function ognuna di queste funzioni:
- L'operazione di Lettura Del Carrello, che restituisce i dati dei prodotti da acquistare.
- La scrittura dell'ordine in un repository.
- L'invio della mail di conferma.
L'estensione Durable Function ci garantisce che le activity chiamate all'interno dell'orchestrazione vengano eseguite almeno una volta.
Tutte le Activity Function vengono orchestrate da un Orchestrator Function
Orchestrator Function
L'Orchestrator Function descrive il modo e l'ordine di esecuzione delle azioni.
Un'orchestration può definire differenti tipi di azioni, che possono essere: activity function, sub-orchestration, attesa di eventi esterni e timer.
L'orchestrator è avviato da un orchestrator client.
Client function
Le Client Function sono funzioni che gestiscono l'istanza dell'orchestrazione. La Client Function è l'effettivo punto di entrata dell'orchestrazione.
Come tuttte le funzioni è possibile attivare la Client Function definendo un trigger che l'attivi, il trigger può arrivare da qualsiasi sorgente tra le quali HTTP, queue, event stream etc.
Quindi la catena delle chiamate che avvengono in un'orchestrazione sono rappresentate nella seguente immagine.
{{ "iscifoni/DurableFunction/durable-concepts.png" | asset_url | img_tag }}
E' il momento d'iniziare a creare la nostra prima Azure Function Durable. Apriamo Visual Studio e creiamo un nuovo progetto di tipo Azure Function
{{ "iscifoni/DurableFunction/CreateStep1.png" | asset_url | img_tag }}
Quando si apre la finestra di selezione del trigger da utilizzare selezioniamo "vuoto". Andremo ad inserire la Durable Function in un secondo momento, ovviamente andremo a selezionare anche la versione V2 delle function in modo da utilizzare .Net Core.
{{ "iscifoni/DurableFunction/CreateStep1-1.png" | asset_url | img_tag }}
Il nostro progetto si presenterà in questo modo.
{{ "iscifoni/DurableFunction/CreateStep2.png" | asset_url | img_tag }}
Ora è il momento di aggiungere la nostra Durable all'interno del progetto, operazione che eseguiremo tramite la voce di menú "nuova funzione di azure".
{{ "iscifoni/DurableFunction/CreateStep3.png" | asset_url | img_tag }}
Selezioniamo Funzione di Azure ed il nome del file .cs che verrà creato.
{{ "iscifoni/DurableFunction/CreateStep4.png" | asset_url | img_tag }}
Nella maschera che si apre selezioniamo Durable Function Orchestration.
{{ "iscifoni/DurableFunction/CreateStep5.png" | asset_url | img_tag }}
Ora abbiamo a disposizione lo scheletro della nostra Azure Function Durable che consiste in:
- Una Client Function
{{ "iscifoni/DurableFunction/FunctionCodeClient.png" | asset_url | img_tag }}
- Una Orchestrator Function
{{ "iscifoni/DurableFunction/FunctionCodeOrche.png" | asset_url | img_tag }}
- Una Activity Function
{{ "iscifoni/DurableFunction/FunctionCodeActivity.png" | asset_url | img_tag }}
Lo scheletro in nostro possesso ora è un semplice esempio, ma andiamo ad analizzare il suo funzionamento.
Il client configurato si espone in HTTP, questa cosa si evince dal fatto che la funzione client (identificabile dalla presenza dell'attributo [OrchestrationClient]), ha l'attributo HttpTrigger; ora fin da subito abbiamo detto che le Azure Function Durable sono un'estensione delle Azure Function, di conseguenza tutti i concetti di base delle function sono validi anche all'interno della nostra orchestration. Di conseguenza possiamo configurare la nostra funzione client in modo da essere triggherata non solo tramite HTTP ma con tutti gli altri trigger disponibili per funzioni (Queue, Blob ed altro).
La nostra funzione client è in grado di accedere a tutte le features delle Azure Function Durable tramite l'oggetto OrchestrationClient.
L'oggetto OrchestrationClient ci permette di:
- Avviare un Orchestrator Function
- Terminare l'esecuzione di un'orchestrazione
- Avere dettagli sullo stato di esecuzione di un'orchestrazione
- Lanciare un evento
- Rieseguire dall'inizio una funzione
- Creare una struttura standard di risposta contenente gli end point da richiamare per poter gestire le orchestrazioni
Di tutte le features che ci espone l'oggetto OrchestrationClient quelle che ci interessano in questo esempio sono due
- Avviare un Orchestrator Function
- Creare una struttura standard di risposta contenente gli end point da richiamare per poter gestire le orchestrazioni
L'avvio dell'Orchestrator Function avviene tramite la seguente chiamata
string instanceId = await starter.StartNewAsync("Function1", null);
La funzione si aspetta in input due parametri
- Il nome della funzione orchestrator da avviare
- il parametro di input da passare, che nel nostro caso è null
L'avvio dell'orchestrazione avviene in modo asincrono quindi il parametro che restituisce (di tipo string), contenente l'instanceId, è molto importante in quanto identifica in modo univoco l'esecuzione dell'orchestrazione che è stata richiesta, ma il fatto che ci venga restituito l'instanceId non vuol dire che l'esecuzione sia terminata, ma soltanto che è stata avviata.
Da questo momento in poi possiamo interagire con l'orchestrazione avviata esclusivamente tramite il suo instanceId; tutte le funzioni di stop, riesecuzione, lettura e stato utilizzano il parametro instanceId per identificare l'istanza dell'Orchestrazione da gestire.
Per accedere alle funzioni di gestione dell'orchestrazione abbiamo due alternative
- Creare funzioni Durable Proxy
In questa soluzione dobbiamo creare tante funzioni durable quante sono le operazioni che ci interessa implementare, ogni funzione tramite l'oggetto orchestrationclient ci permetterà di interagire con l'orchestrazione in esecuzione.
Di seguito un esempio
{{ "iscifoni/DurableFunction/FunctionCodeTerminate.png" | asset_url | img_tag }}
La funzione interrompe l'esecuzione identificata dall'instanceId passato via querystring.
- Utilizzare gli endpoint standard che l'estensione Durable mette a dispozione
In questa soluzione non dobbiamo implementare nulla, in quanto l'estensione Durable ci mette già a disposizione degli endpoint che possiamo chiamare via REST per poter eseguire le operazioni d'interesse sulle istanze delle nostre orchestrazioni.
Per facilitare l'utente, l'oggetto orchestrationclient mette a disposizione un metodo che ci permette di creare una risposta standard contenente tutti gli endpoint che possiamo richiamare al fine di gestire l'istanza appena avviata.
Di seguito un esempio.
return starter.CreateCheckStatusResponse(req, instanceId);
Ora non ci resta che avviare la nostra soluzione e vedere cosa succede.
Ovviamente vedremo partire subito l'emulatore delle Azure Function
{{ "iscifoni/DurableFunction/StartStep1.png" | asset_url | img_tag }}
Al termine dello startup noteremo che l'emulatore ci elenca tutti gli end point che possiamo richiamare
{{ "iscifoni/DurableFunction/StartStep2.png" | asset_url | img_tag }}
Se richiamiamo l'url della funzione con un browser avremo la seguente risposta
{{ "iscifoni/DurableFunction/HttpResponse1.png" | asset_url | img_tag }}
che formattata sarà
{
"id": "c5c396409f204ae7b12baa83ee7b039d",
"statusQueryGetUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/c5c396409f204ae7b12baa83ee7b039d?taskHub=DurableFunctionsHub&connection=Storage&code=eityB60csormSlDXcA46fFY/2ZQWOStr9/G9DHgl1xRx/rSVu5vHgA==",
"sendEventPostUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/c5c396409f204ae7b12baa83ee7b039d/raiseEvent/{eventName}?taskHub=DurableFunctionsHub&connection=Storage&code=eityB60csormSlDXcA46fFY/2ZQWOStr9/G9DHgl1xRx/rSVu5vHgA==",
"terminatePostUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/c5c396409f204ae7b12baa83ee7b039d/terminate?reason={text}&taskHub=DurableFunctionsHub&connection=Storage&code=eityB60csormSlDXcA46fFY/2ZQWOStr9/G9DHgl1xRx/rSVu5vHgA==",
"rewindPostUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/c5c396409f204ae7b12baa83ee7b039d/rewind?reason={text}&taskHub=DurableFunctionsHub&connection=Storage&code=eityB60csormSlDXcA46fFY/2ZQWOStr9/G9DHgl1xRx/rSVu5vHgA==",
"purgeHistoryDeleteUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/c5c396409f204ae7b12baa83ee7b039d?taskHub=DurableFunctionsHub&connection=Storage&code=eityB60csormSlDXcA46fFY/2ZQWOStr9/G9DHgl1xRx/rSVu5vHgA=="
}
Possiamo notare tutti gli endpoint standard utilizzabili per riavviare la funzione durable, stopparla o avere il dettaglio dell'esecuzione.
Ora se chiamiamo l'url che si trova nel parametro "statusQueryGetUri" vedremo il risultato dell'esecuzione che abbiamo avviato.
{{ "iscifoni/DurableFunction/HttpResponseGet1.png" | asset_url | img_tag }}
Che formattato
{
"name": "Function1",
"instanceId": "c5c396409f204ae7b12baa83ee7b039d",
"runtimeStatus": "Completed",
"input": null,
"customStatus": null,
"output": [
"Hello Tokyo!",
"Hello Seattle!",
"Hello London!"
],
"createdTime": "2019-07-27T15:03:07Z",
"lastUpdatedTime": "2019-07-27T15:03:11Z"
}
Ovviamente questo è solo un semplice esempio di Azure Function Durable, ma come potete immaginare possiamo fare molto di più quindi non perdetevi il prossimo articolo dove vedremo come implementare un'orchestrazione complessa.
E come di consueto il mio saluto è
Happy Coding !!!