gRPC-Anwendungen interaktiv mit Python ansprechen
Damit Microservices miteinander kommunizieren können, brauchen sie gemeinsame Schnittstellen. Googles Remote Procedure Call Framework gRPC ist dabei eine moderne Alternative zu JSON-HTTP-Requests. Weil es noch relativ jung ist, gibt es keine etablierten Testing- und Debugging-Tools.
Das Jupyter Notebook jedoch eignet sich als interaktiv programmierbarer Python-Client zu gRPC-Servern. Es bietet eine Weboberfläche, in der einzelne Code-Abschnitte vom Browser aus ausgeführt werden können. Durch die Tab-Completion lassen sich sehr einfach valide Requests erstellen, bearbeiten und abschicken. Mit wenig Aufwand kann man Antwortzeiten messen und unterschiedliche Ergebnisse vergleichen. In diesem Artikel möchte ich Sie ermutigen, für Ihre Microservice-Architekturen gRPC als Kommunikationsschnittstelle einzusetzen und diese mit dem Jupyter Notebook zu testen.
Mikroservices müssen miteinander kommunizieren können
Microservice ist eines der Buzz-Wörter, das so ziemlich jedem, der in der IT arbeitet, schon einmal über den Weg gelaufen ist, ganz unabhängig von Ihrer Lieblings-Programmiersprache oder ob Sie als Entwickler, Administrator oder IT-Projektmanager arbeiten. Von dem Prinzip, statt einer großen Anwendung mehrere kleine zu schreiben, erhofft man sich eine bessere Wartbarkeit der Teilkomponenten und unabhängigere Teams [1].
Wenn Sie in einer Microservice-Architektur arbeiten, so werden Sie sich diese Frage gestellt haben: Welche Technologie verwende ich zur Kommunikation der Microservices untereinander und wie definiere und dokumentiere ich den Standard meiner Schnittstellen? Oft verwendet man REST [2] oder ein HTTP-basiertes Interface mit JSON als Datenformat. Seit 2015 ist eine Alternative aus dem Hause Google öffentlich verfügbar: gRPC, Googles Remote Procedure Call (RPC-)Framework erfüllt genau diesen Zweck der Interprozesskommunikation [3].
Googles RPC-Framework gRPC
Als RPC-Framework erlaubt gRPC den Aufruf von Funktionen in externen Systemen. In der Benutzung ähnelt es den üblichen Web-Service-Bibliotheken: Der Server muss definieren, was passieren soll, wenn man einen bestimmten Endpunkt aufruft. Wie bei jeder anderen API müssen sich Server und Client darüber einig sein, welche Endpunkte zur Verfügung stehen und wie diese zu benutzen und zu interpretieren sind. Bei gRPC wird solches in der Regel explizit über Protocol-Buffers-Definitionen (Protobuf) geregelt. In diesen Dateien definiert man, welche Funktionen zur Verfügung stehen und kann für jede davon angeben, welche Parameter mit der Anfrage geschickt werden und was als Rückgabewert erwartet werden kann. Diese Protobuf-Definitionen werden dann über einen Compiler in eine Programmiersprache seiner Wahl übersetzt. Zur Zeit unterstützt gRPC C++, Java, Python, Go, Ruby, C#, Node.js, Android Java, Objective-C, PHP und Dart. Es gibt auch eine JavaScript-Webversion, welche im Beta-Status verfügbar ist [4].
Unter der Haube spricht gRPC HTTP/2 und bietet damit auch die Möglichkeit der Verschlüsselung und des Full-Duplex-Streamings an. Es können also sowohl die Anfragen, als auch die Antworten als Stream übermittelt werden. Aus Performancegründen haben sich die gRPC-Entwickler gegen einige REST-Konventionen entschieden [5]: Zum Beispiel benutzen sie statische Pfade, sodass man beim Interpretieren von Anfragen nur auf die expliziten Daten zugreifen muss und nicht auf Parameter aus dem Pfad oder GET-Query-Parameter. Des Weiteren wurden Status-Codes festgelegt, die bewusster auf die Verwendung in APIs abzielen. Die Payload wird als serialisierter Protobuf-Bytecode verschickt und ist damit deutlich schmaler als JSON oder XML.
Testen von Server- und Client-Anwendungen
Bei der Arbeit mit Schnittstellen stößt man immer wieder auf das Henne-Ei-Problem: Schreibt man zuerst den Server, dann hat man keinen Client, um den Server zu testen. Schreibt man zuerst den Client, dann braucht man einen Server, damit das Ganze überhaupt funktioniert. Das kann dazu führen, dass man beide Dienste schreibt und sich viele Fehler erst bemerkbar machen, wenn der erste "richtige" Request durch das System läuft. Je nach dem wie sauber man gearbeitet hat, wird das Debuggen nicht einfacher: "Wo genau liegt der Fehler? Vielleicht wird die Anfrage richtig formuliert und abgeschickt, nur mit dem Netzwerk stimmt etwas nicht."
Für HTTP-APIs gibt es Dienste wie apiblueprint [6] oder httpbin [7], die helfen, das Problem zu entkoppeln. Für gRPC ist das Ganze noch nicht so ausgereift: Auf Github findet man einige Ansätze für gRPC-Clients, zum Beispiel grpcc [8] oder grpcurl [9]. Aber gerade, wenn die APIs größer und komplizierter werden, stößt man bei diesen Tools an die Grenzen. Die Entwicklung einer eigenen Testsoftware in seiner präferierten Entwicklungsumgebung kann da angenehmer erscheinen. Google scheint sich der Problematik auch bewusst zu sein: Zur Zeit steht das Verschicken von gRPC-Requests direkt aus dem Browser auf der Roadmap des gRPC-Web-Projektes [10].
GRPC-Server-Schnittstellen interaktiv testen
Schreibt man einen Server, dann kommt der Punkt, an dem man seine Funktionalität testen möchte. Dieser Check ist größer als ein Unit-Test, da er nicht nur einen kleinen Codeabschnitt überprüft. Man könnte ihn Integrationstest nennen, er wird aber oft inoffizieller angewendet. Er antwortet mehr auf die Frage: "Funktioniert alles so, wie ich es erwartet habe?", als dass er den Entwicklungsprozess kontinuierlich überwacht. Wenn man eine Webseite entwickelt, so würde man zum Beispiel irgendwann die Seite zum Testen lokal im Browser aufrufen.
Ähnlich ist die Situation für gRPC-Server: Die API ist komplett definiert, dokumentiert über Protobuf-Definitionen, und nachdem man alles implementiert hat nagt einen die Frage: "Klappt es denn?". Wie oben erwähnt, hat man zur Zeit die Möglichkeit, auf Kommandozeilen-Tools zurückzugreifen oder seine eigene Testsoftware zu erstellen: Wenn man sich für letzteres entscheidet, dann lohnt es sich, den Client in einer interaktiven Programmierumgebung zu schreiben [11], in welcher man das Programm verändern und weiter entwickeln kann, während es bereits läuft. Dieses Vorgehen eignet sich, wenn eine klare Spezifikation des Tests nicht schon vorher festgelegt werden kann. Zum Beispiel, weil man explorativ auf die Funktionalität seiner API zugreifen will.
Das Jupyter Notebook
Das Jupyter Notebook ist eine Software, die interaktives Programmieren leicht und zugänglich macht. Es wird viel im wissenschaftlichen Bereich und von Data Scientists genutzt. Es basiert auf dem Konzept von Scriptsprachen mit Kommandozeilen-Interpretern, die erlauben, dass bei der Programmierung immer nur ein Teil des Programmes geschrieben werden muss, der dann direkt ausgeführt wird. Das Programm braucht also nicht
"fertig" implementiert werden, bevor man es kompilieren und laufen lassen kann.
Das Jupyter Notebook wurde ursprünglich für Python geschrieben, unterstützt mittlerweile aber auch Programmiersprachen in sogenannten Kernels. Mit dabei sind R und Julia, und für viele weitere Sprachen gibt es Community-Projekte [12].
Mit dem Programm Jupyter Notebook erstellt man Dokumente, die passenderweise Jupyter Notebooks genannt werden. In diesen Notebooks ist sowohl Programmiercode enthalten, als auch formatierter Text, mathematische Formeln und Grafiken. Es enthält damit sowohl den für Menschen geschriebenen Teil – also die Erklärungen, Tabellen und Resultate – als auch die Teile, die vom Computer ausgeführt werden können. Jedes Notebook besteht aus mehreren Zellen und jede Zelle kann einzeln in beliebiger Reihenfolge ausgewählt, auf Wunsch geändert und ausgeführt werden. Weil das Jupyter Notebook über den Browser bedient wird, besteht die Möglichkeit, es in einem Docker-Container laufen zu lassen. Den besten Eindruck über das, was alles möglich ist, erhält man, wenn man es einfach direkt online ausprobiert [13].
Warum Jupyter für das Testen von gRPC-Anwendungen geeignet ist
Es ist nicht nur das Testen nach dem initialen Schreiben eines Services: Auch für Auffälligkeiten im täglichen Betrieb eines Servers, kann es erforderlich sein, eine leichte und einfache Möglichkeit zu schaffen, seine Interfaces direkt anzusprechen. Oft hat man einen Standard-Request, der beim Debuggen nur leicht verändert werden muss. Davon könnten einige Elemente zufällig erstellt werden, zum Beispiel Zahlen oder Daten. Bestimmte Parameter sind gegebenenfalls nicht selbsterklärend und benötigen einen Kommentar. Unter Umständen möchte man auch dieses Test-Tool im Team teilen, um anderen einen direkten Weg zu geben, seinen Service zu erreichen.
Genau für diese Anforderungen lässt sich ein Notebook mit dem Python-Kernel für einen Endpunkt oder Service erstellen. Darin kann eine Abfrage in einer Zelle vordefiniert werden, und einzelne Abschnitte dokumentiert werden. Danach kann man über ein einfaches [Shift]+[Enter] die Zelle ausführen: Hat man es so programmiert, wird der Request abgeschickt und das Ergebnis direkt angezeigt. Der Coding-Aufwand im Jupyter Notebook ist dabei identisch zu jeder anderen Programmierumgebung.
Im Folgenden sind einige Gründe aufgelistet, warum Jupyter eine gute Basis bietet, um seine API zu testen:
- Das Jupyter Notebook ist Python:
Und das vollwertig, ohne Einschränkungen! Das ist besonders dann von Vorteil, wenn andere Tools an ihre Grenzen stoßen. Man hat kompletten Zugriff auf alle Funktionen der gRPC-Python-Bibliothek. - Tab-Vervollständigung und Zugriff auf die Dokumentation:
Jupyter unterstützt Tab-Vervollständigung: Man schreibt einfach ein objekt_name.<TAB> und es werden die Attribute dieses Objektes angezeigt. Sogar die Attribute, die erst zur Laufzeit des Objektes hinzugefügt wurden. Tippt man ein ? hinter den Objektnamen, so wird die dazugehörige Dokumentation angezeigt, mit ?? erhält man Zugriff auf den Quellcode. - Auf Fehler spontan reagieren können:
Im Fehlerfall wird das laufende Programm nicht beendet, sondern nur die Ausführung der aktuellen Zelle unterbrochen. Sollte der Service eine Fehlermeldung zurückliefern, hat man so die Möglichkeit, weitere Untersuchungen anzustellen und an der Fehlerbehebung zu arbeiten, ohne dass man sich vorher Gedanken um try-catch-Blöcke machen muss. - Unterstützung von Full-Duplex-Streaming:
Dadurch, dass man im Jupyter Notebook Zellen gezielt auswählen und per Klick ausführen kann, ist es kein Problem, einen Request-Stream manuell zu steuern. Das ist insbesondere hilfreich, wenn man einen Request im Server durch-debuggen möchte, bevor der nächste Teil des Streams geschickt werden soll. - Mit Cell-Magic die Zeit einer Anfrage messen:
Für Jupyter Python-Kernel gibt es die sogenannte Cell-Magic [14]. Mit dem Befehl %%timeit als erste Zeile einer Zelle wird der Code in dieser Zelle mehrmals ausgeführt, dabei die Zeit gemessen und eine einfache Statistik angezeigt. - Zugriff auf den kompletten wissenschaftlichen Python-Stack:
Sollten in der Antwort viele numerische Daten enthalten sein, profitiert man von Python-Libraries wie matplotlib [15] oder Bokeh [16], mit denen man Grafiken erstellen kann. Mit Pandas wird das Speichern von tabellarischen Daten als Excel-Datei zum Einzeiler [17]. - Koordinaten auf interaktiven Karten plotten:
Wenn man einen Dienst schreibt, der mit Geodaten arbeitet, lohnt sich ein Blick auf Folium [18]. Es erlaubt das Einbinden von Open-Street-Map-Karten mit Markern direkt in das Notebook. - Gemacht, um geteilt zu werden:
Jupyter Notebook-Dokumente sind dafür gemacht, dass sie aussagekräftigere Dokumentation enthalten als es normaler Code würde. Wenn Text im Markdown-Format gerendert wird, lässt es sich komfortabler lesen. Es sei dabei jedoch angemerkt, dass die kompilierten Protobuf-Dateien zur Verfügung stehen und im PYTHONPATH vorliegen müssen. Wer also das Notebook weitergeben möchte, muss auch diese Dateien teilen.
Fazit
Für meine Arbeit mit gRPC-Interfaces nutze ich Notebooks, die mit dem eigentlichen Code des Servers in einem Repository liegen. Die Notebooks beinhalten eine etwas ausführlichere Dokumentation, und sind so geschrieben, dass ein einfaches Ausführen aller Zellen einen Request an einen Test-Server abschickt und die Antwort ausgibt. Auf diese Weise gibt es zu jedem Dienst immer auch direkt einen Client, der leichtes Testen ermöglicht. Man profitiert von der Tab-Completion und hat die Möglichkeit, Anfragen als Stream abzuschicken. All das funktioniert technisch einwandfrei. Ob man diese Art und Weise der Interaktion mit seiner API anderen Methoden vorzieht, ist im Endeffekt jedoch zum großen Teil Geschmackssache.
Meiner Meinung nach gibt es noch viel Potential für Softwarelösungen, die das Testen von gRPC-Schnittstellen weiter vereinfachen könnten. Besonders gespannt bin ich auf Entwicklungen in grpc-web, bei denen gRPC Anfragen aus dem Browser verschickt werden sollen. Bis dahin kann ich sehr empfehlen, dem Jupyter Notebook eine Chance zu geben: Verfassen Sie einen Client zum Testen Ihrer gRPC-Microservices in einem Notebook!
- Eberhard Wolff, 2015: Microservices Primer: A Short Overview
- REST
- gRPC
- Github: JavaScript-Webversion im Beta-Status
- gRPC: FAQ
- apiblueprint
- httpbin
- Github: grpcc
- Github: grpcurl
- Github: Roadmap des gRPC-Web-Projektes
- Wikipedia: Interaktive Programmierung
- Github: Jupyter kernels
- Jupyter ausprobieren
- Cell-Magic
- matplotlib
- Bokeh
- Pandas
Speichern von tabellarischen Daten als Excel-Datei - Folium