This is a follow-up post to Exploring using Protobuf in the browser.
There are many different approaches to using Protocol Buffers with GWT, both for messages and services.
The Message API
Each language supported natively by protoc (C++, Python and Java) has a different API for Messages:
- Java uses a builder approach: each message class has a corresponding builder class; the builder instance can only be used once, to initialize a single message instance; only the builder is mutable, a message instance is always immutable.
- In Python, a message instance is always mutable.
- C++ follows the same approach as Python but takes advantage of the
const
keyword to eventually freeze an object, so if a field's type is a message (or string), you have to explicitly ask for a mutable or immutable instance.
These differences are guided by each language's features: C++ can contextually make an object immutable, Java cannot but a builder can easily be given access to the message internals, and finally Python is all about unlocked access to objects, even their internals (while this has changed a bit in recent versions).
Closure Library, because JavaScript has similar features as Python, uses the same approach of always mutable message instances.
GWT is both in the Java and the JavaScript lands, but because it looks like Java, I'd rather go with the same API as the native Protobuf Java API. I'd even use the exact same package and class names and take advantage of GWT's emulation feature (see Overriding one package implementation with another) to only implement the subset of the API that's translatable to JavaScript (i.e. everything that doesn't involve java.io
). Moreover, using this approach, because the developer never actually sees the emulation code, the message and builders could eventually be the very same object, possibly a lightweight JavaScript Overlay Type (this of course would have implications on instanceof
results). Using overlay types though would make it impossible to implement the Message.getDescriptor() method, limiting the API to the subset of MessageLite (but using optimize_for = LITE_RUNTIME
rules out defining services)
Message serialization
In addition to being an IDL with code generators, Protobuf is also an efficient, "language-neutral, platform-neutral, extensible mechanism for serializing structured data". However, due to limitations of browser APIs, the binary format wouldn't be usable (or with a considerable overhead). The structured format that works the best in the browser is undoubtedly JSON, and GWT implementation of Protobuf couldn't be complete without built-in JSON ser/deserialization (and using insertion points, protoc code generator plugins can easily add the same toJSON/parse/mergeFrom APIs on the server-side too.
With GWT however, there's another serialization format: the one used by GWT RPC. I said previously that GWT RPC tightly couples the client and server codebase and would make it impossible to extend messages without deploying them in one go. This isn't completely true though: GWT RPC versions classes based on their fully-qualified name, the fully-qualified name of parent classes, and the name of each of these classes' fields; but when you use a custom field serializer, the serializer class is used instead, which generally doesn't have fields or parent class (as its methods have to be static). This means that such a serializer could very well implement its own "error recovery" algorithm to ignore unknown fields (and narrow-cast numbers, so that a field's type could be changed from e.g. byte to int without fearing exceptions, similar to how Protobuf's binary format works; the exception here would be longs, as they are emulated as a pair of doubles in GWT).
The RPC services
Starting with version 2.3.0, Protobuf warns against using the generic services API but instead use plugins to generate code specific to your RPC implementation. GWT has a pretty good RPC-over-HTTP implementation, so it'd probably be the best fit (even though I believe the generic API could be implemented on the client side, with an overhead comparable to GWT RPC, maybe even smaller if you serialize to/from JSON and keep the deserialized object as-is, in which case the native JSON.parse/stringify browser implementations could be used).
But actually, whichever the chosen API, the underlying implementation could be the other one; i.e. it's possible to implement Protobuf's generic services API on top the GWT RPC protocol, or the GWT RPC API on top of some some other serialization such as JSON (as already done in gwt-rpc-plus).
The major drawback with Protobuf's generic services API is that it relies on Protobuf reflexivity, and removing it from the API would actually make it too different from the original one to justify using it (what would be an RpcChannel without MethodDescriptor?)
Where to go now?
Well, experiment? And make compromises: Is the full Message API worth it if you could have a much lighter-weight implementation of MessageLite based on JavaScript Overlay Types? Is instanceof
support a must-have? Should extensions be supported? Would it be a problem if the emulation of the Protobuf library used interfaces in place of the original classes in some cases? (it would allow the emulated message and builder be the same class)