Wie Protobufs für Grpc Services teilen
Wie teilst du .proto-Dateien über verschiedene Teams und Dienste hinweg? – oder teilst sie gar nicht und lässt jeden Dienst die entsprechenden Client-Bibliotheken selbst generieren und paketieren?
In einem aktuellen Kundenprojekt wollten wir gRPC einführen, um die Interprozesskommunikation für Microservices zu vereinfachen.
gRPC ist eine großartige Technologie, die die Produktivität der Entwickler steigert. In diesem Fall würde es den aktuellen Ansatz ersetzen, der darin besteht, interne REST-Dienste mit Akka HTTP und einem halbautomatischen JSON-Encoder/Decoder zu implementieren. Eine beträchtliche Menge an benutzerdefiniertem Code war erforderlich, der in Zukunft vom gRPC-Compiler generiert wird.
Da alle Dienste in Scala geschrieben sind, passt das Projekt ScalaPB perfekt, um Scala-Code aus den .proto-Definitionsdateien zu generieren.
Die Hauptfrage ist:
Wie teilst du die .proto-Dateien über verschiedene Teams und Dienste hinweg?
- oder teilst sie gar nicht und lässt jeden Dienst die entsprechenden Client-Bibliotheken selbst generieren und paketieren?
Ohne eine ordnungsgemäße Definition weiterzumachen, würde die Entwicklererfahrung beeinträchtigen („Bietet der Dienst eine Client-Bibliothek an, oder wollen sie, dass ich die .proto-Datei kopiere?“). Es könnte sogar zu Laufzeitfehlern wegen falscher/veralteter Definitionen führen.
Es wurde viel zu diesem Thema geschrieben. Ich habe einige der nützlichsten Artikel unten verlinkt, in denen die Autoren großartige Arbeit geleistet haben, um über ihre Erfahrungen und Ansätze zu sprechen. Es gibt jedoch kein einheitliches Verständnis darüber, wie man die .proto-Dateien zwischen Diensten teilt.
Im folgenden Abschnitt werde ich die verschiedenen Möglichkeiten untersuchen, .proto-Definitionen über Dienste hinweg zu teilen, und versuchen, die Vor- und Nachteile zu diskutieren.
Möglichkeiten, Protobuf-Definitionen zu teilen
Ein einziges Repository für .proto-Dateien
Alle .proto-Definitionsdateien in einem einzigen Repository ablegen. Eine Ordnerstruktur definieren, um die Dateien zu organisieren. In den meisten Fällen ein Ordner für jeden Dienst.
Dieses Repository kann verwendet werden, um Client-Bibliotheken durch eine Build-Pipeline zu generieren oder kann als Git-Submodul in Dienste eingebunden werden, damit der Code direkt generiert wird.
Zusammenfassung
- Organisiere alle .proto-Dateien in einem einzigen Repository
- Kann als Git-Submodul in andere Projekte eingebunden werden
- Jeder Dienst hat seinen eigenen Ordner
Vorteile
- Alle Dienstdefinitionen an einem Ort
- Definition aller internen Dienste und Modelle
Nachteile
- Das Repository könnte groß werden
Separates Repository für jeden Dienst
Anstatt alle .proto-Dateien in einem einzigen Repository abzulegen, erstellst du ein dediziertes Repository für jeden Dienst. Leg deinen Dienst ins Repository {service} und die entsprechenden .proto-Dateien in ein Repository namens {service}-proto.
Im Vergleich zum Einzelrepository hast du mehr Unabhängigkeit. Deine Builds sind in Dienste aufgeteilt, was zu besser abgegrenzten Abhängigkeiten führt. Du verlinkst nur die Proto-Bibliotheken für die Dienste, die du implementierst und/oder konsumierst.
Zusammenfassung
- Jeder Dienst hat ein separates Repository wie {service}-proto
- Generiere Client-Bibliotheken aus dem Repository oder verlinke sie mit einem Git-Submodul
Vorteile
- Reduziert die Build-Zeit, da Client-Bibliotheken nur einmal erstellt werden
- Bessere Abgrenzung, da eine Bibliothek nur den Code für einen einzigen Dienst enthält
Nachteile
- Viele kleine Repositories und Build-Pipelines zu verwalten
Kopieren und Einfügen von .proto-Dateien
Der Vollständigkeit halber nennen wir die Möglichkeit, die .proto-Dateien zwischen den Diensten, die implementieren oder konsumieren, zu kopieren und einzufügen. Das könnte für schnelle Prototypen oder Szenarien geeignet sein, in denen du nur einen einzigen Server und Client hast. Sollte aber im Allgemeinen vermieden werden.
Du verlierst den Überblick über die verschiedenen Versionen einer Datei. Bei allgemeinen Definitionen verlierst du leicht den Überblick über den Besitzer.
Zusammenfassung
- Kopiere und füge die .proto-Dateien aus dem Server-Repository in das Client-Repository ein
- Generiere den Code lokal
Vorteile
- Einfach zu verwenden, keine zusätzliche Build-Konfiguration erforderlich
Nachteile
- Mehrere Kopien derselben .proto-Definition
- Kopien werden veraltet
- Es könnte unklar werden, welcher Dienst die „Besitzrechte“ an der .proto-Definition hat
gRPC Server Reflection Protocol
- Der Server stellt das Server Reflection Protocol bereit
- Der Client verwendet den Reflexionsdienst, um Definitionen während der Build- oder Laufzeit abzurufen
Vorteile
- Keine
Nachteile
- Wird (noch) nicht von allen Zielsprachen unterstützt
Verwendung von Bibliotheken vs. Git-Submodule
Unabhängig davon, ob du ein einzelnes oder dediziertes Repository für deine .proto-Dateien verwendest, bleibt die Frage, ob du den generierten Code als Bibliotheken erstellst und veröffentlichst oder Git-Submodule verwendest, um die .proto-Dateien zu verlinken.
Lassen wir uns beide Szenarien betrachten.
Veröffentlichen von generiertem Code als Bibliothek
In diesem Fall generierst du deinen Code und paketierst ihn mit dem Paketmanager für deine Zielsprache (z.B. Jar, Gem, npm package, etc.).
Die generierte Bibliothek kann dann vom Server und den Clients verwendet werden, um die Dienste zu implementieren oder zu konsumieren. Bei diesem Ansatz ist es wichtig, den Entwicklungszyklus zu beachten.
Stell dir den Anwendungsfall vor, bei dem du ein Feld zu einer Nachricht hinzufügen musst – etwa so:
message Person {
optional int32 id = 1;
optional string name = 2;
optional string email = 3;
}
Für diese einfache Änderung musst du eine neue Bibliothek mit einem neuen Versionstag erstellen. Angenommen, deine Bibliothek befindet sich in Version 1.0.0, dann gib ihr die Version 1.1.0 für das zusätzliche Feld und veröffentlicht sie in deinem zentralen Repository (z.B. Nexus).
Der Dienst, der den Server implementiert, muss aktualisiert werden. Aktualisiere also die Abhängigkeit von Version 1.0.0 auf 1.1.0. Dann kannst du alle erforderlichen Änderungen am Code vornehmen (z.B. die Zuordnung des neuen Feldes) und die neue Version des Dienstes erstellen und bereitstellen.
Was ich an diesem Ansatz mag, ist die Tatsache, dass er Entwickler fast dazu zwingt, die erforderlichen Datenstrukturen und (gRPC)-Dienste zu planen. Jede Änderung erfordert eine neue Version der Bibliothek. Andererseits kann dies für schnelles Prototyping nervig sein.
Verlinken von .proto-Dateien als Git-Submodul
Deine .proto-Dateien befinden sich in einem dedizierten Git-Repository, nennen wir es hier my-proto. Du verlinkst das Repository dann aus dem Repository deiner Dienste als Git-Submodul.
Um das my-proto-Repository zu deinem Projekt hinzuzufügen, verwende den folgenden Befehl:
$ cd dein-dienst-repository
$ git submodule add https://github.com/myorg/my-proto
Cloning into 'my-proto'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Du findest einen neuen Ordner namens my-proto in deinem Projekt. Du kannst jetzt deine Build-Konfiguration einrichten, um den Code zu generieren.
Beim Klonen des Repositories werden Submodule standardmäßig nicht initialisiert. Du musst den folgenden Befehl nach dem Klonen manuell ausführen:
$ git submodule update --init
Submodule 'my-proto' (https://github.com/myorg/my-proto) registered for
Cloning into 'my-proto'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Du kannst mit dem Submodul wie mit einem normalen Git-Repository arbeiten. Das bedeutet, dass du Änderungen vornehmen kannst, die du committen und pushen kannst, um sie anderen zur Verfügung zu stellen.
Bei diesem Ansatz könnte die anfängliche Entwicklung eines neuen Dienstes einfacher sein, da du die .proto-Definitionen während der Implementierung des Dienstes problemlos ändern und erweitern kannst. Du bist nicht verpflichtet, jede Änderung zu veröffentlichen und die Abhängigkeitsversion zu ändern.
Andererseits erfordert das Arbeiten mit Git-Submodulen etwas Sorgfalt, um sicherzustellen, dass du gegen die richtige Version baust. Dies kann jedoch in deiner CI/CD-Pipeline durchgesetzt werden.
Fazit
Wir haben die verschiedenen Möglichkeiten betrachtet, .proto-Dateien zwischen Diensten zu teilen, und die Vor- und Nachteile untersucht. Dann haben wir über die Unterschiede zwischen der gemeinsamen Nutzung von generiertem gRPC-Code über deinen Paketmanager und dem Verlinken der .proto-Dateien als Git-Submodule in deinem Projekt gesprochen.
Alle Ansätze haben ihre Vor- und Nachteile. Definiere deinen Ansatz und stelle sicher, dass er in deiner Organisation befolgt wird. Das Verlinken von .proto-Dateien als Git-Submodule könnte die bessere Option für schnelles Prototyping sein, wenn du häufige Änderungen an deinen Proto-Dateien erwartest.
Referenzen
- https://medium.com/namely-labs/how-we-build-grpc-services-at-namely-52a3ae9e7c35
- https://github.com/grpc/grpc-java/blob/master/documentation/server-reflection-tutorial.md#enable-server-reflection
- https://jozefcipa.com/blog/sharing-grpc-protobufs-between-microservices/
Wobei können wir Sie unterstützen?
Senden Sie mir einfach eine Nachricht order rufen Sie mich an. Ich freue mich, von Ihnen zu hören.
Marco Rico
Fractional CTO