Protobuf för serialisering del 1
Protobuf är en kodningsteknik för att serialisera datastrukturer till ett binärt format som tar mindre plats än t.ex. XML eller JSON. På så sätt lämpar sig protobuf bättre för transport över kanaler där bandbredd är en avgörande resurs.
Protobuf är utvecklat av Google och används enligt uppgift mycket mellan deras interna tjänster. Ramverket tillhandahålls med stöd för C++, Java och Pyton, men en mängd andra språk stöds också genom tredjepartsprojekt: C, C#, JavaScript etc.
Protobuf använder sig av en s.k. schemabaserad kodning, vilket innebär att ett kodat meddelande inte kan avkodas (i alla fall inte fullständigt) utan en beskrivning (ett schema) av datastrukturen. Datastrukturerna definieras i ett speciellt språk.
option java_package = "se.cygni.protobuf.proto";
option java_outer_classname = "UserAccountProtos";
message UserAccount {
required int32 id = 1;
required string user_name = 2;
optional string email = 3;
enum AccountState {
ACTIVE = 0;
PENDING_TANDC = 1;
SUSPENDED = 2;
}
required AccountState state = 4;
repeated string service = 5;
}
Med en kompilator (protoc) genereras sedan en protobuf-modell i Java, C++ eller Pyton. Protoc finns att ladda hem för Windows (som funkar utmärkt i CMD och cygwin). För övriga OS får man själv kompilera källkoden eller förlita sig på att ens OS har färdigkompilerade paket att ladda hem. I Ubuntu kan du köra: sudo apt-get install protobuf-compiler
. Nedan genererar vi Java-modellen för ovanstående protobuf-definition.
$ protoc --java_out=target/generated-sources/proto src/main/proto/UserAccountProtos.proto
$ find target/generated-sources/proto/ -type f
target/generated-sources/proto/se/cygni/protobuf/proto/UserAccountProtos.java
För att kompilera din Protobuf modell behöver du följande Protobuf-jar:
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.3.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
UserAccount a1 =
UserAccountProtos.UserAccount.newBuilder()
.setId(100)
.setUserName("jon.edvardsson")
.setState(AccountState.SUSPENDED)
.addService("BOOKS")
.addService("MOVIES")
.addService("MUSIC").build();
System.out.println(a1);
System.out.println(a1.toByteString().toStringUtf8());
Ovan skapar vi ett UserAccount-objekts som vi skriver ut (med toString()). Detta resulterar i en utskrift i protobufs okodade textformat. Därefter skriver vi ut det kodade meddelandet.
id: 100
user_name: "jon.edvardsson"
state: SUSPENDED
service: "BOOKS"
service: "MOVIES"
service: "MUSIC"
djon.edvardsson *BOOKS*MOVIES*MUSIC
Som ni ser är binärformatet mycket kompaktare än textformatet. Framför allt är det två saker som gör detta: (1) fältnamnen kodas inte in i meddelandet utan måste utläsas från schemat genom index, (2) tal och sanningsvärden representeras binärt.
Eftersom det kodade meddelandet inte innehåller någon metadata måste mottagaren veta meddelandetypen samt ha tillgång till schemat för att göra en korrekt avkodning. Man kan avkoda utan denna information, men då framgår inte fältnamnen. Dessutom finns det risk att nästlade strukturer avkodas fel. Nedan följer ett exempel hur man kan använda protoc för att avkoda ett meddelande till rå- eller textformat.
$ cat a1.pbuf|protoc --decode_raw
1: 100
2: "jon.edvardsson"
4: 2
5: "BOOKS"
5: "MOVIES"
5 {
9: 0x43495355
}
$ cat a1.pbuf|protoc --decode=UserAccount src/main/proto/UserAccountProtos.
proto
id: 100
user_name: "jon.edvardsson"
state: SUSPENDED
service: "BOOKS"
service: "MOVIES"
service: "MUSIC"
För att avkoda ett meddelande i Java kan man göra på följande sätt.
ByteArrayInputStream in = new ByteArrayInputStream(a1.toByteArray());
UserAccount a2 = UserAccountProtos.UserAccount.parseFrom(in);
Som ni ser är det ganska enkelt att komma i gång med protobuf. Det finns en del andra ramverk (se jvm-serializers) för effektiv kodning av data, t.ex. Thrift (Facebook), Avro (Apache Hadoop) och naturligtvis ASN.1 med t.ex. BER och DER kodning.
Varför Google utvecklat en egen kodningsstandard (ej ISO) än de som är baserade på ASN.1 är oklart. Kanske beror det på att Protobuf inte kodar in datatyp och längd i meddelandet och på så sätt åstadkommer ett kompaktare format.
I nästa artikel tänkte jag visa hur man integrerar Protobuf i sitt Maven-projekt i en enkel Jersey-baserad webbapp.