Contract-Driven Development 4: Generating controller interfaces
Posted on January 2, 2024 • 4 minutes • 722 words • Other languages: Español
Using swagger codegen maven plugin to generate controller implementable code.
Check the github repo
This is a continuation of Contract-Driven Development 3: Creation of contracts .
Everything we’ll do here, you can find in in the github repo.
Spring City Explorer - Backend: Branch feature/cdd-4
Little fix to the OpenAPI.yaml files before moving on
In the OAS files, when we mention the elements of an enum, it is useful to enumerate each element between quotation marks.
In our current project this is important cause we have some enumerations that have the element NO in it, and without the quotation, the autogeneration of code will interpret NO as FALSE. This could cause some problems down the line.
Before:
enum: [published_desc, published_asc, popularity]
After:
enum: ["published_desc", "published_asc", "popularity"]
What are we looking to achieve?
The idea is that on build time, somehow someway magical but configurable, autogenerated code appears in the target folder of our project, that represents the behaviour we want our controllers to have.
Adding swagger codegen maven plugin to our codebase
For this, I’m gonna use the knowledge provided by Ammar Siddiqui in his github repo increment-service and his youtube video Swagger Codegen in 20 minutes!.
I won’t be following the video exactly step by step, but instead the main ideas he exposes.
Adding dependencies and plugin to pom.xml
Remember that you can check the final pom.xml file in the project github repo.
Here’s a brief description of the dependencies added:
- javax.validation (validation-api): Provides the API for Java Bean Validation, allowing for constraint declarations and validation of Java objects.
- io.springfox (springfox-boot-starter): Integrates SpringFox with Spring Boot applications, enabling Swagger 2 documentation for RESTful services.
- javax.annotation (javax.annotation-api): Provides common annotation types for Java, such as @Generated and @Resource.
- org.slf4j (slf4j-api): The Simple Logging Facade for Java (SLF4J) serves as a simple facade for various logging frameworks.
- ch.qos.logback (logback-classic): Logback Classic Module, a reliable, fast and flexible logging framework, intended as a successor to the popular log4j project.
- org.threeten (threetenbp): Backport of the Java 8 java.time API (JSR-310) for Java 6 and 7, providing an improved date and time API.
Finally, the plugin that is gonna do the heavy lift, io.swagger.codegen.v3 (swagger-codegen-maven-plugin)
- Generates server stubs and client SDKs from an OpenAPI Specification (OAS).
- In its configuration, it’s set up to generate Spring MVC controller interfaces from a specified YAML file, placing them in the defined output directory.
- The configuration is tailored to generate only the interfaces, without additional supporting files.
maven compile -> generated code!
Now everytime you do the “maven compile” task, you’ll see code generated in target/generated-sources/swagger/controllerinterfaces.
If you explore those .java files, you’ll immediatly notice that they are quite complex, with many annotations, comments, and obviously autogenerated stuff. And this is the main statement I want to leave living rent-free in your head after reading this blog:
The intricate processes outlined in the OpenAPI Specification ought to be automated, allowing developers to focus on their core business responsibilities.
Pollito, on his own blog
Creating controllers
Now let’s create the controllers inside a controller package in our src/main/java folder. For each controller:
- Add the @RestController annotation.
- Implement the corresponding interface.
It should look something like this:
Folder structure
ArticleController
package dev.pollito.springcityexplorer.controller;
import dev.pollito.springcityexplorer.api.ArticleApi;
import dev.pollito.springcityexplorer.models.Articles;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ArticleController implements ArticleApi {
@Override
public ResponseEntity<Articles> getArticlesByCountry(String country, Integer limit, Integer offset) {
return null;
}
}
CommentController
package dev.pollito.springcityexplorer.controller;
import dev.pollito.springcityexplorer.api.CommentApi;
import dev.pollito.springcityexplorer.models.CommentPostBody;
import dev.pollito.springcityexplorer.models.Comments;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CommentController implements CommentApi {
@Override
public ResponseEntity<Comments> getComments(Integer limit, Integer offset) {
return null;
}
@Override
public ResponseEntity<Void> postComment(CommentPostBody body) {
return null;
}
}
WeatherController
package dev.pollito.springcityexplorer.controller;
import dev.pollito.springcityexplorer.api.WeatherApi;
import dev.pollito.springcityexplorer.models.Weather;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class WeatherController implements WeatherApi {
@Override
public ResponseEntity<Weather> getWeatherByCity(String city) {
return null;
}
}
If everything went ok, now you’ll be able to see in the Spring -> MVC section of your IDE the endpoints generated by the implementation of the interfaces, with all the proper annotations (RequestMapping, RequestParam, NotNull, Valid, etc.). That’s some work that you saved yourself from doing.
Running the current application
200 OK example
All current implementation are returning null, so there’s no body in the response, but we get OK status.
400 Bad request example
Next steps
- Creating some unit testing for the little code we have… yay?
- Generating feign-client code.