Dieser Beitrag ist eine Zusammenarbeit zwischen mir und meinem großartigen Kollegen, Maxime Rouiller.
Durable Functions? Was ist das? Wenn Sie neu bei Durable sind, empfehle ich Ihnen, hier mit diesem Beitrag zu beginnen, der alle Grundlagen abdeckt, damit Sie richtig einsteigen können. In diesem Beitrag werden wir uns mit einem bestimmten Anwendungsfall beschäftigen, damit Sie ein Durable Function-Muster in Aktion sehen können!
Heute sprechen wir über das Fan Out, Fan In-Muster. Dazu rufen wir die Anzahl offener Issues von GitHub ab und speichern dann, was wir erhalten. Hier ist das Repo, in dem der gesamte Code lebt, den wir in diesem Beitrag besprechen werden.
Über das Fan Out/Fan In-Muster
Dieses Muster haben wir im vorherigen Artikel kurz erwähnt, also lassen Sie es uns wiederholen. Sie würden sich wahrscheinlich für dieses Muster entscheiden, wenn Sie mehrere Funktionen parallel ausführen und dann eine andere Aufgabe mit diesen Ergebnissen durchführen müssen. Man kann sich vorstellen, dass dieses Muster für viele Projekte nützlich ist, da es ziemlich oft vorkommt, dass wir eine Sache basierend auf Daten aus einigen anderen Quellen tun müssen.
Nehmen wir zum Beispiel an, Sie sind ein Restaurant, das Essen zum Mitnehmen anbietet, und es kommen viele Bestellungen herein. Sie könnten dieses Muster verwenden, um zuerst die Bestellung zu erhalten, dann anhand dieser Bestellung die Preise für alle Artikel zu ermitteln, die Verfügbarkeit dieser Artikel und zu prüfen, ob für diese Artikel Verkaufsaktionen oder Sonderangebote gelten. Vielleicht sind die Verkaufsaktionen/Angebote nicht am selben Ort wie Ihre Preise untergebracht, da sie von einer externen Verkaufsfirma verwaltet werden. Möglicherweise müssen Sie auch herausfinden, wie Ihre Lieferwarteschlange aussieht und wer aus Ihrem Personal sie basierend auf seinem Standort erhalten sollte.
Das ist viel Koordination! Aber Sie müssten dann all diese Informationen zusammenfassen, um die Bestellung abzuschließen und zu bearbeiten. Dies ist natürlich ein vereinfachtes, künstliches Beispiel, aber Sie können sehen, wie nützlich es ist, einige Dinge gleichzeitig zu bearbeiten, damit sie von einer endgültigen Funktion verwendet werden können.
So sieht das aus, in abstrakter Codeform und Visualisierung
Siehe den Pen Durable Functions: Pattern #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)
})
Nachdem wir nun wissen, warum wir dieses Muster verwenden möchten, tauchen wir in ein vereinfachtes Beispiel ein, das erklärt, wie.
Einrichtung Ihrer Umgebung für die Arbeit mit Durable Functions
Zuerst einmal. Wir müssen die Entwicklungsumgebung für die Arbeit mit Durable Functions vorbereiten. Lassen Sie uns das aufschlüsseln.
GitHub Persönlicher Zugriffstoken
Um dieses Beispiel auszuführen, müssen Sie in GitHub einen persönlichen Zugriffstoken erstellen. Wenn Sie unter Ihrem Profilbild auf den Dropdown und dann auf *Einstellungen* klicken, wählen Sie im linken Seitenmenü *Entwicklereinstellungen*. Klicken Sie im selben Seitenmenü auf der nächsten Seite auf die Option *Persönliche Zugriffstokens*.

