04 How to Maintain API Documentation for External Invocation and Online Interface Document Management

04 How to Maintain API Documentation for External Invocation and Online Interface Document Management #

In the previous chapter, we got an application up and running. Since services do not exist independently, the service development team will inevitably need to make service calls to other teams, exposing external interfaces. In the early days of development, it was common for everyone to use Word or Excel to manage the interfaces. However, the disadvantages are obvious. Once an interface changes, the documentation needs to be updated accordingly. Unfortunately, many interfaces have been updated without the documentation being kept up to date, and I believe you have also had painful experiences in this regard. This article introduces you to several interface documentation management tools and implements online interface documentation management used in this case study.

Introduction to Several API Tools #

We urgently need an interface documentation tool that can stay in sync with the system interfaces in real time without any additional cost (financial cost, time cost). Here are a few open source API tools that you can choose to use.

RAP2 #

Official website: http://rap2.taobao.org/, formerly known as RAP, developed by Alibaba’s MUX team, later the project was discontinued and transferred to the RAP2 project.

Introduction from the official website: RAP2 is a popular development interface management tool that is usually used in the development mode of front-end and back-end separation to agree on interfaces. RAP2 can automatically generate mock data based on the agreed interfaces, validate the back-end interfaces, and provide a good documentation writing experience. It supports complex mock logic and provides convenience for developers. RAP2 is based on the first generation RAP1 and has completely rebuilt all major functionalities using Node.js and React.

It can be used online or deployed locally. There are detailed deployment instructions for local deployment. From the deployment manual, the product is quite robust and has many dependencies. After deployment, there is still the problem of spending time to keep it in sync with the code. For projects that provide relatively stable interfaces, it is a good choice.

img

APIDOC #

Official website: https://apidocjs.com/, it generates API interface documentation based on well-formatted comments in the code and requires Node.js environment. It is relatively easy to use, but the disadvantage is that when interfaces change, time needs to be spent to synchronize the documentation. It can be an alternative for relatively stable interface systems.

img

Spring REST Docs #

Many API documentation in the Spring family are generated based on this component. According to the usage provided by the official documentation, you need to write corresponding code snippets and follow certain syntax rules to generate offline documentation through project building. The operation is more cumbersome, so it is not recommended to use it. The generated documentation looks like the screenshot below:

img

Swagger #

Official website: https://swagger.io, it is a software that automatically generates and documents RESTful API interfaces, and also provides functional testing. It is a complete framework and standard for generating, describing, calling, and visualizing RESTful web services that is language-independent. The overall goal is to update the client and file system at the same speed as the server. The methods, parameters, and models of the file are tightly integrated into the server-side code, allowing the API to always stay in sync. Swagger makes deployment, management, and use of powerful APIs easier than ever before.

Swagger is used in this case study for API management.

img

Integrating Swagger2 with Spring Boot #

1. Add Dependencies #

Add the necessary jar dependencies for Swagger. Since they are used in multiple submodules, we manage the version of Swagger in the root pom.xml file:

<properties>
        <swagger.version>2.8.0</swagger.version>
</properties>
<!--swagger2 -->
<dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>${swagger.version}</version>
</dependency>
<dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>${swagger.version}</version>
</dependency>

In the submodules, there is no need to configure the version, just import the dependencies. If you need to change the version, modify it directly in the root pom.xml file, and the versions of all dependencies in the submodules will be updated accordingly.

<!--swagger2 -->
<dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
</dependency>

2. Swagger2 Configuration #

Create a config package and add a Swagger configuration class. Use the @EnableSwagger2 annotation to enable Swagger2, and then configure a Docket Bean. Configure the mapping path and the location of the interfaces to be scanned. In the apiInfo method, configure the information of the Swagger2 documentation website, such as the website title, description, contact information, protocols used, and the version of the interfaces.

@EnableSwagger2
@Configuration
public class Swagger2Config {

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

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Member Module API Layer (Based on SpringBoot2+Swagger2).")
                .contact(new Contact("growithus", "https://backkoms.github.io/", "[email protected]"))
                .version("1.0")
                .description("").build();
    }
}

In the newer versions, there is no need to write Swagger-related code annotations in the corresponding Controller classes, reducing code intrusiveness. In older versions, annotations such as @Api, @ApiOperation, @ApiImplicitParams, @ApiImplicitParam, @ApiModel, etc. must be added to the relevant code to make the code more intrusive.

After these two steps, Swagger2 is successfully configured. When you start the project and visit http://localhost:10060/swagger-ui.html, you should see the following UI, indicating successful configuration:

img

img

3. API Verification and Testing #

Write a test class and start the project.

@RestController
@RequestMapping("test")
public class APITestController {

    @PostMapping("/hello") // only support POST requests
    public String hello(String name) {
        return "hello " + name;
    }
}

There are 8 types of request methods: POST, GET, OPTIONS, HEAD, PUT, DELETE, TRACE, and PATCH. To avoid unnecessary requests causing potential threats, it is recommended to only use POST requests. The functionality of @PostMapping("/hello") is equivalent to @RequestMapping(name = "hello", method = { RequestMethod.POST }).

  1. Test using a Shell script:
$ curl -X POST "http://localhost:10060/test?name=world"
hello world

You can see the normal output. Now test if other request methods can successfully request data:

$ curl -X GET "http://localhost:10060/test?name=world"
{"timestamp":"2020-02-01T12:59:09.508+0000","status":405,"error":"Method Not Allowed","message":"Request method 'GET' not supported","path":"/test"}

