Spring Boot – Front Controller and API documentation

S

Hi, and welcome to the 3d article devoted to the theme:  “How to work with ElasticSearch using Java Spring Boot”. Previous article (Part 2: Java Spring Boot, ElasticSearch and docker environment) is located here. Here we will start to investigate Java Spring Boot skeleton project. But at first it would be great to refresh at mind our architecture scheme from the Part 1: Java ElasticSearch

Search microservice architecture
Search microservice architecture

All starts from the Controller. So let’s find it at project structure. It is located the /src/main/java/com/udemy_sergii_java/spring_boot_es folder. Here you will find a SpringBootEsApplication.java file with SpringBootEsApplication class.

SpringBootEsApplication
package com.udemy_sergii_java.spring_boot_es;

//! imports omitted for readability !

@SpringBootApplication
@RestController
public class SpringBootEsApplication {

    @Autowired
    SearchService searchService;

    @RequestMapping(value = "/search", method = RequestMethod.GET)
    public ResponseEntity<List<SearchResponseModel>> search(
            @Validated @ModelAttribute SearchRequestModel searchRequestModel
    ) {
        try {
            HotelSearchCriteriaUrlBuilder builder = new HotelSearchCriteriaUrlBuilder(
                    searchRequestModel
            );
            HotelSearchCriteriaDirector director = new HotelSearchCriteriaDirector(
                     builder
            );
            director.buildCriteria();

            HotelSearchCriteria criteria = director.getCriteria();

            SearchPage<HotelBookingDocument> searchPage = searchService.search(criteria);
            Iterator<SearchHit<HotelBookingDocument>> iterator = searchPage.iterator();
            List<SearchResponseModel> results = new ArrayList<>();

            while (iterator.hasNext()) {
                HotelBookingDocument hotel = iterator.next().getContent();
                results.add(new SearchResponseModel(
                        hotel.getName(), hotel.getCityNameEn())
                );
            }

            return ResponseEntity.ok(results);

        } catch (Throwable t) {
            //implement Exceptions properly - current approach is only for learning purpose
            //you may apply to https://reflectoring.io/spring-boot-exception-handling/
            List<SearchResponseModel> results = new ArrayList<>();
            SearchResponseModel response = new SearchResponseModel("", "");
            response.setError(t.getMessage());
            response.setStatus(HttpStatus.BAD_REQUEST.value());
            results.add(response);

            return ResponseEntity.ok(results);
        }
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringBootEsApplication.class, args);
    }
}

Please, take a look at current controller annotations. That is used by the swagger package which is able to read it properly and create ready documentation for our api that would be avaialble at next url: /swagger-ui.html

# configuration/SwaggerConfig.java

package com.udemy_sergii_java.spring_boot_es.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import springfox.documentation.service.ApiInfo;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build().apiInfo(apiInfo());
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder().title("Udemy ElasticSearch Spring Boot API").version("1.0.0").build();
    }
}

I will try to explain briefly what we have here. We are saying that our API point is able to process all parameters that are defined by the SearchRequestModel. Let’s go inside and see what we have.

# model/request/SearchRequestModel.java

package com.udemy_sergii_java.spring_boot_es.model.request;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiParam;

@ApiModel
public class SearchRequestModel {

    @ApiParam(value = "Page number", defaultValue = "1", required = false)
    private Integer page = 1;

    @ApiParam(value = "Page size", defaultValue = "10", required = false)
    private Integer size = 10;

    @ApiParam(value = "Hotel name", required = false)
    private String hotel;

    @ApiParam(value = "City name", required = true)
    private String city;

    @ApiParam(value = "Latitude", required = false)
    private Float lat;

    @ApiParam(value = "Longitude", required = false)
    private Float lng;

    @ApiParam(value = "Free places", required = false)
    private Boolean fpn;

    @ApiParam(value = "Hotel age", required = false)
    private Integer age;

    public Integer getPage() {
        return page;
    }

    public void setPage(Integer page) {
        this.page = page;
    }

    public Integer getSize() {
        return size;
    }

    public void setSize(Integer size) {
        this.size = size;
    }

    public String getHotel() {
        return hotel;
    }

    public void setHotel(String hotel) {
        this.hotel = hotel;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public Float getLat() {
        return lat;
    }

    public void setLat(Float lat) {
        this.lat = lat;
    }

    public Float getLng() {
        return lng;
    }

    public void setLng(Float lng) {
        this.lng = lng;
    }

    public Boolean getFpn() {
        return fpn;
    }

    public void setFpn(Boolean fpn) {
        this.fpn = fpn;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

That is all familiar properties we already discussed at initial article “ElasticSearch how to build search system”. I will repeat here our main task scheme. (p.s if it is the first article which you start reading from, maybe you would like to read the previous articles in ascending order – in that case you may choose java tag at menu – that will display articles related to current theme sorted by date)

ElasticSearch search
ElasticSearch search

Another model is used for response. We are saying that our response would be represented as a list of SearchResponseModel items

#model/response/SearchResponseModel.java

package com.udemy_sergii_java.spring_boot_es.model.response;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;

@ApiModel
public class SearchResponseModel implements Serializable {
    @ApiModelProperty(notes = "Hotel name.",
            example = "Royal Beach", required = true)
    private String hotel;

    @ApiModelProperty(notes = "City name where hotel is located at English.",
            example = "London", required = true)
    private String city;

    @ApiModelProperty(notes = "Response status.",
            example = "200", required = false)
    private Integer status = 200;

    @ApiModelProperty(notes = "Error text.",
            example = "Exception", required = false)
    private String error = "";

    public SearchResponseModel(String hotel, String city) {
        this.hotel = hotel;
        this.city = city;
    }

    public String getHotel() {
        return hotel;
    }

    public void setHotel(String hotel) {
        this.hotel = hotel;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public String getError() {
        return error;
    }

    public void setError(String error) {
        this.error = error;
    }
}

In human words all that means next – after taking all request parameters , API point returns the list of hotels, where every hotel represented by name and star + we add some service info about status and errors.

So, what is for all those annotations? Lets open our api doc page.

Spring Boot swagger

As you see our GET API point was automatically recognized by the swagger package, moreover if we will expand it – you will see all parameters that can be processed by our API point.

API GET parameters Spring Boot Java

We also  can see how the response looks and even how our models look inside.

Model Response Spring Boot Java

Wow, cool isn’t it?  We have got clear documentation, now any front end developer is able to make integration with our microservice as he knows exactly what parameters we expect and what the final response would be. That is how a good API should look like. So, great, we already have a controller with a well documented API. At the next lecture (Part 4: “Spring Boot ElasticSearch – builder pattern and DTO search criteria object“) we will speak about the builder pattern and DTO search criteria object. If you would like to pass all material more fast, then I propose you to view my on-line course at udemy where you will also find full project skeleton. Below is the link to the course. As the reader of that blog you are also getting possibility to use coupon for the best possible low price. Otherwise, please wait at next articles. Thank you for you attention.


architecture cluster docker elasticsearch flask geo high availability java php programming languages python recommendation systems search systems spring boot symfony