Onze workflow begint al aardig volwassen te lijken. We hebben de input gevalideerd, we hebben berichten verstuurd naar andere groepen en we hebben een email verstuurd en de info daarover in de database geplaatst. Dat laaste hebben we in onze vorige post gezien.
Onze workflow staat nu keurig te wachten tot het een seintje krijgt om verder te gaan (of tot er een time-out plaatsvindt). Hoe doen we dat? Simpel: met Azure Functions!
De link die we in de mail versturen is een link naar een Azure Function. Dus we gaan die maar eens maken. Het is een gewone, recht-toe, recht-aan Azure Function dus niet Durable. Hij is publiek dus hij kan in de class PublicFunctions.cs.
[FunctionName("ApproveAttendee")]
public static async Task<HttpResponseMessage> ApproveAttendee(
[HttpTrigger(
AuthorizationLevel.Anonymous,
"get",
Route = "processApproval/{rowKey}/{isApproved}")]
HttpRequestMessage req,
[OrchestrationClient] DurableOrchestrationClient client,
string rowKey, bool isApproved,
ILogger log
)
{
log.LogWarning($"We received {isApproved} for key {rowKey}.");
return req.CreateResponse(HttpStatusCode.OK);
}
De FunctionName heeft geen prefix. Het is immers een gewone publieke method. We zetten de authenticatie op anonymous: we willen geen authenticatie keys delen met het publiek en de unieke ID is authenticatie genoeg voor ons. Voor de rest doen we niets spannends. We zetten de route goed zodat we de parameters krijgen. Deze route past bij de route die we in de activity hebben waar we de email aanmaken.
Ik vraag ook gelijk een instance van de DurableOrchestrationClient toe: die hebben we later nodig.
Als we dit nu starten, zien we in de debug informatie van de runtime nu ineens twee publieke functions:

We hebben nu dus ApproveAttendee erbij. Ik weet het: de naam is niet helemaal goed want we kunnen potentiele Attendees ook afwijzen maar ik benader dingen graag positief.
Als we nu naar de mail gaan die we verzonden hebben terwijl de runtime draait, kun je op de link klikken (approve of deny, net wat je wilt). De URL is immers geldig nu. Zo gauw je dat doet, zul je het resultaat in de debug zien verschijnen.

Vervolgen van de workflow
Ok. Laten we de code gaan schrijven die de workflow weer verder helpt.
Als eerste hebben we de instanceId van de workflow nodig. Die moeten we immers een signaal sturen. Die instanceId staat in de database. Om die op te halen moeten we een partitionkey en een rowkey hebben. Die hebben we ook: de partitionkey was hardcoded op AttendeeApprovals en de rowkey krijgen we mee in de call naar deze function. Door middel van de juiste injection in de signature van deze method kunnen we de data dus opvragen:
[FunctionName("ApproveAttendee")]
public static async Task<HttpResponseMessage> ApproveAttendee(
[HttpTrigger(
AuthorizationLevel.Anonymous,
"get",
Route = "processApproval/{rowKey}/{isApproved}")]
HttpRequestMessage req,
[OrchestrationClient] DurableOrchestrationClient client,
[Table(
"Attendees",
"AttendeeApprovals",
"{rowKey}",
Connection = "AzureWebJobsStorage")]
StoredOrchestration storedOrchestration,
string rowKey, bool isApproved,
ILogger log
)
{
De method heeft nu een Table erbij gekregen, maar in tegenstelling tot de activity, geven we hier nogal wat extra data mee. In het attribuut Table hebben we nu de Table name, de partitionkey en de rowkey (die komt uit de route) en de Connection weer. Het is in de vorm van een StoredOrchestration, want zo hadden we hem ook weggeschreven. Dit keer is het geen out-parameter want we krijgen hem nu mee.
Als iemand nu deze function aanroept, zal de runtime de juiste data ophalen en in de storedOrchestration plaatsen. Als er geen data is voor deze rowKey krijg je een NULL value terug. Daar kunnen we op controleren: als iemand maar wat verzint en intypt, hebben we geen bijbehorende entry in de tabel en dus hoeven we er niets mee.
Het enige wat ons nu nog rest is de runtime vertellen dat de flow verder mag en wat de beslissing van het bestuur is geweest. De hele method ziet er nu dus zo uit:
[FunctionName("ApproveAttendee")]
public static async Task<HttpResponseMessage> ApproveAttendee(
[HttpTrigger(
AuthorizationLevel.Anonymous,
"get",
Route = "processApproval/{rowKey}/{isApproved}")]
HttpRequestMessage req,
[OrchestrationClient] DurableOrchestrationClient client,
[Table(
"Attendees",
"AttendeeApprovals",
"{rowKey}",
Connection = "AzureWebJobsStorage")]
StoredOrchestration storedOrchestration,
string rowKey, bool isApproved,
ILogger log
)
{
log.LogWarning($"We received {isApproved} for key {rowKey}.");
if (storedOrchestration == null)
{
log.LogWarning("Received illegal request.");
return req.CreateResponse(HttpStatusCode.NotFound);
}
await client.RaiseEventAsync(storedOrchestration.OrchestrationId, "E_BoardHasApproved", isApproved);
return req.CreateResponse(HttpStatusCode.OK);
}
Dus: we halen de data op, we krijgen de storedOrchestration (of niet…). We controleren die data en als die null is, geven we een mooie 404 terug.
Als de data er wel is, roepen we de workflow runtime aan met de method RaiseEventAsync. Die krijgt de naam die we ook al bij WaitForExternalEventAsync hadden gebruikt: “E_BoardHasApproved” en als payload geven we de waarde true of false mee. Daarna geven we een 202 status code terug.
Als je dit nu runt, zul je zien dat zo gauw we de RaiseEventAsync aanroepen, de orchestrator gaat replayen en weer verder gaat.
En nu?
We zijn er bijna. We moeten alleen nog een tweet versturen. Dat doen we in de volgende post!