This guide walks you through creating asynchronous queries to GitHub. The focus is on the asynchronous part, a feature often used when scaling services.
What You Will build
You will build a lookup service that queries GitHub user information and retrieves data
through GitHub’s API. One approach to scaling services is to run expensive jobs in the
background and wait for the results by using Java’s
CompletableFuture interface. Java’s
CompletableFuture is an
evolution from the regular
Future. It makes it easy to pipeline multiple asynchronous
operations and merge them into a single asynchronous computation.
What You Need
Starting with Spring Initializr
For all Spring applications, you should start with the Spring Initializr. The Initializr offers a fast way to pull in all the dependencies you need for an application and does a lot of the set up for you. This example needs only the Spring Web dependency. The following image shows the Initializr set up for this sample project:
The preceding image shows the Initializr with Maven chosen as the build tool. You
can also use Gradle. It also shows values of
The following listing shows the
pom.xml file created when you choose Maven:
The following listing shows the
build.gradle file created when you choose Gradle:
Create a Representation of a GitHub User
Before you can create a GitHub lookup service, you need to define a representation for the data you will retrieve through GitHub’s API.
To model the user representation, create a resource representation class. To do so,
provide a plain old Java object with fields, constructors, and accessors, as the following
Spring uses the Jackson JSON library to convert GitHub’s JSON response into a
User object. The
@JsonIgnoreProperties annotation tells Spring to ignore any
attributes not listed in the class. This makes it easy to make REST calls and produce
In this guide, we grab only the
name and the
blog URL for demonstration purposes.
Create a GitHub Lookup Service
Next, you need to create a service that queries GitHub to find user information. The
following listing (from
src/main/java/com/example/asyncmethod/GitHubLookupService.java) shows how to do so:
GitHubLookupService class uses Spring’s
RestTemplate to invoke a remote REST point
(api.github.com/users/) and then convert the answer into a
User object. Spring Boot
automatically provides a
RestTemplateBuilder that customizes the defaults with any
auto-configuration bits (that is,
The class is marked with the
@Service annotation, making it a candidate for Spring’s
component scanning to detect and add to the application context.
findUser method is flagged with Spring’s
@Async annotation, indicating that it
should run on a separate thread. The method’s return type is
CompletableFuture<User> instead of
User, a requirement for any
asynchronous service. This code uses the
completedFuture method to return a
CompletableFuture instance that is already completed with result of the GitHub query.
Creating a local instance of the
The timing for GitHub’s API can vary. To demonstrate the benefits later in this guide, an extra delay of one second has been added to this service.
Make the Application Executable
To run a sample, you can create an executable jar. Spring’s
@Async annotation works with
web applications, but you need not set up a web container to see its benefits. The
following listing (from
src/main/java/com/example/asyncmethod/AsyncMethodApplication.java) shows how to do so:
The Spring Initalizr created an
@EnableAsync annotation switches on Spring’s ability to run
methods in a background thread pool. This class also customizes the
Executor by defining
a new bean. Here, the method is named
taskExecutor, since this is the
specific method name for which Spring searches. In our case, we want
to limit the number of concurrent threads to two and limit the size of the queue to 500.
There are many more things you can tune. If you do not define an
bean, Spring creates a
SimpleAsyncTaskExecutor and uses that.
There is also a
CommandLineRunner that injects the
calls that service three times to demonstrate the method is executed asynchronously.
You also need a class to run the application. You can find that in
src/main/java/com/example/asyncmethod/AppRunner.java. The following listing shows that
The application shows logging output, showing each query to GitHub. With the help of the
allOf factory method, we create an array of
CompletableFuture objects. By calling
join method, it is possible to wait for the completion of all of the
The following listing shows typical output from this sample application:
2016-09-01 10:25:21.295 INFO 17893 --- [ GithubLookup-2] hello.GitHubLookupService : Looking up CloudFoundry 2016-09-01 10:25:21.295 INFO 17893 --- [ GithubLookup-1] hello.GitHubLookupService : Looking up PivotalSoftware 2016-09-01 10:25:23.142 INFO 17893 --- [ GithubLookup-1] hello.GitHubLookupService : Looking up Spring-Projects 2016-09-01 10:25:24.281 INFO 17893 --- [ main] hello.AppRunner : Elapsed time: 2994 2016-09-01 10:25:24.282 INFO 17893 --- [ main] hello.AppRunner : --> User [name=Pivotal Software, Inc., blog=http://pivotal.io] 2016-09-01 10:25:24.282 INFO 17893 --- [ main] hello.AppRunner : --> User [name=Cloud Foundry, blog=https://www.cloudfoundry.org/] 2016-09-01 10:25:24.282 INFO 17893 --- [ main] hello.AppRunner : --> User [name=Spring, blog=http://spring.io/projects]
Note that the first two calls happen in separate threads (
GithubLookup-1) and the third one is parked until one of the two threads became
available. To compare how long this takes without the asynchronous feature, try
commenting out the
@Async annotation and runing the service again. The total elapsed
time should increase noticeably, because each query takes at least a second. You can
also tune the
Executor to increase the
corePoolSize attribute for instance.
Essentially, the longer the task takes and the more tasks are invoked simultaneously, the
more benefit you see from making things asynchronous. The trade off is handling the
CompletableFuture interface. It adds a layer of indirection, because you are no longer
dealing directly with the results.
Congratulations! You have just developed an asynchronous service that lets you scale multiple calls at once.
The following guides may also be helpful: