Embedding live service contracts documentation with Swagger
Swagger is a REST specification that allows you to generate interactive API documentation.
-
Swagger generates an interactive API console to quickly learn and try the API.
-
Swagger generates the client SDK code needed for implementations on various platforms.
-
Swagger allows to generate client code on a lot of different platforms.
-
Swagger has a strong community with helpful contributors.
In this exercise you are going to create simple REST web service using Swagger API Documentation.
The API documentation generated by the Swagger file is minimal. It shows the resources, parameters, requests, and responses. However, it’s not going to provide any other detail about how your API works. |
Step-by-step instructions to bootstrap the project
Follow Kata1 guide to setup a basic service: Creating Basic Web Service
Swagger Implementation
Add dependency jars
Add the following code to the build script:
compile("io.springfox:springfox-swagger2:2.2.2")
compile("io.springfox:springfox-swagger-ui:2.2.2")
This script will add swagger dependency jars into the application.
Bean configuration
The project contains an entry point in the Application.java file:
@SpringBootApplication
@EnableSwagger2 (1)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean (2)
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().paths(regex("/accountsvc.*")).build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("Spring REST Sample with Swagger").description("Spring REST Sample with Swagger")
.termsOfServiceUrl("http://dockerhost:8100/termsofservice").contact("service@ericsson.com")
.license("Apache License Version 2.0").licenseUrl("http://dockerhost:8100/LICENSE").version("2.0").build();
}
}
1 | We have added @EnableSwagger2 annotation to enable this application to use Swagger. |
2 | The api() method creates a bean which contains the general information about the API and an inventory of the available resources. |
We’re going to rename HomeController we created in Kata1 guide to AccountController and add a get API and describe it with swagger annotation.
Swagger annotation
@Api(value = "accountsvc", description = "Endpoint for account management") (1)
@RequestMapping(value = "/accountsvc")
class AccountController {
@ApiOperation(value = "list accounts", notes = "Lists all accounts. Account informtion contains id, name and email.",
produces = "application/json") (2)
@ApiResponses(value = { @ApiResponse(code = 200, message = "Success", response = Account.class) }) (3)
@RequestMapping(method = GET, path = "/accounts", produces = "application/json")
ResponseEntity<List<Account>> get() {
return new ResponseEntity<List<Account>>(account, OK);
}
@ApiOperation(value = "look up account by id", notes = "Looks up account by id.")
@ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "Account id", required = true, dataType = "int",
paramType = "path", defaultValue = "1") }) (4)
@ApiResponses(value = { @ApiResponse(code = 200, message = "Success", response = Account.class) })
@RequestMapping(method = GET, path = "/accounts/{id}", produces = "application/json")
ResponseEntity<Account> getById(@PathVariable int id) throws Exception {
return new ResponseEntity<Account>(account.get(id - 1), OK);
}
@ApiOperation(value = "add new account", nickname = "Add a new account", response = Void.class)
@ApiResponses(value = { @ApiResponse(code = 201, message = "Success", response = Account.class) })
@RequestMapping(value = "/accounts", method = POST, produces = "application/json", consumes = "application/json")
ResponseEntity<Account> post(@ApiParam(value = "Created Account object")
@RequestBody Account body) { (5)
account.add(body);
return new ResponseEntity<Account>(body, CREATED);
}
@ApiOperation(value = "update an Account", nickname = "Updates existing account with new name and/or email",
response = Void.class)
@ApiResponses(value = { @ApiResponse(code = 201, message = "Success", response = Account.class) })
@RequestMapping(value = "/accounts/{id}", method = PUT, produces = "application/json", consumes = "application/json")
ResponseEntity<Account> put(@PathVariable int id,
@ApiParam(value = "Updated Account object") @RequestBody Account body) {
account.remove(id - 1);
account.add(body);
return new ResponseEntity<Account>(body, OK);
}
@ApiOperation(value = "delete account", notes = "Deletes account by id")
@RequestMapping(value = "/accounts/{id}", method = DELETE)
@ApiResponses(value = { @ApiResponse(code = 400, message = "Account not found", response = void.class) })
void delete(@PathVariable int id) throws Exception {
if (id <= account.size()) {
account.remove(id - 1);
} else {
throw new AccountException("Account not found");
}
}
@ApiModel( value = "Account", description = "Account resource representation" ) (6)
class Account {
@ApiModelProperty( value = "Account id", required = true )
private int id;
@ApiModelProperty( value = "Account name", required = true )
private String name;
@ApiModelProperty( value = "Account email", required = false )
private String email;
public Account() {
}
1 | @Api annotation describes a top-level api. In order to make Swagger aware of your endpoint, you need to annotate your class with @Api annotation. |
2 | @ApiOperation annotation provides detailed description of what a certain method does. |
3 | @ApiResponses annotation represents a type of response from a server. This can be used to describe both success codes as well as errors. If your Api has different response classes, you can describe them here by associating a response class with a response code. |
4 | @ApiImplicitParams annotation represents a single parameter in an API Operation.This allows you to manually define a parameter in a fine-tuned manner. |
5 | @ApiParam annotation represents a single parameter in an Api Operation. A parameter is an input to the operation.When working with path or query parameter, you should always provide clarification of what this parameter represents. |
6 | @ApiModel annotation models classes to provide the model schema, which helps in understanding/documenting the request response structure using the specific annotation using @ApiModel annotations. |
server:
port: 8100
endpoints:
health:
sensitive: false
shutdown:
enabled: true
Build and launch
Build the service:
$ cd accountsvc $ gradle build
Note that this command will build the code, generate the jar and run all the necessary tests.
Building a Docker image
Build the project and generate Dockerfile by run the following command:
$ gradle prepDocker ... :prepDocker Run command: docker build -t msvcdojo/accounts-service:0.0.1 build/docker BUILD SUCCESSFUL
Everything was prepared for you, so you can just copy the suggested command from the console and run it:
$ docker build -t msvcdojo/accounts-service:0.0.1 build/docker Sending build context to Docker daemon 12.95 MB Step 1 : FROM java:8 Step 2 : MAINTAINER Igor Moochnick "igor@igorshare.com" Step 3 : VOLUME /tmp Step 4 : EXPOSE 8100 Step 5 : ADD accountsvc-0.0.1.jar accountsvc.jar Step 6 : ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -jar /accounts-service.jar Successfully built cb55f79088db
This has created a Dockerimage with the tag msvcdojo/accounts-service:0.0.1. Now let’s see it in the Docker repository:
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
msvcdojo/accounts-service 0.0.1 fdb0ace78a40 2 minutes ago 660.6 MB
Start container
Let’s start the container and map the service port to the external world by running command:
$ docker run -it --name=accountsvc -p 8100:8100 msvcdojo/accounts-service:0.0.1
The following output you’ll see on the screen.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.3.2.RELEASE)
2016-02-26 20:35:16.875 INFO 1 --- [ main] svcdojo.Application : Starting Application on 013471f370b4 with PID 1 (/accountsvc.jar started by root in /)
2016-02-26 20:35:16.882 INFO 1 --- [ main] svcdojo.Application : No active profile set, falling back to default profiles: default
2016-02-26 20:35:17.065 INFO 1 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@2b3bb14b: startup date [Fri Feb 26 20:35:17 UTC 2016]; root of context hierarchy
2016-02-26 20:35:19.908 INFO 1 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'beanNameViewResolver' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2016-02-26 20:35:20.252 INFO 1 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'managementServletContext' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.actuate.autoconfigure.EndpointWebMvcHypermediaManagementContextConfiguration; factoryMethodName=managementServletContext; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/actuate/autoconfigure/EndpointWebMvcHypermediaManagementContextConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; factoryMethodName=managementServletContext; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.class]]
2016-02-26 20:35:22.289 INFO 1 --- [ main] org.eclipse.jetty.util.log : Logging initialized @8055ms
2016-02-26 20:35:22.437 INFO 1 --- [ main] e.j.JettyEmbeddedServletContainerFactory : Server initialized with port: 8100
2016-02-26 20:35:22.448 INFO 1 --- [ main] org.eclipse.jetty.server.Server : jetty-9.2.14.v20151106
2016-02-26 20:35:22.539 INFO 1 --- [ main] application : Initializing Spring embedded WebApplicationContext
..
2016-02-26 20:35:29.295 INFO 1 --- [ main] s.w.ClassOrApiAnnotationResourceGrouping : Group for method put was accountsvc
2016-02-26 20:35:29.313 INFO 1 --- [ main] s.w.ClassOrApiAnnotationResourceGrouping : Group for method put was accountsvc
2016-02-26 20:35:29.489 INFO 1 --- [ main] application : Initializing Spring FrameworkServlet 'dispatcherServlet'
2016-02-26 20:35:29.489 INFO 1 --- [ main] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2016-02-26 20:35:29.554 INFO 1 --- [ main] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 62 ms
(1)
2016-02-26 20:35:29.602 INFO 1 --- [ main] o.eclipse.jetty.server.ServerConnector : Started ServerConnector@1d857713{HTTP/1.1}{0.0.0.0:8100}
2016-02-26 20:35:29.607 INFO 1 --- [ main] .s.b.c.e.j.JettyEmbeddedServletContainer : Jetty started on port(s) 8100 (http/1.1)
2016-02-26 20:35:29.638 INFO 1 --- [ main] svcdojo.Application : Started Application in 14.14 seconds (JVM running for 15.407)
1 | Listening port of the web service |
Play time
Swagger Endpoint Resource Listing
The Resource Listing serves as the root document for the API description. It contains general information about the API and an inventory of the available resources.
By default, this document SHOULD be served at the /api-docs path.
$ curl http://localhost:8100/v2/api-docs
The output of this CURL command will look like this:
{
"swagger": "2.0",
"info": {
"description": "Spring REST Sample with Swagger",
"version": "2.0",
"title": "Spring REST Sample with Swagger",
...
},
"host": "localhost:8100",
"basePath": "/",
"tags": [{
"name": "accountsvc",
"description": "Endpoint for account management"
}
"paths": {
"/accountsvc/delete/{id}": {
"delete": {
"tags": ["accountsvc"],
"summary": "delete Account By Id",
"description": "Deletes an account by passing accountId",
"operationId": "deleteUsingDELETE",
"consumes": ["application/json"],
"produces": ["*/*"],
...
}
}
}
}
Running it with Swagger UI
Swagger UI takes Swagger specification files, presents them visually and allows you to execute operations.
$ curl http://localhost:8100/swagger-ui.html
The output of this CURL command will look like this: