MS Certificering: AZ-204, deel 8

Ingediend door Dirk Hornstra op 04-dec-2021 23:36

Ik ben begonnen met de voorbereidingen die Microsoft Azure zelf aanbiedt voor AZ-204: link, even naar beneden scrollen en je ziet de 4 blokken. Ik ben begonnen met het derde blok, omdat dit over Virtuele Machines gaat en deze als eerste item in de PDF van Microsoft met de specs voor deze certificering genoemd staat: link, "Implement IaaS solutions: provision virtual machines".

Hoewel het 1 blok is (en ik er 10 november tot 17 november mee bezig ben geweest: link) denk je: dan heb je al heel wat gedaan. Nou nee, want als je de PDF bekijkt, is dit maar 1 vinkje van de 45 items die je moet beheersen. Ik ga op 17 november (mijn vrije woensdag) daarom door met het eerste blok: Create Serverless Applications: link.

Dit deel bestaat uit 11 modules, ik begin met 16150 ervaringspunten. We zullen zien waar ik mee eindig.

We beginnen met het kiezen voor de juiste Azure service voor wat je nodig hebt. We beginnen met een voorbeeld-case. Je hebt een fietsverhuursysteem op een universiteit en mag dat nu ook inzetten op een andere universiteit. Waarbij daar al een eigen systeem draait wat je moet combineren met jouw systeem. Kosten mogen niet te hoog zijn, het is juni en eind augustus moet het klaar zijn.

Bepaalde processen (fiets uitlenen, weer innemen, controle, reparaties uitvoeren) worden onder de noemer "workflows" gehangen. In Azure zijn daar een 4-tal tools voor:

  • Logic Apps
  • Microsoft Power Automate
  • Webjobs
  • Azure Functions


Al deze diensten hebben overeenkomende eigenschappen. Ze kunnen input-data aangeboden krijgen. Ze kunnen acties uitvoeren. Er kunnen voorwaarden ingesteld worden. En ze kunnen allemaal output genereren.

Als je kiest voor de design-first aanpak, dus je gaat eerst zaken uittekenen, dan kom je al gauw uit bij Logic Apps. Je kunt hier visueel je flow uitwerken. Achter de schermen wordt dat met JSON opgebouwd, dus je kunt het ook in JSON-formaat uitwerken/aanpassen. Logic Apps is een populaire tool voor integraties, vooral omdat er meer dan 200 connectors zijn die je kunt gebruiken: link. Dus als je niet wilt integreren met IFTTT zou dit een goed alternatief zijn. Zo heb je een integratie met Twitter, met Office365 Outlook connector of je maakt je eigen, zolang je maar een REST API aan kunt bieden: link.

Zonder de gebruiker(s) tekort te doen, Power Automate is er voor de noobs. Je hebt geen IT-kennis nodig om de losse onderdelen aan elkaar te koppelen. Je hebt vier verschillende flows die je kunt bouwen:

  • automated: door een trigger aan een event te koppelen wordt de actie gestart (er wordt ergens een document geüpload, vervolgens wordt dit proces gestart).
  • button: een herhalende taak die je start door op een knop in je mobiele app te klikken: link.
  • scheduled: de taak wordt op vaste intervallen/tijdstippen gestart.
  • business proces: modellering van een businessproces, zoals het voorraadproces of het klachtenproces.


Onder de motorkap draait bij Power Automate de Logic Apps-tool. Dus alle connector en acties die daar beschikbaar zijn heb je hier ook. En je kunt met Power Automate je eigen connectors maken: link.

Als developers hier niet voldoende aan hebben, ze willen/moeten eigen code maken om meer grip op de performance te krijgen of omdat er eigen code in het businessproces uitgevoerd moet worden, daarvoor zijn Webjobs en Azure Functions beschikbaar.

Webjobs maken onderdeel uit van Azure App Service. Op Azure App Service kun je een website, REST API, mobiele back-end draaien. Webjobs zijn scripts die uitgevoerd moeten worden, bijvoorbeeld om een geüploade afbeelding te verkleinen naar een thumbnail-formaat. Je hebt 2 types:

  • continuous: je controleert continu of er in een map een nieuw bestand geüpload is.
  • triggered: door een event of handmatige actie wordt het script gestart.


Er zijn verschillende programmeertalen beschikbaar, shell-script (Windows, bash, powershell), PHP, Python, node.js, javascript. Met SDK 2.x kun je ASP.NET gebruiken, met SDK 3.x inmiddels ook .NET Core.

Als je "snel" code wilt uitvoeren heb je Azure Functions. Ook hier geldt dat er meerdere programmeertalen zijn, javascript, C#, java, powershell, Python. In dit document zie je bij welke versie je welke talen nog meer beschikbaar hebt (ik zie F#, Go/Rust): link.

Je kunt de code via het Azure Portal maken, maar als je voor versiebeheer gaat, kan dit ook via Github of Azure DevOps Services.

Je kunt uit een hoop sjablonen kiezen. De site geeft de voorbeelden van een HTTPTrigger, TimerTrigger, BlobTrigger, CosmosDBTrigger.

Omdat Azure Functions redelijk simpel zijn, wordt vaak voor deze oplossing gekozen. Er zijn echter een paar punten om naar Webjobs te kijken:

  • als je wilt dat de code onderdeel uitmaakt van een reeds bestaande Azure App Service en ook daar beheerd kan worden
  • het JobHost object is het object wat luistert naar de events en getriggerd wordt. Als je daar meer controle over wilt hebben zul je moeten kiezen voor Webjobs.


De verschillen en overeenkomsten worden nog even naast elkaar gezet:

  Azure WebJobs Azure Functions
Supported languages C# if you are using the WebJobs SDK C#, Java, JavaScript, PowerShell, and so on
Automatic scaling No Yes
Development and testing in a browser No Yes
Pay-per-use pricing No Yes
Integration with Logic Apps No Yes
Package managers NuGet if you are using the WebJobs SDK NuGet and NPM
Can be part of an App Service application Yes Yes (hosted under App Service plan)
Provides close control of JobHost Yes No


We gaan door met Azure Functions, het voorbeeld van het boek is dat je bij een leverancier van liften werkt. Daarin zitten sensoren die temperatuurgegevens doorsturen en op basis daarvan wordt bepaald of onderhoud gepleegd moet worden. Dat aanleveren gebeurt op veel verschillende manieren, via uploads, event-hubs, gegevens uit databases opvragen.

Eerst wordt uitgelegd wat serverless is: link. Op basis van "Function as a Service" of microservices worden acties uitgevoerd, waarbij de cloud-provider voor de infrastructuur zorgt en jij je daar dus niet druk over hoeft te maken. Ook voor Azure Functions geldt dat je met verschillende programmeertalen aan de slag kunt, F#, javascript, C#, python, powershell core. Je kunt gebruik maken van Nuget en NPM, dus populaire code-bibliotheken kunnen ook gebruikt worden.

Je betaalt voor gebruik, je hoeft geen server te onderhouden en kunt de code/business logic schrijven in de taal die je het beste past.

Functies zonder "state" zijn het makkelijkst hiervoor in te zetten. Heeft je code dat wel nodig, dan kan gebruik gemaakt worden van een storage service.

Toch zijn er wel een aantal beperkingen. Zo is de time-out standaard 5 minuten. Je kunt deze aanpassen, maar 10 minuten is wel het maximum. Als jouw functie langer dan 10 minuten nodig heeft, dan zul je deze moeten hosten in een VM. Daar naast heb je dat als de trigger binnen komt als een HTTP request en het antwoord moet als een HTTP response teruggegeven worden, dan heb je een maximale time-out van 2.5 minuut. Je kunt "hier omheen patchen" met behulp van Durable Functions, waardoor er geen time-outs optreden: link.

En je moet kijken naar de frequentie. Wordt je functie continu door meerdere clients aangeroepen, dan worden kosten per gebruik waarschijnlijk hoog en is het gebruik van een VM aan te raden. Zo kunnen er een nieuwe functie app aangemaakt worden, maar dat kan maar 1x in de 10 seconden. En tot maximaal 200 stuks. Let wel, 1 functie app kan meerdere clients serveren, dus je zit niet aan een "limiet" van 200 requests.

We gaan vervolgens een Function App in de portal aanmaken. Je kunt kiezen of je in Javascript of Powershell wilt werken, ik kies voor Javascript.

Er zijn 2 soorten service-plans:

  • Consumption plan: dit is de standaard die je voor Azure Functions gebruikt.
  • Azure App Service Plan: door een VM te gebruiken wordt voorkomen dat time-outs optreden. Dit is eigenlijk niet echt "serverless", maar kan een oplossing zijn voor je time-out problemen.


Als je een Function App maakt moet deze aan een Storage Account gekoppeld worden (bestaand of nieuw aan te maken). De app gebruikt dit om te loggen en om execution triggers te beheren. Als je voor de Consumption plan gekozen hebt is dit ook de plek waar code en configuratiebestanden opgeslagen worden.

Een trigger zorgt dat een functie gestart wordt. Een trigger is afkomstig uit een bepaalde service, deze services worden ondersteund:

Service Beschrijving van de trigger
Blob Storage Start een functie wanneer een nieuwe of bijgewerkte blob wordt gedetecteerd.
Azure Cosmos DB Een functie starten wanneer invoegingen en updates worden gedetecteerd.
Event Grid Start een functie wanneer een gebeurtenis wordt ontvangen van Event Grid.
HTTP Start een functie met een HTTP-aanvraag.
Microsoft Graph-gebeurtenissen Start een functie als reactie op een binnenkomende webhook van Microsoft Graph. Elke instantie van deze trigger kan reageren op één Microsoft Graph-resourcetype.
Queue Storage Start een functie wanneer een nieuw item in een wachtrij wordt ontvangen. Het wachtrijbericht wordt geleverd als invoer voor de functie.
Service Bus Start een functie als reactie op berichten uit een Service Bus-wachtrij.
Timer Start een functie volgens een schema.

Voor input en output kun je bindingen gebruiken. Hiervoor hoef je zelf geen code te schrijven, maar kun  je gebruiken wat Azure zelf al levert. Hier kun je een overzicht vinden: link.

Als je aan het ontwikkelen bent en zaken wilt testen, dan kun je zaken "loggen" naar het Streaminglogboek. De syntax is:


// javascript:
context.log('Enter your logging statement here');
// C#:
log.LogInformation("Enter your logging statement here");
// Powershell:
Write-Host "Enter your logging statement here"

Bij het aanmaken van een functie geef je aan of je deze uitvoert als Function, Admin of Anonymous. Bij Admin gebruik je een globale "master"-key, met anonymous kan iedereen erbij en via function ga je in de portal bij de functie naar het onderdeel Ontwikkelaar - Functiesleutels om de te gebruiken sleutel op te vragen. Deze geef je in de header mee met de eigenschap "x-functions-key:".

Dit hele blok bestaat uit 11 onderdelen. De eerste 2 hebben we gehad, we gaan door met Azure Functions en hoe we die met triggers gaan starten: link.

Het scenario is van een kapsalon, de afspraken zitten in time-slots. Maar als een klant de afspraak vergeet, te laat komt, dat kost geld. Dus jij, de developer, mag een oplossing bedenken. Als iemand een afspraak maakt of verandert krijg hij/zij een berichtje en op de ochtend van de afspraak wordt er een bericht verstuurd.

We beginnen met een Timer-trigger. Hiervoor heb je een Timestamp parameter name nodig, dat is een identifier om de timer via code te kunnen bereiken. En een Schedule, het schema met tijdstippen waarop een timer getriggerd moet worden (CRON-schema). We zien hoe je een stukje C# elke 20 seconden uit laat voeren.

Hierna gaan we door met de Http-trigger. Hier heb je de al eerder genoemde type autorisatie, function, admin, anonymous. In het voorbeeld gaan we voor "anonymous" en via code-en-test krijgen we een rechstreekse URL die aan te roepen is. Goed voor test, niet te gebruiken bij een productie-omgeving.

De volgende casus is iets uitgebreider. Het gaat om een fotograaf die een foto upload naar zijn blob-storage en dan automatisch een tweet de wereld in wil sturen. We krijgen eerst de uitleg wat Azure Storage is, opslag van data die aan deze eisen voldoet:

  • Highly available
  • Secure
  • Scalable
  • Managed


Blob-storage wordt vaak gebruikt voor "ongestructureerde data", dus documenten, afbeeldingen. Voornamelijk gebruikt voor:

  • Storing files
  • Serving files
  • Streaming video and audio
  • Logging data


Als je een blob-trigger maakt, dan geef je daarbij een pad aan. Als dat "samples-workitems/{name}" is, dan gaat dit om blob-container "samples-workitems" en pak je alle bestanden. Wil je alleen de afbeeldingen doen, dan filter je op {name}.png. Omdat je deze parameter hier {name} noemt zal je Azure Function deze als variabele met de naam "name" binnen krijgen. Vervolgens maken we de blob met naam "samples-workitems" aan en uploaden een .PNG bestand, in de code-en-test, log-tabblad zien we inderdaad de melding dat het bestand geüpload is en daardoor de trigger "getriggerd" is.

Dat deel was niet moeilijk, we gaan nu door met het aan elkaar knopen van Azure Functions met input- en output-bindings: link.

Met behulp van bindings kun je connectie maken met data-streams en hoef je zelf geen code te maken (zoals je dat anders met een database of web API zou doen). Je hebt 2 types, input binding: dat is de source van je data en output binding: dat is de bestemming van je data. Ook zijn er triggers die kunnen dienen als input binding, zoals de Event Grid notificatie kan als trigger ingesteld worden. De lijst is uitgebreid, maar hier zijn een aantal veel voorkomende bindings:

  • Blob Storage
  • Azure Service Bus Queues
  • Azure Cosmos DB
  • Azure Event Hubs
  • External files
  • External tables
  • HTTP endpoints


Een binding heeft 3 parameters. De name is de naam van de variabele zoals die in de functie gebruikt wordt, type: het soort service waar we mee verbinden en direction, hiermee geef je aan of het inkomend of uitgaand is. Veel bindings hebben nog een vierde parameter, de connection, waarmee je aangeeft waarmee de verbinding gemaakt wordt (bv. connectiestring met de database). In het bestand function.json worden deze gegevens opgeslagen.

Een overzicht met bindingen kun je hier vinden: link. Bindingexpressie is een speciale tekst in function.json, functieparameters of code. De eerder genoemde {name}. Een aantal types zijn:

  • App-instellingen
  • Bestandsnaam activeren
  • Trigger-metagegevens
  • JSON-nettoladingen
  • Nieuwe GUID
  • Huidige datum en tijd


Ale het om app-instellingen gaat zet je de naam tussen procent-tekens, zoals bijvoorbeeld "%Environment%/newblob.txt".

In de oefening maken we een Cosmos DB aan. Vervolgens maken we daarbinnen een container. De naam van de database en de naam van de container moet uit tekst bestaan, maximaal 255 tekens. Bij het toevoegen van een item zie je dat er ook systeemvelden toegevoegd worden:

Property Description
_rid Resource ID is a unique identifier that is also hierarchical per the resource stack on the resource model. It is used internally for placement and navigation of the item resource.
_self Unique addressable URI for the resource.
_etag Required for optimistic concurrency control.
_attachments Addressable path for the attachments resource.
_ts Timestamp of the last update of this resource.


We maken een nieuwe Azure Function aan (HTTPTrigger) en voegen een input-source toe, daar kiezen we Azure Cosmos DB.

Om de code werkend te krijgen, plaatsen we in index.js deze code:

module.exports = function (context, req) {

    var bookmark = context.bindings.bookmark

    if(bookmark){
        context.res = {
        body: { "url": bookmark.url },
        headers: {
        'Content-Type': 'application/json'
            }
        };
    }
    else {

    context.res = {
        status: 404,
        body : "No bookmarks found",
        headers: {
        'Content-Type': 'application/json'
        }
    };
    }

    context.done();
 };

En in functions.js deze code:


{
  "bindings": [
    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    },
    {
      "name": "bookmark",
      "direction": "in",
      "type": "cosmosDB",
      "connectionStringSetting": "your-database_DOCUMENTDB",
      "databaseName": "func-io-learn-db",
      "collectionName": "Bookmarks",
      "id": "{id}",
      "partitionKey": "{id}"
    }
  ]
}

Dit werkte bij mij niet. En maar de boel aanpassen. Testen, aanroepen doen, steeds een 404 terug krijgen. En dan uiteindelijk zie je waar het door komt: de automatische vertaling in Azure! Bij het aanmaken van je container in Cosmos DB is je container-ID "Bladwijzers". In de Azure Functions, bij het toevoegen van de invoer-binding is de collectionName "Bookmarks". Nee dus! Dat moet ook Bladwijzers zijn. Nadat ik dit aangepast hebt werkt het. Maar goed, hiermee ben ik nu wel (dieper) in Functions en Cosmos DB gedoken.

En door met uitvoer-bindingen, we schrijven data weg naar een storage-account met een wachtrij. Niet heel ingewikkeld.

Tijd voor het volgende blok, long-running serverless workflow with Durable Functions: link.

Als je langdurige functies met "state" nodig hebt, dan maak je hiervan gebruik. Met deze aanpak maak je gebruik van de voordelen van serverless functions: alleen betalen voor gebruik en laat je Azure de andere zaken afhandelen, zoals monitoring, synchronisatie en runtime zaken.

We gaan het voorbeeld van het boek volgen, een organisatie krijgt voorstellen binnen voor projectplannen. Deze moeten beoordeeld worden, er zit dus een menselijke component in de flow. Omdat deze uit meerdere stappen bestaat, er eigen business logic in zit is het (te) kostbaar om zelf een systeem hiervoor te bouwen.

Durable Functions is een uitbreiding op Azure Functions. Het inrichten gebeurt op basis van een orchestration function. Hiermee bouw je in code de flow en hoef je geen workflow ontwerp tool aan te schaffen of zelf zaken in JSON uit te typen.

Je hebt 3 type functies:

Client function: deze functies reageren op een trigger (is het entry point), orchestrator function: hiervoor al benoemd en activity function: "de unit of work", dus de echte code die uitgevoerd moet worden.

Er zijn een aantal patronen die gevolgd worden voor het bouwen van een durable function:

  • Function chaining: allemaal losse functies die achter elkaar zitten en zo tot een resultaat leiden;
  • Fan out/fan in: na de eerste functie gaan er een aantal processen parallel acties uitvoeren, het gecombineerde resultaat is de output;
  • Async HTTP API's: structuur voor langdurige acties bij externe partijen. Door te pollen kan de status opgevraagd worden, als de actie afgerond is wordt je naar het resultaat geleid;
  • Monitor: het herhalend controleren of iets aan bepaalde voorwaarden voldoet;
  • Menselijke interactie: in de flow moet bijvoorbeeld iemand iets goedkeuren of afkeuren. Omdat de menselijke factor niet altijd beschikbaar is (of reageert) kan gewerkt worden met time-outs en "compensation logic" om dat af te vangen;


Met Logic Apps kun je ook een soort flow instellen, maar dat doe je op basis van configuratiebestanden/visuele weergave. En zo zijn er nog een aantal verschillen:

Task Azure Durable Functions Azure Logic Apps
Development Code-first (imperative) Design-first (declarative)
Connectivity About a dozen built-in binding types. You can write code for custom bindings. Large collection of connectors. Enterprise Integration Pack for B2B. You can also build custom connectors.
Actions Each activity is an Azure Function. You write the code for activity functions. Large collection of ready-made actions. You integrate custom logic through custom connectors.
Monitoring Azure Application Insights Azure portal, Azure Monitor logs
Management REST API, PowerShell, Visual Studio Azure portal, REST API, PowerShell, Visual Studio, Visual Studio Code extension


We gaan de voorbeeld-case uitwerken. Dus een nieuwe Function App. Daarna de npm install van durable functions:


npm install durable-functions

Wachten tot de installatie klaar is en dan via Overview - Restart een herstart van de omgeving.
Let op, in het voorbeeld wordt van node.js aangegeven dat je versie 12 LTS moet installeren, maar je moet kiezen voor 14 LTS (anders krijg je een melding over zaken updaten, omdat een Math.Random() in een oude versie niet altijd het gewenste resultaat geeft. Tenminste, gaat nog niet goed, want ik krijg in het rood te zien "kan opdracht niet uitvoeren". Dus eerst maar even een herstart gedaan om te kijken of het dan beter gaat. npm -v laat me namelijk ook al niets zien. Na de herstart duurt het even, maar dan krijg ik wel netjes in het groen 6.14.11, dus de node versie kan ik opvragen. Wederom de install van durable-functions aangeroepen. Ah, dat is de fout. npm moet geüpgrade worden naar versie 7 of hoger. Onder Configuratie, Algemene Instellingen kan ik Node.js die op 14 staat bijwerken naar 16, dat uitgevoerd. En dan geeft mijn npm -v een versie 7.21.1. Vervolgens een foutmelding dat er nog een oude lock-file is, die met een rm package-lock.json verwijderd en rm -R node_modules ook de npm packages verwijderd. Maar nog steeds een foutmelding "Kan de opdracht niet uitvoeren.". Na reboot en nogmaals install krijg ik de melding dat ik npm install -g npm@8.1.4 moet uitvoeren. Nadat ik dat gedaan heb en die install van durable-functions krijg ik de melding dat alles "up-to-date" is. Dan gaan we door met de opdracht, kijken of het werkt.

We maken een Durable Functions HTTP Starter aan. De index.js ziet er zo uit:

const df = require("durable-functions");

module.exports = async function (context, req) {
    const client = df.getClient(context);
    const instanceId = await client.startNew(req.params.functionName, undefined, req.body);

    context.log(`Started orchestration with ID = '${instanceId}'.`);

    return client.createCheckStatusResponse(context.bindingData.req, instanceId);
};

Hierna maken we een nieuwe functie aan op basis van Durable Functions Orchestrator. Hier moeten we de code aanpassen voor onze case van goedkeuren en afkeuren:

const df = require("durable-functions");

module.exports = df.orchestrator(function* (context) {
    const outputs = [];

    /*
    * We will call the approval activity with a reject and an approved to simulate both
    */

    outputs.push(yield context.df.callActivity("Approval", "Approved"));
    outputs.push(yield context.df.callActivity("Approval", "Rejected"));

    return outputs;
});

We maken nog een functie aan, de functie die wat gaat doen, dus op basis van een Durable Functions Activity. Hierbij vervangen we de code met:


module.exports = async function (context) {
    return `Your project design proposal has been -  ${context.bindings.name}!`;
};

Bij de HTTP Starter kun je de functie URL opvragen. Die bevat: .../api/orchestrators/{functionName}?code=...
De {functionName} vervang je met de naam van je orchestration-functie, je krijgt dan een lijst met URL's terug. Als je de URL die gekoppeld zit aan "statusQueryGetUri" opent kun je zien wat het resultaat is.

We gaan door met Durable Functions en Timers. Met deze code stuur je 10 dagen een herinnering:

const df = require("durable-functions");
const moment = require("moment");

module.exports = df.orchestrator(function*(context) {
    for (let i = 0; i < 10; i++) {
        const deadline = moment.utc(context.df.currentUtcDateTime).add(i, 'd');
        yield context.df.createTimer(deadline.toDate());
        yield context.df.callActivity("SendReminder");
    }
});

En met deze code kijk je of een functie je resultaat terug gegeven heeft of dat er een time-out opgetreden is:

const df = require("durable-functions");
const moment = require("moment");

module.exports = df.orchestrator(function*(context) {
    const deadline = moment.utc(context.df.currentUtcDateTime).add(30, "s");

    const activityTask = context.df.callActivity("GetQuote");
    const timeoutTask = context.df.createTimer(deadline.toDate());

    const winner = yield context.df.Task.any([activityTask, timeoutTask]);
    if (winner === activityTask) {
        // success case
        timeoutTask.cancel();
        return true;
    }
    else
    {
        // timeout case
        return false;
    }
});

We gaan vervolgens door met een voorbeeld-case. Met een npm install typescript en npm install moment kunnen we dit uitvoeren.
We maken nog een Durable Functions Activity aan en vullen de index.js met deze inhoud:


module.exports = async function (context) {
    return `ESCALATION : You have not approved the project design proposal - reassigning to your Manager!  ${context.bindings.name}!`;
};

We zien inderdaad dat na de 20 seconden time-out de eindstatus "escalation" geworden is. Hiemee is deze module afgerond.

We gaan door met het volgende blok, develop, test, and publish Azure Functions by using Azure Functions Core Tools: link.

Met de Azure Functions Core Tools kun je lokaal (vanaf de command-line) de functies maken en testen en vervolgens naar de Azure-omgeving overzetten. Je kunt dit uitvoeren met het commando func. Een "function project" wat je lokaal hebt is vergelijkbaar met een "function app" in de Azure omgeving. Met het commando func init zet je een nieuw project op. Meestal heb je daarin de bestanden host.json, waarin runtime informatie opgenomen staat en local.settings.json voor je lokale configuratie.

Met func new kun je een functie toevoegen. Met func start kun je een host opstarten, zodat je de URL's in je browser kunt openen en je lokale code kunt testen. Het voorbeeld van Azure voeren we uit in de CLI. Daar starten we de host, maar we kunnen alleen "lokaal" bij die URL, het func start proces moet in de achtergrond uitgevoerd worden. Dat kan met dit statement:


func start &> ~/output.txt &

-- met cat ~/output.txt kun je nog even kijken wat de URL is die je aan gaat roepen

curl "http://localhost:7071/api/simple-interest?principal=5000&rate=.035&term=36" -w "\n"

-- vervolgens achtergrondproces afsluiten:

pkill func

Het volgende deel gaat over het publiceren naar Azure. Met func azure functionapp publish <app_name> voer je dat uit. Let op, app_name is de naam die de functie in Azure krijgt! We krijgen de commando's aangeleverd, dit voer je uit:

RESOURCEGROUP="learn-12a2f1ea-2453-4065-966c-1fd500de03cf"
STORAGEACCT=learnstorage$(openssl rand -hex 5)
FUNCTIONAPP=learnfunctions$(openssl rand -hex 5)

az storage account create \
  --resource-group "$RESOURCEGROUP" \
  --name "$STORAGEACCT" \
  --kind StorageV2 \
  --location centralus

az functionapp create \
  --resource-group "$RESOURCEGROUP" \
  --name "$FUNCTIONAPP" \
  --storage-account "$STORAGEACCT" \
  --runtime node \
  --consumption-plan-location centralus \
  --functions-version 3

cd ~/loan-wizard
func azure functionapp publish "$FUNCTIONAPP"

Ik krijg een foutmelding bij de publish, iets met een versie 2 met wat niet versie 2 is. Met func azure functionapp publish "$FUNCTIONAPP" --force is het gefixt.

Als ik mijn werk wil valideren krijg ik een foutmelding. Mogelijk komt het doordat de code de output controleert en die is 6300.0000001 en niet zoals genoemd 6300.
Quickfix in de code doorgevoerd: context.res = { body: parseInt(principal * rate * term) };

Yes, dan krijg ik wel mijn punten!

Tijd voor het volgende blok, Develop, test, and deploy an Azure Function with Visual Studio: link.

We maken in Visual Studio een Azure Function en draaien die lokaal. De site laat zien dat vanaf meerdere plaatsen naar Azure gedeployed kan worden. Vanuit Visual Studio. Maar ook met een az functionap deployment source config-zip -g <resource-group> -n <function-app-name> --src <zip-file> kun je met een ZIP-bestand je deployment uitvoeren.

Hierna maken we in de portal een Azure Function App aan en deployen we de code vanuit Visual Studio naar deze plek. Maar VS geeft de foutmelding dat dit abonnement dit niet ondersteunt, dus dat kan ik niet testen. Nadat ik wat anders ben gaan doen, herstart van de pc lukt het me wel. Door met het toevoegen van de volgende opdracht, een nieuw x-unit testproject toevoegen. We krijgen een aantal code-snippets waarmee we kunnen testen, dat gaat goed.

Even 1 van de functies, op zich niet heel spannend, maar wel is de DefaultHttpRequest met DefaultHttpContext interessant, ik kende die niet, maar dat is natuurlijk supermakkelijk als je dingen wilt testen waarin de code zelf "hard" erop vertrouwt dat de code in een web-omgeving draait;

        public void TestWatchFunctionSuccess()
        {
            var queryStringValue = "abc";
            var request = new DefaultHttpRequest(new DefaultHttpContext())
            {
                Query = new QueryCollection
                (
                    new System.Collections.Generic.Dictionary<string, StringValues>()
                    {
                        { "model", queryStringValue }
                    }
                )
            };

            var logger = NullLoggerFactory.Instance.CreateLogger("Null Logger");

            var response = WatchPortalFunction.WatchInfo.Run(request, logger);
            response.Wait();

            // Check that the response is an "OK" response
            Assert.IsAssignableFrom<OkObjectResult>(response.Result);

            // Check that the contents of the response are the expected contents
            var result = (OkObjectResult)response.Result;
            dynamic watchinfo = new { Manufacturer = "abc", CaseType = "Solid", Bezel = "Titanium", Dial = "Roman", CaseFinish = "Silver", Jewels = 15 };
            string watchInfo = $"Watch Details: {watchinfo.Manufacturer}, {watchinfo.CaseType}, {watchinfo.Bezel}, {watchinfo.Dial}, {watchinfo.CaseFinish}, {watchinfo.Jewels}";
            Assert.Equal(watchInfo, result.Value);
        }

Dit was niet heel spannend, door met het volgende blok, Monitor GitHub events by using a webhook with Azure Functions: link.

De voorbeeld-case is dat het management wil weten hoe vaak de WIKI bijgewerkt wordt en wie de "contributors" zijn. In dit bedrijf wordt de Github Wiki gebruikt. We beginnen weer met een Function App te maken, weer op basis van een HTTP trigger. Ik heb dit nu zo vaak gedaan, dat ik het eindelijk begin te leren :)

