Thursday, July 17, 2014

AppEngine With Patience

My previous blog post was about using Google Cloud Endpoints as REST framework. I was a bit skeptical when I finished the blog but now that I've played with it for a coupled of days I have to say it's truly amazing (and very easy).

One of the first services I usually write when trying out a new REST framework is a service to retrieve the server time.  This is useful in a couple of ways, it lets you exercise the REST framework without getting caught up in a lot of business logic and it let's you know if the server is running quickly and easily.  Here is the code for that service:

package com.xyz.pdq;

import java.util.Calendar;

import com.com.xyz.pdq.Status;
import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;

@Api(name="statusAPI",
     version= "v1",
     namespace = @ApiNamespace(ownerDomain = "com.xyz.pdq",
        ownerName = "com.xyz.pdq",
        packagePath=""))
public class ServerStatus 
{
  @ApiMethod(name="serverTime", httpMethod="get")
  public Status serverTime()
  {
    Status status = new Status();
    status.setCurrentTime(Calendar.getInstance().getTime().toString());
    return status;
  }

}

A little bit heavy on annotations I suppose, but they're pretty easy to understand and mostly copy and paste between classes you want to use as services.  The name attribute in the Api annotation is important, and the @ApiMethod is entirely optional, I believe if you don't specify an httpMethod the default is post, however this is pretty much a moot point as you will see if you keep reading.

So that is pretty cool stuff.  Arguably easier or as easy as jersey - however since we're using AppEngine  it's absolutely easier because there are ZERO configuration changes or jars to import.  Did you hear that?  No configuration or extra jars - that is AMAZING!

But wait that's not all!  Once you have your service working the way you like it there is a little maven command that will generate a discovery document for you

 mvn appengine:endpoints_get_discovery_doc

So, what is a discovery document and why is that important?  It is a description of the services contained in that file and it can be used to generate client code to hit those services (or endpoints).

Once you generate that file you can run a command using Google's Library generator to auto generate a whole bunch of code.  You download the source and compile it yourself using XCode, an important note there was one line of code that was wouldn't compile.  It's on line 34 of the FHUtils.m file here is the naughty line:

    NSMutableCharacterSet *setBuilder = [NSCharacterSet characterSetWithRange:NSMakeRange('a', 26)];
and this is the very simple fix:
    NSMutableCharacterSet *setBuilder = [NSMutableCharacterSet characterSetWithRange:NSMakeRange('a', 26)];

Hardly worth noting but if you are in a panic that is how you fix it.  Follow the instructions on the link and you should be generating all kinds of code.  Now there are some pretty specific instructions you need to follow.  There is a section labeled "Adding Required files to your iOS project" step 1g has you doing something I didn't even know was possible!  You can disable arc management on a file by file basis!  Holy smokes - that is a useful trick.

Once you get it all up and running you have probably added over 20 files to your project - it's a lot but keep in mind you didn't have to write it - and since it's code you can jump in and take a look to see how Google thinks things should be done!

Here is a snippet of code that uses the auto generated code that we created above:
    static GTLServiceStatusAPI *service = nil;
    if (!service)
    {
        service = [[GTLServiceStatusAPI allocinit];
        service.retryEnabled = YES;
    }
    GTLQueryStatusAPI *query = [GTLQueryStatusAPI queryForServerTime];

    [service executeQuery:query completionHandler:^(GTLServiceTicket *ticket, GTLStatusAPIStatus *object, NSError *error){
        if (error != nil)
        {
            NSLog(@"Error getting time: %@", [error localizedDescription]);
        }
        else
        {
            NSString *serverTime = [object currentTime];
            NSLog(@"Server time is %@", serverTime);
        }

    }];

Here is the same code in pseudo code so you can get the theory down:

ServiceObject service = new ServiceObject
ServiceQuery query = new ServiceQuery

[service executeQuery: query handleCallback:^(ServiceTicket ticket, ServiceResponse response, Error error){
  handle response
}] 

That pattern is followed when ever you want to call the server - posting and getting is abstracted away from you, even the URL that you're communicating is hidden away.  All in all an amazing framework.

There is so much magic in serializing the response and deserializing it on the backend, I can't believe how easy it was to get working.  It is sort of like writing EJB's in the old days except this actually works.

There were a couple of gotchas I feel obligated to mention.  
  • Your AppEngine app seems to need to be deployed out on app engine.  This means you can't hit the dev server while you're developing the client code.  There is probably a very clever work around but it wasn't worth the effort for me.
  • Also since the project we created in the previous blog is all maven based I ended up writing a couple of scripts to:
    1. deploy to appengine (if you have 2Factor authentication enabled this is a bit of a hassle) 
    2. generate the code once the discovery file was generated.  This process is well documented I just wrote a script to be lazy.
My next post will hopefully cover using Objectify as a persistence layer and how you can use JUnit tests to test it.  Exciting stuff!

Cheers!

-Aaron




No comments: