Introduction to Swagger
Swagger is a powerful tool to enable sharing REST contracts with each other. It’s useful in any architecture today and even better if you are thinking about integrating with a 3rd party service. I think everyone was in the situation that developers have to agree in certain HTTP endpoints, naming, parameters, methods, response types, response codes, etc. Even when there is a consensus, how people share the agreement? In a Word document? In an email? Sure, it’s working fine as well but there is another, formalized possibility to do this which is Swagger contract.
Swagger contract is a tool which implements the OpenAPI specification which is essentially giving you a language to describe your API in a standard way and it also gives you a couple of other things like an API editor, code generator, etc.
Example API
Consider the following API for a user management service. I’m keeping it as simple as possible with two endpoints, one for getting the list of users and one for creating a new user.
swagger: "2.0" info: description: "This is a sample specification for user service" version: "1.0.0" title: "User Service contract" tags: - name: "user" paths: /user: get: tags: - "user" summary: "Gets a list of users" description: "" operationId: "getUsers" consumes: - "*/*" produces: - "application/json" responses: 200: schema: $ref: "#/definitions/ListUserResponse" description: "List of all users" post: tags: - "user" summary: "Creates a new user" operationId: "createUser" consumes: - "application/json" produces: - "application/json" parameters: - in: "body" name: "user" description: "The user data" required: true schema: $ref: "#/definitions/UserCreateRequest" responses: 201: description: "User successfully created" 400: description: "Validation error" definitions: ListUserResponse: type: "array" items: $ref: "#/definitions/UserResponse" UserResponse: type: "object" properties: id: type: "integer" name: type: "string" UserCreateRequest: type: "object" properties: name: type: "string"
The endpoint is located at /user and two methods are supported, GET and POST. It’s specified which MIME types are supported for which one and the incoming parameters are also defined along with the return type of the API. I’m not going into much details, but if you are interested, take a look at swagger.io.
Introduction to Swagger Codegen
Simply just describing the API has a very big advantage because now people have a common language to read and write APIs. What could be even better, if you can generate some code from the API description to avoid the boilerplate code for each and every endpoint you want to call or implement. Let’s assume you want to have a server for the API I showed but you don’t want to manually define all the mappings and all the other stuff but you want to have the base code ready just by using the Swagger contract.
Fortunately, this is where Swagger Codegen comes into the picture as it’s specifically doing code generation from a contract. You can use Swagger Codegen easily via CLI, Maven, as well as other ways.
You just have to provide a couple of information for the generator and voila, you have the stubs. This basic info is, the input file, output directory, the language you want to use the stub for and after these, only customization is left.
Using the generator in Gradle is not that simple but I have to say it’s not that difficult either, let’s check it out.
Swagger Codegen with Gradle
First, starting with just generating the server-side stub for the contract. We’ll need an empty Gradle project which means a build.gradle and a settings.gradle file and of course optionally a Gradle wrapper.
My project has the build.gradle and the settings.gradle , additionally I have Gradle wrapper and I’ve put the Swagger contract file into the api folder, named swagger.yml.
Let’s take a look at the project structure, defined in the settings.gradle
def name = 'user-service' def rootName = "${name}-contract" def serverName = "${name}-server" rootProject.name = rootName file(serverName).mkdir() include serverName
We’ll have a subproject, called user-service-server which will be just a placeholder to refer from the buildscript. Now let’s see how the build.gradle looks like.
buildscript { repositories { mavenCentral() } dependencies { classpath("io.swagger:swagger-codegen:2.3.1") } }
First of all, the buildscript is depending on the Swagger Codegen as we’ll invoke classes directly from it and of course Maven Central is necessary as well.
apply plugin: 'base' group = 'com.arnoldgalovics.blog' import io.swagger.codegen.DefaultGenerator import io.swagger.codegen.config.CodegenConfigurator
Now the base plugin will be applied as we need the base tasks, like cleaning up the build directory. Group is just the groupId and the 2 imports are important as these classes will be used in the buildscript to trigger the code generation. Now we can define the basics for the generated subproject, user-service-server.
subprojects { repositories { mavenCentral() } apply plugin: 'java' apply plugin: 'maven' group = 'com.arnoldgalovics.blog' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 targetCompatibility = 1.8 compileJava.options.fork = true /* Setting the build directory of the subproject to user-service-contract/build/[projectName] */ project.buildDir = "${rootProject.buildDir}/${project.name}" // Source folder is now different as the build directory is set to a custom path sourceSets { main { java { srcDir "${project.buildDir}/src/main/java" } } } // This is just for generating the source jar task sourcesJar(type: Jar, dependsOn: classes) { classifier = 'sources' from sourceSets.main.allSource } artifacts { archives sourcesJar } }
At the beginning, it’s just a standard Gradle project with Java and Maven plugins. Then the build directory of the subproject is overwritten, for example in case of user-service-server, it will be user-service-contract/build/user-service-server. The last thing is to generate a JAR file with the sources.
I defined a couple of properties in advance also for the generator.
ext.appName = 'user-service' ext.apiPackage = 'com.arnoldgalovics.blog.userservice.api' ext.modelPackage = 'com.arnoldgalovics.blog.userservice.model' ext.swaggerFile = "${rootDir}/api/swagger.yml"
Now the tricky part comes, the actual generation. Here, we can simply refer to the subproject we defined in the settings.gradle and execute the generation inside of it.
project("${rootProject.appName}-server") { // user-service-server // Dependencies for the generated sources dependencies { compile('org.springframework.boot:spring-boot-starter-web:1.5.9.RELEASE') compile('io.springfox:springfox-swagger2:2.7.0') } // Actual task for generating the server task generateServer { doLast { def config = new CodegenConfigurator() config.setLang("spring") config.setApiPackage(rootProject.apiPackage) // Package to be used for the API interfaces config.setModelPackage(rootProject.modelPackage) // Package to be used for the API models config.setInputSpec(rootProject.swaggerFile.toString()) // The swagger API file config.setOutputDir(project.buildDir.toString()) // The output directory, user-service-contract/build/user-service-server/ config.setAdditionalProperties([ 'dateLibrary' : 'java8', // Date library to use 'useTags' : 'true', // Use tags for the naming 'interfaceOnly' : 'true' // Generating the Controller API interface and the models only ]) new DefaultGenerator().opts(config.toClientOptInput()).generate() // Executing the generation } } /* Defining the order of the tasks and the dependency between them. As this is a Java project, it will execute the generateServer task first, then the compilation of the generated files. */ compileJava.dependsOn('generateServer') }
What’s important here is that we’re defining the dependencies for the generated sources, in this particular case, we’ll need Spring MVC (for this I used spring-boot-starter as it contains everything which we need) and springfox for the Swagger annotations.
Then a special task is defined to generate the server-side of the contract. There are plenty of configuration possibilities, here I defined only the basics like the language to use, the packages and some additional, language specific properties. After we’re done with the configuration, we’ll execute the generation with the DefaultGenerator class by invoking the generate method.
In order to execute it before the compilation process – as Swagger generates only the sources of course – I wired in the generateServer task right before the compileJava task.
Executing ./gradlew clean build install will generate the interfaces and the models from the Swagger API under the user-service-contract/build/user-service-server/libs folder in JAR format, along with the sources. Also a pom.xml is generated under the poms folder. Having the install task as well will simply put the artifacts into the Maven local repository. This is necessary as we want to use them for the actual service.
Let’s take a deeper look into the generated files. Under the src/main/java/com/arnoldgalovics/blog/userservice/api folder, a UserApi.java is generated which has the defined endpoints from the Swagger contract. It has the proper Spring MVC annotations and it has some template implementation as well with some other default methods.
@Api(value = "User", description = "the User API") public interface UserApi { Logger log = LoggerFactory.getLogger(UserApi.class); default Optional<ObjectMapper> getObjectMapper() { return Optional.empty(); } default Optional<HttpServletRequest> getRequest() { return Optional.empty(); } default Optional<String> getAcceptHeader() { return getRequest().map(r -> r.getHeader("Accept")); } @ApiOperation(value = "Creates a new user", nickname = "createUser", notes = "", tags={ "user", }) @ApiResponses(value = { @ApiResponse(code = 201, message = "User successfully created"), @ApiResponse(code = 400, message = "Validation error") }) @RequestMapping(value = "/user", produces = { "application/json" }, consumes = { "application/json" }, method = RequestMethod.POST) default ResponseEntity<Void> createUser(@ApiParam(value = "The user data" ,required=true ) @Valid @RequestBody UserCreateRequest user) { if(getObjectMapper().isPresent() && getAcceptHeader().isPresent()) { } else { log.warn("ObjectMapper or HttpServletRequest not configured in default UserApi interface so no example is generated"); } return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } @ApiOperation(value = "Gets a list of users", nickname = "getUsers", notes = "", response = ListUserResponse.class, tags={ "user", }) @ApiResponses(value = { @ApiResponse(code = 200, message = "List of all users", response = ListUserResponse.class) }) @RequestMapping(value = "/user", produces = { "application/json" }, consumes = { "*/*" }, method = RequestMethod.GET) default ResponseEntity<ListUserResponse> getUsers() { if(getObjectMapper().isPresent() && getAcceptHeader().isPresent()) { if (getAcceptHeader().get().contains("application/json")) { try { return new ResponseEntity<>(getObjectMapper().get().readValue("\"\"", ListUserResponse.class), HttpStatus.NOT_IMPLEMENTED); } catch (IOException e) { log.error("Couldn't serialize response for content type application/json", e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } } else { log.warn("ObjectMapper or HttpServletRequest not configured in default UserApi interface so no example is generated"); } return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } }
Under the model package, we have the generated models for the responses and for the requests. They are simple POJOs with a couple of jackson annotations, setters, getters. For example UserResponse.java looks like this
/** * UserResponse */ @Validated @javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-05-18T21:52:03.728+02:00") public class UserResponse { @JsonProperty("id") private Integer id = null; @JsonProperty("name") private String name = null; public UserResponse id(Integer id) { this.id = id; return this; } /** * Get id * @return id **/ @ApiModelProperty(value = "") public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public UserResponse name(String name) { this.name = name; return this; } /** * Get name * @return name **/ @ApiModelProperty(value = "") public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public boolean equals(java.lang.Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } UserResponse userResponse = (UserResponse) o; return Objects.equals(this.id, userResponse.id) && Objects.equals(this.name, userResponse.name); } @Override public int hashCode() { return Objects.hash(id, name); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("class UserResponse {\n"); sb.append(" id: ").append(toIndentedString(id)).append("\n"); sb.append(" name: ").append(toIndentedString(name)).append("\n"); sb.append("}"); return sb.toString(); } /** * Convert the given object to string with each line indented by 4 spaces * (except the first line). */ private String toIndentedString(java.lang.Object o) { if (o == null) { return "null"; } return o.toString().replace("\n", "\n "); } }
Using the generated sources
Now as we have the artifacts ready to be used in the Maven local repository, let’s start with a new project which will use the generated code. I’ll create a Gradle project here as well just to stick with the same build tool but it doesn’t really matter. I’ve created a project called user-service and this is how the build.gradle looks like:
buildscript { ext { springBootVersion = '1.5.13.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' group = 'com.arnoldgalovics.blog' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 repositories { mavenLocal() // Use maven local as the artifacts were deployed there mavenCentral() } dependencies { compile('org.springframework.boot:spring-boot-starter-web') compile('com.arnoldgalovics.blog:user-service-server:0.0.1-SNAPSHOT') // Generated artifacts }
Completely standard setup for a Spring Initializr project, the only important thing here is that it’s configured to use the Maven local repository and as a dependency, it’s using the generated artifacts. After importing the project to your favorite IDE, we can access the generated API interface and can create the following UserApiController class.
@Controller public class UserApiController implements UserApi { @Override public ResponseEntity<Void> createUser(UserCreateRequest user) { // TODO: implementation return null; } @Override public ResponseEntity<ListUserResponse> getUsers() { // TODO: implementation return null; } }
Here, as you can see it’s implementing the generated UserApi interface which defines the endpoints.
Now we are ready to start the application including the endpoints that are defined by the Swagger API.
The full project is available on my GitHub. If you enjoyed the article, share it and let me know your thoughts on it in comments or on Twitter.
Well written, would u like to share with us how to generate client side code with swagger? thinks
Glad you liked it. It’s already available within my Swagger Codegen with Gradle series but here you go: https://blog.arnoldgalovics.com/generating-feign-clients-with-swagger-codegen-and-gradle/