gRPC - Eine praktische Einführung mit Tipps und Tricks

Als ich mich mit dem Thema gRPC beschäftigt habe, war das Thema ein Monat alt gewesen. Ich dachte als Blog-Schreiber, dass das Thema für ein Artikel schon veraltet ist.

Erst 3 Monate später erschienen erste Artikel, die mich persönlich enttäuscht haben. Die meisten Artikel bezogen sich lediglich auf das Beispiel, dass quasi in Visual Studio per Default-Template schon vorgefertigt zu finden ist, den GreeterService.

Ein weiteres gutes Beispiel zu gRPC hat Hannes Preishuber in Verbindung mit Blazor verfasst:

http://blog.ppedv.de/post/gRPC-und-Blazor

Woanders bezahlt man Geld und man bekommt nur einen GreeterService!

In meinem ersten Artikel habe ich gRPC vorstellt, welche Vorteile es mit sich bringt und in welchen Szenarien gRPC gut einsetzbar ist.

In diesem Artikel möchte ich ein paar praktische Beispiele darbieten und wir bauen unseren eigenen gRPC – Service. Nebenbei möchte ich auch ein paar Raffinessen der Proto-File darstellen.

Let’s go!

In meinem Beispiel verwende ich Visual Studio 2019 mit .NET Core 3.1.

1.) Wir erstellen ein neues Projekt (File->New Project)

2.) Wählen das Template „gRPC Service“ mit aus. Danach auf Next.

image_thumb1

3.) Für unsere Service verwenden wir als Projektnamen: EmployeeService  und die Solution nenne ich GrpcSamples. Danach auf Create.

image_thumb7

4.) In nächsten Dialog überprüfen oder passen wir die .NET Core 3.1 an. Danach auf Create

Nachdem wir die Vorlage erstellt haben, sehen wir im Solution-Explorer (Menü->View->SolutionExplorer), den viel zu oft illustrierten GreeterService.

image_thumb9

Ich muss erwähnen, dass ich mit Begeisterung im deutschen Sprachraum mir einige Artikel gekauft habe und wurde mit dem einfachen GreeterService-Beispiel abgespeist. Das Beispiel ist Gut, um eine Einfache Unäre Kommunikation darzustellen, aber zeigt kaum die Stärken von gRPC. Des Weiteren sind auch weitere benutzerdefinierte Beispiele sehr unterlichtet. Hier möchte ich eine saubere Einstiegslinie aufzeigen!

Code First mit Proto!

Wer einen WCF – Services schon entwickelt hat, wird sich sofort in dem Code First Ansatz in gRPC zurechtfinden.

Zuerst beschreiben wir die Schnittstelle, die sich Proto-File nennt und sind in unserem Projekt im Verzeichnis Protos zu finden.

Unser Code-First Ansatz ist nicht wie in WCF ein C#-Interface mit seinen Methodenbeschreibungen, sondern gRPC hat seine eigene IDL-Language mit dazu gepackt. Der Name lautet, wie zu erahnen, Proto3.

In der greet.proto – File stehen alle Definitionen von Service und seinen RPC-Methode sowie die Message-Strukturen, die wir für unsere Request und Response verwenden.

In unserem Beispiel möchte ich die Fähigkeit aufzeigen, dass wir unsere Proto-File Definition aufteilen können und in jeweils eigene Proto-Files aufsplitten.

Für unser Beispiel benötigen wir 4 Proto-Files.

1.) EmployeeModel -> wir definieren hier unseren eigentlichen Employee-Datensatz in Form einer Proto-Message.

2.) EmployeeRequests -> Hier werden die Anfrage-Messages definiert und verwenden z.B. bei bei der Insert-Methode das EmployeeModel um einen Datensatz mit zu übertragen. Man kann in Proto die Message in eine Composition bringen.

3.) EmployeeResponse -> Wie bei EmployeeRequest, wird in EmployeeResponse die Response-Messages definiert und verwenden je nach UseCase auch das EmployeeModel.

4.) Employee -> Unsere Service Definition mit seinen ganzen RCP – Methoden. Für die Methoden Parameter stehen unser EmployeeRequest bereit und für die Antworten die EmployeeResponse.

