Kolumne: Stropek as a Service

Missverstandene Azure Functions – Mehr als nur Serverless
Keine Kommentare

Serverless ist ein heißes Thema im Bereich Cloud-Computing. Entwicklungsteams können sich damit mehr auf ihre Kernaufgaben konzentrieren und machen sich noch weniger Gedanken über Server, als das schon bei Platform as a Service (PaaS) der Fall ist.

Wenn man nach Serverless in der Microsoft-Azure-Cloud fragt, werden üblicherweise als erstes Azure Functions genannt. Sie kommen dem Versprechen, Server gedanklich außen vor lassen zu können, am nächsten. Bei meiner Arbeit mit Kunden, die in Richtung Cloud-Computing und im Speziellen in Richtung Azure gehen wollen, merke ich aber häufig, dass Azure Functions missverstanden werden. Sie sind weit mehr als nur die Serverless-Variante von Web-APIs.

Architektur und Programmiermodell

Wer sich für Azure Functions entscheidet, entscheidet sich nicht zwangsläufig für Serverless. Functions lassen sich in Azure auch in klassischen PaaS App Services betreiben. Eine Entscheidung für Functions bedeutet aber, sich auf Event-getriebene Softwarearchitektur einzulassen. Man muss außerdem bereit sein, auf viele der aus ASP.NET bekannten Mechanismen wie z. B. Middleware Pipelines zu verzichten. Das Programmiermodell von Functions ist ein anderes als das von ASP.NET.

Event-driven Architecture

Die aus meiner Sicht wichtigste Änderung durch Functions ist, dass Anwendungsteile nicht mehr eng durch Funktionsaufrufe, sondern lose durch Events miteinander verknüpft sind. Eine Function kann beispielsweise über einen eingehenden HTTP Request ausgelöst (triggered) werden. Neben einer Antwort in Form einer HTTP Response signalisiert sie ein Event beispielsweise durch das Versenden einer Nachricht über einen Message Broker wie Azure Service Bus oder Event Grid. Darauf reagieren weitere Functions, die wiederum zu neuen Events führen. Events werden nicht immer explizit, sondern häufig auch implizit ausgelöst, indem man beispielsweise etwas in der Azure CosmosDB oder im Blob Storage speichert.

C# 8.0 Spickzettel

Kostenlos: C# 8.0 – neue Sprachfeatures auf einen Blick

Der C#-8.0-Spickzettel fasst die neuen Features der Sprache zusammen mit Blick auf das aktuelle .NET Core 3.0 bzw. .NET Standard 2.1. Jetzt herunterladen und schneller & effektiver programmieren!

Listing 1 zeigt exemplarisch, wie das Functions-Programmiermodell in C# aussieht. Trigger und Bindings werden durch Attribute im Code festgelegt. Eine solche Function könnte bei einer Kennzeichenerkennungslösung eingesetzt werden. Die Function wird bei Upload eines Kamerabildes in den Blob Storage aktiviert, als Ergebnis erzeugt sie eine Nachricht in einem Topic im Service Bus. Nachgelagerte Funktionen könnten sich um Prüfung der Durchschnittsgeschwindigkeit, das Vorhandensein einer Vignette etc. kümmern.

[FunctionName("ProcessCarImage")]
[return: ServiceBus("plate-read", Connection = "SECCTRL_SEND_PLATE_READ", EntityType = EntityType.Topic)]
public async Task<Message> Run(
  [BlobTrigger("car-images/cameras/{camera}/{name}", Connection = "SECCTRL_CAR_IMAGES")]CloudBlockBlob imageBlob,
  string camera,
  string name,
  ILogger log
)
{ ... }

Vor- und Nachteile

