Nutzung von ProtoBuffer/protobuf Protokoll mit Go

Nutzung von ProtoBuffer/protobuf Protokoll mit Go

Bei Protocol Buffers (kurz protobuf) handelt es sich um ein von Google entwickelter Mechanismus mit dem man sprach- und plattformneutral strukturierte Daten serialisieren kann. Google spricht davon, dass man es mit XML vergleichen kann, allerdings bezeichnen sie Protocol Buffers als kleiner, schneller und einfacher.

Google’s data interchange format

Ausgangspunkt für die Arbeit mit protobuf ist eine .proto Datei (eine IDL - Interface Definition Language). Diese wird mit Hilfe eines Compilers in Source-Code für die gewünschte Programmiersprache übersetzt.

Anders als bei anderen Datenformaten ermöglicht es protobuf die Schnittstellenbeschreibung nachträglich anzupassen ohne sämtliche Clients, die gegen die alte Version compiliert wurden, zu “brechen”.

Weitere Informationen zu Protocol Buffers findet man auf der Projektseite bei Google.

Installation für Go

Wie oben bereits beschrieben wird ein Compiler benötigt, mit dem man die .proto Dateien in Sourcecode überführen kann. Dieser kann entweder als Package über eine entsprechende Packetverwaltung wie apt oder direkt als Binary von der ProtoBuf GitHub-Seite installiert werden.

Zum Beispiel:

$ sudo apt-get install protobuf-compiler

Zusätzlich wird ein sprachabhängiges Plugin benötigt, mit dem der eigentliche Sourcecode erzeugt wird. Ansonsten droht folgender Fehler wenn man Quellcode für Go erzeugen möchte:

$ protoc --go_out=. *.proto
protoc-gen-go: program not found or is not executable
--go_out: protoc-gen-go: Plugin failed with status code 1.

Für Go findet man ein protobuf Plugin im GitHub. Installiert werden kann es mittels:

$ go get -u github.com/golang/protobuf/protoc-gen-go

Durch den go get Aufruf wird das Plugin unter $GOPATH/bin bzw. $GOBIN installiert. Das sollte dazu führen, dass das Plugin im $PATH liegt und vom Compiler gefunden werden kann.

Wichtig ist sicherzustellen, dass das Binary im PATH liegt und zum Compiler gefunden werden kann!

Jetzt ist das Tooling bereit und es kann richtig losgehen.

ProtoBuf Go Code generieren

Im Beispiel soll ein einfacher Datentyp HelloProto serialisiert werden (Datei hello.proto). Er besteht aus zwei Feldern message und important. Jedem Feld muss ein Datentyp sowie eine eindeutige ID zugewiesen werden.

hello.proto

syntax = "proto3";

package main;

message HelloProto {
	string message = 1;
	bool important = 2;
}

Die angegebenen Datentypen können entweder skalar oder zusammengesetzt sein. Mit Hilfe der eindeutigen ID wird das Feld im Binärdatenstorm identifiziert und sollte nicht mehr geändert werden. Neue IDs können später ohne Probleme hinzugefügt werden. Im obigen Beispiel erhält das Attribut message die ID 1 und important die ID 2.

Der Go-Quelllcode kann nun mit folgendem Kommando erzeugt werden:

$ protoc --go_out=. *.proto

Daraus entsteht die Datei hello.pb.go mit einem type Hello World und weiteren Hilfsmethoden.

type HelloWorld struct {
	Message              string   `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
	Important            bool     `protobuf:"varint,2,opt,name=important,proto3" json:"important,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

Protobuf Anwendung

Zum Serialisieren der Datentypen gibt es im github.com/golang/protobuf/proto Package die Funktionen Marshal() bzw. Unmarshal(). Im Beispiel hier wird ein neues Objekt in eine Datei geschrieben und anschließend wieder ausgelesen und ausgegeben.

Ausschnitt aus main.go

hello := &HelloWorld{Message: "Hello World"}
fmt.Println(proto.MarshalTextString(hello))

b, err := proto.Marshal(hello)
if err != nil {
    return fmt.Errorf("could not encode hello %v", err)
}

f, err := os.OpenFile("db.blob", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
    return fmt.Errorf("could not open db %v", err)
}
defer f.Close()

_, err = f.Write(b)
if err != nil {
    return fmt.Errorf("could not write to %v", err)
}

b, err = ioutil.ReadFile("db.blob")
if err != nil {
    return fmt.Errorf("could not read file %v", err)
}

newWorld := &HelloWorld{}
proto.Unmarshal(b, newWorld)

fmt.Println(newWorld.Message)

Roundtrip in Go

Das komplette Beispiel kann auch in GitHub gefunden werden.

package main

import (
	"fmt"
	"io/ioutil"
	"os"

	"github.com/golang/protobuf/proto"
)

func main() {

	hello := &HelloWorld{Message: "Hello World"}
	write(hello)

}

func write(hello *HelloWorld) error {

	fmt.Println(proto.MarshalTextString(hello))

	b, err := proto.Marshal(hello)
	if err != nil {
		return fmt.Errorf("could not encode hello %v", err)
	}

	f, err := os.OpenFile("db.blob", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		return fmt.Errorf("could not open db %v", err)
	}
	defer f.Close()

	_, err = f.Write(b)
	if err != nil {
		return fmt.Errorf("could not write to %v", err)
	}

	b, err = ioutil.ReadFile("db.blob")
	if err != nil {
		return fmt.Errorf("could not read file %v", err)
	}

	newWorld := &HelloWorld{}
	proto.Unmarshal(b, newWorld)

	fmt.Println(newWorld.Message)

	return nil
}

Services mit ProtoBuf

Ein weiterer Aspekt, der in diesem Artikel nicht angesprochen wurde, ist die Definition von Services. Auch das ist möglich. Darüber lassen sich RPC basierte Systeme beschreiben und auch generieren. Eine Anwendung ist das gRPC Protokoll was in einem weiteren Artikel besprochen wird.

service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

22.02.2019

 

Kennen Sie schon das Buch zum Thema?

Der praktische Soforteinstieg für Developer und Softwarearchitekten, die direkt mit Go produktiv werden wollen.

  • Von den Sprachgrundlagen bis zur Qualitätssicherung
  • Architekturstil verstehen und direkt anwenden
  • Idiomatic Go, gRPC, Go Cloud Development Kit
  • Cloud-native Anwendungen erstellen
Microservices mit Go Buch

zur Buchseite beim Rheinwerk Verlag Rheinwerk Computing, ISBN 978-3-8362-7559-0 (als PDF, EPUB, MOBI und Papier)

Kontakt

Source Fellows GmbH

Source Fellows GmbH Logo

Lerchenstraße 31

72762 Reutlingen

Telefon: (0049) 07121 6969 802

E-Mail: info@source-fellows.com