Dann erscheint eine Aufforderung, und Sie können auf die Schaltfläche
Für die Option "Scopes/Berechtigungen" empfehle ich die Auswahl von "repos", womit Sie dann auf die Schaltfläche *Token generieren* klicken und den Token in Ihre Zwischenablage kopieren können. Bitte beachten Sie, dass Sie Ihren Token niemals committen sollten. (Er wird widerrufen, wenn Sie es tun. Fragen Sie mich, woher ich das weiß.) Wenn Sie weitere Informationen zur Erstellung von Tokens benötigen, finden Sie weitere Anweisungen hier.
Functions CLI
Zuerst installieren wir die neueste Version der Azure Functions CLI. Dies können wir tun, indem wir dies in unserem Terminal ausführen
npm i -g azure-functions-core-tools@core --unsafe-perm true
Verunsichert Sie das unsafe perm Flag? Mich hat es auch verunsichert. Was es eigentlich tut, ist die Verhinderung des Wechsels von UID/GID beim Ausführen von Paket-Skripten, was notwendig ist, da das Paket selbst ein JavaScript-Wrapper um .NET ist. Die Installation mit Brew ohne ein solches Flag ist ebenfalls verfügbar, und weitere Informationen dazu finden Sie hier.
Optional: Einrichtung des Projekts in VS Code
Absolut nicht notwendig, aber ich arbeite gerne in VS Code mit Azure Functions, da es eine hervorragende lokale Fehlerbehebung bietet, was bei serverlosen Funktionen normalerweise eine Qual ist. Wenn Sie es noch nicht installiert haben, können Sie dies hier tun
Kostenlose Testversion für Azure einrichten und Speicherkonto erstellen
Um dieses Beispiel auszuführen, müssen Sie eine kostenlose Testversion für Azure ausprobieren. Sie können ins Portal gehen und sich in der linken Ecke anmelden. Sie erstellen ein neues Blob-Speicherkonto und rufen die Schlüssel ab. Da wir das alles erledigt haben, sind wir bereit loszulegen!
Einrichtung unserer Durable Function
Werfen wir einen Blick auf das eingerichtete Repo. Wir klonen oder forken es
git clone https://github.com/Azure-Samples/durablefunctions-apiscraping-nodejs.git
So sieht die anfängliche Dateistruktur aus.

