Einführung

Im Laufe der Jahre wurden viele verteilte Programmiermodelle und Frameworks eingeführt, wie z.B. die Common Object Request Broker Architecture (CORBA), das Microsoft Distributed Component Object Model (DCOM), COM+, Java Remote Method Invocation (RMI), Akka, Microsoft Service Fabric actors und viele andere. In diesem Buch wird unser Beitrag, die Distributed Application Runtime (Dapr), vorgestellt, der von der Community bisher gut aufgenommen wurde. Dapr ist eine neue verteilte Laufzeitumgebung, die aktiv entwickelt wird. Der beste Weg, um aktuelle Informationen zu erhalten, ist die offizielle Website von Dapr zu besuchen. In diesem Buch geht es nicht um die API-Details, sondern um Hintergrundinformationen darüber, wie Dapr aufgebaut ist und wie wir es in Zukunft weiterentwickeln wollen. Wir hoffen, dass das Buch dir hilft, die Architektur und Designphilosophie von Dapr zu verstehen, damit du Dapr besser in deinen eigenen Anwendungen einsetzen und einen großen Beitrag zur Dapr-Gemeinschaft leisten kannst.

Was ist Dapr?

Dapr ist eine ereignisgesteuerte, portable Laufzeitumgebung für die Entwicklung von Microservices für die Cloud und die Kanten. Sie nutzt einen Companion Container oder Prozess, um die Bausteine bereitzustellen, die verteilte Anwendungen benötigen, wie z. B. Zustandsmanagement, Service Discovery, zuverlässiges Messaging, Beobachtbarkeit und vieles mehr, worauf später noch näher eingegangen wird. Die Dapr-Begleitprozesse, auch Sidecars genannt, stellen eine Standard-API-Oberfläche über HTTP/gRPC-Protokolle zur Verfügung. Dadurch kann Dapr jede Programmiersprache unterstützen, die HTTP oder gRPC unterstützt, ohne dass SDKs für Bibliotheken in den Anwendungscode eingebunden werden müssen. Dapr-Sidecars sind miteinander verbunden, um eine isolierte, verteilte Laufzeit für eine verteilte Anwendung zu bilden, wie in Abbildung I-1 dargestellt.

Als wir die Sidecar-Architektur einigen potenziellen Kunden vorstellten, waren einige von ihnen sofort von der Idee angetan. Obwohl unser Bauchgefühl uns sagte, dass wir auf dem richtigen Weg waren, verbrachten wir viel Zeit damit, darüber zu diskutieren, warum Dapr notwendig war und wie Dapr das Cloud Native Programming Model verändern würde. Die zentrale Frage war die folgende: Warum brauchen wir noch ein weiteres Programmiermodell für verteilte Anwendungen? Oder anders gesagt: Was macht Dapr einzigartig und nützlich? Werfen wir einen Blick darauf, was es bietet.

Dapr sidecars work with application code by delivering distributed building blocks through standard HTTP/gRPC protocols
Abbildung I-1. Dapr-Sidecars arbeiten mit dem Anwendungscode zusammen, indem sie verteilte Bausteine über standardmäßige HTTP/gRPC-Protokolle bereitstellen

Ein Programmiermodell für ein heterogenes Umfeld

Ein Programmiermodell ist per Definition eine Sammlung von Meinungen, die beschreiben, wie nach Meinung des Modellentwicklers eine bestimmte Art von Programmen geschrieben werden sollte. Je mehr Meinungen ein Programmiermodell vertritt, desto mehr Orientierung bietet es den Entwicklern. Allerdings führt es auch zu einer stärkeren Bindung an ein bestimmtes Framework oder eine bestimmte Implementierung, was problematisch sein kann. Moderne Microservice-Anwendungen bestehen oft aus Diensten, die von verschiedenen Teams oder externen Anbietern geschrieben wurden, und es gibt eine noch nie dagewesene Mobilität in der modernen IT-Belegschaft. Es ist oft schwierig, alle dazu zu bringen, sich auf eine bestimmte Programmiersprache oder ein bestimmtes Framework einzulassen.

Wir sind uns bewusst, dass wir in der heutigen Softwarewelt die Vielfalt begrüßen und nach Wegen suchen müssen, wie Entwickler mit unterschiedlichen Fähigkeiten, Arbeitsstilen und Vorlieben harmonisch zusammenarbeiten können. Wir wollen, dass Entwickler ihre Geschäftslogik ausdrücken können, ohne Angst haben zu müssen, an einen bestimmten Anbieter gebunden zu sein. Auf der Suche nach einer soliden gemeinsamen Basis für alle Entwicklergemeinschaften haben wir festgestellt, dass ein Microservice auf einer hohen Abstraktionsebene eine Verarbeitungseinheit ist, die einige Zugriffsendpunkte zur Verfügung stellt, die ihrerseits oft über HTTP sprechen. Daher schien HTTP ein vernünftiger gemeinsamer Nenner für die Entwickler von Diensten zu sein. Um beispielsweise den Status von Dapr zu speichern, muss der Anwendungscode lediglich eine POST-Anfrage mit einer Schlüssel/Wertesammlung als JSON-Dokument an eine vom Dapr-Sidecar bereitgestellte Status-API senden. Das Senden einer POST-Anfrage an einen REST-API-Endpunkt ist eine so gängige Praxis, dass wir in unseren zahlreichen Interviews mit Entwicklern unterschiedlicher Qualifikationsstufen noch nie einen Entwickler getroffen haben, der dies als problematisch empfunden hat.1

Die Standardisierung von HTTP ermöglicht maximale Interoperabilität zwischen Diensten, die in verschiedenen Programmiersprachen geschrieben wurden. In diesem Buch wirst du sehen, wie Dapr einige einzigartige interoperable Szenarien ermöglicht, z. B. den Aufruf eines Akteurs, der in einer anderen Programmiersprache als der Aufrufer geschrieben ist.

Hilfreicher, weniger rechthaberisch

Viele bestehende verteilte Programmiermodelle versuchen, Entwickler/innen auf sehr enge Pfade zu zwingen, um sie davor zu schützen, häufige Fehler beim verteilten Rechnen zu machen. Doch wenn Entwickler/innen nichts über die Besonderheiten des verteilten Rechnens wissen, ist das ein zweischneidiges Schwert. Einerseits schützt es sie davor, sich selbst in den Fuß zu schießen. Andererseits hindert es sie daran, mit komplexeren Szenarien effektiv umzugehen. Wir sehen oft, dass Entwickler versuchen, die Beschränkungen von Frameworks zu umgehen, was zu seltsamen und unnatürlichen Missbräuchen und Antipatterns führt. Einige Frameworks versuchen sogar, eine fehlerfreie Umgebung für die Entwickler zu schaffen, indem sie Fehler aufzeichnen und eine Wiederholungs- oder Wiederherstellungslogik einsetzen, damit die Entwickler nichts davon mitbekommen. Das bedeutet, dass die Entwickler/innen nie aus ihren Fehlern lernen; sie machen immer wieder dieselben Fehler und das zugrunde liegende Framework korrigiert sie stillschweigend, was zu unnötigem Overhead und zusätzlicher Latenz führt.

Dapr versucht, hilfreich, aber weniger rechthaberisch zu sein. Es stellt Entwicklern gängige Funktionen mit Standardverhalten zur Verfügung, die auch von unerfahrenen Entwicklern sicher genutzt werden können. Andererseits hält es die Entwickler nicht davon ab, ihre Fähigkeiten in der verteilten Programmierung zu erweitern und bei Bedarf fortgeschrittene Konstrukte zu verwenden. Zum Beispiel bietet die Dapr-Verwaltung standardmäßig optimistische Gleichzeitigkeit. Dadurch können die meisten Transaktionen abgeschlossen werden, ohne sich gegenseitig zu behindern. Wenn es zu Konflikten kommt, versucht Dapr nicht, die Fehler vor den Entwicklern zu verbergen; stattdessen wird von den Entwicklern erwartet, dass sie mit solchen Fehlern umgehen.2 Glücklicherweise ist der Umgang mit Antworten von HTTP-Anfragen auch eine grundlegende Fähigkeit für Webservice-Entwickler. Im Falle eines Konflikts wird ein expliziter 409 Conflict Code zurückgegeben. Der Entwickler kann die Antwort einfach an den Client zurückschicken, was in Webanwendungen üblich ist (da wir normalerweise keine langwierigen automatischen Wiederholungsversuche auf dem Server durchführen wollen). Die Offenlegung des Fehlers erhöht also nicht unbedingt den Aufwand für den Dienstentwickler. Andererseits kann der Entwickler, wenn er sich entscheidet, etwas mit dem Fehler zu tun, dies gerne tun. Das Ergebnis ist eine gesunde Beziehung zwischen dem Framework und dem Entwickler - der Entwickler hat die meiste Zeit keine Probleme und kann selbst entscheiden, wie stark er in die Fehlerbehandlung einbezogen werden möchte.

Erfinde das Rad nicht neu!

Viele Frameworks versuchen, "Full-Stack"-Lösungen anzubieten, die alle Aspekte der verteilten Programmierung abdecken. Da es sich um Komplettlösungen handelt, überlegen sie selten, wie sie sich mit anderen integrieren lassen - es ist entweder ihr Weg oder der Highway.

Dapr verfolgt einen anderen Ansatz. Viele der Komponenten von Dapr sind pluggable, einschließlich der State Stores und Messaging. Dieses Design gibt Entwicklern große Flexibilität bei der Auswahl der Dienste, die sie mit Dapr nutzen wollen. Da Dapr dynamische Bindungen zulässt, können Entwickler oder Betreiber außerdem die Dienste auswählen, die für den aktuellen Entwicklungskontext am besten geeignet sind. Wenn eine Anwendung beispielsweise in einem unverbundenen Modell an den Kanten eingesetzt wird, kann sie an einen lokalen Redis-Speicher in einem Docker-Container gebunden werden. Wenn dieselbe Anwendung in der Cloud eingesetzt wird, kann ihr Zustandsspeicher an einen global replizierten Datenspeicher wie Azure Cosmos DB gebunden werden.

Dapr wendet das gleiche Prinzip für zuverlässiges Messaging an. Anstatt zu versuchen, ein brandneues Nachrichtensystem zu implementieren, ist Dapr so konzipiert, dass es mit bewährten Nachrichtenbussen arbeitet. Dapr bietet eine gemeinsame, HTTP-basierte Fassade für diese Nachrichtenbusse und nimmt den Entwicklern die Konfigurationsverwaltung ab. Dieses Design ermöglicht es Entwicklern, mit minimalem Aufwand zuverlässiges Messaging zu implementieren, während das Betriebsteam große Flexibilität bei der Auswahl, der Konfiguration und dem Betrieb von Messaging-Systemen hat.

Dapr nutzt andere Open-Source-Systeme als integrale Bestandteile und arbeitet gut mit gängigen Open-Source-Lösungen zusammen. Die Dapr-Laufzeit basiert auf bewährten Web-Frameworks, namentlich Fast HTTP; Dapr injiziert sich selbst als Sidecar-Container, während es auf Kubernetes läuft; Dapr-Sidecars arbeiten gut mit Service-Mesh-Sidecars zusammen; Dapr verwendet OpenTelemetry als Standard-Tracing-Lösung; und die Liste geht weiter. Dapr ist als Knotenpunkt von Bausteinen konzipiert. Es war nie dazu gedacht, ein in sich geschlossenes Framework zu sein, das alles von Grund auf liefert. Dieses Design ermöglicht es Dapr, Entwicklern mit großer Flexibilität neue Funktionen zu bieten. Außerdem kann die Community neue Funktionen in das Ökosystem einbringen, um noch mehr Serviceentwickler zu unterstützen.

Dieses offene Design macht die Dapr Core Runtime auch sehr leicht. Zum Zeitpunkt der Erstellung dieses Artikels benötigt sie etwa 40 MB Festplattenspeicher und etwa 4 MB Arbeitsspeicher. Sie läuft mit 0,1 vCPU-Kernen und fügt den Dienstaufrufen einen Overhead im Submillisekundenbereich hinzu. Die leichtgewichtige Laufzeit reduziert den Ressourcenverbrauch und verbessert die Sidecar-Injection-Zeit. Dadurch eignet sich Dapr für dynamisch skalierte Szenarien mit hoher Speicherdichte wie IoT- und Big-Data-Anwendungen.

Vereinheitlichtes Programmiermodell

Es gibt verschiedene Programmiermodelle für unterschiedliche Arten von Diensten: zustandslose Dienste, zustandsabhängige Dienste, Funktionen, Akteure, MapReduce-Aufträge und andere. Dapr nimmt keine harte Trennung zwischen diesen Diensttypen vor. Stattdessen werden alle Dienste als Verarbeitungseinheiten betrachtet, die Eingaben entgegennehmen und Ausgaben erzeugen.

Hinweis

Mit Dapr kannst du alle Dienste auf eine einheitliche Weise schreiben. Du kannst die Dienste später umkonfigurieren, damit sie sich anders verhalten.

Stell dir vor, du schreibst einen zustandslosen Webservice und rekonfigurierst ihn dann so, dass er sich wie eine Funktion mit Ein- und Ausgabebindungen verhält, und hostest ihn in einer serverlosen Umgebung deiner Wahl. Oder du kannst ihn zu einem zustandsfähigen Dienst machen, indem du einen State Store einführst. Außerdem kannst du den Dienst identitätsbewusst machen, so dass er als Akteur agieren kann. Eine solche Flexibilität war bis zu Dapr unvorstellbar.

Viele Unternehmensanwendungen bestehen aus Diensten unterschiedlicher Typen - z. B. einem zustandslosen Web-Frontend, einem zustandsorientierten Backend und einer Reihe von Workern, die als Akteure modelliert und aktiviert werden. Die Entwickler sind oft gezwungen, all diese Programmiermodelle zu lernen, oder sie sind versucht, alle Diensttypen in das einzige Programmiermodell zu zwingen, mit dem sie am besten zurechtkommen. Wir haben zwar schon einige erfolgreiche Projekte mit "reinen Funktionen" oder "reinen Akteuren" gesehen, aber die meisten Unternehmensanwendungen umfassen mehrere Programmiermodelle, Frameworks und sogar Hosting-Umgebungen.

Dapr bietet ein einheitliches Programmiermodell, das Funktionen über ein standardisiertes HTTP/gRPC-Protokoll bereitstellt. Entwickler müssen nicht mehr auf einen bestimmten Diensttyp abzielen, wenn sie mit der Entwicklung ihres Dienstes beginnen. Stattdessen rufen sie, wenn sie bestimmte Funktionen wie Zustandsverwaltung oder Bindungen benötigen, einfach die entsprechende API auf dem Sidecar der Anwendung auf. Sogar verschiedene Routen zu ein und demselben Dienst können verschiedene Rollen einnehmen und unterschiedliche Eigenschaften aufweisen. Ein Dienst kann zum Beispiel als Instanz eines Akteurs aktiviert oder als zustandsabhängiger Dienst aufgerufen werden, der Aggregationen für alle Akteursinstanzen bereitstellt.

Ein weiteres Beispiel für die Nützlichkeit des einheitlichen Programmiermodells ist die Möglichkeit, einen Webservice durch Ereignisse von beliebten Cloud-Diensten über Bindungen auszulösen. Abbildung I-2 zeigt einen Foto-Handler eines Webdienstes, der nicht nur durch Browser-Clients, sondern auch durch Blob-Ereignisse von Azure Storage und Amazon S3 ausgelöst wird - ohne Code-Änderungen. Der Service-Entwickler muss keine spezifischen Service-APIs lernen, um mit diesen Diensten zu kommunizieren - alle Details werden durch Bindungen abstrahiert.

Dapr bindings
Abbildung I-2. Dapr-Bindungen

Im weiteren Verlauf dieser Einführung geben wir dir einen kurzen Überblick über die Architektur von Dapr und wie es funktioniert. Die Grundidee von Dapr ist in der Tat sehr einfach, aber wir freuen uns zu sehen, wie aus dieser einfachen Idee schnell eine lebendige Gemeinschaft wächst. Wir hoffen, dass die Idee auch bei dir Anklang findet.

Dapr Architektur

Dapr besteht aus der Dapr-CLI, einer Laufzeitumgebung und einer Reihe von Kontrollplattendiensten. Außerdem wird es mit einer erweiterbaren Bausteinbibliothek ausgeliefert, die gängige Bausteine enthält: wie State Stores, Messaging Backbones, Bindings und Observability.3 Im Laufe des Buches werden wir alle diese Bausteine ausführlich behandeln, daher ist das Folgende nur eine kurze Einführung in einige von ihnen, um dir einen Überblick zu verschaffen:

Dapr CLI
Das Dapr CLI ist ein plattformübergreifendes Kommandozeilentool, mit dem du deine Dapr-Instanzen konfigurieren, verwalten und überwachen kannst. Außerdem bietet es Zugang zu nützlichen Tools wie dem Dapr-Dashboard.
Dapr Gastgeber

Der Dapr-Host hostet eine Instanz der Dapr-Laufzeitumgebung. Die gebräuchlichste Form ist ein Docker-Container, der in einen Pod injiziert wird, um Seite an Seite mit dem Benutzercode in Kubernetes zu laufen.4 Dapr kann auch im Standalone-Modus als Dienstprozess oder Daemon laufen.

Der Dapr-Host implementiert Kommunikationsprotokolle wie HTTP und gRPC.

Dapr API
Die Dapr-API definiert die programmierbare Schnittstelle zur Dapr-Laufzeit.5
Dapr-Laufzeit
Die Dapr-Laufzeit implementiert die Dapr-API. Sie ist der Kern der Funktionalität von Dapr.
Dapr-Operator
Der Dapr-Operator ist eine Kubernetes-spezifische Komponente, die den Kubernetes-Modus von Dapr unterstützt. Er verwaltet Konfigurationen und Bindungen, die als benutzerdefinierte Kubernetes-Ressourcen implementiert sind.
Dapr Seitenwageninjektor
Diese Komponente kümmert sich um die Dapr-Sidecar-Container-Injektion, wenn Dapr im Kubernetes-Modus läuft.
Dapr-Vermittlungsdienst
Der Placement Service verwaltet Routen zu Dapr-Instanzen oder Service-Partitionen. Er unterhält eine Routentabelle, die Anfragen zu einer bestimmten Akteurs-ID oder Partitions-ID an dieselbe Dapr-Laufzeitinstanz weiterleitet. In Kapitel 5 findest du weitere Informationen zu diesem Dienst.
Dapr Sentry
Dapr Sentry ist eine integrierte Zertifizierungsstelle (CA) für die Ausstellung und Verwaltung von Zertifikaten.
Baustein: Staatliche Läden
Dapr speichert den Status von Akteuren und zustandsabhängigen Diensten in konfigurierbaren State Stores. Redis ist der standardmäßige lokale State Store, aber auch andere, einschließlich On-Cluster-Stores, können in Dapr eingebunden werden. Außerdem gibt es eine wachsende Liste von unterstützten State Stores, die von der Community zur Verfügung gestellt werden, darunter Azure Cosmos DB, Cassandra, etcd, Firestore, Memcached, MongoDB, ZooKeeper und viele mehr. In Kapitel 2 erfährst du, wie du eigene State Stores definieren und konfigurieren kannst.
Baustein: Pub/Sub
Standardmäßig bietet Dapr eine einmalige Nachrichtenübermittlung und konfiguriert Redis Streams als Messaging-Backbone für zuverlässiges Messaging. In Kapitel 3 findest du weitere Informationen zum Messaging und wie du das Messaging-Verhalten anpassen kannst.
Baustein: Bindungen
Dapr verwendet Bindungen, um Anwendungscode mit verschiedenen Ein- und Ausgabekanälen zu verbinden. Es definiert eine sehr einfache Bindungsschnittstelle und ruft diese Schnittstelle in einem Single-Thread-Verfahren auf. Dieses Design macht das Schreiben einer neuen Bindung zu einer relativ einfachen Aufgabe, die oft in nur wenigen Stunden erledigt werden kann.
Baustein: Beobachtbarkeit
Dapr ist mit OpenTelemetry integriert, einer Reihe von Open-Source-Bibliotheken zum Sammeln von Anwendungsmetriken und verteilten Traces. In Kapitel 1 erfährst du, wie du verteiltes Tracing konfigurierst und wie du Anwendungsmetriken sammelst.

Du bist fast bereit, Dapr in die Hand zu nehmen. Die Beispiele für den Einstieg im folgenden Abschnitt verwenden die HTTP/gRPC-Schnittstellen, aber wir bieten auch einige sprachspezifische Erfahrungen an, wie im Folgenden erklärt wird.

Sprachunterstützung

Dapr ist sprachunabhängig. Es bietet verteilte Anwendungsbausteine über HTTP oder gRPC. Wir erkennen jedoch den Wunsch vieler Entwickler an, sprachspezifische, stark typisierte Software Development Kits (SDKs) zu verwenden. Dapr wird mit einigen SDKs ausgeliefert, die Akteure in gängigen Sprachen wie C#, Python und Java unterstützen. Wir gehen davon aus, dass die Community in Zukunft weitere SDKs für zusätzliche Programmiersprachen beisteuern wird.

Tipp

Auch wenn du dich für ein sprachspezifisches Akteursframework auf Basis von Dapr entscheidest, bleiben deine Akteure interoperabel mit Akteuren, die mit anderen Dapr-Akteursframeworks oder der reinen Dapr-Laufzeit implementiert wurden .

Die Dapr-Laufzeitimplementierung und die Bindungen werden mit Go entwickelt, der Sprache der Wahl in der Open-Source-Gemeinde. Theoretisch können auch weitere Dapr-API-Implementierungen in anderen Programmiersprachen erscheinen - diese Entscheidung überlassen wir der Community.

Okay, genug der Theorie. Jetzt lass uns Dapr in die Tat umsetzen!

Erste Schritte mit Dapr

Du hast verschiedene Möglichkeiten, um mit Dapr zu starten: den Standalone-Modus, den Kubernetes-Modus oder eines der sprachspezifischen SDKs. Der Standalone-Modus ist der einfachste Weg, um auf einem lokalen Rechner loszulegen. Der Kubernetes-Modus ist die Art, wie Dapr in einer Produktionsumgebung eingesetzt wird. Mit den SDKs kannst du schnell mit Dapr (insbesondere mit Dapr-Akteuren) in einer vertrauten Sprache arbeiten. Dieser Abschnitt führt dich durch alle drei Wege.

Hallo, Welt! mit Dapr Standalone-Modus

Der Tradition folgend, beginnen wir unsere Reise mit einer "Hello, World"-Anwendung, die auf ein greeting -Ereignis hört und mit einer "Hello, World!"-Nachricht antwortet.

Dapr auf deinem Rechner installieren

Das Dapr CLI bietet einen init Befehl, mit dem du die Dapr-Laufzeitumgebung auf deinem lokalen Rechner oder einem Kubernetes-Cluster starten kannst. Um Dapr im Standalone-Modus zu installieren, befolge diese Schritte:

  1. Stelle sicher, dass Docker auf dem Rechner läuft. Das Dapr CLI verwendet Docker, um den Redis-State-Store und den Dapr-Platzierungsdienst auszuführen.

  2. Lade das Release Binary von GitHub herunter:

    • Windows: dapr_windows_amd64.zip

    • Linux: dapr_liux_amd64.zip

    • macOS: dapr_darwin_amd64.zip

    • Linux ARM Geräte: dapr_linux_arm.zip

  3. Entpacke die Dapr CLI-Binärdatei in einen Ordner deiner Wahl.

  4. Um das CLI leicht zugänglich zu machen, kannst du die Binärdatei unter macOS und Linux in den Ordner /user/local/bin verschieben oder den Ordner aus Schritt 3 unter Windows zu deiner Umgebungsvariablen PATH hinzufügen.

  5. Initialisiere Dapr durch Ausführen:

    dapr init

Der Befehl init startet zwei Container: einen Redis-Container, der für zuverlässiges Messaging und den Standard-State-Store verwendet wird, und einen Dapr-Placement-Service-Container, der die Platzierung von Akteuren und partitionierten Diensten verwaltet. Wenn du eine Fehlermeldung erhältst, die besagt, dass "Der Container konnte nicht gestartet werden", hast du wahrscheinlich einen bestehenden Container, der die erforderlichen Ports bereithält (6379 für Redis, 50005 für den Platzierungsdienst unter macOS/Linux und 6050 für den Platzierungsdienst unter Windows). In diesem Fall musst du die Container, die diese Ports halten, herunterfahren und den Befehl init erneut ausführen. Wenn alles klappt, solltest du etwas sehen wie:

Making the jump to hyperspace...
Downloading binaries and setting up components...
Success! Dapr is up and running

Sobald du Dapr initialisiert hast, kannst du die aktuelle CLI-Version und die Dapr-Laufzeitversion überprüfen, indem du

dapr --version

Zum Zeitpunkt der Erstellung dieses Artikels kehrt dieser Befehl zurück:

cli version: 0.1.0
runtime version: 0.1.0

Jetzt können wir zu unserer Bewerbung kommen!

Erstellen der Hello, World-Anwendung

Jetzt ist es an der Zeit, eine Hello, World-Anwendung zu erstellen. Da Dapr sprachunabhängig ist, werden wir in diesem Buch Beispielcode in verschiedenen Programmiersprachen schreiben. Codebeispiele in verschiedenen Sprachen findest du im Dapr Sample Repository.

Für dieses Beispiel werden wir Go verwenden. Erstelle mit deinem bevorzugten Code-Editor (z. B. Visual Studio Code) eine neue Datei main.go mit folgendem Inhalt:

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
)

type incomingEvent struct {
    Data interface{} `json:"data"`
}

func main() {
    http.HandleFunc("/greeting",
          func(w http.ResponseWriter, r *http.Request) {
        var event incomingEvent
        decoder := json.NewDecoder(r.Body)
        decoder.Decode(&event)
        fmt.Println(event.Data)
        fmt.Fprintf(w, "Hello, World!")
    })
    log.Fatal(http.ListenAndServe(":8088", nil))
}

Dieser Code startet einen Webserver an Port 8088 und wenn er eine Anfrage an die /greeting Route erhält, gibt er den Request Body aus (von dem er annimmt, dass er ein data Feld enthält) und liefert den String "Hello, World!"

Starten deiner Anwendung durch einen Dapr-Sidecar-Prozess

Starte in einer Befehlszeile oder einem Terminalfenster eine neue Dapr-Laufzeitinstanz als Sidecar Prozess deiner Anwendung. Beachte, dass der Befehl dapr run es dir ermöglicht, die Befehlszeile zum Starten deiner Anwendung - in diesem Fall go run main.go- nachzustellen:

dapr run --app-id hello-dapr --app-port 8088 --port 8089 go run main.go

Dieser Befehl sollte eine Ausgabe wie die folgende erzeugen:

Starting Dapr with id hello-dapr. HTTP Port: 8089. gRPC Port: 52016
== DAPR == time="2019-11-11T21:22:15-08:00" level=info msg="starting Dapr Runtime 
-- version 0.1.0 -- commit 4358565-dirty"
== DAPR == time="2019-11-11T21:22:15-08:00" level=info msg=
  "log level set to: info"
== DAPR == time="2019-11-11T21:22:15-08:00" level=info msg="standalone mode
configured"
== DAPR == time="2019-11-11T21:22:15-08:00" level=info msg="dapr id: hello-dapr"
== DAPR == time="2019-11-11T21:22:15-08:00" level=info msg="loaded component
messagebus (pubsub.redis)"
== DAPR == time="2019-11-11T21:22:15-08:00" level=info msg=
  "loaded component statestore (state.redis)"
== DAPR == time="2019-11-11T21:22:15-08:00" level=info msg="application protocol:
http. waiting on port 8088"
You’re up and running! Both Dapr and your app logs will appear here
...

Die Ausgabe zeigt, dass die Dapr-Laufzeitumgebung im Standalone-Modus gestartet wurde, dass sie mit einer Anwendung an Port 8088 verbunden ist, dass sie Redis als State Store und Messaging-Backbone verwendet und dass sie sowohl auf HTTP-Verkehr (am angegebenen Port 8089) als auch auf gRPC-Anfragen (an einem zufälligen Port 52016) wartet. Dapr zeigt seine Laufzeitprotokolle und deine Anwendungsprotokolle im selben Terminalfenster an. So kannst du die Interaktionen zwischen dem Dapr-Sidecar und deiner Anwendung leicht nachvollziehen.

Bevor wir fortfahren, werfen wir einen Blick auf die Befehlszeilenschalter, die wir im vorangegangenen Befehl verwendet haben:

app-id
Jeder Dapr-Sidecar-Prozess wird durch eine String-ID identifiziert. Dapr-Laufzeitinstanzen verwenden diese IDs, um sich gegenseitig anzusprechen.
app-port
Der Anwendungsport.
port
Der HTTP-Port, auf den der Dapr-Sidecar-Prozess hört.

Tabelle I-1 fasst die dapr run Befehlszeilenschalter zusammen, die zum Zeitpunkt der Erstellung dieses Artikels verfügbar waren. Ausführlichere Informationen zu den einzelnen Schaltern findest du in Kapitel 1, und du kannst jederzeit den Befehl dapr run --help verwenden, um die neuesten Informationen zu den unterstützten Schaltern zu erhalten.

Tabelle I-1. dapr run Befehlsschalter
Schalter Verwendung Standard
app-id Die Dapr-Seitenwagen-ID N/A
app-port Der Anwendungsport N/A
config Die Dapr-Konfigurationsdatei N/A
enable-profiling Ermöglicht die Profilerstellung über einen HTTP-Endpunkt false
grpc-port Der Dapr gRPC-Port -1 (zufällig)
log-level Legt den Grad der Ausführlichkeit des Protokolls fest: debug, info, warning, error, fatal, oder panic info
max-concurrency Steuert die Anzahl der zulässigen gleichzeitigen Anrufe -1 (unbegrenzt)
port Der HTTP-Port von Dapr -1 (zufällig)
profile-port Der Port, an dem der Profilserver lauschen soll -1 (zufällig)
protocol Weist Dapr an, HTTP oder gRPC für die Kommunikation mit der App zu verwenden http

Dapr in die Tat umsetzen

Es ist an der Zeit, mit Dapr Spaß zu haben! Der Dapr-Sidecar bietet eine /<version>/invoke/<action-id>/method/<methodname> Route, über die Kunden eine Methode in deiner Anwendung direkt aufrufen können. Zum Beispiel wird eine Anfrage an die /v1.0/invoke/hello-dapr/method/greeting Route an den /greeting Handler in deiner Go-Anwendung weitergeleitet.

Um dies zu testen, starte einen Browser und navigiere zu dieser Adresse:

http://localhost:8089/v1.0/invoke/hello-dapr/method/greeting

Du solltest eine "Hello, World!"-Nachricht zurückbekommen. Herzlichen Glückwunsch, du hast soeben eine Webmethode in deiner Anwendung über Dapr aufgerufen!

Nun, vielleicht ist das für sich genommen nicht sehr aufregend. Später in diesem Buch wirst du sehen, wie du mit dem Dapr-Sidecar Funktionen wie verteiltes Tracing und HTTPS-Terminierung nutzen kannst, ohne zusätzlichen Code zu schreiben.

Als Nächstes machen wir die Anwendung zustandsfähig. Wie bereits erwähnt, ist der Übergang von zustandslosen zu zustandsfähigen Anwendungen mit anderen Frameworks nicht immer einfach, aber mit Dapr ist der Prozess ganz natürlich.

Hinweis

Dapr unterstützt diese Verben durch direkten Aufruf: GET, POST, DELETE und PUT.

Zustand hinzufügen

Die Zustandsverwaltung ist eine der Fähigkeiten, die Dapr-Sidecars für deine Anwendung bieten. In Kapitel 2, , werden wir dieses Thema ausführlicher besprechen, aber jetzt wollen wir dir anhand eines kurzen Beispiels zeigen, wie wir unsere "Hello, World"-Anwendung in eine zustandsorientierte Anwendung verwandeln. Wir speichern einen einfachen Schlüsselwert in einem State Store als Anwendungsstatus.

Definiere den Redis-Zustandsspeicher

Zuerst müssen wir dem Dapr-Sidecar mitteilen, dass ein State Store verfügbar ist. Im Standalone-Modus erreichst du das, indem du eine State-Store-Beschreibungsdatei zu einem Komponentenordner unter dem Ordner hinzufügst, in dem deine Anwendung läuft.

Wenn du den Befehl dapr init ausführst, erstellt die Dapr CLI automatisch den Komponentenordner mit einer Standardkonfiguration für den Redis-Zustandsspeicher und eine Redis-Messaging-Backbone-Konfiguration. Im Folgenden findest du zum Beispiel die Standardkonfigurationsdatei redis.yaml, die das Dapr CLI erzeugt:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.redis
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""

Wir werden das Dateischema in Kapitel 2 besprechen. Beachte zunächst, dass die Datei die Redis-Host-Adresse als localhost:6379 ohne Passwort definiert. Du solltest diese Datei an deine Redis-Einstellungen anpassen, wenn du einen anderen Redis-Server verwendest.

Die Anwendung aktualisieren, um den Zustand zu behandeln

Deine Anwendung kann den Status vom Dapr-Sidecar über die /<version>/state/store name<key> Route abfragen und sie kann den Status speichern, indem sie ein Statusobjekt an die /<version>/state/store name Route sendet. Wenn du einen Status sendest, kannst du mehrere Schlüssel/Wert-Paare als JSON-Array senden:

[
    {
        "key": "key-1",
        "value": "some simple value"
    },
    {
        "key": "key-2",
        "value" : {
            "field-a" : "value-a",
            "field-b" : "value-b"
        }
    }
]

Wenn du einen Status zurückbekommst, wie z.B. den Wert "key-1" in diesem Beispiel, erhältst du den Wert selbst, kodiert als JSON-Objekt. In diesem Fall würdest du some simple value zurückbekommen.

Aktualisiere deinen Anwendungscode wie hier gezeigt:

1     package main
2     
3     import (
4         "bytes"
5         "encoding/json"
6         "fmt"
7         "io/ioutil"
8         "log"
9         "net/http"
10       "strconv"
11   )
12   
13   type stateData struct {
14       Key   string `json:"key"`
15       Value int    `json:"value"`
16   }
17   
18   func main() {
19       http.HandleFunc("/greeting", 
20           func(w http.ResponseWriter, r *http.Request) {
21           resp,:= http.Get("http://localhost:8089/v1.0/state/statestore/
               mystate")
22           defer resp.Body.Close()
23           body,:= ioutil.ReadAll(resp.Body)
24           strVal := string(body)
25           count := 0
26           if strVal != "" {
27               count, _ = strconv.Atoi(strVal)
28               count++
29           }
30   
31           stateObj:=[]stateData{stateData{Key: "mystate", Value: count}}
32           stateData, _ := json.Marshal(stateObj)
33           resp, = http.Post("http://localhost:8089/v1.0/state/statestore", 
34               "application/json", bytes.NewBuffer(stateData))
35           if count == 1 {
36               fmt.Fprintf(w, "I’ve greeted you " +
37                 strconv.Itoa(count)+" time.")
38           } else {
39               fmt.Fprintf(w, "I’ve greeted you " +
40                 strconv.Itoa(count)+" times.")
41           }
42       })
43       log.Fatal(http.ListenAndServe(":8088", nil))
44   }

Wenn nun der /greeting Handler aufgerufen wird, fordert er den Statuswert mit einem mystate Schlüssel an, indem er eine GET-Anfrage an http://localhost:8089/v1.0/state/statestore/mystate sendet (wobei 8089 der Port ist, auf dem der Dapr-Sidecar lauscht). Dann wird der Wert erhöht und an http://localhost:8089/v1.0/state/statestore> gesendet, um ihn zu erhalten. Als Nächstes wollen wir unsere Anwendung testen.

Testen der Anwendung

Um die Anwendung zu testen, musst du sie mit dem Dapr-Sidecar starten:

dapr run --app-id hello-dapr --app-port 8088 --port 8089 go run main.go

Sobald der Dapr-Sidecar gestartet ist, solltest du ein paar neue Zeilen im Terminalfenster sehen, die anzeigen, dass der State Store gefunden und initialisiert wurde:

== DAPR == time="2019-11-11T22:01:03-08:00" level=info msg="dapr id: hello-dapr"
== DAPR == time="2019-11-11T22:01:03-08:00" level=info msg="loaded component
statestore (state.redis)"
== DAPR == time="2019-11-11T22:01:03-08:00" level=info msg="loaded component
messagebus (pubsub.redis)"
== DAPR == time="2019-11-11T22:01:03-08:00" level=info msg="application protocol:
http. waiting on port 8088"
You’re up and running! Both Dapr and your app logs will appear here.

== DAPR == time="2019-11-11T22:01:15-08:00" level=info msg=
  "application discovered on port 8088"
== DAPR == 2019/11/11 22:01:15 redis: connecting to localhost:6379
== DAPR == 2019/11/11 22:01:15 redis: connected to localhost:6379 
  (localAddr: [::1]:55231, remAddr: [::1]:6379)

Starte einen Browser und navigiere zu http://localhost:8089/v1.0/invoke/hello-dapr/method/greeting. Wenn du die Seite aktualisierst, solltest du sehen, dass die Anzahl der Grüße steigt.

Drücke nun Strg-C im Terminal, um sowohl das Dapr-Sidecar als auch deine Anwendung anzuhalten. Dadurch wird ein kompletter Absturz der Anwendung (und des Dapr-Sidecars) simuliert. Wenn du deine Anwendung mit dem Dapr-Sidecar erneut startest, wirst du feststellen, dass der Anwendungsstatus erhalten geblieben ist. Der Grund dafür sollte offensichtlich sein: Das Dapr-Sidecar speichert den Status im externen Redis-Speicher.

Wenn du den in Redis gespeicherten Status überprüfen willst, kannst du den Docker-Befehl exec verwenden, um dich mit dem Redis-Server zu verbinden und die Schlüssel abzufragen (den Namen des Redis-Containers erhältst du mit docker ps):

docker exec -it <name of your Redis container> redis-cli

Abbildung I-3 zeigt, dass ich auf meinem Rechner einen Hash-Schlüssel hello-dapr-mystate mit einem Feld data mit dem Wert 6 und einem Feld version mit dem Wert 7 habe (wie die Versionierung funktioniert, wird in Kapitel 2 erklärt).

Querying Redis records
Abbildung I-3. Redis-Datensätze abfragen

Jetzt, wo deine Anwendung auf einem lokalen Rechner läuft, wollen wir sehen, wie du dieselbe Anwendung in einem Kubernetes-Cluster zum Laufen bringst.

Hallo, Welt! mit Dapr Kubernetes Modus

Um die Verwendung von Dapr in Kubernetes zu demonstrieren, erstellen wir zwei Dienste: einen Python-Dienst, der regelmäßig Nachrichten versendet, und einen Node.js-Dienst, der die Nachrichten abhört.

Damit du loslegen kannst, brauchst du einen Kubernetes-Cluster mit aktivierter rollenbasierter Zugriffskontrolle (RBAC). Außerdem musst du kubectl für deinen Cluster konfigurieren.

Hinweis

In diesem Buch wird davon ausgegangen, dass du mit Kubernetes vertraut bist, daher überspringen wir hier jede Einführung in Kubernetes.

Dapr installieren

Wenn du das Dapr CLI installiert hast, kannst du den folgenden Befehl verwenden, um Dapr in den Kubernetes-Cluster zu booten, für den dein kubectl derzeit konfiguriert ist:

dapr init --kubernetes

Oder du kannst Dapr mit Helm einsetzen:

helm repo add dapr https://daprio.azurecr.io/helm/v1/repo
helm repo update
helm install dapr/dapr --name dapr --namespace dapr-system

Um schnell zu überprüfen, ob Dapr konfiguriert wurde, kannst du mit dem folgenden Befehl die Pods in deinem Kubernetes-Cluster auflisten (wenn du Helm benutzt hast, musst du den Schalter -n dapr-system zum folgenden Befehl hinzufügen, da die Helm-Tabelle Dapr-Pods unter dem Namensraum dapr-system bereitstellt):

kubectl get pods

Du solltest vier Dapr-bezogene Pods sehen -dapr-operator, dapr-placement, dapr-sentry und dapr-sidecar-injector- wie in der folgenden Beispielausgabe gezeigt:

NAME                                    READY   STATUS    RESTARTS   AGE
dapr-operator-76888fdcb9-x9ljc           1/1    Running   0          10s
dapr-placement-666b996945-dh55v          1/1    Running   0          9s
dapr-sentry-68997bc894-c49ww             1/1    Running   0          10s
dapr-sidecar-injector-744d97578f-dkcbq   1/1    Running   0          9s

Nachdem Dapr in Kubernetes richtig initialisiert wurde, können wir jetzt mit der Implementierung unseres Nachrichtenempfängers und -senders beginnen.

Codierung des Nachrichtenempfängers

Wir werden den Empfänger mit Node.js schreiben. Anstatt das Projekt mit npm zu initialisieren, werden wir alle Anwendungsdateien von Grund auf neu erstellen. Die Anwendung ist ganz einfach: Sie hört auf eine /greeting Route und gibt aus, was sie erhält. Um loszulegen, befolge diese Schritte:

  1. Erstelle einen neuen Ordner namens node_receiver.

  2. Erstelle eine neue app.js-Datei in dem neuen Ordner. Wie bereits erwähnt, ist die Anwendung ganz einfach: Sie hört auf POST-Anfragen an eine /greeting Route und gibt den Request Body aus. Wie du sehen kannst, hat der folgende Code nichts mit Dapr zu tun. Es handelt sich um einen einfachen Node.js-Server, der absolut keine Kenntnisse über Dapr hat:

    const express = require(’express’);
    const bodyParser = require(’body-parser’);
    
    const app = express();
    const port = 8088;
    app.use(bodyParser.json());
    
    app.post(’/greeting’, (req, res) => {
        console.log(req.body);
        res.status(200).send();
    });
    
    app.listen(port,
          ()=> console.log(`Receiver is running on port ${port}`));
  3. Erstelle eine neue project.json-Datei im selben Ordner, wie folgt:

    {
      "name": "node_receiver",
      "version": "1.0.0",
      "description": "",
      "main": "app.js",
      "author": "",
      "license": "ISC",
      "dependencies": {
        "body-parser": "^1.18.3",
        "express": "^4.16.4"
      }
    }

Bevor wir weitermachen, wollen wir sicherstellen, dass die Node.js-App von alleine funktioniert:

  1. Gehe in den Ordner node_receiver.

  2. Installiere die erforderlichen Abhängigkeiten:

    npm install
  3. Starte die Anwendung:

    node app.js
  4. Verwende ein Tool wie Postman, um eine POST-Anfrage an http://localhost:8088/greeting mit der folgenden JSON-Nutzlast zu senden:

    {
      "msg": "Hello, World!"
    }

Du solltest die Meldung in der Konsole der Anwendung sehen.

Jetzt ist es an der Zeit, die Anwendung als Docker-Container zu verpacken, damit wir sie in Kubernetes einsetzen können:

  1. Erstelle ein Dockerfile unter dem Ordner node_receiver:

    FROM node:8-alpine
    WORKDIR /usr/src/app
    COPY . .
    RUN npm install
    EXPOSE 8088
    CMD [ "node", "app.js" ]
  2. Erstelle das Docker-Image mit docker build (ersetze <username> durch deinen Docker Hub Account-Namen):

    docker build -t <username>/node_receiver .
  3. Mach einen Testlauf, indem du den Container startest und eine POST-Anfrage an ihn sendest:

    docker run --rm -p 8088:8088 <username>/node_receiver
  4. Wenn du mit dem Test fertig bist, drückst du Strg-C, um ihn zu beenden und den Container zu entfernen.

  5. Pushe das Image zu Docker Hub (vorausgesetzt, du hast dich mit deinem Docker Hub-Konto bei Docker Hub angemeldet):

    docker push <username>/node_receiver

Jetzt ist der Empfänger fertig. Als Nächstes machen wir mit dem Sender weiter.

Codierung des Absenders der Nachricht

Wir schreiben den Absender mit Python. Er wird alle fünf Sekunden eine Nachricht senden:

  1. Erstelle einen neuen Ordner namens python_sender.

  2. Erstelle eine neue app.py-Datei im neuen Ordner mit folgendem Inhalt:

    import time
    import requests
    import os
    
    n = 0
    while True:
        n = (n + 1) % 1000000
        message = {"msg" :"Hello, World! " + str(n)}
        try:
            resp = requests.post("""http://localhost:3500/v1.0/invoke/
                nodereceiver/method/greeting""", json=message)
        except Exception as e:
            print(e)
        time.sleep(5)
  3. Erstelle im gleichen Ordner ein Dockerfile mit diesem Inhalt:

    FROM python:3.7.1-alpine3.8
    COPY . /app
    WORKDIR /app
    RUN pip install requests
    ENTRYPOINT ["python"]
    CMD ["app.py"]
  4. Erstelle und verteile den Docker-Container:

    docker build -t <username>/python_sender .
    docker push <username>/python_sender

Damit ist der Codierungsteil abgeschlossen. Als Nächstes erstellen wir Kubernetes-Artefakte, um sowohl den Sender als auch den Empfänger einzusetzen.

Erstellen des Kubernetes-Artefakts

Zum Schluss können wir Kubernetes Deployment-Spezifikationen erstellen, die sowohl den Sender- als auch den Empfänger-Container einsetzen. Befolge dazu die folgenden Schritte:

  1. Erstelle eine neue app.yaml-Datei mit dem folgenden Inhalt (ersetze <username> durch den Namen deines Docker Hub-Kontos ersetzen). Um das Dapr Sidecar zu aktivieren, musst du mit dapr.io/enabled und dapr.io/id annotieren. Wenn du eingehende Anrufe erwartest, musst du auch die Annotation dapr.io/port hinzufügen. Der Dapr Sidecar Injector reagiert auf diese Annotationen und injiziert Dapr Sidecar Container in deine Application Pods:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nodereceiver
      labels:
        app: node
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: node
      template:
        metadata:
          labels:
            app: node
          annotations:
            dapr.io/enabled: "true"
            dapr.io/id: "nodereceiver"
            dapr.io/port: "8088"
        spec:
          containers:
          - name: node
            image: <username>/node_receiver
            ports:
            - containerPort: 8088
            imagePullPolicy: Always
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: pythonsender
      labels: 
        app: python
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: python
      template:
        metadata:
          labels:
            app: python
          annotations:
            dapr.io/enabled: "true"
            dapr.io/id: "pythonsender"
        spec:
          containers:
          - name: pythonsender
            image: <username>/python_sender
            imagePullPolicy: Always
  2. Stelle die Datei mit kubectl bereit:

    kubectl apply -f ./app.yaml
  3. Sobald die Datei verteilt ist, kannst du mit kubectl die verteilten Pods abfragen. Du solltest einen nodereceiver Pod und einen pythonsender Pod sehen, wie in der folgenden Beispielausgabe dargestellt. Wie du sehen kannst, enthält jeder Pod zwei Container - einen Anwendungscontainer und einen automatisch injizierten Dapr-Sidecar-Container:

    $ kubectl get pods
    NAME                            READY     STATUS    RESTARTS   AGE
    ...
    nodereceiver-7668f7899f-tvgk9   2/2       Running   0          8m
    pythonsender-5c7c54c446-nkvws   2/2       Running   0          8m
  4. Um zu sehen, ob der Sender und der Empfänger miteinander kommunizieren, verwenden Sie den folgenden Befehl (ersetzen Sie <postfix> durch die tatsächliche Postfix-Pod-ID in deiner Umgebung):

    kubectl logs nodereceiver-<postfix> node

    Dieser Befehl erzeugt eine Ausgabe wie die folgende:

    Receiver is running on port 8088
    { msg: ’Hello, World! 2’ }
    { msg: ’Hello, World! 3’ }
    { msg: ’Hello, World! 4’ }
    { msg: ’Hello, World! 5’ }
    { msg: ’Hello, World! 6’ }

Jetzt weißt du, wie du Dapr-Sidecars über das HTTP-Protokoll nutzen kannst. Im nächsten Abschnitt erfährst du, wie du stattdessen das gRPC-Protokoll verwenden kannst. Wenn du kein Interesse an der Verwendung von gRPC hast, kannst du den nächsten Abschnitt auslassen.

gRPC verwenden

gRPC ist ein Open-Source-Remote-Procedure-Call-System (RPC), das von Google entwickelt wurde. Es verwendet Protocol Buffers (Protobuf), einen effizienten Datenserialisierungsmechanismus, der ebenfalls von Google entwickelt wurde, sowohl als Schnittstellenbeschreibungssprache (IDL) als auch als Datenserialisierungsmethode. Wie bei anderen RPC-Systemen kommuniziert ein gRPC-Client mit einem gRPC-Server über einen gRPC-Stub, der mit gRPC-Tools automatisch erstellt werden kann.

Hinweis

Eine detaillierte Einführung in gRPC liegt außerhalb des Rahmens dieses Buches. Siehe https://grpc.io für weitere Details.

gRPC verwendet HTTP/2 als Kommunikationsprotokoll. Diese Version hat mehrere Leistungsvorteile gegenüber HTTP/1.1. Dazu gehören Funktionen wie proaktiver Daten-Push, um mehrfache Anfragen für Ressourcen auf einer Seite zu vermeiden, Multiplexing mehrerer Anfragen über eine einzige TCP-Verbindung, HTTP-Header-Datenkompression und Anfrage-Pipelines.

Seit seiner Einführung hat gRPC in der Webservice- und Microservice-Gemeinschaft große Popularität erlangt. Dapr ist da keine Ausnahme: Es nutzt gRPC für die Kommunikation zwischen Dapr sidecars und bietet native gRPC-Unterstützung für die Kommunikation mit Clients und Anwendungscode.

Es ist Zeit für ein paar Übungen. In der folgenden Übung wirst du den gRPC-Client von Dapr verwenden, um eine Methode auf einem Server aufzurufen.

Aufrufen einer Anwendung von einem gRPC-Client

In diesem Abschnitt werden wir einen gRPC-Client erstellen, der die Methode /greeting in unserem Hello, World-Dienst aufruft. Wir verwenden die vierte Programmiersprache, die du in dieser Einführung kennenlernst: C#.

Hinweis

Dapr ist sprachneutral, und um das zu beweisen, möchten wir in diesem Buch so viele Programmiersprachen wie möglich abdecken. Zum Glück sind moderne Programmiersprachen im Allgemeinen recht gut lesbar .

Voraussetzungen

Um die folgende Übung durchzuführen, brauchst du

  • .NET Core SDK 2.2 oder höher

  • Visual Studio 2013 oder neuer, oder Visual Studio Code (wir haben die folgenden Schritte mit Visual Studio 2017 und Visual Studio 2019 getestet)

  • Ein Git-Client

Klone das Dapr-Repository

Da wir einen gRPC-Client automatisch generieren wollen, müssen wir auf die Protobuf-Definitionsdatei von Dapr zugreifen (normalerweise mit der Erweiterung .proto). Du kannst die Datei erhalten, indem du das GitHub-Repository dapr/dapr klonst:

git clone https://github.com/dapr/dapr.git
Hinweis

Da das Dapr-Repository auf Go basiert, solltest du der Go-Konvention folgend das Repository in deinen lokalen Ordner $GOPATH/src/github.com/dapr/dapr klonen.

Erstelle die Client-Anwendung

Wir werden eine einfache C#-Konsolenanwendung als gRPC-Client mit Visual Studio erstellen:

  1. Erstelle eine neue .NET Core Konsolenanwendung namens grpc-client.

  2. Füge ein paar NuGet-Pakete zu deinem Projekt hinzu - du brauchst diese Tools, um den gRPC-Client automatisch zu generieren:

    • Grpc.Tools

    • Grpc.Protobuf

    • Grpc

  3. Füge dem Projekt einen neuen Ordner namens protos hinzu.

  4. Klicke mit der rechten Maustaste auf den Ordner protos und wähle Add®Existing Items. Wähle die Datei $GOPATH/src/github.com/dapr/dapr/pkg/proto/dapr/dapr.proto aus (du kannst auch einen Link zu der Datei hinzufügen, anstatt die Datei lokal zu kopieren).

  5. Klicke mit der rechten Maustaste auf die neu hinzugefügte Datei und wähle die Menüoption Eigenschaften. Im Fenster Eigenschaften änderst du die Build Action in "Protobuf Compiler" und gRPC Stub Classes in "Client only", wie in Abbildung I-4 gezeigt.


    dapr.proto properties
    Abbildung I-4. dapr.proto-Eigenschaften
  6. Ersetze den gesamten Code in der Datei Program.cs durch den folgenden Code:

    using Google.Protobuf.WellKnownTypes;
    using Grpc.Core;
    using System;
    
    namespace grpc_client
    {
        class Program
        {
            static void Main(string[] args)
            {
                Channel channel = new Channel("localhost:3500", 
                      ChannelCredentials.Insecure);
                var client = new 
                      Dapr.Client.Grpc.Dapr.DaprClient(channel);
                Value val = new Value();
                val.StringValue = "Hi, Dapr.";
                Metadata metadata = new Metadata();
                var result = client.InvokeService(new 
                      Dapr.Client.Grpc.InvokeServiceEnvelope
                {
                     Method = "greeting",
                     Id = "hello-dapr",
                     Data = Any.Pack(val)
                }, metadata);
                Console.WriteLine(result.Data.Value.ToStringUtf8());
            }
        }
    }

Dieser Code verwendet gRPC, um die Zeichenkette "Hallo, Dapr." über ein Dapr-Sidecar auf localhost:3500 mit der ID hello-dapr an eine greeting Methode zu senden.

Teste den Client

Für diesen Test verwenden wir die Go-Anwendung Hello, World, die wir zuvor erstellt haben:

  1. Starte die Go-Anwendung mit einem Dapr-Sidecar. Beachte, dass du in diesem Fall den Schalter grpc-port verwenden musst, um einen gRPC-Port anzugeben:

    dapr run --app-id hello-dapr --app-port 8087 --port 8089 --grpc-port 3500 
      go run main.go
  2. In Visual Studio drückst du Strg-F5, um den Client zu starten. Das Programm sollte nach dem Ausdruck der Zeichenkette "Hello, World!" anhalten.

  3. Die Go-Anwendung erwartet derzeit ein JSON-Objekt mit einem data Feld. Der Client sendet jedoch nur einen einfachen String. Wenn du möchtest, dass die Go-Anwendung die Nutzdaten korrekt anzeigt, kannst du den /greeting Handler aktualisieren, um den Body direkt zu drucken, anstatt zu versuchen, ihn zu entschlüsseln:

    txt, _ := ioutil.ReadAll(r.Body)
    fmt.Println(string(txt))

Damit ist die Arbeit auf der Client-Seite abgeschlossen. Als Nächstes werden wir uns der Serverseite zuwenden.

Einen gRPC Server schreiben

Jetzt schreiben wir den Dienst Hello, World so um, dass er einen gRPC-Endpunkt anstelle eines HTTP-Endpunkts bereitstellt. Und um dem Trend zu folgen, in jedem Beispiel eine andere Programmiersprache zu verwenden, benutzen wir dieses Mal Java.

Voraussetzungen

Um die folgende Übung durchzuführen, brauchst du

  • Das Java Development Kit (JDK)

  • Ein Git-Client

  • Maven

Klone das Dapr-Repository (falls nötig)

In diesem Beispiel verwenden wir die Protobuf-Definition aus dem Dapr-Repository. Wenn du es noch nicht getan hast, klone das Repository:

git clone https://github.com/dapr/dapr.git

Erstellen Sie die Serveranwendung

Die folgende Anleitung verwendet das Maven Kommandozeilen-Tool (mvn). Es gibt zwar Plug-ins für IDEs wie Eclipse und IntelliJ, aber wir sind der Meinung, dass die Verwendung des Kommandozeilen-Tools am deutlichsten macht, was genau passiert. Hier sind die Schritte zur Erstellung der Serveranwendung:

  1. Verwende mvn, um ein neues Projekt mit dem Namen grpc-server zu erstellen. Beachte, dass der Befehl die Gruppen-ID io.dapr angibt, die mit der Protobuf-Beschreibungsdatei im Dapr-Repository übereinstimmt. Wenn du eine andere Gruppen-ID verwenden möchtest, musst du deine Protobuf-Beschreibung in Schritt 4 aktualisieren, damit sie mit dem Parameterwert übereinstimmt:

    mvn archetype:generate -DgroupId=io.dapr -DartifactId=grpc-server
    -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
  2. Wechsle in den Ordner grpc-server:

    cd grpc-server
  3. Erstelle einen neuen Ordner src/main/proto (für Linux/macOS musst du den Schalter -p hinzufügen):

    mkdir src/main/proto
  4. Kopiere $GOPATH/src/github.com/dapr/dapr/pkg/proto/dapr/dapr.proto in diesen Ordner. Wir werden diese Datei in dieser Anleitung wiederverwenden, weil sie die Definition des App-Dienstes enthält, die wir brauchen. Du kannst mit deinen eigenen Protobuf-Definitionsdateien beginnen, aber du musst sicherstellen, dass deine Dienstdefinition mit der App-Dienstdefinition in der Datei dapr.proto kompatibel ist.

  5. Aktualisiere die Datei pom.xml, um die notwendigen Abhängigkeiten einzuschließen:

    <dependencies>
      ...
      <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-netty</artifactId>
        <version>1.14.0</version>
      </dependency>
      <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-protobuf</artifactId>
        <version>1.14.0</version>
      </dependency>
      <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-stub</artifactId>
        <version>1.14.0</version>
      </dependency>
    </dependencies>
  6. Füge das Build-Skript zur pom.xml-Datei hinzu:

    <build>
      <extensions>
        <extension>
          <groupId>kr.motd.maven</groupId>
          <artifactId>os-maven-plugin</artifactId>
          <version>1.5.0.Final</version>
        </extension>
      </extensions>
      <plugins>
        <plugin>
          <groupId>org.xolstice.maven.plugins</groupId>
          <artifactId>protobuf-maven-plugin</artifactId>
          <version>0.5.1</version>
          <configuration>
            <protocArtifact>
            com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}
            </protocArtifact>
            <pluginId>grpc-java</pluginId>
            <pluginArtifact>
            io.grpc:protoc-gen-grpc-java:1.14.0:exe:${os.detected.classifier}
            </pluginArtifact>
          </configuration>
          <executions>
            <execution>
              <goals>
                <goal>compile</goal>
                <goal>compile-custom</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
  7. Führe den Maven Build-Befehl aus (mvn). Dadurch werden die Quelldateien, die du brauchst, im Ordner target\generated-sources\protobuf\grpc-java\io\dapr erzeugt:

    mvn -DskipTests package
  8. Füge eine neue Datei src/main/java/io/dapr/AppServiceImpl.java mit folgendem Code hinzu. Dies ist eine typische gRPC-Server-Implementierung, die eine der vom App-Service definierten Methoden implementiert (invokeService, die aufgerufen wird, wenn Dapr eine direkte Aufrufanfrage an die App sendet):

    package io.dapr;
    
    import com.google.protobuf.Any;
    import com.google.protobuf.StringValue;
    
    import io.dapr.DaprGrpc.DaprImplBase;
    import io.dapr.DaprProtos.*;
    
    public class AppServiceImpl extends DaprImplBase {
      @Override
      public void invokeService(InvokeServiceEnvelope request,
          io.grpc.stub.StreamObserver<InvokeServiceResponseEnvelope> 
          responseObserver) {
        System.out.println(request);
        Any response = Any.pack(StringValue.newBuilder()
            .setValue("Hello, World!").build());
        InvokeServiceResponseEnvelope envelope = 
            InvokeServiceResponseEnvelope.newBuilder()
            .setData(response).build();
        responseObserver.onNext(envelope);
        responseObserver.onCompleted();
      }
    }
  9. Ändere src/main/java/io/dapr/App.java:

    package io.dapr;
    
    import io.grpc.*;
    
    public class App {
      public static void main( String[] args ) throws Exception {
        Server server = ServerBuilder.forPort(8090)
            .addService(new AppServiceImpl())
            .build();
    
        server.start();
        System.out.println("Server started.");
        server.awaitTermination();
      }
    }
  10. Führe den Maven Build-Befehl erneut aus, um sicherzustellen, dass alles kompiliert wird:

    mvn -DskipTests package

