Einführung
Wir freuen uns, die Veröffentlichung einer umfassenden Überarbeitung der Go-API für Protokollpuffer, dem sprachunabhängigen Datenaustauschformat von Google, bekannt zu geben.
Voraussetzungen für die Aktualisierung der API
Die ersten Protokollpufferbindungen für Go wurden im März 2010 von Rob Pike eingeführt . Go 1 wird erst in zwei Jahren veröffentlicht.
In den zehn Jahren seit der ersten Veröffentlichung ist das Paket zusammen mit Go gewachsen und hat sich weiterentwickelt. Die Anfragen seiner Benutzer sind ebenfalls gewachsen.
Viele Leute möchten Programme mit Reflektion schreiben, um mit Protokollpuffermeldungen zu arbeiten. Mit dem Paket
reflectkönnen Sie Go-Typen und -Werte anzeigen, jedoch die Systeminformationen des Protokollpuffertyps weglassen. Beispielsweise müssen wir möglicherweise eine Funktion schreiben, die das gesamte Protokoll betrachtet und alle Felder löscht, die mit vertraulichen Daten versehen sind. Anmerkungen sind nicht Teil des Typsystems von Go.
Ein weiterer häufiger Bedarf besteht darin, andere Datenstrukturen als die vom Protokollpuffer-Compiler generierten zu verwenden, wie beispielsweise einen dynamischen Nachrichtentyp, der Nachrichten des zum Kompilierungszeitpunkt unbekannten Typs darstellen kann.
Wir haben auch festgestellt, dass eine häufige Problemquelle die Schnittstelle ist
proto.Message, das die Werte der generierten Nachrichtentypen identifiziert, überspringt die Beschreibung des Verhaltens dieser Typen. Wenn Benutzer Typen erstellen, die diese Schnittstelle implementieren (häufig versehentlich durch Einbetten einer Nachricht in eine andere Struktur) und Werte dieser Typen an Funktionen übergeben, die generierte Nachrichtenwerte erwarten, stürzen Programme ab oder verhalten sich unvorhersehbar.
Alle drei Probleme haben dieselbe Wurzel und eine Lösung: Die Schnittstelle
Messagemuss das Verhalten der Nachricht vollständig definieren, und Funktionen, die mit Werten arbeiten, Messagemüssen jeden Typ frei akzeptieren, der die Schnittstelle korrekt implementiert.
Da es nicht möglich ist, die vorhandene Definition des Nachrichtentyps unter Beibehaltung der API-Kompatibilität des Pakets zu ändern, haben wir beschlossen, an einer neuen inkompatiblen Hauptversion des Protobuf-Moduls zu arbeiten.
Heute freuen wir uns, dieses neue Modul veröffentlichen zu können. Wir hoffen es gefällt euch.
Betrachtung
Reflexion ist das Flaggschiff der neuen Implementierung. Genau wie das Paket
reflecteine Ansicht der Typen und Werte von Go bietet , bietet das Paket google.golang.org/protobuf/reflect/protoreflect eine Ansicht der Werte gemäß dem Protokollpuffertypsystem.
Eine vollständige Paketbeschreibung
protoreflectwürde für diesen Beitrag zu lange dauern, aber wir werden trotzdem sehen, wie wir die zuvor erwähnte Protokollbereinigungsfunktion schreiben können.
Zuerst müssen wir eine Datei schreiben
.proto, die eine Erweiterung wie google.protobuf.FieldOptions definiert, damit wir die Felder mit vertraulichen Informationen versehen können oder nicht.
syntax = "proto3";
import "google/protobuf/descriptor.proto";
package golang.example.policy;
extend google.protobuf.FieldOptions {
bool non_sensitive = 50000;
}
Mit dieser Option können wir bestimmte Felder als nicht sensibel markieren.
message MyMessage {
string public_name = 1 [(golang.example.policy.non_sensitive) = true];
}
Als nächstes müssen wir eine Go-Funktion schreiben, die einen beliebigen Nachrichtenwert annimmt und alle vertraulichen Felder entfernt.
// Redact pb.
func Redact(pb proto.Message) {
// ...
}
Diese Funktion akzeptiert
proto.Message - eine Schnittstelle, die von allen generierten Nachrichtentypen implementiert wird. Dieser Typ ist ein Alias für den im Paket definierten Typ protoreflect:
type ProtoMessage interface{
ProtoReflect() Message
}
Um zu vermeiden, dass der generierte Nachrichtennamensraum gefüllt wird, enthält die Schnittstelle nur eine Rückgabemethode
protoreflect.Message, die den Zugriff auf den Nachrichteninhalt ermöglicht.
(Warum Alias? Weil es
protoreflect.Messageeine entsprechende Methode gibt, die das Original zurückgibt proto.Message, und wir den Importzyklus zwischen den beiden Paketen vermeiden müssen.)
Die Methode
protoreflect.Message.Rangeruft eine Funktion für jedes ausgefüllte Feld in der Nachricht auf.
m := pb.ProtoReflect()
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
// ...
return true
})
Die Bereichsfunktion wird aufgerufen
protoreflect.FieldDescriptor, um den Typ des Protokollpuffers und des Protoreflekts des Feldes zu beschreiben. Wert, der den Wert des Feldes enthält.
Die Methode
protoreflect.FieldDescriptor.Optionsgibt die Feldoptionen als Nachricht zurück google.protobuf.FieldOptions.
opts := fd.Options().(*descriptorpb.FieldOptions)
(Warum Typzusicherung ? Da das generierte Paket von
descriptorpbabhängt protoreflect, kann das Protoreflect-Paket keinen bestimmten Optionstyp zurückgeben, ohne den Importzyklus aufzurufen.)
Anschließend können wir die Optionen überprüfen, um den Wert der booleschen Variablen unserer Erweiterung zu ermitteln:
if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) {
return true // non-sensitive
}
Beachten Sie, dass hier wir betrachten das Feld Descriptor , nicht das Feld Wert . Die Informationen, die uns interessieren, beziehen sich auf das Protokollpuffertypsystem, nicht auf Go.
Dies ist auch ein Beispiel für einen Bereich, in dem wir die
protoPaket- API vereinfacht haben . Das Original hat proto.GetExtensionsowohl einen Wert als auch einen Fehler zurückgegeben. Das neue gibt proto.GetExtensionnur den Wert zurück und gibt den Standardwert für das Feld zurück, wenn es fehlt. Erweiterungsdecodierungsfehler werden an gemeldet Unmarshal.
Sobald wir das Feld identifiziert haben, das bearbeitet werden muss, ist es einfach genug, es zu löschen:
m.Clear(fd)
Alles in allem sieht unsere Bearbeitungsfunktion folgendermaßen aus:
// Redact pb.
func Redact(pb proto.Message) {
m := pb.ProtoReflect()
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
opts := fd.Options().(*descriptorpb.FieldOptions)
if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) {
return true
}
m.Clear(fd)
return true
})
}
Eine bessere Version könnte rekursiv in Nachrichtenwertfelder absteigen. Wir hoffen, dass dieses einfache Beispiel eine Einführung in die Reflexion in einem Protokollpuffer und dessen Verwendung bietet.
Versionen
Wir nennen die ursprünglichen Versionsprotokollpuffer Go APIv1 und die neuere Version APIv2. Da APIv2 nicht abwärtskompatibel mit APIv1 ist, müssen wir jeweils unterschiedliche Modulpfade verwenden.
(Diese Version der API nicht die gleiche wie die Version des Protokolls Puffer Sprache ist:
proto1, proto2, und proto3. APIv1 und APIv2 - diese bestimmte Implementierung in der Go, die Unterstützung sowohl die Sprachversion proto2und proto3)
im Modul github.com/golang/protobuf - APIv1.
In google.golang.org/protobuf Modul - APIv2. Wir haben die Notwendigkeit genutzt, den Importpfad zu ändern, um zu einem Pfad zu wechseln, der nicht an einen bestimmten Hosting-Anbieter gebunden ist. (Wir betrachten
google.golang.org/protobuf/v2um klarer zu machen, dass dies die zweite Hauptversion der API ist, sich aber langfristig für einen kürzeren Weg als beste Wahl entschieden hat.)
Wir wissen, dass nicht alle Benutzer mit der gleichen Geschwindigkeit auf die neue Hauptversion des Pakets migrieren werden. Einige werden schnell wechseln; andere bleiben möglicherweise auf unbestimmte Zeit in der alten Version. Selbst innerhalb desselben Programms verwenden einige Teile möglicherweise eine API und andere eine andere. Daher ist es wichtig, dass wir weiterhin Programme unterstützen, die APIv1 verwenden.
github.com/golang/protobuf@v1.3.4Ist die neueste Version von APIv1 vor APIv2.github.com/golang/protobuf@v1.4.0Ist eine Version von APIv1, die auf APIv2 basiert. Die API ist dieselbe, aber die grundlegende Implementierung wird von der neuen API unterstützt. Diese Version enthält Funktionen zum Konvertieren zwischenproto.MessageAPIv1 und APIv2, um den Übergang zwischen ihnen zu erleichtern.google.golang.org/protobuf@v1.20.0— APIv2.github.com/golang/protobuf@v1.4.0, , APIv2, APIv1, .
(Warum haben wir mit einer Version begonnen
v1.20.0? Aus Gründen der Übersichtlichkeit. Wir erwarten nicht, dass APIv1 jemals erreicht v1.20.0wird. Daher sollte eine einzige Versionsnummer ausreichen, um eindeutig zwischen APIv1 und APIv2 zu unterscheiden.)
Wir beabsichtigen, APIv1 weiterhin zu unterstützen, ohne Fristen festzulegen.
Diese Anordnung stellt sicher, dass jedes Programm nur eine Protokollpufferimplementierung verwendet, unabhängig davon, welche Version der API es verwendet. Auf diese Weise können Programme die neue API schrittweise oder gar nicht implementieren und gleichzeitig die Vorteile der neuen Implementierung beibehalten. Das Prinzip der Auswahl der Mindestversion bedeutet, dass Programme in der alten Implementierung verbleiben können, bis die Betreuer beschließen, sie auf die neue zu aktualisieren (direkt oder durch Aktualisieren der Abhängigkeiten).
Zusätzliche Funktionen, auf die Sie achten sollten
Das Paket
google.golang.org/protobuf/encoding/protojsonkonvertiert Protokollpuffernachrichten mithilfe der kanonischen JSON-Zuordnung von und nach JSON und behebt eine Reihe von Problemen mit dem alten Paket jsonpb, die nur schwer zu ändern waren, ohne dass bestehende Benutzer neue Probleme hatten.
Das Paket
google.golang.org/protobuf/types/dynamicpbbietet eine Implementierung proto.Messagefür Nachrichten, deren Protokollpuffertyp zur Laufzeit bestimmt wird.
Das Paket
google.golang.org/protobuf/testing/protocmpbietet Funktionen zum Vergleichen des Protokollpuffers einer Nachricht mit einem Paket github.com/google/cmp.
Dieses Paket
google.golang.org/protobuf/compiler/protogenbietet Unterstützung für das Schreiben von Protokollpuffer-Compiler-Plugins.
Fazit
Das Modul
google.golang.org/protobufist eine umfassende Überarbeitung der Go-Unterstützung für den Protokollpuffer und bietet erstklassige Unterstützung für Reflection, benutzerdefiniertes Messaging und eine bereinigte API. Wir beabsichtigen, die vorherige API weiterhin als Wrapper für die neue API zu unterstützen, damit Benutzer die neue API schrittweise in ihrem eigenen Tempo implementieren können.
Unser Ziel mit diesem Update ist es, die Vorteile der alten API zu stärken und ihre Schwächen zu beheben. Nachdem wir jede Komponente der neuen Implementierung fertiggestellt hatten, haben wir begonnen, sie in der Google-Codebasis zu verwenden. Diese schrittweise Einführung gab uns Vertrauen sowohl in die Benutzerfreundlichkeit der neuen API als auch in die bessere Leistung und Korrektheit der neuen Implementierung. Wir sind zuversichtlich, dass es produktionsbereit ist.
Wir freuen uns sehr über diese Veröffentlichung und hoffen, dass sie dem Go-Ökosystem für das nächste Jahrzehnt oder länger gut dienen wird!
Erfahren Sie mehr über den Kurs.