The output indicates that the requested method is not supported, only POST requests are supported.

  1. If Swagger is not used, you can use the Postman tool to debug the interface.

img

  1. Now that Swagger functionality has been integrated, open http://localhost:10060/swagger-ui.html, find the corresponding method under api-test-controller, click “try it out”, enter the parameters, and execute the “Execute” functionality. Check the normal output:

img

img

Using Swagger, you can directly perceive interface changes online and perform interface testing directly on the UI without relying on third-party components.

If there are required fields in the parameters, how should we set them? In this case, you must use the code annotation @ApiImplicitParam.

@ApiImplicitParam(required = true, name = "name", dataType = "String", value = "Name of the person", defaultValue = "world",paramType="query")
/*name: parameter name
        value: explanation of parameter in Chinese
        required: whether the parameter is mandatory
        paramType: where the parameter is located
            · header --> Get the request parameter using @RequestHeader
            · query --> Get the request parameter using @RequestParam
            · path (used for RESTful interfaces) --> Get the request parameter using @PathVariable
            · body (not commonly used)
            · form (not commonly used)
        dataType: parameter type, default String, other values dataType="Integer"
        defaultValue: parameter default value
*/

Several commonly used annotations, slight difference from old versions, are as follows:
    
@Api                     // Used on classes to specify the purpose of the class
@ApiOperation            // Used on methods to provide method documentation
@ApiImplicitParams       // Used on methods to include a group of parameter explanations
@ApiImplicitParam        // Used in ApiImplicitParams to provide explanations for method parameters
@ApiResponses            // Used to represent a group of responses
@ApiResponse             // Used in @ApiResponses to provide information on an error. [code: Error code, message: Information, response: Exception class thrown.]
@ApiModel                // Used on entity classes to describe model information
@ApiModelProperty        // Used on parameters of entity classes to describe parameter information

After adding the above annotations, the test class evolves as follows:

@RestController
@RequestMapping("test")
@Api("Test Class")
public class APITestController {

    @ApiOperation("Greeting")
    @PostMapping("/hello")
    @ApiImplicitParam(required = true, name = "name", dataType = "String", value = "Name of the person", defaultValue = "world", paramType = "query")
    public Hello hello(String name) {
        Hello hello = new Hello();
        hello.setName(name);
        return hello;
    }

}

@ApiModel
class Hello {

    @ApiModelProperty("Name")
    private String name;

    @ApiModelProperty("Age")
    private int age = 10;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

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

Reopen the address http://localhost:10060/swagger-ui.html and you will find that the parameter has become a required item with a default value, and the output response value is in the JSON data format of the Hello value object.

#### 4. Blocking in the production environment

Using Swagger greatly improves the efficiency of interface development and testing. If not restricted, it poses a potential security risk when exposed in the production environment. Therefore, it is normal to use the Swagger interface debugging function in the development and testing environment, but the function should be blocked in the production environment. In the previous article, it is mentioned that the multi-environment configuration is used for this purpose.

The implementation method is very simple, just add the annotation `@Profile({ "dev", "test" })` to the Swagger configuration class to indicate that the swagger interface debugging function can only be used normally in the dev or test environment, and this class will not be loaded in the production environment, thus achieving the goal of blocking in the production environment. After starting, it is found that the swagger-ui.html page can no longer be used to test interfaces.

@EnableSwagger2
@Configuration
@Profile({ "dev", "test" })
public class Swagger2Config {
}

Note: The `@Profile` annotation can be used for any class annotated with `@Component` or `@Configuration`.

#### 5. Adding a token as a unified parameter

Many internal services require users to log in before they can be used normally. For example, users have to log in to sign in and receive points. If the token parameter is added to each interface, the duplication is too high and the design is not elegant enough. In general, it is more appropriate to put the token in the request header. The goal is to display the input position of the token explicitly on the Swagger page.

Modify the Swagger configuration class as follows:

@EnableSwagger2
@Configuration
@Profile({ "dev", "test" })
public class Swagger2Config {

    @Bean
    public Docket createRestApi() {
        ParameterBuilder parameterBuilder = new ParameterBuilder();
        List<Parameter> parameters = new ArrayList<>();
        parameterBuilder.modelRef(new ModelRef("String")).name("token").parameterType("header").description("Token").defaultValue("").required(false).build();
        parameters.add(parameterBuilder.build());

        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
                .apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()).build().globalOperationParameters(parameters).securitySchemes(getSecuritySchemes());
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Member Module Interface Layer (Based on SpringBoot2+Swagger2).")
                .contact(new Contact("growithus", "https://backkoms.github.io/", "[email protected]"))
                .version("1.0")
                .description("").build();
    }

    private List<ApiKey> getSecuritySchemes() {
        List<ApiKey> keys = new ArrayList<>();
        ApiKey key  = new ApiKey("token", "token", "header");
        keys.add(key);
        return keys;
    }
}

After starting the project, open the swagger-ui.html page and you will find an additional "Authorize" button. Open it and enter a valid token to log in. For other interfaces, you can directly use them without having to enter the token multiple times (token verification can be implemented later using AOP programming).

By doing this, Swagger can be used in the project normally. You can directly copy the configuration of this example for reuse later.

### Thinking question

Each sub-service module will expose interfaces externally. If there are many service calls, it will be cumbersome to switch between interface pages. Is there any way to simplify it?