(Diese Visualisierung wurde mit meinem CLI-Tool erstellt.)
In local.settings.json ändern Sie GitHubToken in den Wert, den Sie zuvor von GitHub erhalten haben, und tun dasselbe für die beiden Speicher-Schlüssel – fügen Sie die Schlüssel aus dem zuvor eingerichteten Speicherkonto ein.
Führen Sie dann aus
func extensions install
npm i
func host start
Und jetzt läuft es lokal!
Den Orchestrator verstehen
Wie Sie sehen, haben wir im Verzeichnis FanOutFanInCrawler eine Reihe von Ordnern. Die Funktionen in den Ordnern GetAllRepositoriesForOrganization, GetAllOpenedIssues und SaveRepositories sind die Funktionen, die wir koordinieren werden.
Hier ist, was wir tun werden
- Der Orchestrator startet die Funktion
GetAllRepositoriesForOrganization, an die wir den Organisationsnamen übergeben, der ausgetInput()aus der FunktionOrchestrator_HttpStartabgerufen wird. - Da dies wahrscheinlich mehr als ein Repo sein wird, erstellen wir zuerst ein leeres Array, durchlaufen dann alle Repos und rufen
GetOpenedIssuesauf und fügen diese dem Array hinzu. Was wir hier ausführen, wird alles parallel ausgeführt, da es nicht innerhalb des `yield` im Iterator liegt. - Dann warten wir, bis alle Tasks ausgeführt sind, und rufen schließlich
SaveRepositoriesauf, das alle Ergebnisse in Blob Storage speichert.
Da die anderen Funktionen ziemlich Standard sind, schauen wir uns den Orchestrator kurz an. Wenn wir in das Orchestrator-Verzeichnis schauen, sehen wir, dass es mit den Dateien index.js und function.json eine ziemlich traditionelle Einrichtung für eine Funktion hat.
Generatoren
Bevor wir uns dem Orchestrator widmen, machen wir einen *kurzen* Abstecher zu Generatoren, denn ohne sie können Sie den Rest des Codes nicht verstehen.
Ein Generator ist nicht die einzige Möglichkeit, diesen Code zu schreiben! Er könnte auch mit anderen asynchronen JavaScript-Mustern umgesetzt werden. Es ist einfach so, dass dies eine ziemlich saubere und lesbare Art ist, ihn zu schreiben, also schauen wir ihn uns kurz an.
function* generator(i) {
yield i++;
yield i++;
yield i++;
}
var gen = generator(1);
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
console.log(gen.next()); // {value: undefined, done: true}
Nach dem anfänglichen kleinen Sternchen nach function* können Sie das Schlüsselwort `yield` verwenden. Das Aufrufen einer Generatorfunktion führt nicht die gesamte Funktion auf einmal aus; stattdessen wird ein Iterator-Objekt zurückgegeben. Die Methode `next()` durchläuft diese nacheinander, und wir erhalten ein Objekt, das uns sowohl den `value` als auch `done` mitteilt – wobei `done` ein boolescher Wert ist, der angibt, ob wir alle `yield`-Anweisungen durchlaufen haben. Im obigen Beispiel können Sie sehen, dass für den letzten Aufruf von `.next()` ein Objekt zurückgegeben wird, bei dem `done` `true` ist, was uns mitteilt, dass wir alle Werte durchlaufen haben.
Orchestrator-Code
Wir beginnen mit der `require`-Anweisung, die wir für die Funktionsweise benötigen
const df = require('durable-functions')
module.exports = df(function*(context) {
// our orchestrator code will go here
})
Es ist erwähnenswert, dass das Sternchen dort eine Iterator-Funktion erstellt.
Zuerst holen wir den Organisationsnamen aus der Funktion Orchestrator_HttpStart und rufen alle Repos für diese Organisation mit GetAllRepositoriesForOrganization ab. Beachten Sie, dass wir `yield` innerhalb der Repositories-Zuweisung verwenden, damit die Funktion in sequenzieller Reihenfolge ausgeführt wird.
const df = require('durable-functions')
module.exports = df(function*(context) {
var organizationName = context.df.getInput()
var repositories = yield context.df.callActivityAsync(
'GetAllRepositoriesForOrganization',
organizationName
)
})
Dann erstellen wir ein leeres Array namens `output`, erstellen eine `for`-Schleife aus dem Array, das alle Repos der Organisation enthält, und verwenden dieses, um die Issues in das Array einzufügen. Beachten Sie, dass wir hier kein `yield` verwenden, damit alle parallel laufen, anstatt nacheinander zu warten.
const df = require('durable-functions')
module.exports = df(function*(context) {
var organizationName = context.df.getInput()
var repositories = yield context.df.callActivityAsync(
'GetAllRepositoriesForOrganization',
organizationName
)
var output = []
for (var i = 0; i < repositories.length; i++) {
output.push(
context.df.callActivityAsync('GetOpenedIssues', repositories[i])
)
}
})
Schließlich, wenn alle diese Ausführungen abgeschlossen sind, speichern wir die Ergebnisse und übergeben sie an die Funktion SaveRepositories, die sie in Blob Storage speichert. Dann geben wir die eindeutige ID der Instanz (`context.instanceId`) zurück.
const df = require('durable-functions')
module.exports = df(function*(context) {
var organizationName = context.df.getInput()
var repositories = yield context.df.callActivityAsync(
'GetAllRepositoriesForOrganization',
organizationName
)
var output = []
for (var i = 0; i < repositories.length; i++) {
output.push(
context.df.callActivityAsync('GetOpenedIssues', repositories[i])
)
}
const results = yield context.df.Task.all(output)
yield context.df.callActivityAsync('SaveRepositories', results)
return context.instanceId
})
Nun haben wir alle Schritte, die wir benötigen, um alle unsere Funktionen mit diesem einzigen Orchestrator zu verwalten!
Bereitstellen
Jetzt der spaßige Teil. Lasst uns deployen! 🚀
Um Komponenten bereitzustellen, verlangt Azure von Ihnen, die Azure CLI zu installieren und sich damit anzumelden.
Zuerst müssen Sie den Dienst bereitstellen. Schauen Sie sich die bereitgestellte Datei provision.ps1 an, um sich mit den Ressourcen vertraut zu machen, die wir erstellen werden. Dann können Sie die Datei mit dem zuvor generierten GitHub-Token wie folgt ausführen
.\provision.ps1 -githubToken <TOKEN> -resourceGroup <ResourceGroupName> -storageName <StorageAccountName> -functionName <FunctionName>
Wenn Sie PowerShell nicht installieren möchten, können Sie die Befehle in provision.ps1 auch manuell ausführen.
Und da haben wir es! Unsere Durable Function ist einsatzbereit.