Spring Boot [How to Document Elegantly]

Posted by PBD817 on Tue, 02 Jul 2019 18:44:16 +0200

Guidance:

Interface documentation is often used when working together as a team. Often we write a lot of documents in repetitive formats by hand, which reminds me of two things programmers hate most: writing documents without them.Ha-ha, if you've used swagger, your friends should know all about its convenience. If you haven't used swagger yet, you're just about to write a RESTful API document. Here's an article Build a powerful RESTful API document using Swagger2 in Spring Boot It can help you build an online document in a short time. Sometimes we need to generate an offline document. What can we do?Let's go with this question.

About swagger:

Swagger - Front End Separated Contract

API design via Swagger, a conversation with Tony Tam

A short summary to better generate the RESTful API documentation and provide the appropriate testing capabilities as follows:

Write offline documentation:

Swagger provides us with the ability to generate online documents, but sometimes customers need an api for offline documents. Is there a better way to help us generate offline documents through swagger?

That's what we're doing today: Springfox and Spring Rest Docs
1. Preparatory knowledge:
It is recommended that you know swagger, Asciidoc, asciidoctor-maven-plugin, and SpringBoot Testing.The corresponding information can be Google by itself.
2. About Springfox and Spring Rest Docs:
The official website is Springfox: Automated JSON API documentation for API's built with Spring.We can understand that documents are automatically generated for SPI built on Spring.

Introducing pom dependency:

In fact, our idea is to convert swagger online documents into staticdocs, introduce some related Spring Rest Docs dependent spring-restdocs-mockmvc, offline documents dependent springfox-staticdocs, because to generate documents when unit testing, then test the related spring-boot-starter-test.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.6.1</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.6.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.restdocs</groupId>
    <artifactId>spring-restdocs-mockmvc</artifactId>
    <version>1.1.2.RELEASE</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-staticdocs</artifactId>
    <version>2.6.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.8</version>
</dependency>

Using the Maven plug-in:

We use the asciidoctor-maven-plugin plugin to convert the Asciidoc format to HTML5 format
Learn more about: Introduction to Use

 <plugin>
    <groupId>org.asciidoctor</groupId>
    <artifactId>asciidoctor-maven-plugin</artifactId>
    <configuration>
        <!--To configure index.adoc Get Path for-->
        <!--<sourceDirectory>${asciidoctor.input.directory}</sourceDirectory>-->
        <outputDirectory>${asciidoctor.html.output.directory}</outputDirectory>
        <sourceDocumentName>index.adoc</sourceDocumentName>
        <attributes>
            <doctype>book</doctype>
            <toc>left</toc>
            <toclevels>3</toclevels>
            <generated>${generated.asciidoc.directory}</generated>
        </attributes>
    </configuration>
    <executions>
        <execution>
            <id>output-html</id>
            <phase>test</phase>
            <goals>
                <goal>process-asciidoc</goal>
            </goals>
            <configuration>
                <backend>html</backend>
                <attributes>
                    <snippets>${project.build.directory}/generated-snippets</snippets>
                </attributes>
            </configuration>
        </execution>
    </executions>
</plugin>

Write test classes to generate offline documentation:

import cn.sunxyz.domain.UserInfo;
import com.alibaba.fastjson.JSON;
import io.github.robwin.markup.builder.MarkupLanguage;
import io.github.robwin.swagger2markup.GroupBy;
import io.github.robwin.swagger2markup.Swagger2MarkupConverter;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import springfox.documentation.staticdocs.SwaggerResultHandler;

import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@AutoConfigureMockMvc
@AutoConfigureRestDocs(outputDir = "target/generated-snippets")
@RunWith(SpringRunner.class)
@SpringBootTest
public class DocumentationBuild {

    private String snippetDir = "target/asciidoc/generated-snippets";
    private String outputDir = "target/asciidoc";

    @Autowired
    private MockMvc mockMvc;

    @After
    public void Test() throws Exception {
        // Get swagger.json and write to the output Dir directory
        mockMvc.perform(get("/v2/api-docs").accept(MediaType.APPLICATION_JSON))
                .andDo(SwaggerResultHandler.outputDirectory(outputDir).build())
                .andExpect(status().isOk())
                .andReturn();

        // Read the swagger.json generated in the previous step, convert it to asciiDoc, and write it to outputDir
        // This outputDir must match the <generated></generated>tag configuration within the plug-in
        Swagger2MarkupConverter.from(outputDir + "/swagger.json")
                .withPathsGroupedBy(GroupBy.TAGS)// Sort by tag
                .withMarkupLanguage(MarkupLanguage.ASCIIDOC)// format
                .withExamples(snippetDir)
                .build()
                .intoFolder(outputDir);// output
    }

    @Test
    public void TestApi() throws Exception {
        mockMvc.perform(get("/api/user/1")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andDo(MockMvcRestDocumentation.document("Query Users", preprocessResponse(prettyPrint())));

        UserInfo userInfo = new UserInfo();
        userInfo.setName("lisi");
        userInfo.setAge(23);
        userInfo.setAddress("Jinan");
        userInfo.setSex("male");

        mockMvc.perform(post("/api/user").contentType(MediaType.APPLICATION_JSON)
                .content(JSON.toJSONString(userInfo))
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().is2xxSuccessful())
                .andDo(MockMvcRestDocumentation.document("New Users", preprocessResponse(prettyPrint())));
    }


}

Since maven's plug-in has been configured previously, you only need to execute tests to generate the appropriate documentation, as shown below:

Supplement:

Swagger configuration:

@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

    @Bean
    public Docket configSpringfoxDocket_all(ApiInfo apiInfo) {
        return new Docket(DocumentationType.SWAGGER_2)
                .produces(Sets.newHashSet("application/json"))
                .consumes(Sets.newHashSet("application/json"))
                .protocols(Sets.newHashSet("http", "https"))
                .apiInfo(apiInfo)
                .forCodeGeneration(true)
                .select().paths(regex("/api.*"))
                .build();
    }

    @Bean
    public Docket createUserInfoRestApi(ApiInfo apiInfo) {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("user")
                .produces(Sets.newHashSet("application/json"))
                .consumes(Sets.newHashSet("application/json"))
                .protocols(Sets.newHashSet("http", "https"))
                .apiInfo(apiInfo)
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.sunxyz.controller"))
                .paths(regex("/api/user.*"))
                .build();
    }

    @Bean
    public ApiInfo apiInfo() {
        return new ApiInfoBuilder().title("Springfox REST API")
                .description("Descriptions.")
                .termsOfServiceUrl("http://springfox.io")
                .license("Apache License Version 2.0")
                .licenseUrl("https://github.com/springfox/springfox/blob/master/LICENSE")
                .version("2.0")
                .build();
    }

}

Related source is managed github

Reference material:

Spring REST Docs

SpringBoot project generates documentation for RESTfull API

Introduction to Spring REST Docs

asciidoctor-maven-plugin

Topics: Java Spring JSON REST Maven