Ein kleiner Nachteil bringt das keyword „messages“ mit sich. In der Praxis stellt das message – Schlüsselword, die Beschreibung von Request-Message und Response-Messages, allerdings möchte man nicht jedes Mal, wenn wir einen Datensatz übertragen, den Datensatz an sich auflisten. Das wäre ein redundanter Code. Hierfür kann man das keyword messages auch dafür verwenden um einen Datensatz zu deklarieren.

Also haben wir für messages zwei Beschreibungsfelder. In diesem Fall splitten wir die Proto-File in EmployeeModel.proto (für Datensätze) und in die EmployeeRequest/EmployeeResponse auf. Mit dem Zusatz, dass wir die EmployeeModel-Message als Datentypen in unseren EmployeeRequest/EmployeeResponse verwenden.

Lasst uns die Proto-File erstellen:

Um unsere erste eigene Proto-Datei zu erstellen führen sie folgende Schritte aus:

1.) klicken Sie bitte mit der rechten Maustaste auf den Ordner Protos-> Add -> New Item

2.) Wählen sie folgendes Template aus

image_thumb19

3.) Benennt das Protocol Buffer File nach „Employee.proto“.

4.) Danach klicken Sie auf „Add“

5.) Erstellen Sie eine neue Proto-Datei mit dem Namen: EmployeeModel.proto (Schritt 1-4).

6.) Erstellen Sie eine neue Proto-Datei mit dem Namen: EmployeeRequest.proto (Schritt 1-4).

7.) Erstellen Sie eine neue Proto-Datei mit dem Namen: EmployeeResponse.proto (Schritt 1-4)

Das Proto-Verzeichnis wird dann folgendermaßen aussehen:

image_thumb21

Zuerst öffnen wir die EmployeeModel.proto – Datei

image_thumb23

Was wir bei allen Proto-Dateien in Zeile 1 sehen, ist die Festlegung des Syntax auf Proto3.

In Zeile 3 wird angegeben in welchen Namespace sich später die generierte C#-Klasse befindet.

Um eine Datenstruktur für unseren Employee anzulegen schreibt ihr folgenden Code:

image_thumb25

Mithilfe des Keywords „message“ wird eine Nachricht definiert. In Proto ist es allerdings auch möglich Datensätze zu definieren.

Wichtig vor allem ist, dass man die Felder mit einem Index verzieht.

Bei einfachen Datentypen, wie bool / int32 oder string, erhöht sich der Index immer um den Wert 1. Man benötigt den Index, damit man bei der Serialisierung und Deserialisierung ein Mapping zu den Datentypen hat.

Im nächsten Schritt erweitern wir unser EmployeeModel mit einem Datentyp, den jeder C# Entwickler aus seiner Programmiersprache her kennt. Das Enum!

In Proto3 kann man mithilfe des Schlüsselwortes „enum“ eigene Datentypen erstellen. In unserem Beispiel werden wir eine Aufzählung von Akademische Titel darstellen. Wichtig ist bei den Proto3-Enums, dass man immer einen Default-Status mit in das Enum einbaut. Siehe folgender Code

image_thumb32

In unserem Beispiel haben wir das enum Title erstellt. Der Default-Wert ist in diesem Fall, Nothing mit dem Indexwert 0.

Diesen Datentyp, können wir in unser EmployeeModel implementieren. Siehe folgender Code:

image_thumb34

Ich habe bewusst den Enum Datentyp Title an der zweiten Position unseren EmployeeModels gesetzt, um zu demonstrieren, dass sich auch bei einem Enum der Indexwert sich nur um den Wert 1 erhöht.

Nun wechseln wir in die EmployeRequest.proto – Datei und implementieren folgenden SourceCode

image_thumb63

Mithilfe von „import“ können wir unsere EmployeeModel.proto verfügbar machen und können auf das EmployeeModel zugreifen.

Unsere CreateEmployeeRequest dient lediglich als Wrapper.

In der EmployeeResponse.proto – Datei schaut der Proto-Code

image_thumb69

Die CreateEmployeeResponse ist sehr schlicht gehalten und gibt mit einem einfachen bool an, ob der Datensatz auf der Serverseite auch erfolgreich angelegt wurde.