Ein Vorteil der Event-getriebenen Architektur liegt auf der Hand: Die verschiedenen Anwendungsteile – möglicherweise Microservices – sind nicht eng miteinander verwoben. Es können leicht Verarbeitungsteile verändert, ausgetauscht oder hinzugefügt werden. Es gibt aber auch wesentliche Nachteile. Hier einige Beispiele:

  • Die Zusammenhänge sind durch die lose Kopplung weniger offensichtlich. Es ist schwierig, sich in eine komplexe Anwendung, die nur als Sammlung lose gekoppelter Functions vorliegt, einzuarbeiten.
  • Fehlersuche (z. B. Debugging, Analyse von Traces) ist aufwendiger und setzt in der Praxis spezielle Monitoringkomponenten wie Azure Application Insights oder Jaeger voraus.
  • Durch lose Kopplung mit Hilfe eines Message Broker sind Verarbeitungsschritte von Haus aus asynchron. Innerhalb eines C#-Programms steht mit async/await ein Werkzeug zur Verfügung, das asynchrone Programmierung drastisch vereinfacht. Über lose gekoppelte Functions hinweg funktioniert dieser Mechanismus aber nicht mehr. Die Gesamtlogik wird schwieriger zu schreiben, schwerer zu verstehen und dadurch fehleranfälliger.
  • Für die Authentifizierung zwischen den Microservices müssen neue Strategien gefunden und erlernt werden. Die aus ASP.NET gewohnten Middleware Pipelines, z. B. für die Validierung von OpenID-Connect-Tokens im HTTP-Header, gibt es in der Form nicht.
  • Serverless Functions setzen eine hochentwickelte Basisinfrastruktur wie die von Azure voraus. Wer auf eine Public-Cloud wie Azure verzichten will, braucht lokal ein entsprechend konfiguriertes Kubernetes-Cluster mit begleitenden Services (z. B. Message Broker, Monitoring, Trace, Auto-Scaling). Ein solches aufzubauen und zu betreiben, setzt Spezialwissen voraus, das oft nicht vorhanden und am Markt schwer zu bekommen ist.

Von Stateless zu Stateful

Azure Functions stellen die gewohnten Entwicklungsmuster, aber nicht nur durch Events, auf den Kopf. Die zweite, große Änderung betrifft den Application und Resource State. Jahrelang haben wir als Webentwickler gelernt, dass Web-APIs stateless sein sollen. Wenn wir einen persistenten State brauchen, dann lagern wir ihn in externe Datenspeicher aus (z. B. Azure SQL Database, CosmosDB, Blob Storage etc.) oder speichern ihn in Zusammenarbeit mit dem Client (z. B. Cookies, IndexedDB etc.).

Functions bieten für die Verwaltung von State alternative Programmiermodelle im Vergleich zu Web-APIs, die mit normalem ASP.NET entwickelt werden. Die erste Variante, die auch bereits seit längerem für den Produktionseinsatz freigegeben ist, sind die Durable Functions. Im Gegensatz zu normalen Functions können sie lange laufen (d. h. Minuten, Stunden, Tage oder noch länger). Ihr State überlebt dabei auch das Entfernen des Function-Codes aus dem Hauptspeicher. Sehen wir uns an, wie das konzeptionell funktioniert.

Deterministische Orchestrator Functions

Der Algorithmus einer Durable Function wird als Orchestrator Function geschrieben. Es handelt sich um eine ganz normale C#-Funktion, die allerdings deterministisch sein muss. Das bedeutet, dass sie bei gleichen Eingangsparametern bei mehrfacher Ausführung immer das gleiche Ergebnis liefert. In der Praxis ist das schwer sicherzustellen. Was, wenn eine Orchestrator Function auf die aktuelle Zeit zugreift und sich in Abhängigkeit dazu verschieden verhält? Was, wenn die Function eine Entscheidung auf Basis des aktuellen Lagerstands eines Produkts trifft? Führt man eine solche Function mehrfach aus, würde sie abhängig vom Zeitpunkt der Ausführung unterschiedliche Ergebnisse liefern, also nicht mehr deterministisch sein.

Durable Functions lösen dieses Problem, indem nicht-deterministische Operationen als Activity Functions implementiert werden. Statt beispielsweise DateTime.UtcNow aufzurufen, verwendet man DurableOrchestrationContext.CurrentUtcDateTime. Entsprechend dem Event Sourcing-Pattern speichert die Functions Runtime je Orchestration-Instanz alle Aufrufe von Activity Functions sowie die jeweiligen Ergebnisse im Azure Blob Storage. Wenn es für die Orchestration Function wieder etwas zu tun gibt, wird die gesamte Function einfach von vorne ausgeführt. Activity Functions, die bereits durchgelaufen sind, werden nicht erneut ausgeführt, sondern es wird ihr vorheriges Ergebnis zurückgegeben. Für die Entwicklerin ist das alles transparent, man muss sich um nichts davon explizit selbst kümmern. Die Gesamtlogik der Orchestrator Function ist im C#-Code klar ersichtlich, da selbst lange dauernde Operationen wie eine Interaktion mit Endbenutzern z. B. über Slack hinter einer Activity Function und async/await versteckt sind.

Durable Entities

Durable Entities sind der jüngste Spross in der Familie der Azure Functions. Wie bei Durable Functions handelt es sich um Stateful Services. Das Programmiermodell sieht aber grundlegend anders aus. Es orientiert sich am Actor Model. Eine Durable Entity (dauerhafte Entität) wird in der Regel als Klasse modelliert (in Ausnahmefällen kann es auch eine Funktion sein), die Properties und Methoden anbietet. Jede Instanz der Klasse hat eine eindeutige ID. Von außen kann mit Hilfe der ID auf den State und die Methoden der Klasse zugegriffen werden. Dabei kommen C#-Interfaces zum Einsatz, um Typsicherheit zu garantieren.

Der Inhalt der Properties einer Durable Entity wird automatisch von der Functions Runtime als JSON Blob persistiert. Wenn auf eine Entity zugegriffen wird, die nicht im Hauptspeicher ist, wird sie aus dem Blob wiederhergestellt, ohne dass man dafür Code schreiben müsste. Änderungen an den Properties werden automatisch gespeichert.
Durable Entities eignen sich überall dort, wo der State der Anwendung durch viele kleine Einheiten mit Logik und State repräsentiert wird. Hier einige Beispiele:

  • Benutzerbezogener Warenkorb in einer E-Commerce-Lösung
  • Benutzer in einem Multiplayer-Onlinespiel
  • Punktekonto in einem Loyalitätsprogramm

Fazit

Wenn es um Azure Functions geht, wird meistens primär ihr Serverless-Betriebsmodell betont. Tatsächlich ist das aber nur ein kleiner Aspekt der Functions, der sogar optional ist. In der täglichen Arbeit führt die Entscheidung für Azure Functions zu einer grundlegend veränderten Softwarearchitektur. Statt vieler HTTP-basierter Web-APIs baut man eine Gesamtlösung in Form lose gekoppelter Funktionsbausteine, die über Events logisch miteinander verknüpft sind. State landet nicht mehr zwangsläufig in einer externen Datenbank, sondern wird bei Bedarf automatisch durch die Functions Runtime verwaltet.

Der richtige Einsatz von Functions braucht Übung. Etablierte Designmuster von ASP.NET lassen sich nicht unverändert übernehmen. Man muss ein Gespür dafür entwickeln, wann welche Functions im Vergleich zu einer Web-API Vorteile bringen. Wer sich für Cloud-Computing in der Azure-Cloud entscheidet, sollte Functions meiner Meinung nach auf jeden Fall eine Chance geben, da sie an den richtigen Stellen eingesetzt Entwicklungsaufwand und vor allem den Aufwand beim Anwendungsbetrieb reduzieren können.

Windows Developer

Windows DeveloperDieser Artikel ist im Windows Developer erschienen. Windows Developer informiert umfassend und herstellerneutral über neue Trends und Möglichkeiten der Software- und Systementwicklung rund um Microsoft-Technologien.

Natürlich können Sie den Windows Developer über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. Außerdem ist der Windows Developer weiterhin als Print-Magazin im Abonnement erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -