Einführung in die Google Protocol Buffers

Google Protocol Buffers: Mark-Set-Go!

Marc Teufel

Google stellt seine Protocol Buffer, einen Teil der eigenen Infrastruktur, der Open Source Community zur Verfügung. Protocol Buffer sind ein eigens von Google entwickeltes Format, das den Austausch strukturierter Daten zwischen Anwendungen vereinfachen soll und als Alternative zu XML betrachtet werden kann.

Ursprünglich für die interne Verwendung entwickelt, stellt Google sein eigenes Datenaustauschformat (die so genannten Protocol Buffers) nun als Open-Source-Projekt der breiten Öffentlichkeit zur Verfügung. Protocol Buffer sind ein binäres Format um strukturierte Daten zwischen Anwendungen, die wiederum in unterschiedlichen Programmiersprachen implementiert sein können, zu serialisieren und auszutauschen. Zur Verfügung stehen dabei APIs für die Verwendung des Protokolls in C++, Python und Java.

Warum nicht XML nehmen?

Anstelle dieses proprietären Formates könnte man eigentlich auch XML verwenden, Google gibt jedoch mehrere Gründe an, die gegen XML und für deren Protocol Buffer sprechen. So seien Protocol Buffer einfacher zu definieren, drei bis zehn Mal kleiner als XML und dabei zwanzig bis hundert Mal schneller. Listing 1 bringt einen Typ Person mit Name und E-Mail zunächst in XML und dann im Textformat des Protocol Buffers.

Listing 1 : XML vs. Protocol Buffer (Quelle : Google)
John Doejdoe@example.com

# Textual representation of a protocol buffer.
# This is *not* the binary format used on the wire.
person {
  name: "John Doe"
  email: "jdoe@example.com"
}

Intern gehalten werden die Daten vom Protocol Buffer jedoch nicht im Textformat, sondern binär, was bedeutet, dass die Daten im Speicher letztlich 28 Bytes lang wären und zwischen 100 und 200 Nanosekunden zum Parsen aufwenden würden. Das vergleichbare XML ist (wenn man alle Spaces entfernt) mindestens 69 Bytes lang und würde bis zu 10.000 Nanosekunden zum Parsen benötigen, argumentiert Google. Das binäre, interne Datenformat kann dabei jedoch jederzeit, etwa zu Debuggingzwecken, in eine für den Menschen lesbare Form gebracht und ausgegeben werden. Schließlich führt Google an, dass viele XML-APIs umständlich zu verwenden wären und argumentiert, dass der Protocol Buffer sich nahtlos in die Wunschprogrammiersprache integrieren und eine einfach zu bedienende Programmierschnittstelle mitbringen würde. Im Falle von Java haben wir es am Ende letztlich mit einfachen POJOs (Plain Old Java Objects) zu tun, die mithilfe vom Protocol Buffer-Framework zur Verfügung gestellten Buildern zusammengestellt und verwaltet werden.

Eigener Dialekt zur Definition von Datenstrukturen

Ausgangspunkt ist dabei zunächst die Definition der Datenstruktur in einem einfachen Format, das im Wesentlichen aus einer Auflistung von Name-Wert-Paaren besteht und in ihrem Aufbau an Java-Artefakte erinnert. Nachdem die Struktur definiert und abgespeichert wurde, können aus der so entstandenen .proto-Datei mit einem speziellen Compiler, dem protoc, entsprechende Klassen für die gewünschte Zielsprache generiert werden. Listing 2 zeigt eine solche beispielhafte Struktur (das Beispiel wurde dem Java-Tutorial entnommen, das auf der Webseite zum Projekt erhältlich ist).

Listing 2 : Definition der Datenstruktur
package tutorial;

option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

message AddressBook {
  repeated Person person = 1;
}

Definiert wird hier ein Adressbuch, das aus beliebig vielen Personen bestehen kann. Ausgedrückt wird dies durch das Schlüsselwort repeated. In einer .proto-Datei können verschiedene Typen deklariert werden, auch Schachtelungen sind möglich. Im Listing kommt dies zur Anwendung, um jeder Person ein oder mehrere Telefonnummern zuzuordnen, die nicht nur die Nummer an sich speichert, sondern auch noch, um welche Art von Nummer es sich handelt. Interessanterweise wird dieses Attribut nicht durch einen normalen Datentyp (string, int32 usw.) dargestellt, sondern durch eine Enumeration. Eingeleitet werden diese durch das Schlüsselwort enum. Bei der Definition der einzelnen Attribute, etwa beim Namen einer Person, kann neben den Datentypen noch angegeben werden, ob es sich um ein Mussfeld handelt (required) oder ob das Feld bei der späteren Benutzung der Struktur auch leer bleiben darf (optional).

Die anfänglich verwirrende Zuweisung einer Nummer zum jeweiligen Attribut (zum Beispiel required string name = 1;) legt einen Identifizierer fest, vergleichbar mit einem Primary Key in einer Datenbank.  Mit diesem Tag wird die Datenstruktur später intern referenziert. Google empfiehlt die Nummern 0-15 für die Attribute zu verwenden, die am häufigsten verwendet werden, weil diese Zahlen nur ein Byte lang sind (Hex 0-F) und schneller im Zugriff sind als Zwei-Byte-Werte.

Um spezielle Konfigurationsoptionen für die spätere Zielprogrammiersprache zu hinterlegen, gibt es das Schlüsselwort option. Im Falle von Java kann man hier festlegen, in welches Package (java_package) und unter welchem Klassennamen (java_outer_classname) der entsprechende Code generiert werden soll. Wird der letzte Parameter weggelassen, so wird der Klassenname aus dem Dateinamen der .proto-Datei abgeleitet.

Hat man die Struktur einmal festgelegt, so steht als nächst das Kompilieren an. Dies erfolgt durch das Kommandozeilenwerkzeug protoc. Folgender Aufruf würde aus der Proto-Datei addressbook.proto Java-Klassen erzeugen:

protoc -I=$PROTO_DIR --java_out=$ZIEL_DIR addressbook.proto

Um Code für Python oder C++ zu erzeugen würde man die folgenden Schalter verwenden:

--python_out
--cpp_out  
Geschrieben von
Marc Teufel
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.