Reactive Spring Boot Restful Web Service + Google Books API
Alexandre Ladeira
Posted on November 20, 2019
The idea of this post, the first of a series, is to demonstrate how to write a Spring Boot Restful Web Service that access a remote API in a reactive way. My intention is to add more features, like caching and database, always using the reactive paradigm. In this first one, I will describe how you can start from zero to the point that you have a functional web service.
Getting Started
I based this on the Spring tutorial Building a Reactive RESTful Web Service, and build it from the scratch, copying only the initial Gradle build file. The project structure is as this following image.
I created a default Spring Boot Application class, the only difference is the @ConponentScan annotation that points to the base package of the application.
package dev.alexladeira.springboot.reactive;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = {"dev.alexladeira.springboot.reactive"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Now the game begins!
Writing the Router class
Thought the Router class I put the route that you going to expose, this is a simple class that create the link between the service that I was exposing and the handler that I create to handle the request.
package dev.alexladeira.springboot.reactive.routes;
import dev.alexladeira.springboot.reactive.handlers.GoogleBooksHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
@Configuration
@ComponentScan({"handlers"})
public class SearchRouter {
@Bean
public RouterFunction<ServerResponse> search(GoogleBooksHandler googleBooksHandler) {
return RouterFunctions.route(RequestPredicates.GET("/search"), googleBooksHandler::search);
}
}
Until now I did not need to use Spring Reactor, this changed when I wrote the handler.
Writing the Handler and The Service classes
I created the handler to handle the request, call the external resource and create a response. Here I got the searchTerm from the request object and pass it to the service that is responsible for returning some information about the books that has the word that I passed as a parameter, in this example I was using the Google Books API.
package dev.alexladeira.springboot.reactive.handlers;
import dev.alexladeira.springboot.reactive.domain.google.GoogleBook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import dev.alexladeira.springboot.reactive.services.GoogleBooksService;
import java.util.ArrayList;
import java.util.List;
@Component
@ComponentScan({"services"})
public class GoogleBooksHandler {
@Autowired
private GoogleBooksService googleBooksService;
public Mono<ServerResponse> search(ServerRequest request) {
String searchTerm = request.queryParam("searchTerm").orElse(null);
return searchTerm != null ? ServerResponse.ok().body(BodyInserters.fromPublisher(this.googleBooksService.getBooksBy(searchTerm).reduce(new ArrayList<GoogleBook>(), (list, googleBookServiceResponse) -> {
list.addAll(googleBookServiceResponse.items);
return list;
}), List.class)) : ServerResponse.badRequest().build();
}
}
The job of the search method is just get the books from the Google Books API, reduce then to a list of GoogleBooks type, a java class that has only the info that I want the service to return, and create the response. If the service receive and empty call (without any parameters), then an error is thrown.
The service class is as follow, a call to the API via a WebClient object
package dev.alexladeira.springboot.reactive.services;
import dev.alexladeira.springboot.reactive.domain.google.GoogleBookServiceResponse;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
@Service
public class GoogleBooksService implements GenericService<GoogleBookServiceResponse> {
private WebClient webClient = WebClient.builder().baseUrl("https://content.googleapis.com").build();
@Override
public Flux<GoogleBookServiceResponse> getBooksBy(String searchTerm) {
return this.webClient.get().uri(uriBuilder -> uriBuilder
.path("/books/v1/volumes")
.queryParam("q", searchTerm)
.queryParam("maxResults", MAX_RESULTS)
.build()).retrieve().bodyToFlux(GoogleBookServiceResponse.class).timeout(TIMEOUT);
}
}
This class extends GenericService, a class created to concentrated some information that is going to be used by the others services that I will create in the future, the constants MAX_RESULTS and TIMEOUT.
Conclusion... for now
It's time start the server and see the results, point your browser to http://localhost:8080, if everything went well, you will see this:
[{"volumeInfo":{"title":"Learning Spring Boot 2.0","authors":["Greg L. Turnquist"],"printType":"BOOK"}},{"volumeInfo":{"title":"Spring Boot 2.0 Projects","authors":["Mohamed Shazin Sadak
ath"],"printType":"BOOK"}},{"volumeInfo":{"title":"Spring: Microservices with Spring Boot","authors":["Ranga Rao Karanam"],"printType":"BOOK"}},{"volumeInfo":{"title":"Pro Spring Boot","
authors":["Felipe Gutierrez"],"printType":"BOOK"}},{"volumeInfo":{"title":"Mastering Spring Boot 2.0","authors":["Dinesh Rajput"],"printType":"BOOK"}}]
The source code is at github.com/alexladeira/gs-reactive-rest-service. I will follow this post with others, always trying to show what I've being learning in SpringBoot topic.
Posted on November 20, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.