Teste den gRPC-Server

Der gRPC-Server ist jetzt bereit für einen Testlauf. In diesem Walk-Through verwenden wir den C# gRPC-Client, den wir in dieser Einführung erstellt haben:

  1. Erstelle und starte den gRPC-Server mit Maven:

    mvn -DskipTests package exec:java -Dexec.mainClass=io.dapr.App
  2. Starte einen Dapr-Sidecar. Beachte, dass das (Anwendungs-)Protokoll in diesem Fall auf grpc eingestellt ist und dass ein Dummy-Befehl echo a verwendet wird, weil wir uns an einen bestehenden Prozess anhängen:

    dapr --app-id hello-dapr --app-port 8090 --protocol grpc --grpc-port 
      3500 echo a
  3. Führe den C# gRPC-Client in Visual Studio aus. Du solltest sehen, dass die Anfrage vom gRPC-Server protokolliert und die Nachricht "Hello, World!" an den Client zurückgegeben wird.

Bevor wir diese Einführung abschließen, wollen wir kurz auf ein weiteres Dapr-Feature eingehen: Bindings, die den Schlüssel zum Schreiben von plattformunabhängigem Code darstellen.

Bindungen

Mit Bindungen kannst du deine Anwendungen an verschiedene Ereignisquellen oder Ziele binden. So kannst du plattformunabhängigen Code schreiben, der sich dynamisch an verschiedene Umgebungen und Kontexte anpassen lässt. Du kannst zum Beispiel eine Eingabebindung verwenden, um dich an einen Azure Blob Speicherung Container zu binden und die Bindung auslösen zu lassen, sobald ein neuer Blob in den Container gelegt wird, oder du kannst eine Ausgabebindung verwenden, um eine AWS Lambda Expression aufzurufen, wenn dein Servicezustand einen bestimmten Schwellenwert überschreitet. In Kapitel 3 werden wir uns ausführlicher mit Bindungen befassen, aber jetzt wollen wir erst einmal ein paar schnelle Experimente machen, um zu sehen, wie Bindungen funktionieren.

