Go Opaque API: Manuelle Migration

Beschreibt eine manuelle Migration zur Opaque API.

Die Opaque API ist die neueste Version der Protocol Buffers Implementierung für die Programmiersprache Go. Die alte Version wird jetzt Open Struct API genannt. Eine Einführung finden Sie im Blogbeitrag Go Protobuf: Releasing the Opaque API.

Dies ist eine Benutzeranleitung zur Migration von Go Protobuf Verwendungen von der älteren Open Struct API zur neuen Opaque API.

Der Leitfaden für generierten Code liefert weitere Details. Diese Anleitung vergleicht die alte und die neue API Seite an Seite.

Nachrichtenkonstruktion

Angenommen, es gibt eine protobuf-Nachricht, die wie folgt definiert ist

message Foo {
  uint32 uint32 = 1;
  bytes bytes = 2;
  oneof union {
    string    string = 4;
    MyMessage message = 5;
  }
  enum Kind {  };
  Kind kind = 9;
}

Hier ist ein Beispiel, wie diese Nachricht aus literalen Werten konstruiert wird

Open Struct API (alt)Opaque API (neu)
m := &pb.Foo{
  Uint32: proto.Uint32(5),
  Bytes:  []byte("hello"),
}
m := pb.Foo_builder{
  Uint32: proto.Uint32(5),
  Bytes:  []byte("hello"),
}.Build()

Wie Sie sehen, ermöglichen die Builder-Strukturen eine nahezu 1:1-Übersetzung zwischen der Open Struct API (alt) und der Opaque API (neu).

Verwenden Sie im Allgemeinen Builder zur besseren Lesbarkeit. Nur in seltenen Fällen, wie z. B. beim Erstellen von Protobuf-Nachrichten in einer heißen inneren Schleife, kann es vorteilhafter sein, Setter anstelle von Buildern zu verwenden. Weitere Details finden Sie in den Opaque API FAQ: Soll ich Builder oder Setter verwenden?.

Eine Ausnahme zum obigen Beispiel ist die Arbeit mit Oneofs: Die Open Struct API (alt) verwendet für jeden Oneof-Fall einen Wrapper-Strukturtyp, während die Opaque API (neu) Oneof-Felder wie normale Nachrichtenfelder behandelt.

Open Struct API (alt)Opaque API (neu)
m := &pb.Foo{
  Uint32: myScalar,  // could be nil
  Union:  &pb.Foo_String{myString},
  Kind:   pb.Foo_SPECIAL_KIND.Enum(),
}
m := pb.Foo_builder{
  Uint32: myScalar,
  String: myString,
  Kind:   pb.Foo_SPECIAL_KIND.Enum(),
}.Build()

Für die Menge der Go-Strukturfelder, die mit einer Oneof-Union verbunden sind, darf nur ein Feld gefüllt werden. Wenn mehrere Oneof-Fallfelder gefüllt sind, gewinnt das letzte (in der Reihenfolge der Felddeklaration in Ihrer .proto-Datei).

Skalare Felder

Angenommen, es gibt eine Nachricht, die mit einem skalaren Feld definiert ist

message Artist {
  int32 birth_year = 1;
}

Protobuf-Nachrichtenfelder, für die Go skalare Typen verwendet (bool, int32, int64, uint32, uint64, float32, float64, string, []byte und enum), haben Get- und Set-Zugriffsmethoden. Felder mit expliziter Präsenz haben auch Has- und Clear-Methoden.

Für ein Feld vom Typ int32 namens birth_year werden die folgenden Zugriffsmethoden dafür generiert

func (m *Artist) GetBirthYear() int32
func (m *Artist) SetBirthYear(v int32)
func (m *Artist) HasBirthYear() bool
func (m *Artist) ClearBirthYear()

Get gibt einen Wert für das Feld zurück. Wenn das Feld nicht gesetzt ist oder der Nachrichtenempfänger nil ist, gibt es den Standardwert zurück. Der Standardwert ist der Zero Value, es sei denn, er wird explizit mit der Default-Option gesetzt.

Set speichert den bereitgestellten Wert im Feld. Es führt zu einem Panic, wenn es auf einem nil-Nachrichtenempfänger aufgerufen wird.

