Spring REST Docs 와 Swagger


API 문서 

우리 회사는 보통 word 로 API 문서를 만들었다. 수정사항 업데이트 하기가 매우 귀찮았고 까먹을 때가 많았다. 

 

Spring REST Docs 

  • RESTful (자원을 이름으로 구분하여 해당 자원의 상태를 주고받는 모든 것 ) 서비스에 대한 문서 생성을 도와준다. 
  • 테스트를 통해 API 문서를 만듬 
  • HTML, PDF 문서로 생성가능 Hosting 도 가능 
  • 변경에 따른 최신화 보장 

Swagger

  • 문서에 대한 명세 보다는 API를 쉽게 호출해 볼수 있는 것에 초점 
  • 테스트 코드가 없어도 문서화 가능 (Controller에 Annotation만 붙이면됨)
  • API 테스트 UI 제공 

OpenAPI Spec

  • Rest API 에 대한 설명, 생성, 사용 및 시각화 하기 위한 인터페이스 파일 사용
  • YAML 로 작성하면 이를 Swagger HTML로 바꿔주는 Spec

사용법 

아래 Entity를 이용해서 Controller에 CURD를 작성했다. 

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String account;
    private String email;
    private String phoneNumber;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;



}

build.gradle

아래 공식 문서를 참조 한다. 그런데 나의 환경에서는 마지막 단계인 통합 Document 구성이 안되었다….. 글 마지막에 troubleshoot 방법으로 해결 했다. 

https://docs.spring.io/spring-restdocs/docs/2.0.5.RELEASE/reference/html5/#getting-started-build-configuration 

Test Code

아래 위치에 java 파일을 생성 한다. 

@ExtendWith({RestDocumentationExtension.class, SpringExtension.class})
@SpringBootTest
public class UserApiDoc {

    //org.springframework.test.web.servlet
    private MockMvc mockMvc;

    @BeforeEach
    public void setup(WebApplicationContext context, RestDocumentationContextProvider restDocumentation){
        this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
            .apply(documentationConfiguration(restDocumentation)) // documentationConfiguration 가 resDocumentation 의 인스턴스를 얻게 해줌 
            .build();
        System.out.println("before ~~~~~~~~~");
    }

    @Test
    //org.springframework.restdocs.request.RequestDocumentation
    //org.springframework.restdocs.payload.PayloadDocumentation
    public void testRead() throws Exception{
        this.mockMvc.perform(get("/api/user/{id}", 1L))
            .andDo(print())
            .andDo(document("user",
                pathParameters(
                    parameterWithName("id").description("user id")
                ),
                responseFields(
                    fieldWithPath("resultCode").description("response code"),
                    fieldWithPath("data.id").description("id"),
                    fieldWithPath("data.account").description("account"),
                    fieldWithPath("data.email").description("email"),
                    fieldWithPath("data.phoneNumber").description("phone number"),
                    fieldWithPath("data.createdAt").description("create"),
                    fieldWithPath("data.updatedAt").description("modify")
                )
            ));
    }
}

 

JUnit5 기반으로 작성. 스프링 부트 기반 테스트를 위해 @SpringBootTest(어플을 띄움)를 Class에 선언해 주고 테스트 수행전에 mockMvc를 설정해준다. 그리고 테스트에서 API 전송후 그 Result를 저장한다. 

성공하면 build/generated-snippets/{user}/*.adoc 형태로 저장 된다. 다음으로 asciidoc 파일을 만들어 발생한 asciidoc을 html로 변환 한다. 

user.adoc

:snippets: build/generated-snippets

= RESTful Notes API Guide
:doctype: user
:icons: font
:source-highLighter: highlightjs
:toc: left
:toclevels: 4
:sectnums:
:sectlinks:
:sectanchors:


[[api]]
== User Api

include::{snippets}/user/curl-request.adoc[]
include::{snippets}/user/http-request.adoc[]
include::{snippets}/user/path-parameters.adoc[]
include::{snippets}/user/http-response.adoc[]
include::{snippets}/user/response-fields.adoc[]그 

이렇게 한 뒤 Build를 하게 되면 build/docs/asciidoc 에 user.html 파일이 생성된다. 이를 was 에서 접근할 수 있도록 resource 폴더에 옮기려면 아래 처럼  build.gradle 파일을 수정 한다. 

bootJar {
    dependsOn asciidoctor
    // from ("build/docs/asciidoc") { 
  // 	into 'static/docs'
    // }
    copy {
        from "${asciidoctor.outputDir}"
        into "src/main/resources/static/docs/"
    }

}

 

한번 더 build를 하면 src/main/resources/static/docs/user.html 파일이 생성된 것을 알 수 있고 localhost:8080/docs/user.html로 접근시 화면을 볼 수 있다. 

 

restdocs-api-spec

Swagger에서 돌릴 수 OpenAPI Spec 으로 구성된 yaml 파일을 자동으로 생성해주는 Open Source

https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.1.md#oasDocument

build.gradle

plugins {
    ...
    id 'com.epages.restdocs-api-spec' version '0.11.4'
    
}

dependencies {
    ...
    testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.11.4' //2.2

}

// open api 3 spec 사용시 
openapi3 {
    server = 'http://localhost:8080'
    title = 'User API'
    description = 'User Api Description'
    // tagDescriptionsPropertiesFile = 'src/docs/tag-descriptions.yaml'
    version = '0.1.0'
    format = 'yaml'

    copy {
        from "build/api-spec"
        into "src/main/resources/static/docs/"
    }
}

//from : https://github.com/ePages-de/restdocs-api-spec#gradle

 

Test Code 

//import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document;
//import static com.epages.restdocs.apispec.ResourceDocumentation.resource;
//import com.epages.restdocs.apispec.ResourceSnippetParameters;

    public void testRead() throws Exception{
        this.mockMvc.perform(get("/api/user/{id}", 1))
            .andDo(print())
            .andDo(document("user",
                pathParameters(
                    parameterWithName("id").description("user id")
                ),
                responseFields(
                    fieldWithPath("resultCode").description("response code"),
                    fieldWithPath("data.id").description("id"),
                    fieldWithPath("data.account").description("account"),
                    fieldWithPath("data.email").description("email"),
                    fieldWithPath("data.phoneNumber").description("phone number"),
                    fieldWithPath("data.createdAt").description("create"),
                    fieldWithPath("data.updatedAt").description("modify 시간")
                )
            ));

        this.mockMvc.perform(get("/api/user/{id}", 1))
            .andDo(document("user",
                preprocessRequest(prettyPrint()),
                preprocessResponse(prettyPrint()),
                resource(
                    ResourceSnippetParameters.builder().description("User Information CURD").summary("User Information")
                    .pathParameters(
                        parameterWithName("id").description("user id")
                    )
                    .responseFields(
                        fieldWithPath("resultCode").description("response code"),
                        fieldWithPath("data.id").description("id"),
                        fieldWithPath("data.account").description("account"),
                        fieldWithPath("data.email").description("email"),
                        fieldWithPath("data.phoneNumber").description("phone number"),
                        fieldWithPath("data.createdAt").description("create"),
                        fieldWithPath("data.updatedAt").description("modify 시간")
                    ).build()
                )
            ));
    }

 

테스트 코드 실행 후 gradle openapi3 하게 되면 yaml 파일이 만들어 진다.

Swagger를 Docker에 올려 해당 yaml 파일을 호스팅 하고 싶다면 아래 명령으로 docker를 실행 시킨다. 

docker run -p 80:8080 -e URLS="[{url: 'http://localhost:8080/docs/openapi3.yaml', name: 'user'}, {url: 'https://petstore.swagger.io/v2/swagger.json', name: 'sample'}]" -v /tmp/swagger:/usr/share/nginx/docs/ swaggerapi/swagger-ui

 

Application 에서 CORS 헤더를 주기 위해 WebConfig 라는 java 파일을 만들고 아래와 같이 코딩한다. 

package org.yg.practice.flyway.configs;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer{

    @Override
    public void addCorsMappings(CorsRegistry registry){
        System.out.println("add mapping ~!");
        registry.addMapping("/**");

    }
}

 

 

그 후 localhost로 접속 시 Swagger UI를 볼 수 있다. 

참고 

fast campus

Trouble Shoot

Spring Rest Docs 공식 사이트 가이드 대로 Gradle 세팅을 진행 했는데 asciidoctor 에서 에러가 발생하면서 build가 되지 않았다. 그래서 아래 URL을 참고하여 문제를 해결 했다. 

https://github.com/spring-projects/spring-restdocs/issues/680 

 

build.gradle

plugins {
    ... 
    //id "org.asciidoctor.convert" version "1.5.9.2"
    id 'org.asciidoctor.jvm.convert' version '3.2.0'
    
}

configurations {
    asciidoctorExt
}

dependencies {
    ...
    asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' 
    ...
}


asciidoctor { 
    configurations 'asciidoctorExt'
    ...
}

 

 

다른 부분은 공식 문서와 같게 하니 잘 동작 했다. 



블로그 구독하기 !!

You may also like...