Binden im Standalone-Modus

Einer der Hauptvorteile des Bindens ist die Trennung von Belangen. Wenn du Bindungen verwendest, gestaltest du deine Anwendung so, dass sie Ereignisse von einer bestimmten Ereignisquelle empfängt. Du gibst jedoch keine Details über die Ereignisquelle an. Dann kannst du (oder jemand, der deine Anwendung bedient) später entscheiden, deine Anwendung an eine völlig andere Ereignisquelle zu binden, ohne deinen Code zu verändern. Du kannst deine Anwendungen sogar umbinden, während sie laufen.

Dapr wird mit ein paar vorgefertigten Bindungen ausgeliefert. Im Standalone-Modus wird jede Bindung durch eine Metadaten-Datei im Komponentenordner beschrieben. In der folgenden Anleitung agieren wir zunächst als Entwickler und schreiben eine einfache HTML-Seite mit JavaScript, die Ereignisse an ein Ziel namens target sendet. Dann agieren wir als Anwendungsbetreiber und konfigurieren die target als Azure Event Hub.

Entwickler: Schreibe eine einfache HTML-Seite

In diesem Teil werden wir eine HTML-Seite mit einer Schaltfläche schreiben. Wenn die Schaltfläche angeklickt wird, sendet die App eine Nachricht an eine Bindung namens target, indem sie eine POST-Anfrage an den Dapr-Sidecar mit der Nachricht an target stellt:

  1. Erstelle einen neuen Ordner mit dem Namen html-app.

  2. Erstelle in diesem Ordner eine index.html-Datei mit dem folgenden Inhalt. Der Code erstellt eine Schaltfläche, die, wenn sie angeklickt wird, mithilfe von jQuery eine POST-Anfrage an den Dapr-Sidecar auf http://localhost:3500/v1.0/bindings/target sendet:

    <!DOCTYPE html>
    <html>
        <head>
            <script src="jquery.min.js"></script>
        </head>
        <body>
            <button onclick="postMessage()">Click me!</button>
        </body>
        <script>
            function postMessage() {
                $.ajax(
                    {
                        type: "POST",
                        url: "http://localhost:3500/v1.0/bindings/target",
                        contentType:"application/json",
                        data: JSON.stringify({"data": "Hello, World!"})
                    });
            }
        </script>
    </html>

