© Andrey Yurlov/shutterstock.com
Long-Running-Workflows als Serverless Functions in Azure

Durchhaltevermögen


Azure Functions bringen viele Eigenschaften mit, die einem die Arbeit erleichtern. Für länger laufende Prozesse sind sie allerdings weniger gut geeignet. Hier helfen Durable und Entity Functions weiter.

Serverless Functions [1] sind aus meiner Sicht eine großartige Erweiterung von Microsoft Azure, die sich nicht umsonst zunehmend größerer Beliebtheit erfreut. Die Gründe: Man muss sich weder um die Auswahl der richtigen Anzahl und Größe von Servern kümmern noch um die Konfiguration des Autoscalings. Schon gar nicht ist man damit beschäftigt, virtuelle Maschinen aktuell zu halten. APIs in der Cloud kommen durch die Serverless-Technologie wie der sprichwörtliche Strom aus der Steckdose. Auch dort steckt eine gewaltige Ingenieursleistung dahinter, immer die richtige Menge Strom zum richtigen Zeitpunkt anzubieten. So ist es auch mit Serverless Functions. Man verpackt seinen Code, übergibt ihn Microsoft und lässt es deren Problem sein, die notwendige Infrastruktur für die gerade anstehende Last bereitzustellen. Nach dem sogenannten Consumption Plan zahlt der Nutzer für das, was er tatsächlich verbraucht, und die Kosten fallen sogar auf null, wenn gerade niemand die Cloud-Software nutzt [2].

Die zweite Besonderheit von Azure Functions ist das Programmiermodell: Es ist Event-getrieben. Events können dabei einerseits die üblichen HTTP Requests sein, falls das zu entwickelnde API ein Web-API sein soll. Es gibt aber auch eine große Anzahl anderer Events, auf die man reagieren kann [3]. Hier einige Beispiele:

  • Eine Datei wird auf den Blob Storage hochgeladen.

  • Eine Datenänderung geschieht in Cosmos DB.

  • Eine Nachricht kommt von einem IoT-Gerät.

  • Über den Service-Bus kommt eine Nachricht von einem anderen Microservice.

  • Ein Timer informiert darüber, dass ein eingestellter Zeitpunkt erreicht wurde.

Das Konzept von Azure Functions passt daher perfekt, wenn man eine Software in Form lose gekoppelter Microservices aufbauen möchte.

Wozu Durable Functions?

Die klassischen Azure Functions haben zwei Eigenschaften, die man beim Design berücksichtigen muss. Erstens müssen sie ihre Aufgabe in relativ kurzer Zeit erledigen. Das Standard-Timeout sind fünf Minuten (Functions mit HTTP-Trigger müssen sogar in knapp vier Minuten antworten), bei Bedarf kann es auf bis zu zehn Minuten erhöht werden [4]. Zweitens sind Serverless Azure Functions stateless. Der Entwickler muss sich selbst um das Speichern von State kümmern, beispielsweise in anderen Azure-PaaS- oder Serverless-Diensten wie Azure SQL Database oder Cosmos DB.

Aufgrund dieser beiden Einschränkungen sind Azure Functions für lange laufende Prozesse nicht gut geeignet. Stellen Sie sich vor, Ihre Serverless Function soll während der Ausführung mit einem Benutzer über einen Slack-Bot kommunizieren. Es ist nicht vorhersagbar, wie schnell der Benutzer reagiert. Es können Minuten oder sogar Stunden vergehen. Eine Function würde mit großer Wahrscheinlichkeit in ein Timeout laufen.

In solchen Situationen helfen Durable Functions und Entity Functions. Sie sind dafür konzipiert, lange zu laufen und sich um das State-Management selbst zu kümmern. Wir konzentrieren uns nun auf diese Varianten der Azure Functions und gehen davon aus, dass Sie als Leserin oder Leser grundlegendes Wissen über die klassischen Azure Functions haben. Falls Ihnen diese Erfahrung fehlt, empfehle ich das Durcharbeiten eines entsprechenden Tutorials, wie man es zum Beispiel unter [5] findet.

Programmieren mit Durable Functions

Mit Durable Functions implementiert man lange laufende Workflows. Im Gegensatz zu anderen Workflowtools wird dafür aber keine eigene deklarative Sprache (z. B. domänenspezifische Sprache (DSL), XML, JSON) verwendet, sondern ganz normales C# (Azure Functions unterstützt auch andere Programmiersprachen, hier beschränken wir uns aber auf C#). Aus der Codestruktur lässt sich der Ablauf des Workflows gut ablesen. Die einzelnen Aktivitäten des Workflows, die eventuell länger dauern könnten, sind hinter await-Aufrufen versteckt.

Das allein ermöglicht es aber noch nicht, lange laufende Workflows in C# zu programmieren. Azure Functions sind serverless. Die Serverlandschaft, auf der Ihr C#-Code läuft, kann sich daher ständig ändern. Es kommen Server dazu oder der Server, auf dem eine Workflowinstanz gerade ausgeführt wird, fällt weg. Wie gehen Durable Functions damit um? Die Antwort auf diese Frage scheint auf den ersten Blick absurd: Durable Functions werden immer wieder von Beginn an ausgeführt.

Die einzelnen Aktivitäten innerhalb des Workflows müssen aus diesem Grund deterministisch sein. Das bedeutet, dass sie bei jedem Durchlauf einer Workflowinstanz bei gleichen Eingabeparametern das gleiche Ergebnis liefern müssen. Entsprechend dem Event-Sourcing-Pattern speichert die Durable Functions Runtime jede Aktion einer Workflowinstanz und deren Ergebnis automatisch in Azure Storage ab. Startet die Function einer Workflowinstanz später von vorn, wird vor dem Aufruf der jeweiligen Aktion geprüft, ob sie bei einem früheren Durchlauf der Instanz schon einmal ausgeführt wurde. Falls ja, wird die Aktion nicht erneut durchgeführt, sondern ihr zuvor ermitteltes Ergebnis wird gelesen und zurückgegeben. Dadurch schadet es nichts, dass immer wieder von vorn begonnen wird. Die schon durchgeführten Aktionen werden quasi übersprungen.

Viele C#-Funktionen sind nicht von Haus aus deterministisch. Man denke an die aktuelle Uhrzeit, die Erzeugung einer neuen GUID, externe Web-APIs, Zufallszahlen etc. Solche APIs dürfen in Durable Functions nicht verwendet werden. Ihr API bietet Alternativen mit ähnlicher Funktionalität an, die mit der Durable Functions Runtime kompatibel sind [6].

Entspannt abwarten

Listing 1 enthält ein Beispiel für eine Durable Function, die das Human Interaction Application Pattern [7...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang