Common Patterns in Java Play Web Service

Overview

I am embarking on a new project where the server side will be mostly RESTful web services developed using Play (Java edition). These services will depend on other external services. Below are a few common patterns that will arise. the goal is to use non-blocking (reactive style) programming as much as possible.

Imported packages

Most API classes used here are available from these packages.

import play.*;
import play.mvc.*;
import play.libs.ws.*;
import play.libs.*;
import play.libs.F.Promise;

Consuming External Services

Simple Web Service Call

public static Promise<Result> index() {
    Promise<WSResponse> p1 = 
      WS.url("http://www.example.com").get();
    Promise<Result> p2 = p1.map(example -> {
        String body = example.getBody();
        System.out.println(body);

        return ok("We are all done");
    });     

    return p2;
} 

The sequence of operations is as follows:

  1. WS.url() returns a WSRequestHolder object.
  2. The get() of WSRequestHolder returns a Promise. We can not directly return the promise to Play. Play expects a Promise from a controller method.
  3. We call the map() method of the Promise to map it to a Promise. The `Promise“ is then returned to Play.
  4. Play waits for the `Promiseto fulfill. That will happen after thePromise“ is fulfilled and the associated lambda function has returned.

Sequential Web Service Calls

If multiple web service calls depend up on each other they need to be called in sequence.

public static Promise<Result> index() {
  Promise<Result> p = 
    WS.url("http://www.example.com").get().flatMap(example -> {
    System.out.println("Example came back.");
    return 
      WS.url("http://www.google.com").get().map(google -> {
      System.out.println("Google came back.");
      return ok("We are all done");
    });
  });

  return p;
}

Here, first a call is made to http://www.example.com. When that call completes successfully a call is made to http://www.google.com.

The example and google variables are of type WSResponse.

The outer calls must use flatMap() while the last call can use map(). The reason for this is as follows. The lambda expression for gogle.com returns a Promise. If we used map() with this call then map() would return Promise&lt;Promise&gt;. This is not what we want. We want to obtain the very last Promise. On the other hand flatMap() will combine its own promise with the inner promise and return the inner promise. Because the two promises are combined, anyone waiting for the returned promise will basically wait for the final call to complete. In summary, flatMap returns a promise that gives us two advantages:

  1. The promise returns the value returned by the last (inner most) promise.
  2. Waiting for the promise will basically make sure that all calls have ended.

Parallel Web Service Calls

Sometimes you will need to call multiple web services and these calls don not depend up on each other. They should be made in parallel to save time. The key is to efficiently wait for all of the calls to finish. This is basically the scatter gather pattern.

public static Promise<Result> index() {
  Promise<WSResponse> p1 = 
    WS.url("http://www.example.com").get().map(response -> {
    System.out.println("Example came back.");
    return response;
  });
  Promise<WSResponse> p2 = 
    WS.url("http://www.google.com").get().map(response -> {
    System.out.println("Google came back.");
    return response;
  });
  //Create a combine promise to wait for all promises to fulfill
  Promise<Result> combinedPromise = 
    Promise.sequence(p1, p2).map( v -> {
    System.out.println("We are all done");
    String exampleBody = p1.get(0).getBody();
    String googleBody = p2.get(0).getBody();
    return ok(String.format("All done. Ex: %d Go: %d",
      exampleBody.length(), googleBody.length()));
  });

  return combinedPromise;
}

Processing a JSON Response

Let’s say there’s an external web service that returns a movie as follows:

{
"name": "House of Haunted Souls",
"released": 1973,
"genre": ["horror", "comedy"]
}

We can consume it as follows:

public static Promise<Result> index() {
    Promise<WSResponse> p1 = 
    WS.url("http://localhost/movie.json").get();
    Promise<Result> p2 = p1.map(response -> {
        Movie m = Json.fromJson(response.asJson(), Movie.class);

        return ok(m.name);
    });     

    return p2;
}       

static class Movie {
    public String name;
    public String released;
    public int rating;
    public String[] genre;
}

Returning a JSON Document

public static Promise<Result> index() {
    Promise<WSResponse> p1 = 
      WS.url("http://localhost/movie.json").get();
    Promise<Result> p2 = p1.map(response -> {
        Movie m = Json.fromJson(response.asJson(), Movie.class);
        m.rating = 5;
        return ok(Json.toJson(m));
    });     

    return p2;
} 

Basically we create a Result object by passing ok() a com.fasterxml.jackson.databind.JsonNode.

The content type of the response will be set to application/json; charset=utf-8.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s