Hierna wordt uitgelegd hoe je in Github een webhook toevoegt (Settings, Webhooks, Add Webhook). Webhooks kunnen hun data op 2 manieren aangeleverd krijgen:

  • application/json, hierbij zit de JSON in de body van de content;
  • application/x-www-form-urlencoded, hierbij zit de JSON in de parameter met de naam payload;


Alle webhook events en de payload die Github gebruikt kun je hier nalezen: link. Het gaat in dit geval om het "Gollum" event (ik dacht dat die meer met ringen had, dan met wiki's), maar als je een WIKI host op Github via een eigen repo, kun je de wijzigingen daarmee monitoren, zelfde url, maar directe link naar dit item: link.

In de webhook - Recent Deliveries kun je zien wat er gepost wordt (en je Azure Function daarop aanpassen). We gaan vervolgens nog kijken naar het afschermen van je webhook, dat kan met een geheim (secret). Deze wordt meegegeven in de header, is gebaseerd op SHA1, de key hiervan is x-hub-signature.

We voegen daarvoor in index.js eerst de crypto-bibliotheek toe:

const Crypto = require('crypto');

Kijk vervolgens bij je keys en kopieer de eerste. Pas vervolgens de code aan:

const hmac = Crypto.createHmac("sha1", "<default key>");
const signature = hmac.update(JSON.stringify(req.body)).digest('hex');
const shaSignature = `sha1=${signature}`;
const gitHubSignature = req.headers['x-hub-signature'];
...
if (!shaSignature.localeCompare(gitHubSignature)) {
        if (req.body.pages[0].title) {
            context.res = {
                body: "Page is " + req.body.pages[0].title + ", Action is " + req.body.pages[0].action 
+ ", Event Type is " + req.headers['x-github-event']
            };
        }
        else {
            context.res = {
                status: 400,
                body: ("Invalid payload for Wiki event")
            }
        }
    }
    else {
        context.res = {
            status: 401,
            body: "Signatures don't match"
        };
    }

Ik vind de syntax vreemd, omdat je met een !shaSignature lijkt te zeggen "wanneer NIET voldoet aan...", maar het blijkt dat dit aan de functie localCompare ligt: link. Als namelijk 2 objecten gelijk zijn, geeft deze functie een 0 terug, wat gelijk staat aan false. Verwarrend!

Door met het volgende blok, Enable automatic updates in a web application using Azure Functions and SignalR Service: link.

Een mooie case, we gaan een website opzetten die aandelenkoersen toont. De data moet alleen bijgewerkt worden als er echt wat met de koers gebeurt.

We zien hoe een stagiair de eerste opzet gemaakt heeft en de data via pollen opgehaald wordt, elke 5 seconden wordt gekeken of er aangepaste koersen zijn. Ook zien we nog een stukje CORS, waarmee je van het ene domein een aanroep naar een ander domein kan/mag doen;


"Host" : {
    "LocalHttpPort": 7071,
    "CORS": "http://localhost:8080",
    "CORSCredentials": true
  }

Hierna gaan we wat acties op de eigen pc doen en daar hebben we de volgende tools voor nodig:


En dit GIT-project wordt als basis gebruikt: link.

Een aantal statements om de Cosmos DB aan te maken, daarna gaan we met npm install en daarna npm start de boel uitvoeren. Tenminste, ik krijg een foutmelding dat de lite-server niet gevonden kan worden. Een StackOverflow post geeft aan dat je dit moet uitvoeren:

npm install lite-server@2.3 --save-dev

Dat werkt nog niet, maar als ik in package.json de "lite-server": "^2.6.1" toe voeg aan dependencies (staat alleen in devDependencies) dan werkt het wel. Wel lijkt het alsof ik dit in Visual Studio Code lokaal op mijn laptop uit zou moeten voeren en niet via de CLI.

Daarna wordt SignalR genoemd. Een stabiel pakket wat zorgt voor een "vaste verbinding" met de browser (en niet zoals HTTP stateless). Op basis van mogelijkheden worden connecties gemaakt, als de browser HTML5 kan, dan gaat het via de WebSockets API. Als dat niet lukt, valt het protocol terug op Server Sent Events (EventSource). Als dat niet lukt, gaan we over naar AJAX en als dat al niet lukt, dan wordt het Forever Frame (en dat werkt alleen in Internet Explorer).

In het volgende deel maken we een SignalR account aan. Hierna voeren we een update door, om met Azure Functions te werken moet de mode op "serverless" staan. Dit gaat niet optimaal, naast het feit dat localhost een HSTS op https heeft staan, wil de connectie met de poort 7071 ook niet lukken. Nou ja, dan gaat het maar om de theorie.

Door met het maken van een statische website. We vragen in de Cloud Shell eerst de tenant op:


az account list --query "[?name=='Concierge Subscription'].tenantId" -o tsv

Deze code gaan we toevoegen in Visual Studio Code. Via File, Preferences, Settings, Extensions, Azure Configuration.

Ik weet niet hoe het zit, maar mijn pc is traag, het uploaden duurt en duurt maar, deze oefening ronden we af.

Door met het volgende blok, Expose multiple Azure Function apps as a consistent API by using Azure API Management: link.

Door te combineren met Azure Functions en Azure API Management kun je een complete microservice architectuur opzetten. Het voorbeeld wat we hier gaan behandelen is een online winkel die met behulp van Azure Functies met business-logic opgebouwd is. Het plan is om de shop voor externe partners beschikbaar te maken.

Azure API Management (APIM) is een beheerbare cloud-dienst waar je API's kunt publiceren, beveiligen, transformeren, beheren en monitoren. Bij de keuze voor de Tier kun je kiezen voor de Consumption Tier.

Bij het voorbeeld wordt gebruik gemaakt van dit project wat je op Github kunt vinden: link. Er worden 2 Functions gemaakt, een OrderDetail en een ProductDetail.

We gaan naar de functies en kijken hoe deze aangeroepen kunnen worden. Vervolgens gaan we bij de Function App naar het onderdeel API en dan naar API Management. We voegen hier een item toe. Hiermee krijg je een eigen URL: xxxx -apim.azure-api.net Kies vervolgens voor Export. Als dat afgerond is klik je op de knop "Link API".

Je kunt hier je Azure Function koppelen. Je krijgt een aantal velden die je op de standaard kunt laten staan, bij de API URL suffix moet je wel even een goede naam geven (in dit geval products), zodat je API nu bereikbaar is op xxxx-apim.azure-api.net/products
In het volgende scherm staat een test-tab en als we ook hier de aanroep naar deze url met ?id=3 uitvoeren krijgen we de productdetails terug.

Microservices zijn handig voor een "agile werkwijze", teams kunnen zelfstandig met hun deel aan de slag, onderdelen kunnen snel bijgewerkt worden. Maar er kleven ook een aantal nadelen aan:

  • Client apps zijn gekoppeld aan de microservices. Als je de locatie of definitie aan wilt passen kan dat zorgen dat de configuratie van de client bijgewerkt moet worden of dat de client in zijn geheel bijgewerkt moet worden.
  • Elke microservice kan op een eigen domein/IP-adres draaien. Als je 5 microservices hebt en ze hebben allemaal andere naamgeving, kan een opdrachtgever denken "dat is een houtje-touwtje oplossing die daar gebouwd is".
  • Standaarden aanhouden is soms moeilijk. Heeft het ene team iets gebouwd wat XML oplevert, heeft het andere team JSON services opgeleverd.
  • Elk team is zelf verantwoordelijk voor de veiligheid van de eigen microservice. Terwijl je dat mogelijk centraal wilt kunnen beheren (zodat je zeker weet dat de validatie/controles voldoende zijn en een team niet zegt: daar hebben we geen tijd voor gehad...).


API Management helpt om deze pijnpunten op te lossen.

  • Je koppelt client apps aan de business-logic, niet aan de onderliggende techniek. Je kunt je Azure Function een andere naam geven of ergens anders laten draaien, als je de configuratie in APIM aanpast merkt de client dat niet.
  • Met API Management policies kun je zaken afdwingen, bijvoorbeeld dat alle output JSON is.
  • En met policies kun je security vereisten afdwingen.


We roepen de URL aan, waarbij we de key gebruiken (die je bij het menu-item API kunt vinden).

Het wordt tijd voor het laatste blok, Build serverless apps with Go: link.

De voordelen van Azure Function worden nog benoemd, waaronder de lage kosten. Pas na 1 miljoen keer uitgevoerd te zijn ga je de kosten per gebruik betalen (ik zie geen tijdsduur, maar dat zal wel per maand zijn). Om ook Go of Rust te kunnen gebruiken, moet je "custom handlers" gebruiken. Hiermee kun je bijna elke taal naar Azure Functions krijgen. In de basis is een custom handler een webserver. De Function host zorgt dat er events op de server aangeroepen worden. Daar kun je dan je eigen code draaien.

In dit voorbeeld gaan we aan de slag met Go. We krijgen wat uitleg over de bibliotheken die je daarbij gebruikt, fmt, log, net/http.

Je moet wat aanvullende dingen doen om je project te configureren;

Om daarmee te kunnen werken, moet je weten met welke poort je moet verbinden. Die poort kun je in Go opvragen met dit commando: customHandlerPort, exists := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT")

In functions.js stel je de customHandler in. Dat kan er zo uit zien:


"customHandler": {
   "defaultExecutablePath": "mygoapp.exe"
}

Je moet request forwarding activeren. Hiermee wordt een kopie van het originele request aangemaakt en wordt het originele pad behouden.


// huidige structuur:
hello/
  function.json
// toevoeging:
mux.HandleFunc("/api/hello", helloHandler)

De Function Host krijgt een HTTP response terug, deze response wordt teruggegeven aan de aanroepende applicatie.

Dit zou dan de basis Go web-applicatie moeten doen/worden:


/*

Read the port.
Instantiate an HTTP server instance.
Define routes and route handlers.
Start listening on the port.
*/

customHandlerPort, exists := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT")
if !exists {
   customHandlerPort = "8080"
} // 1)
mux := http.NewServeMux() // 2)
mux.HandleFunc("/order", orderHandler) // 3)
fmt.Println("Go server Listening on: ", customHandlerPort)
log.Fatal(http.ListenAndServe(":"+customHandlerPort, mux)) // 4)

We lopen de code door in Visual Studio Code (nieuw project, customhandler). Maak een bestand server.go. En voer in je terminal go build server.go uit. Dat werkt dus niet. Logisch, want ik heb geen GO op mijn pc staan. Die eerst installeren: link. Met go build server.go maak je een server.exe aan. En die plaats je dan in de defaultExecutablePath.

Lokaal getest, dat werkt. Dit was een HttpTrigger, we gaan nu door met een QueueTrigger. Een queue kan zowel als input-binding als output-binding gebruikt worden. Strikt gezien is voor input geen binding nodig, voor output wel. Er wordt nog een opmerking gemaakt dat momenteel voor Queue's alleen output-bindings ondersteund worden.

Als je lokaal gaat ontwikkelen kun je een Queue emulator gebruiken: Azurite-link. En voor dit voorbeeld downloaden we ook de Azure Storage Explorer, zodat je vanaf je desktop bij je Azure opslag kunt komen: link. In het voorbeeld wordt gezegd dat je Azure: Start Queue Service moet uitvoeren, maar die staat niet in de lijst als je F1 indrukt. In plaats daarvan moet je invoeren: Azurite: Start Queue Service

Via de Azure Storage Explorer voegen we bij de Queue's een nieuwe Queue toe met de naam items. Om je applicatie hiermee te verbinden, pas in je local.settings.json aan:


    "AzureWebJobsStorage": "UseDevelopmentStorage=true"

We maken een map aan met een map queueTrigger. Je maakt daar een function.json bestand aan en vult dit met onderstaande:


{
  "bindings": [{
    "name" "queueItem",
    "type": "queueTrigger",
    "direction": "in",
    "queueName" : "items",
    "connection": "AzureWebJobsStorage"
  }]
}

Er komt wel wat binnen, maar ik zag de tekst niet. En ik zie al hoe dat komt, in het eerste stuk tekst wordt gesproken over

  var reqData map[string]interface{}
  json.Unmarshal(invokeRequest.Data["queueItem"], &reqData)

en in het volgende deel over:

var parsedMessage string
json.Unmarshal(invokeRequest.Data["queueItem"], &parsedMessage)

en dit:

fmt.Println(parsedMessage) // your message

Met mijn copy-paste acties had ik een Unmarshal  naar &reqData gedaan, maar een Println van &parsedMessage (die dan niet gevuld wordt natuurlijk). De Unmarshal(..) omgezet naar &parsedMessage en dan zie ik de tekst: het werkt!

Hiermee heb ik het eerste blok, Create serverless applications, afgerond! Wel de opmerking dat dit 8 uur materiaal/werk is, maar ik niet heel veel vinkjes op de specs voor dit examen heb kunnen zetten. Die missende onderdelen zal ik dus zelf nog moeten uitvoeren/bekijken hoe het werkt om alle stof onder de knie te krijgen. In ieder geval, door met de volgende blokken!