Zuletzt wechseln wir in die Employee.proto – Datei um unseren Service zu schreiben:

image_thumb67

Zuerst importieren wir unsere vorgefertigten Messages aus den Dateien EmployeeModel.proto (Als RückgabeTyp unseren GetEmployees RPC-Methode), EmployeeRequest.proto und EmployeeResponse (beide werden für unsere CreateEmployee RPC-Methode benötigt).

In Zeile 13 beginnt unsere eigentliche Service-Definition. Hierzu möchte ich sagen, dass wir hier noch nicht den Namen EmployeeService einsetzten, weil das später zu Namenskonflikten führt. Daher den Service-Suffix im Namen weglassen.

In Zeile 14 wird mit rpc die CreateEmployee -Methode definiert und erwartet ein CreateEmployeeRequest als Parameter und gibt eine CreateEmployeeResponse an den Client zurück.

Die Create-Employee – Methode erhält eine einfache Message und gibt eine einfache Message zurück. In diesem Fall spricht man auch von einer unären RPC-Methode.

In Zeile 15: Die GetEmployees benötigt keinen Parameter und gibt lediglich eine Liste an EmployeeModels zurück.

In vielen Beispielen wird void als eine leere message nachgebaut. Google bietet allerdings void als Definition in Proto an. Man muss lediglich in Zeile wie in Zeile 11: die empty.proto importieren.

Da wir eine Liste an den Client zurückgeben, sprechen wir hier von serverseitigem Streaming.

Jetzt kommt protocol buffer ins Spiel!

Bevor wir unsere Projektmappe „rebuilden“, müssen wir unser Proto-Property Einstellungen anpassen:

1.) Rechtsklich auf eine Proto-Datei

2.) Wähle Properties aus.

3.) Setze die Einstellungen wir im Screenshot

image_thumb56

4.) Wende diese Einstellung auch bei den anderen erstellen Proto-Dateien an.

In diesem Fall sagen wir bei Build Action, dass wir den Protocolbuf Compiler beim Kompilierungsvorgang für unsere Proto-Datei verwenden.

Die Einstellung gRPC StubClasses sagt aus, welche Art von C# generiert werden soll. Mit Server only werden nur die Service – Klassen generiert. Spielend einfach!

Wenn wir jetzt unsere EmployeeService-Projektmappe kompilieren, fängt der Protocol Buffer Compiler an die Proto-Dateien auszuwerten und generiert auf der Serverseite unseren gRPC Service in C#-Code. Protocol Buffer bietet dieses Feature auch für jede andere beliebige Programmiersprache an.

Weiter zur Service-Implementierung

Als ich meinen ersten gRPC-Service geschrieben habe, wunderte ich mich, warum ich keine generierten Files in meiner Projekt-Mappe wiedergefunden habe.

Der Protobuf-Compiler hat die C# Files in folgendes Verzeichnis gepackt: EmployeeService\obj\Debug\netcoreapp3.1.

image_thumb73

Dass die erstellen Klassen nicht in der Projektmappe ausfindig sind, ist auch gut so, weil die Klassen sich immer wieder auf neue neu generieren, wenn man die Projektmappe neu kompiliert.

In unserem Beispiel erstellen wir im Projekt-Ordner Service eine neue Klasse mit dem Namen EmployeeService.

image_thumb75

Im nächsten Schritt erben wir von dem eigentlich generierten Code der uns die Protocol Buffer Engine generiert hat.

Wie leiten EmployeeService von der Klasse Employee.EmployeeBase ab. Zusätzlich wird noch ein weiteres wird noch der Namespace EmployeeService.Protos mit using hinzugefügt.

image_thumb77

Damit wir eine Datenbank simulieren, verwenden wir eine Liste von unseren EmployeModel als DB-Mock.

image_thumb88

Als nächstes rufen wir public override und bekommen diese Methoden zum überschreiben angeboten:

image_thumb90

Wir sehen nun die in Proto-Abgebildeten RPC-Methoden, als angebotene virtual – Methoden. Diese überschreiben wir sehr gerne.

image_thumb92

Die zweite Methode GetEmployees streamen wir an den Client eine Liste aller Employees.

