Oh nein! Nicht noch mehr Fachjargon! Was genau bedeutet der Begriff Durable Functions? Durable functions haben mit Serverless-Architekturen zu tun. Es ist eine Erweiterung von Azure Functions, die es Ihnen ermöglicht, zustandsbehaftete Ausführungen in einer Serverless-Umgebung zu schreiben.
Stellen Sie sich das so vor. Es gibt einige große Vorteile, auf die sich die Leute normalerweise konzentrieren, wenn sie über Serverless Functions sprechen
- Sie sind günstig
- Sie skalieren mit Ihren Anforderungen (nicht unbedingt, aber das ist der Standard für viele Dienste)
- Sie ermöglichen es Ihnen, ereignisgesteuerten Code zu schreiben
Lassen Sie uns kurz über den letzten Punkt sprechen. Wenn Sie ereignisgesteuerten Code schreiben können, können Sie Ihre operativen Anforderungen in kleinere Funktionen aufteilen, die im Wesentlichen sagen: Wenn diese Anfrage eingeht, führe diesen Code aus. Sie kümmern sich nicht um die Infrastruktur, die wird für Sie erledigt. Das ist ein ziemlich überzeugendes Konzept.
In diesem Paradigma können Sie Ihren Workflow in kleinere, wiederverwendbare Teile aufteilen, was diese wiederum einfacher zu warten macht. Dies ermöglicht es Ihnen auch, sich auf Ihre Geschäftslogik zu konzentrieren, da Sie die Dinge auf den einfachsten Code reduzieren, der auf Ihrem Server ausgeführt werden muss.
Hier kommen Durable Functions ins Spiel. Sie können sich wahrscheinlich vorstellen, dass Sie mehr als eine Funktion benötigen, um sie auszuführen, wenn Ihre Anwendung größer wird und mehr Zustände verwalten muss. Und in vielen Fällen müssen Sie diese koordinieren und die Reihenfolge festlegen, in der sie ausgeführt werden sollen, damit sie effektiv sind. Es ist erwähnenswert, dass Durable Functions ein Muster sind, das nur in Azure verfügbar ist. Andere Dienste haben Variationen dieses Themas. Zum Beispiel heißt die AWS-Version Step Functions. Wir sprechen also über etwas Spezifisches für Azure, aber es gilt auch allgemeiner.
Durable in Aktion, einige Beispiele
Nehmen wir an, Sie verkaufen Flugtickets. Sie können sich vorstellen, dass wir, wenn eine Person ein Ticket kauft, Folgendes tun müssen:
- Überprüfen Sie die Verfügbarkeit des Tickets
- Eine Anfrage senden, um den Sitzplan zu erhalten
- Holen Sie sich ihre Meilenpunkte, wenn sie ein Treuemitglied sind
- Senden Sie ihnen eine mobile Benachrichtigung, wenn die Zahlung durchgeht und sie eine App installiert haben/Benachrichtigungen angefordert haben
(Normalerweise gibt es mehr, aber wir verwenden dies als Basisbeispiel)
Manchmal werden all diese gleichzeitig ausgeführt, manchmal nicht. Wenn sie zum Beispiel ihr Ticket mit ihren Meilenprämien kaufen möchten. Dann müssten Sie zuerst die Prämien prüfen und dann die Verfügbarkeit des Tickets. Und dann einige dunkle Magie anwenden, um sicherzustellen, dass kein Kunde, nicht einmal Datenwissenschaftler, den Algorithmus hinter Ihrem Prämienprogramm wirklich verstehen kann.
Orchestrierungsfunktionen
Unabhängig davon, ob Sie diese Funktionen gleichzeitig ausführen, nacheinander ausführen oder basierend auf der Erfüllung einer Bedingung ausführen, möchten Sie wahrscheinlich das verwenden, was als Orchestrierungsfunktion bezeichnet wird. Dies ist ein spezieller Funktionstyp, der Ihre Workflows definiert und wie Sie erwarten, die anderen Funktionen orchestriert. Sie speichern ihren Fortschritt automatisch beim Prüfpunkt, wann immer eine Funktion wartet, was äußerst hilfreich für die Verwaltung komplexen asynchronen Codes ist.
Ohne Durable Functions geraten Sie in ein Organisationsproblem. Nehmen wir an, eine Funktion hängt davon ab, dass eine andere ausgelöst wird. Sie könnten die andere Funktion direkt von der ersten aufrufen, aber wer auch immer den Code wartet, müsste in jede einzelne Funktion einsteigen und im Hinterkopf behalten, wie sie aufgerufen wird, während er sie separat wartet, wenn Änderungen erforderlich sind. Es ist ziemlich einfach, in etwas zu geraten, das dem Callback-Hell ähnelt, und das Debugging kann sehr schwierig werden.
Orchestrierungsfunktionen hingegen verwalten den Zustand und das Timing aller anderen Funktionen. Die Orchestrierungsfunktion wird durch einen Orchestrierungstrigger ausgelöst und unterstützt sowohl **Inputs** als auch **Outputs**. Sie können sehen, wie nützlich das wäre! Sie verwalten den Zustand umfassend an einem Ort. Außerdem können die Serverless Functions ihre Aufgaben auf das beschränken, was sie ausführen müssen, wodurch sie wiederverwendbarer und weniger anfällig werden.
Lassen Sie uns einige mögliche Muster durchgehen. Wir gehen über die einfache Verkettung hinaus und sprechen über einige andere Möglichkeiten.
Muster 1: Funktionsverkettung
Dies ist die direkteste Implementierung aller Muster. Es ist buchstäblich ein Orchestrator, der ein paar verschiedene Schritte steuert. Der Orchestrator löst eine Funktion aus, die Funktion wird beendet, der Orchestrator registriert sie, und dann wird die nächste ausgeführt und so weiter. Hier ist eine Visualisierung davon in Aktion
Siehe den Pen Durable Functions: Muster #1 - Verkettung von Sarah Drasner (@sdras) auf CodePen.
Hier ist ein einfaches Beispiel für dieses Muster mit einem Generator.
const df = require("durable-functions")
module.exports = df(function*(ctx) {
const x = yield ctx.df.callActivityAsync('fn1')
const y = yield ctx.df.callActivityAsync('fn2', x)
const z = yield ctx.df.callActivityAsync('fn3', y)
return yield ctx.df.callActivityAsync('fn3', z)
})
Ich liebe Generatoren! Wenn Sie damit nicht vertraut sind, schauen Sie sich diesen großartigen Vortrag von Bodil zu diesem Thema an).
Muster 2: Fan-out/Fan-in
Wenn Sie mehrere Funktionen parallel ausführen müssen und basierend auf den Ergebnissen eine weitere Funktion auslösen müssen, ist ein Fan-out/Fan-in-Muster möglicherweise genau das Richtige für Sie. Wir sammeln die Ergebnisse, die von den Funktionen aus der ersten Gruppe zurückgegeben werden, um sie in der letzten Funktion zu verwenden.
Siehe den Pen Durable Functions: Muster #2, Fan Out, Fan In von Sarah Drasner (@sdras) auf CodePen.
const df = require('durable-functions')
module.exports = df(function*(ctx) {
const tasks = []
// items to process concurrently, added to an array
const taskItems = yield ctx.df.callActivityAsync('fn1')
taskItems.forEach(item => tasks.push(ctx.df.callActivityAsync('fn2', item))
yield ctx.df.task.all(tasks)
// send results to last function for processing
yield ctx.df.callActivityAsync('fn3', tasks)
})
Muster 3: Asynchrone HTTP-APIs
Es ist auch ziemlich üblich, dass Sie eine Anfrage an eine API stellen müssen, deren Dauer unbekannt ist. Viele Dinge wie die Entfernung und die Anzahl der verarbeiteten Anfragen können die Dauer unbekannt machen. Es gibt Situationen, in denen einige dieser Arbeiten zuerst asynchron, aber parallel ausgeführt werden müssen, und dann eine Funktion ausgeführt wird, wenn die ersten API-Aufrufe abgeschlossen sind. Async/await ist perfekt für diese Aufgabe.
Siehe den Pen Durable Functions: Muster #3, Asynchrone HTTP-APIs von Sarah Drasner (@sdras) auf CodePen.
const df = require('durable-functions')
module.exports = df(async ctx => {
const fn1 = ctx.df.callActivityAsync('fn1')
const fn2 = ctx.df.callActivityAsync('fn2')
// the responses come in and wait for both to be resolved
await fn1
await fn2
// then this one this one is called
await ctx.df.callActivityAsync('fn3')
})
Sie können sich hier weitere Muster ansehen! (Abzüglich Animationen. 😉)
Erste Schritte
Wenn Sie mit Durable Functions experimentieren und mehr lernen möchten, gibt es hier ein großartiges Tutorial mit entsprechenden Repos, die Sie forken und bearbeiten können. Ich arbeite auch mit einem Kollegen an einem weiteren Beitrag, der sich mit einem dieser Muster befassen wird und bald veröffentlicht wird!
Alternative Muster
Azure bietet mit Logic Apps etwas ziemlich Einzigartiges, das es Ihnen ermöglicht, Workflows visuell zu gestalten. Ich bin normalerweise selbst eine reine Code-Fraktion, aber eine der überzeugenden Eigenschaften von Logic Apps ist, dass sie fertige Konnektoren mit Diensten wie Twilio und SendGrid haben, sodass Sie diesen etwas lästigen, meist Boilerplate-Code nicht schreiben müssen. Es kann auch mit Ihren bestehenden Funktionen integriert werden, sodass Sie nur die Teile, die mit Mittelschichtsystemen verbunden sind, abstrahieren und den Rest von Hand schreiben können, was die Produktivität erheblich steigern kann.