Search code examples
javaweb-servicesplayframework

java nested http call API library


I was reading some http webservice API from playframework, link below.

I am not able to understand how this nested work with the flatmap.

Can someone give me some hints how to crack down this big chunk of functions call.

from http://www.playframework.com/documentation/2.2.x/JavaWS

Composing results If you want to make multiple calls in sequence,
this can be achieved using flatMap:

public static Promise<Result> index() {
    final Promise<Result> resultPromise = WS.url(feedUrl).get().flatMap(
           new Function<WS.Response, Promise<Result>>() {
                public Promise<Result> apply(WS.Response response) {
                    return WS.url(response.asJson().findPath("commentsUrl").asText()).get().map(
                        new Function<WS.Response, Result>() {
                            public Result apply(WS.Response response) {
                                return ok("Number of comments: " + response.asJson().findPath("count").asInt());
                            }
                        }
                );
            }
        }
);
    return resultPromise;
}

Solution

  • flatMap and map are common Scala (or more generally, functional programming) functions. They both accept a function as a parameter. In order to translate the Play WS API into Java (and pretty much everything else), Scala's function type needed to be re-implemented in Java, so that you can take full advantage of the WS library. It's being done here in a similar way the Scala compiler does it. Function<A,B> is an abstract type that requires an apply method. The parameter(s) of apply are the parameters of the function, and the return type of apply is that of the function.

    If you have this function in Java:

    public String int2String(Integer integer) {
        return integer.toString();
    }
    

    It would be equivalent to this:

    new Function<Integer, String>() {
        public String apply(Integer integer) {
            return integer.toString();
        }
    }
    

    Let's start simpler with the case of just one WS call. WS.url(...).get() returns a Promise<WS.Response>. Promise is a container class for a promised value. In order to handle the value it contains (or will eventually contain), we need to use the map function. For a Promise<WS.Response>, map will accept a Function<WS.Response, T> as a parameter, where T is the type you want to map the response to.

    As an example, let's define a Function that will just return the body of the WS.Response in a Play HTTP Result:

    Function<WS.Response, Result> echo = new Function<WS.Response, Result>() {
        public Result apply(WS.Response response) {
            return ok(response.asText());
        }
    }
    

    Now let's use this Function in a WS call in a controller:

    public static Promise<Result> index() {
    
        final Promise<Result> resultPromise = WS.url("http://google.com").get().map(echo);
    
        return resultPromise;
    
    }
    

    Once the Promise has been fulfilled, the echo function defined earlier will be executed inside map, returning the Promise<Result>. The two previous blocks of code could also be written like this (combined into one with an anonymous function):

    public static Promise<Result> index() {
    
        final Promise<Result> resultPromise = WS.url("http://google.com").get().map(
            new Function<WS.Response, Result>() {
                public Result apply(WS.Response response) {
                    return ok(response.asText());
                }
            }
        );
    
        return resultPromise;
    }
    

    As a crude example, let's say we need to make two WS calls. The second WS call will depend on the first. Perhaps the first call will give us some URL we will use to make the second WS call.

    This is where flatMap comes into picture. We will need two functions to accomplish this task. The first function is what is passed to flatMap, which will be executed when the first WS.Response is received. This first function will use the first response to make the second WS call, which returns another Promise<WS.Response> that must be mapped to get our final result. So we map the second result with a second function that translates the WS.Response into our Result.

    So what happened? If we used map instead of flatMap in both instances, the chain of events would go something like this:

    The first get() returned a Promise<WS.Response>, then we map the contained WS.Response to a Promise<Result>. That, however, would leave us with a Promise<Promise<WS.Response>>, which is not very desirable. Using flatMap in the outer function instead will flatten the Promises into a single Promise<Result>. Simiarly, if you were doing 3 or more nested calls, you would map each result to an inner function, and have just one flatMap at the outer level to flatten everything at the end.

    This all of course looks much more beautiful in Scala.