Bei Byte-Feldern wird das Aufrufen von Set mit einem nil []byte als gesetzt betrachtet. Zum Beispiel gibt das sofortige Aufrufen von Has danach true zurück. Das sofortige Aufrufen von Get danach gibt ein Slice der Länge Null zurück (kann entweder nil oder ein leeres Slice sein). Benutzer sollten Has zur Bestimmung der Präsenz verwenden und sich nicht darauf verlassen, ob Get nil zurückgibt.

Has meldet, ob das Feld gefüllt ist. Es gibt false zurück, wenn es auf einem nil-Nachrichtenempfänger aufgerufen wird.

Clear löscht das Feld. Es führt zu einem Panic, wenn es auf einem nil-Nachrichtenempfänger aufgerufen wird.

Beispiel-Codeausschnitte mit einem String-Feld in

Open Struct API (alt)Opaque API (neu)
// Getting the value.
s := m.GetBirthYear()

// Setting the field.
m.BirthYear = proto.Int32(1989)

// Check for presence.
if s.BirthYear != nil {  }

// Clearing the field.
m.BirthYear = nil
// Getting the field value.
s := m.GetBirthYear()

// Setting the field.
m.SetBirthYear(1989)

// Check for presence.
if m.HasBirthYear() {  }

// Clearing the field
m.ClearBirthYear()

Nachrichtenfelder

Angenommen, es gibt eine Nachricht, die mit einem nachrichtentyp-Feld definiert ist

message Band {}

message Concert {
  Band headliner = 1;
}

Protobuf-Nachrichtenfelder vom Typ Nachricht haben Get-, Set-, Has- und Clear-Methoden.

Für ein nachrichtentyp-Feld namens headliner werden die folgenden Zugriffsmethoden dafür generiert

func (m *Concert) GetHeadliner() *Band
func (m *Concert) SetHeadliner(*Band)
func (m *Concert) HasHeadliner() bool
func (m *Concert) ClearHeadliner()

Get gibt einen Wert für das Feld zurück. Es gibt nil zurück, wenn es nicht gesetzt ist oder wenn es auf einem nil-Nachrichtenempfänger aufgerufen wird. Die Prüfung, ob Get nil zurückgibt, ist äquivalent zur Prüfung, ob Has false zurückgibt.

Set speichert den bereitgestellten Wert im Feld. Es führt zu einem Panic, wenn es auf einem nil-Nachrichtenempfänger aufgerufen wird. Das Aufrufen von Set mit einem nil-Zeiger ist äquivalent zum Aufrufen von Clear.

Has meldet, ob das Feld gefüllt ist. Es gibt false zurück, wenn es auf einem nil-Nachrichtenempfänger aufgerufen wird.

Clear löscht das Feld. Es führt zu einem Panic, wenn es auf einem nil-Nachrichtenempfänger aufgerufen wird.

Beispiel-Codeausschnitte

Open Struct API (alt)Opaque (neu)
// Getting the value.
b := m.GetHeadliner()

// Setting the field.
m.Headliner = &pb.Band{}

// Check for presence.
if s.Headliner != nil {  }

// Clearing the field.
m.Headliner = nil
// Getting the value.
s := m.GetHeadliner()

// Setting the field.
m.SetHeadliner(&pb.Band{})

// Check for presence.
if m.HasHeadliner() {  }

// Clearing the field
m.ClearHeadliner()

Wiederholte Felder

Angenommen, es gibt eine Nachricht, die mit einem wiederholten nachrichtentyp-Feld definiert ist

message Concert {
  repeated Band support_acts = 2;
}

Wiederholte Felder haben Get- und Set-Methoden.

Get gibt einen Wert für das Feld zurück. Es gibt nil zurück, wenn das Feld nicht gesetzt ist oder der Nachrichtenempfänger nil ist.

Set speichert den bereitgestellten Wert im Feld. Es führt zu einem Panic, wenn es auf einem nil-Nachrichtenempfänger aufgerufen wird. Set speichert eine Kopie des Slice-Headers, der bereitgestellt wird. Änderungen am Slice-Inhalt sind im wiederholten Feld sichtbar. Daher gibt das Aufrufen von Get unmittelbar nach dem Aufrufen von Set mit einem leeren Slice denselben Slice zurück. Für die Wire- oder Text-Marshaling-Ausgabe ist ein übergebener nil-Slice von einem leeren Slice nicht zu unterscheiden.

Für ein wiederholtes nachrichtentyp-Feld namens support_acts auf der Nachricht Concert werden die folgenden Zugriffsmethoden dafür generiert

func (m *Concert) GetSupportActs() []*Band
func (m *Concert) SetSupportActs([]*Band)

Beispiel-Codeausschnitte

Open Struct API (alt)Opaque API (neu)
// Getting the entire repeated value.
v := m.GetSupportActs()

// Setting the field.
m.SupportActs = v

// Get an element in a repeated field.
e := m.SupportActs[i]

// Set an element in a repeated field.
m.SupportActs[i] = e

// Get the length of a repeated field.
n := len(m.GetSupportActs())

// Truncate a repeated field.
m.SupportActs = m.SupportActs[:i]

// Append to a repeated field.
m.SupportActs = append(m.GetSupportActs(), e)
m.SupportActs = append(m.GetSupportActs(), v...)

// Clearing the field.
m.SupportActs = nil
// Getting the entire repeated value.
v := m.GetSupportActs()

// Setting the field.
m.SetSupportActs(v)

// Get an element in a repeated field.
e := m.GetSupportActs()[i]

// Set an element in a repeated field.
m.GetSupportActs()[i] = e

// Get the length of a repeated field.
n := len(m.GetSupportActs())

// Truncate a repeated field.
m.SetSupportActs(m.GetSupportActs()[:i])

// Append to a repeated field.
m.SetSupportActs(append(m.GetSupportActs(), e))
m.SetSupportActs(append(m.GetSupportActs(), v...))

// Clearing the field.
m.SetSupportActs(nil)

Maps (Zuordnungen)

Angenommen, es gibt eine Nachricht, die mit einem Map-typ-Feld definiert ist

message MerchBooth {
  map<string, MerchItems> items = 1;
}

Map-Felder haben Get- und Set-Methoden.

Get gibt einen Wert für das Feld zurück. Es gibt nil zurück, wenn das Feld nicht gesetzt ist oder der Nachrichtenempfänger nil ist.

Set speichert den bereitgestellten Wert im Feld. Es führt zu einem Panic, wenn es auf einem nil-Nachrichtenempfänger aufgerufen wird. Set speichert eine Kopie der bereitgestellten Map-Referenz. Änderungen an der bereitgestellten Map sind im Map-Feld sichtbar.

Für ein Map-Feld namens items auf der Nachricht MerchBooth werden die folgenden Zugriffsmethoden dafür generiert

func (m *MerchBooth) GetItems() map[string]*MerchItem
func (m *MerchBooth) SetItems(map[string]*MerchItem)

Beispiel-Codeausschnitte

Open Struct API (alt)Opaque API (neu)
// Getting the entire map value.
v := m.GetItems()

// Setting the field.
m.Items = v

// Get an element in a map field.
v := m.Items[k]

// Set an element in a map field.
// This will panic if m.Items is nil.
// You should check m.Items for nil
// before doing the assignment to ensure
// it won't panic.
m.Items[k] = v

// Delete an element in a map field.
delete(m.Items, k)

// Get the size of a map field.
n := len(m.GetItems())

// Clearing the field.
m.Items = nil
// Getting the entire map value.
v := m.GetItems()

// Setting the field.
m.SetItems(v)

// Get an element in a map field.
v := m.GetItems()[k]

// Set an element in a map field.
// This will panic if m.GetItems() is nil.
// You should check m.GetItems() for nil
// before doing the assignment to ensure
// it won't panic.
m.GetItems()[k] = v

// Delete an element in a map field.
delete(m.GetItems(), k)

// Get the size of a map field.
n := len(m.GetItems())

// Clearing the field.
m.SetItems(nil)

Oneofs

Für jede Oneof-Union-Gruppierung gibt es eine Which-, Has- und Clear-Methode auf der Nachricht. Es gibt auch eine Get-, Set-, Has- und Clear-Methode auf jedem Oneof-Fallfeld in dieser Union.

Angenommen, es gibt eine Nachricht, die mit den Oneof-Feldern image_url und image_data in avatar wie folgt definiert ist

message Profile {
  oneof avatar {
    string image_url = 1;
    bytes image_data = 2;
  }
}

Die generierte Opaque API für diesen Oneof wird sein

func (m *Profile) WhichAvatar() case_Profile_Avatar {  }
func (m *Profile) HasAvatar() bool {  }
func (m *Profile) ClearAvatar() {  }

type case_Profile_Avatar protoreflect.FieldNumber

const (
  Profile_Avatar_not_set_case case_Profile_Avatar = 0
  Profile_ImageUrl_case case_Profile_Avatar = 1
  Profile_ImageData_case case_Profile_Avatar = 2
)

Which meldet, welches Fallfeld gesetzt ist, indem es die Feldnummer zurückgibt. Es gibt 0 zurück, wenn keines gesetzt ist oder wenn es auf einem nil-Nachrichtenempfänger aufgerufen wird.

Has meldet, ob eines der Felder innerhalb des Oneof gesetzt ist. Es gibt false zurück, wenn es auf einem nil-Nachrichtenempfänger aufgerufen wird.

Clear löscht das aktuell gesetzte Fallfeld im Oneof. Es führt zu einem Panic auf einem nil-Nachrichtenempfänger.

Die generierte Opaque API für jedes Oneof-Fallfeld wird sein

func (m *Profile) GetImageUrl() string {  }
func (m *Profile) GetImageData() []byte {  }

func (m *Profile) SetImageUrl(v string) {  }
func (m *Profile) SetImageData(v []byte) {  }

func (m *Profile) HasImageUrl() bool {  }
func (m *Profile) HasImageData() bool {  }

func (m *Profile) ClearImageUrl() {  }
func (m *Profile) ClearImageData() {  }

Get gibt einen Wert für das Fallfeld zurück. Es gibt den Zero Value zurück, wenn das Fallfeld nicht gesetzt ist oder wenn es auf einem nil-Nachrichtenempfänger aufgerufen wird.

Set speichert den bereitgestellten Wert im Fallfeld. Es löscht auch implizit das zuvor im Oneof-Union gefüllte Fallfeld. Das Aufrufen von Set auf einem Oneof-Nachrichten-Fallfeld mit einem nil-Wert setzt das Feld auf eine leere Nachricht. Es führt zu einem Panic, wenn es auf einem nil-Nachrichtenempfänger aufgerufen wird.

Has meldet, ob das Fallfeld gesetzt ist oder nicht. Es gibt false zurück, wenn es auf einem nil-Nachrichtenempfänger aufgerufen wird.

Clear löscht das Fallfeld. Wenn es zuvor gesetzt war, wird auch die Oneof-Union gelöscht. Wenn die Oneof-Union auf ein anderes Feld gesetzt ist, wird die Oneof-Union nicht gelöscht. Es führt zu einem Panic, wenn es auf einem nil-Nachrichtenempfänger aufgerufen wird.

Beispiel-Codeausschnitte

Open Struct API (alt)Opaque API (neu)
// Getting the oneof field that is set.
switch m.GetAvatar().(type) {
case *pb.Profile_ImageUrl:
   = m.GetImageUrl()
case *pb.Profile_ImageData:
   = m.GetImageData()
}

// Setting the fields.
m.Avatar = &pb.Profile_ImageUrl{"http://"}
m.Avatar = &pb.Profile_ImageData{img}

// Checking whether any oneof field is set
if m.Avatar != nil {  }

// Clearing the field.
m.Avatar = nil

// Checking if a specific field is set.
_, ok := m.GetAvatar().(*pb.Profile_ImageUrl)
if ok {  }

// Clearing a specific field
_, ok := m.GetAvatar().(*pb.Profile_ImageUrl)
if ok {
  m.Avatar = nil
}

// Copy a oneof field.
m.Avatar = src.Avatar
// Getting the oneof field that is set.
switch m.WhichAvatar() {
case pb.Profile_ImageUrl_case:
   = m.GetImageUrl()
case pb.Profile_ImageData_case:
   = m.GetImageData()
}

// Setting the fields.
m.SetImageUrl("http://")
m.SetImageData([]byte("…"))

// Checking whether any oneof field is set
if m.HasAvatar() {  }

// Clearing the field.
m.ClearAvatar()

// Checking if a specific field is set.
if m.HasImageUrl() {  }

// Clearing a specific field.
m.ClearImageUrl()

// Copy a oneof field
switch src.WhichAvatar() {
case pb.Profile_ImageUrl_case:
  m.SetImageUrl(src.GetImageUrl())
case pb.Profile_ImageData_case:
  m.SetImageData(src.GetImageData())
}

Reflexion

Code, der das Go reflect-Paket auf Proto-Nachrichtentypen verwendet, um auf Strukturfelder und Tags zuzugreifen, funktioniert bei der Migration weg von der Open Struct API nicht mehr. Der Code muss auf protoreflect migriert werden.

Einige gängige Bibliotheken verwenden Go reflect im Hintergrund, Beispiele sind