Eine solche POST ist möglich, weil der Dapr-Sidecar standardmäßig regionsübergreifende Anfragen zulässt. Du kannst das Verhalten ändern, indem du den Wert des Schalters allowed-origins änderst (siehe Tabelle I-1).

Operator: Definiere eine Azure Event Hub Bindung

Jetzt setzen wir den Hut des Operators auf und definieren, was genau target ist. In dieser Schritt-für-Schritt-Anleitung definieren wir target als einen Azure Event Hub.

Hinweis

Wir gehen davon aus, dass du mit Azure im Allgemeinen vertraut bist und weißt, wie du das Azure Portal und insbesondere die Azure Cloud Shell nutzen kannst.

Befolge diese Schritte, um die Azure Event Hub-Bindung zu definieren:

  1. Melde dich bei Azure Cloud Shell an.

  2. Erstelle einen neuen Azure Event Hub Namensraum:

    az eventhubs namespace create --name <namespace name> --resource-group 
      <resource group name> -l <region, such as westus>
  3. Erstelle einen neuen Azure Event Hub Event Hub:

    az eventhubs eventhub create --name <event hub name> --resource-group 
      <resource group name> --namespace-name <namespace name>
  4. Rufe den Event Hub Verbindungsstring ab. Was du in Schritt 6 brauchst, ist das Feld primaryConnectionString in der Ausgabe:

    az eventhubs namespace authorization-rule keys list --resource-group 
    <resource group name> --namespace-name <namespace name> 
      --name RootManageSharedAccessKey
  5. Erstelle einen neuen Komponentenordner.

  6. Füge eine neue eventhub.yaml-Datei unter dem Ordner hinzu. Du musst die Datei <your connection string> durch den Verbindungsstring aus Schritt 4 ersetzen. Beachte, dass du am Ende des Verbindungsstrings ;<event hub name> an das Ende des Verbindungsstrings anhängen musst, um den genauen Namen des Eventhubs anzugeben:

    apiVersion: dapr.io/v1alpha1
    kind: Component
    metadata:
      name: target
    spec:
      type: bindings.azure.eventhubs
      metadata:
      - name: connectionString
        value: <your connection string>

Ereignisse über Dapr senden

Jetzt sind wir bereit, ein Ereignis über Dapr an target zu senden. Wie du siehst, hat der HTML-Code keine Ahnung von Azure Event Hub. Er veröffentlicht einfach ein Ereignis an den Dapr-Sidecar, der auflöst, was target bedeutet und das Ereignis an das gewünschte Ziel weiterleitet. Um das Ereignis zu senden:

  1. Starte die HTML-Seite in einem Browser.

  2. Starte den Dapr-Seitenwagen:

    dapr run --port 3500 echo a
  3. Klicke auf die Schaltfläche Click Me auf der HTML-Seite.

Okay, es passiert nicht viel. Wenn du dich jedoch in dein Azure Portal einloggst und die eingehenden Nachrichten in deinem Event Hub beobachtest, siehst du, dass Nachrichten über Dapr gesendet werden, wie in Abbildung I-5 dargestellt.

Event Hub metrics in Azure Portal
Abbildung I-5. Event Hub Metriken im Azure Portal

Wie jedes andere Dapr-Feature funktionieren auch die Bindungen gut unter Kubernetes. Wir werden uns das als Nächstes ansehen.

Bindung im Kubernetes-Modus

Falls du es noch nicht bemerkt hast: Die Datei eventhub.yaml ist eine Kubernetes Custom Resource Definition (CRD) Datei. Du kannst diese Datei direkt in einem Kubernetes-Cluster bereitstellen, und Dapr-Sidecars können sie als Bindung verwenden.

Um eventhub.yaml in einem Kubernetes-Cluster einzusetzen, verwende den folgenden Befehl:

kubectl apply -f ./eventhub.yaml

Der Dapr-Operator-Dienst auf Kubernetes überwacht die CRDs der Bindungen auf Änderungen und benachrichtigt alle Dapr-Sidecars im Cluster, wenn eine Änderung festgestellt wird. Das bedeutet, dass du deine Bindungsdefinitionen dynamisch aktualisieren kannst, um deine Anwendungen an verschiedene Quellen zu binden, während die Anwendungen laufen.6 Das ist in vielen Szenarien sehr nützlich. Wenn du zum Beispiel eine Kantenanwendung hast, die in die Cloud ausbricht, kann sie dynamisch entweder an Kanten- oder an Cloud-basierte Dienste gebunden werden, um sich an Umgebungsveränderungen anzupassen.

Zusammenfassung

Diese Einführung hat einen kurzen Überblick über Dapr gegeben, ein sprachneutrales Programmiermodell, das einen Sidecar verwendet, um Microservices-Bausteine - einschließlich Bindungen, Zustandsverwaltung, zuverlässiges Messaging und Akteursfunktionen - für den Anwendungscode bereitzustellen, ohne dass der Anwendungscode Dapr-spezifische Bibliotheken oder SDKs enthalten muss.

Ein wichtiges Ziel von Dapr ist es, die Details der Infrastruktur von den Entwicklern zu abstrahieren. Durch die Dapr-Bausteine wird der Anwendungscode vollständig von der zugrundeliegenden Infrastruktur isoliert, so dass die Anwendung während des Betriebs auf verschiedenen Plattformen konfiguriert und umkonfiguriert werden kann, ohne dass der Anwendungscode geändert werden muss. Dies ist eine leistungsstarke Funktion, die Szenarien wie Kanten- und Multicloud-Anwendungen ermöglicht.

Wir hoffen, du findest Dapr interessant. Im weiteren Verlauf des Buches werden wir auf alle Dapr-Komponenten eingehen und verschiedene Anwendungsszenarien mit Dapr behandeln. Außerdem werden wir dir zeigen, wie du Dapr auf verschiedene Arten erweitern kannst. Wir hoffen, dass du dich der Community anschließt und dabei hilfst, Dapr zu einem einfachen, effizienten und leistungsstarken verteilten Programmiermodell für alle zu machen!

1 Wir haben die gRPC-Unterstützung später eingeführt. Die Dapr-API ist unabhängig von Lieferprotokollen definiert. Zum Zeitpunkt der Erstellung dieses Artikels ziehen wir auch andere kabelgebundene Protokolle wie den Data Distribution Service (DDS) in Betracht, insbesondere für IoT-Szenarien.

2 Dapr bietet konfigurierbare automatische Wiederholungsversuche, um vorübergehende Fehler zu behandeln. Das verhindert jedoch nicht, dass ein Fehler im Anwendungscode auftaucht, wenn die Wiederholungsrichtlinie abläuft.

3 Dapr wird aktiv weiterentwickelt, und die Liste der Komponenten wird wahrscheinlich noch wachsen. Zum Zeitpunkt der Erstellung dieses Artikels denken wir zum Beispiel darüber nach, auch Ausführungsrichtlinien und benutzerdefinierte Middleware als Plug-in-Komponenten zu verwenden. Eine aktuelle Komponentenliste findest du in der Online-Dokumentation von Dapr unter .

4 In diesem Buch gehen wir davon aus, dass du mit den allgemeinen Kubernetes-Begriffen und -Konzepten vertraut bist. Eine detaillierte Einführung in Kubernetes Pods findest du in der Kubernetes-Dokumentation.

5 Auch die API-Oberfläche von Dapr wächst mit der Zeit.

6 Zum Zeitpunkt der Erstellung dieses Artikels ist das Dapr-Team noch dabei, das genaue Verhalten bei der Übernahme neuer CRD-Änderungen zu klären - und ob dafür ein Neustart des Containers erforderlich ist. Bitte informiere dich in der Online-Dokumentation über die neuesten Updates.

Get Dapr lernen now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.