Saturday, February 9, 2013

Jersey vs Google Cloud Endpoints as REST-service at Google AppEngine

Last week I wrote about how to use Google Cloud Endpoints (or "Endpoints" for short) to create a JSON-producing REST-service at Google App Engine. This week I will present an alternate approach, using the Jersey REST framework with JSON POJO support. In this post I will discuss the rationale as to why, while in the next post I will describe how.

So why do we need another approach? Why not use Google's own REST implementation instead of pushing extra jar's to AppEngine bloating the solution? Well the implementations are different of course and I can think of several reasons to choose Jersey over Google Clound Endpoints:

  1. Google Endpoints is still in beta
    Google themselves don't recommend you using Endpoints in production systems yet. This should be reason enough. The poor documentation and error messages in the framework are others.
  2. Better flexibility
    The Endpoints framework enforces a strict RESTful architecture. Although you may view this as a good thing, most real-world "REST services" today diverge to some extent from the true RESTful principles as outlined by the Godfather of REST himself, Roy Fielding in this blog post. A simpler, pragmatic approach to REST is not necessarily a bad thing though.
  3. Better portability
    By using a third-party API for endpoint-generation you will have a smoother ride if you at any point need to migrate to a different platform than Google AppEngine.

The kicker for me was the flexibility. The true RESTful architecture leaves no room for business logic on the server. All operations must be dumb CRUD-operations. While this results in a highly scalable architecture, it's not one fitted for all back-end scenarios. If you want to create a RPC-service based on JSON over HTTP* (is there a name for this?), you need a framework that allows you to define the input and output types arbitrarily and map them to the HTTP verbs as you wish. Google Endpoints does not that. In short, these two restrictions are the one causing trouble (or consitency, depending on your point of view=:

Update 11th feb 2013: See last paragraph with an update that refutes this "kicker".

*If you do this, be careful not to call it a "REST Service", as Roy Fielding will hunt you down for public scalding.
  1. The URI will be named after the class name of the return type
  2. If you have a complex object as input (POJO), the output must be of same type

What if you want to create an advanced search routine which takes a filter object as input and returns the list of entities as output? In Jersey you can do that the following way:

@Path("/api-base/user")
 public class User {
  @POST
  @Produces(MediaType.APPLICATION_JSON)
  @Consumes(MediaType.APPLICATION_JSON)
  public ArrayList searchUsers(UserFilter filter) {    
   ArrayList users = new ArrayList();
   // Populate users through search logic
   return users;
  }
 }
 
 public class UserFilter {
  public String text;
  public String zipCode;
  public String maxAge;
 }

You would then use this search function by doing an HTTP POST to http://host/api-base/user where the POST payload is a JSON representation of the UserFilter object, e.g. { "text" : "roger" }

If we try to do the same with Endpoints we need to adhere to rule #2 mentioned above, and thus we'll be unable to take a UserFilter object as input while returning ArrayList<user>. We could get around it by creating a parameter for each of the optional filter parameters, but this would force the consumer to always pass something for these values as the Endpoints framework would produce a 404-error otherwise:

@Api(name = "api-base")
 public class Api {
  public ArrayList getUsers(@Named String text, @Named String zipCode, @Named String maxAge) {    
   ArrayList users = new ArrayList();
   // Populate users through search logic
   return users;
  }
 }

To use this you would then to a HTTP GET to http://host/api-base/user/TEXT/ZIP/MAXAGE where the uppercase-text in that URL are the parameter values. This is not very flexible as you need to pass all parameters always.

Summary

I'm aware this is a superficial and subjective comparison of the two frameworks. There are a lot of other factors that I haven't considered, like performance and security. I do believe I've highlighted the main differences from a developer's and architect's point of view though - and if nothing else I hope to get feedback and counter-arguments to my points.

Finally I'd like to underline that the Google Cloud Endpoints framework still is in Beta, and that all my arguments in this post may be void tomorrow. This is the state of the framework as of today though, and I hope my post can help people make the right decision when it comes to framework for REST-ish services at AppEngine.


Update 11th feb 2013:.

I had not read the documentation well enough. There is indeed full flexibility when it comes to URL generation and input-output-class types with Google Endpoints - Good news! Thanks to Dan Holevoet from Google pointing this out in the comments.

Implementing the search example above using Google Endpoints is done like this:

@Api(name = "api-base")
 public class Api {
  @ApiMethod(httpMethod = "POST", path = "searchUsers")
  public ArrayList getUsers(UserFilter filter) {    
   ArrayList users = new ArrayList();
   // Populate users through search logic
   return users;
  }
 }

Or you could implement it with optional GET-parameters this way:

@Api(name = "api-base")
 public class Api {
  @ApiMethod(httpMethod = "GET", path = "searchUsers")
  public ArrayList getUsers(@Named("text") String text, @Named("zipCode") String zipCode, @Named("maxAge") String maxAge) {    
   ArrayList users = new ArrayList();
   // Populate users through search logic
   return users;
  }
 }

5 comments:

  1. The Python version of Google Cloud Endpoints doesn't enforce these same sorts of things.

    ReplyDelete
  2. With regards to (1), this is a blanket recommendation for our trusted testers, because we'd like to be aware of any developers interested in launching before they launch. We have spoken individually with several testers who wished to deploy Endpoints in production and given them our blessing. (As far as I know, those testers haven't seen any issues with their production APIs.)

    As for (2), there's no such restriction. It's extremely easy to make a RESTful API with Endpoints, but no requirement to structure your API in this way. In fact, the example application we use, Tic Tac Toe, has both REST (Score) and RPC (Board) APIs: https://github.com/GoogleCloudPlatform/appengine-endpoints-tictactoe-java

    ReplyDelete
  3. Thanks Dan for these comments and for enlightening me on the flexibility on the API. I read the documentation once more and got quite embarassed when I noticed the "path" and "httpMethod"-parameters under the @ApiMethod annotation section. Both this post and my previous has been updated with this new knowledge. I'm starting to like Google Cloud Endpoints more and more :-)

    ReplyDelete
  4. ..again it's really very interesting those researches and tests of this post !

    ReplyDelete
  5. Thanks, that "httpMethod = "POST"," is what I was missing all this while. This post helped!

    ReplyDelete