Thursday, July 14, 2016

GWT RPC's future

This post is about why I do not think that GWT RPC is something valuable to be carried forward.
I am also being slightly harsh with GWT RPC which has served many GWT applications as a good RPC mechanism. Please keep in mind that this post is not about assigning blame, I do think that the engineers who designed GWT RPC did a great job, they just had other more important goals in mind.

When I first discovered GWT RPC in somewhat 2008 I thought it was magical. Send all my Java objects over the wire and just use them on the client side. All I need to do is define a simple interface. I loved it.

But as time went by I have started to dislike GWT RPC more and more. This blog post is about the bad choices that GWT RPC has made and why I do not think its a good choice to be carried forward for applications that transpile Java to JavaScript. However this does not mean that its not possible to port it forward (with some changes), but I think its not worth it there are simply better alternatives.

Bad choices

GWT.create call type inheritance broken

AsyncChronousVersion instance = GWT.create(SynchronousVersion.class);
Why do I need to pass in the synchronous version? From a type perspective this does not make any sense what so ever.
The synchronos version extend the RemoveService interface, the asynchronos version did extend nothing. Why not simply use the Asynchronous version all the way?
If you know what a GWT.create call looks like in the compiler you realize that this is really broken.

Exploding code size

Let's take a look at the second problem that GWT RPC has that is way more severe.
Assume we have this simple RPC interface:

public interface MyServiceAsync {
  void doSomething(List strings, AsyncCallBack callback);

The interface defines a simple method that takes a list of strings. The problem with this code becomes apparent when we take a look at the serializers and deserializers that have to be generated for this class. Since we need to have a serializer for every concrete type that we have in the program, you end up with potentially hundereds of serializers just for this one method.
The GWT team always recommended to be as specific as possible to get rid of this flaw, but this is against a core design principle in Java. We want List on the interface, not ArrayList.

Doing a simple search for subtypes of ArrayList in a hello world GWT applications returns 16, this is obviously a bad choice.

Version skew during deployment

Because of the need for serializers that were specific to your current applications, GWT RPC had a serious issue with version skew. Make a slight changes to your problem and you might have ended up causing GWT RPC to fail between these version of your app. When you have multiple thousand servers running you will always have an old client talking to a new server or an old client talking to a new server. Not dealing with this is unacceptable for a RPC system and has cost many teams at Google headaches.

Slow compiles due to global analysis

If you want to generate all the serializers of a type you need to have global knowledge of your program. You need to be able to answer questins like:

  • "Who is implementing this interface?"
  • "Who is subclassing this class"
This means that you can not incrementally compile GWT RPC code since you would not be able to answer these questions correctly. The only reasons that super dev mode works with GWT RPC is that we do the initial slow full world compile and then keep track of these types as you update your code.If you want really fast compiles, which we want for GWT 3.0, you really do not want any global knowledge.

GWT RPC can not be done with a Java annotation processor

All other GWT code generators can be changed to be Java annotation processors, since they do not require global knowledge. Since GWT RPC requires global analysis it can not be easily ported.

But I really like GWT RPC, what can I do?

Well as I said earlier in this post, you can port it to an APT, but you need to make changes to it:

  - Change the interface to list all the types it needs so you do not require global knowledge
  - Remove the synchronous or asynchronous interface and generate one from the other
  - Replace the GWT.create call
  - Make the serialization format compatible with different versions of your app, by the way this is what we do with GWT proto RPC, which unfortunately is not open source.

This could potentially look like this:

public interface MyServiceAsync {
  void doSomething(@Serializer(accept = {ArrayList.class}) List strings, ...)

// MyServiceAsync_Factory is generated by an APT
MyServiceAsync service = MyServiceAsync_Factory.create();

I hope this blog post helps people that really like GWT RPC to understand why there are better choices one can make for a RPC mechanism and why we should be striving to have something better.