One of the most usual tasks requested for back-end developers is generating a microservices oriented API. When we talk about microservices, we refer to small sets of endpoints, each one of them resolving a different problem, which can be deployed individually in a fast and simple way, so they are easy to escalate in case we need to take care of high amount of requests in a certain area. This is a great advantage compared to the classic monolithic architecture, which requires the deployment of all the services at once, taking more time and resources.
REST services are more efficient than SOAP services, and also easier to securize, so it’s no surprise they are becoming so popular, but deploying and testing the services can be troublesome. Fortunately there are some frameworks which provide us some easy-to-use tools.
Let’s split this issue in 3 steps:
Generating the REST service using JSON.
Booting the service with Spring boot.
Document it and get a user-friendly interface for tests with Swagger.
In our example code, we are going to simulate a small system to handle a videogame database via REST. The code packages will be defined as follows:
The API package will contain all the REST API, dividing it by layers (services, data access and data transfer objects), and providing the controller with the endpoints.
To set up the swagger system we will need a configuration file, plus the API package classes should get new annotations to document its content.
Finally we will add the generic Spring boot runner, which may be used for any package and requires very little tuning.
Process
1. Designing a dummy RESTful web service
The first step is creating a basic structure for the REST service. We’ll split it up in different layers:
The controller would be VideogameController, and would contain all the endpoints, we’ll leave this for later as, we’ll talk about setting it up with swagger.
The interface IVideogameService will define the service layer, which would have the “normal” endpoints.
The IVideogameDAO gives an idea of the methods available through the Data Access Layer. In order to void much otrouble setting up a database connection or a real repository, we will mock it.
A good practice is to split the design and the implementation by using interfaces. It’s not really necessary, but it will make the code more reusable.
graph TD
A[VideogameController]
B[IVideogameService]
C[IVideogameDao]
A --> B;
B --> C;
Therefore the Service would simulate commonly known as CRUD operations: get (find), set (update), add (save) and remove (delete).
But in this case we want to keep it simple since we are focusing on the services layer., so instead of configuring a database connection and writting some queries, we are going to mock it with a simple Map structure.
The Data Transfer Object or DTO isn’t really interesting right now, as it would be just as simple as a class with a couple of Strings to define its attributes. So we will skip its content for now.
Finally, we will refer to the endpoint as the Controller, and since we would be using a REST service with JSON, we will need to add its maven dependency.
The Controller for Spring MVC would be an extra layer where most of the interesting things we are studying today will happen. First of all, we are going to connect it with the service, and set up the REST via spring JAX-RS annotations:
RequestMapping configures the path.
RequestMapping states the method expects an HTTP request.
RequestBody gets the information from the HTTP body.
PathVariable gets a value from the URL.
ResponseStatus will store the status code to send back.
ResponseBody gets the information from the HTTP body
With this we are all set-up, so let’s find somewhere to run this code.
2. Let’s boot it!
Spring boot is a tool to generate a “just run” application, which can be set up with only a few lines of code an annotations, without any xml configuration. This makes it really interesting in order to run and test Java microservices really fast.
Let’s start by adding its maven dependencies and setting up its just run configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<dependencies> <!-- spring boot setup ability --> <dependency> <groupId>org.springframework.boot>/groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <!-- Just run configuration --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
Then, we need to set it up in the Java code itself. The longest way to configure it would be using the following annotations:
RestController, it’s an Spring-MVC, which sets up this class as controller for a REST service
EnableAutoConfiguration, this configures the boot service according to dependencies we have included. Since we used TesController, the system will consider to add Spring MVC and Tomcat.
ComponentScan, scans the components of the package and its children packages.
But all this 3 annotations can be reduced to a single one: SpringBootApplication (scanBasePackages = { “replace_with_main_package” }), which is the one used in the example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Generic SpringBoot, only configured by setting the scanBasePackages * restricts the scanned packages */ @SpringBootApplication(scanBasePackages = { "org.thalion.snippet.swagger" }) publicclassSpringbootRunner { publicstaticvoidmain(String[] args) { SpringApplication.run(SpringbootRunner.class, args); } }
So just by running this class, a local server will be deployed, and we are ready to test the service… but that’s not intuitive. Let’s make it better.
3. Making it pretty and easy to test, plus avoiding setting up the client side
Documenting an API to make it easy to understand for other developers, and make it friendly for the testers is no easy task. Swagger is a framework which, using a few extra annotations, is able to generate a simple web user interface with REST calls to the API documented, using the metadata available. We are going to use the springfox version, as it comes already bundled with the correct annotations.
❕This was the stable version when the post was originally written.
Api: for controller classes, it sets the API endpoint documentation.
Apimodel: for Data Transfer Objects class names.
ApiModelProperty: for Data Transfer Objects attributes.
ApiOperation: they go above the methods or services available on an endpoint.
ApiParam: for input parameter on a method.
ApiResponse: for output parameters. You can have more than one with a set of ApiResponses (e.g. also add the 404 error, etc).
Then, in order to fully set this up, there are 2 important points to recheck: the DTO and the controller.
Let’s start with the DTO, as it’s the most straight-forward part: we only need to document the information about the class definition itself and the attributes.
import java.util.Collection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.thalion.snippet.swagger.api.dto.Videogame; import org.thalion.snippet.swagger.api.service.IVideogameService; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; @RestController @RequestMapping("/api/videogames") publicclassVideogameController { @Autowired private IVideogameService service; @ResponseStatus(HttpStatus.OK) @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "Get Videogames", notes = "Returns all the videogame data") @ApiResponses({ @ApiResponse(code = 200, message = "Returns this information") }) public Collection<Videogame> getAllVideogames() { return service.findAll(); } @ResponseStatus(HttpStatus.OK) @RequestMapping(method = RequestMethod.GET, value = "{name}", produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "Get the info for one videogame", notes = "Returns the info from one videogame") @ApiResponses({ @ApiResponse(code = 200, message = "Exists this information") }) public Videogame getVideogameByName( @ApiParam(defaultValue = "default", value = "The name of the videogame to return") @PathVariable String name) { return service.findOne(name); } @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "Create videogame information", notes = "Create a videogame entry") @ApiResponses({ @ApiResponse(code = 201, message = "The videgame entry was created successfully") }) public ResponseEntity<String> createVideogame(@RequestBody Videogame videogame) { StringvideogameCreatedId= service.save(videogame); returnnewResponseEntity<String>(videogameCreatedId, HttpStatus.CREATED); } @ResponseStatus(HttpStatus.NO_CONTENT) @RequestMapping(method = RequestMethod.PUT, value = "{name}") @ApiOperation(value = "Update videogame information", notes = "Update a videogame information entry") @ApiResponses({ @ApiResponse(code = 204, message = "The videgame entry was updated successfully") }) publicvoidupdateVideogame( @ApiParam(defaultValue = "Default", value = "The name of the videogame to update") @PathVariable String name, @RequestBody Videogame videogame) { videogame.setName(name); service.update(videogame); } @ResponseStatus(HttpStatus.NO_CONTENT) @RequestMapping(method = RequestMethod.DELETE, value = "{name}") @ApiOperation(value = "Delete videogame", notes = "Deletes a videogame entry") @ApiResponses({ @ApiResponse(code = 204, message = "The videgame entry was deleted successfully") }) publicvoiddeleteInfo( @ApiParam(defaultValue = "Default", value = "The name of the videogame to delete") @PathVariable String name) { service.delete(name); } }
Once all the documentation annotations have been written down, we just need to configure the API page with the help of a Docket builder, which provides the primary graphic interface for our swagger implementation.
Since we are working with an API, we are only going to worry about the URLs under the /api/ pattern, which we can select with the help of regular expressions.
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.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 publicclassSwaggerConfig { @Bean public Docket newsApi() { returnnewDocket(DocumentationType.SWAGGER_2).groupName("api-videogames") .apiInfo(apiVideogames()).select().paths(PathSelectors.regex("/api.*")).build(); } private ApiInfo apiVideogames() { returnnewApiInfoBuilder().title("Videogames REST api POC") .description("PoC of a REST api, to test both Springboot and Swagger") .termsOfServiceUrl("https://creativecommons.org/licenses/by/4.0/") .contact("abcd@mail.com") .license("GNU General Public License v3.0").licenseUrl( "https://www.gnu.org/licenses/gpl-3.0.en.html").version("3.0").build(); } }
And we finally get this beautiful webpage with all the endpoints, which we can easily interact with on localhost:8080/swagger-ui.html. We can interact with the different methods and set up the arguments to do quick tests, avoiding the need to write a client or using a REST requests generator like SoapUI.
Congratulations! your demo is completely set up and ready to go.