Damit wir mithilfe von IServerStreamWriter.WriteAnsync eine Liste an den Client streamen können, müssen wir unsere überrschrieben GetEmployees-RPC Methode als async umdefinieren.

An dieser Stelle ist auch zu erwähnen, dass der Empty Datentyp in der Parameterliste der RPC-Methode in unserem C# Code aus dem Namespace Google.Protobuf.WellKnownTypes stammt.

image_thumb95

Die letzte Codezeile müssen wir in der Startup.cs schreiben.

Hier registrieren wir unseren Service als Request-Endpunkt in der Configure-Methode.

image_thumb97

Danach sollte unserer Service lauffähig sein.

Lass uns den Client schreiben!

Zuerst erstellen wir in unserer Solution ein neues Projekt und wähle eine Konsolen-Anwendung.

image_thumb99

Bennen diese nach EmployeeClient und klicken auf Create.

image_thumb101

Bevor wir unsere erste Zeile Code schreiben klicken wir im SolutionExplorer mit einem Rechtsklick auf unser EmployeeClient-Projekteintrag und wählen Manage NuGet Packages aus.

image_thumb106

Hier installieren wir folgende Packages nach fester Reihenfolge

Grpc.Net.Client:

image_thumb112

Grpc.Tools

image_thumb116

Google.Protobuf

image_thumb118

Danach klicken mit einem Rechtsklick auf unser EmployeeClient-Projekteintrag und erstellen einen Ordner mit dem Namen Protos.

image_thumb121

Der Service ist ja Serverseitig beschrieben, aber der Client weiß nichts von seinen Methoden.

Daher werden Proto-Files auch auf Client-Seite eingesetzt und haben eben den benötigten Ordner für unseren folgenden Schritt angelegt.

Wir kopieren unsere erstellen Proto-Dateien vom EmployeeService-Projekt zu unserem EmployeeClient.

image_thumb124image_thumb125

Wir markieren im EmployeeClient alle unsere Employee-Proto Dateien.

Danach Rechtsklick->Properties.

Hier müssen wir den Proto-Files angeben, das die gRPC-Stub Klassen auf Client-Seite generiert werden.

image_thumb128

Danach können wir unseren ersten zwei Zeilen Code in der Programm.cs schreiben.

image_thumb130

Wir bauen zuerst einen Channel auf und übergeben diesen an unseren EmployeeClient.

Wir ersten uns ein CreateEmployeeRequest-Objekt und instanziieren und initiieren das EmployeeModel-Objekt.

Beim eigentlichen Aufruf werden zwei CreateEmployeeAsync und CreateEmployee angeboten. Für den asynchronen Aufruf muss noch die Main-Methode mit async Task verändert werden.

image_thumb132

Vor unserem ersten Test müssen folgendes tun.

1.) In der SolutionExplorer mit einem Rechtsklick auf die Solution

2.) Set Startup Projects… auswählen

In der folgenden Dialogbox müssen wir folgendes tun:

1.) Wähle Multiple startup projects aus

2.) Setze den Service in der Startreihenfolge an erster Stelle

3.) Wähle bei beiden Projekten den Wert von Action auf Start!

image_thumb134

Das ist auch unser Stichwort. Wir können jetzt unsere erste Methode testen

Wer nicht mit dem Debugger die Kommunikation mitverfolgen möchte kann auch in der Service-Methode die Request-Anfrage erleben und es wird der HTTP – Code ausgegeben.

image_thumb136

Auf der Clientseite kann mit mithilfe des Debuggers sich das CreateEmployeeResponse – Objekt anschauen und sehen, dass wir die Success-Property auf True gesetzt und an den Client übermittelt haben.

Unser zweite Methode die wir implementieren ist eine Auflistung aller Employees.

Dafür schreiben wir folgenden Code:

image_thumb138

Wenn man die zweite Methode testet merkt man, dass unsere Mock-Liste nicht 5-Einträge groß ist.

Bei einer Datenbankanbindung sollte dieser Effekt nicht auftreten.

Hier nochmal die Komplette Übersicht zu unserer Program.cs

image_thumb145

Kommentare sind geschlossen