Azure Durable Functions, deel 9: errors en robuuste functies

Onze workflow is bijna klaar. We hebben nog een stap te gaan: het al dan niet versturen van de tweet met daarin de tekst “{attendee} komt naar onze bijeenkomst!” Deze tweet is optioneel: niet iedereen wil dat delen met de wereld.

Ik ga niet de code schrijven die daadwerkelijk naar Twitter gaat. Die voorbeelden zijn genoeg te vinden. Echter: Twitter wil wel eens fouten genereren. Je bent over je limiet heen, of Twitter is tijdelijk down. Hoe gaan we daar mee om? Laten we dat eens simuleren in de activity:

Uiteraard hebben we eerst een DTO nodig:

namespace MeetingRegistration
{
    public class TweetData
    {
        public string Name { get; set; }
    }
}

We hadden ook gewoon een string kunnen doorgeven, maar ik hou van expressieve code. Vandaar deze class.

De activity:

[FunctionName("A_SendTweet")]
public static void SendTweet([ActivityTrigger] TweetData tweetData, ILogger log)
{
    log.LogWarning($"About to send a tweet about {tweetData.Name}.");
    if (new Random().NextDouble() > 0.3)
    {
        throw new Exception("Twitter is down!");
    }
    log.LogWarning("Successfully sent out the tweet...");
}
    

Ok, ik geef toe: Twitter is wel wat robuuster dan dat. 1 in de 5 tweets lukken slechts. Maar dit geeft me wel mooi de kans om te laten zien hoe we de boel wat stabieler kunnen krijgen.

De orchestrator zouden we als volgt kunnen maken (na het wachten op het externe event):

var tweetData = new TweetData {Name = attendee.Name};
int maxCount = 5;
bool tweetIsSent = false;
while ((!tweetIsSent) && (maxCount-- > 5))
{
    try
    {
        await ctx.CallActivityAsync("A_SendTweet", tweetData);
        tweetIsSent = true;
    }
    catch (Exception ex)
    {
        log.LogError("Oeps!" + ex.Message);
        // Wait 5 seconds before retrying
        Task.Delay(5000);        
    }
}

if (!tweetIsSent)
{
    log.LogError("Tweet did not get sent after retrying.. too bad!");
}

Zo iets dus. Maar dit is niet echt handig: ten eerste is dit niet deterministisch, dus dit zou eigenlijk in een activity moeten. Daarnaast: het is weer plumbing. En daar willen we zo veel mogelijk vooraf.

Dit is hoe bovenstaande code WEL moet:

var tweetData = new TweetData {Name = attendee.Name};

var retryOptions = new RetryOptions(TimeSpan.FromSeconds(5.0), 5);
try
{
    await ctx.CallActivityWithRetryAsync("A_SendTweet", retryOptions, tweetData);
}
catch (Microsoft.Azure.WebJobs.FunctionFailedException functionFailedException)
{
    log.LogError("Tweet did not get sent after retrying.. too bad!\n" + functionFailedException.Message);
}

We beginnen weer met een TweetData instance. Hierna maken we een instance aan van een RetryOptions class. Deze heeft als constructor parameters de tijd die tussen retries in moet zitten, en een aantal keer dat het opnieuw geprobeerd mag worden. Er zijn nog meer opties, zoals de initiƫle tijd tussen de eerste call en de tweede, of de tijd moet toenemen tussen pogingen door en nog meer van dat soort dingen.

We roepen in een try catch CallActivityWithRetryAsync aan en geven, naast de normale parameters, nu ook de RetryOptions mee. Als er nu iets fout gaat in de activity, wordt deze, na 5 seconden, nog een keer aangeroepen. En dit tot een maximum van 5 keer. Als het dan nog niet gelukt is, krijgen we een FunctionFailedException en daar kunnen we wel wat mee.

Het zal je niet verbazen dat CallSubOrchestratorAsync ook een dergelijke variant heeft: CallSubOrchestratorWithRetryAsync. En die werkt net zo.

Ik raad je aan om deze variant zo veel mogelijk te gebruiken op plekken waar er maar iets fout zou kunnen gaan. Op deze manier is je function veel robuuster geworden.

Oh ja: ik weet dat ik NIET controleer of de parameter wantsTweet wel op true staat… Maar dat soort code laat ik graag aan jou over. Het vervuilt mijn voorbeelden zo…

En nu?

Onze workflow is klaar! We kunnen aan het werk en we kunnen inschrijvingen verwachten op onze events. Maar wij als ontwikkelaars zijn nog niet klaar (zijn we dat ooit?).

Want: onze approvalAttendee database loopt lekker vol met data die we niet meer nodig hebben. Maak je geen zorgen: dat gaan we in de volgende post oplossen!

Gepubliceerd door Dennis Vroegop

Passionate Azure Cloud Solutions Architect. I am a enthusiastic guitar player (though not as good at it as I'd like to be) and in my daytime job I teach software developers to be better at their job. Married to my wonderful wife Diana with whom I try to raise our daughter Emma.

One thought on “Azure Durable Functions, deel 9: errors en robuuste functies

Geef een reactie

Vul je gegevens in of klik op een icoon om in te loggen.

WordPress.com logo

Je reageert onder je WordPress.com account. Log uit /  Bijwerken )

Google photo

Je reageert onder je Google account. Log uit /  Bijwerken )

Twitter-afbeelding

Je reageert onder je Twitter account. Log uit /  Bijwerken )

Facebook foto

Je reageert onder je Facebook account. Log uit /  Bijwerken )

Verbinden met %s

Deze site gebruikt Akismet om spam te bestrijden. Ontdek hoe de data van je reactie verwerkt wordt.

%d bloggers liken dit: