본문 바로가기
Spring

API 문서 자동화 - Spring REST Docs

by WangTak 2021. 11. 27.
반응형
Document RESTful services by combining hand-written documentation with auto-generated snippets produced with Spring MVC Test.
The aim of Spring REST Docs is to help you produce accurate and readable documentation for your RESTful services.

출처: https://docs.spring.io/spring-restdocs/docs/current/reference/html5/

 

위 글은 Spring REST Docs의 공식 문서에 있는 글입니다. Spring REST Docs를 사용하면 저희가 만든 API에 읽기 쉬운 문서를 자동으로 만들어준다고 하니 적용하는 방법을 알아보겠습니다.

실무에서 협업을 해보니깐 API 문서가 필수적임을 느꼈습니다.

 

먼저 정리에 앞서 프로젝트 구축 환경에 대한 정보(버전 정보)는 다음과 같습니다.

  • 개발 도구: IntelliJ Ultimate
  • Spring Boot: 2.5.7
  • Java 11
  • Gradle Version: 7.2
  • JUnit 5
  • Spring REST Docs: 2.0.5.RELEASE
프로젝트 구축 환경 버전마다 Spring REST Docs를 구현하는 방식이 다르기 때문에 이 점 참고 바랍니다.
Gradle Version 확인은 [gradle > wrapper > gradel-wrapper.properties]에 있는 distributionUrl에 있는 bin 앞에 숫자입니다.

 

build.gradle

plugins {
   id "org.asciidoctor.jvm.convert" version "3.3.2" // 1-1
   id "org.asciidoctor.convert" version "1.5.9.2" // 1-2
}

dependencies {
   asciidoctor 'org.springframework.restdocs:spring-restdocs-asciidoctor:2.0.5.RELEASE' // 2
   testImplementation "org.springframework.restdocs:spring-restdocs-mockmvc:2.0.5.RELEASE" // 3
}

ext { // 4
   snippetsDir = file('build/generated-snippets')
}

test { // 5
   outputs.dir snippetsDir
}

asciidoctor { // 6
   dependsOn test // 7
   inputs.dir snippetsDir // 8
}

bootJar { // 9
   dependsOn asciidoctor // 9-1
   from ("${asciidoctor.outputDir}/html5") { // 9-2
      into 'static/docs'
   }
}
  1. Asciidoctor 플러그인을 적용시켜줍니다.
    • Gradle 버전이 7 이상일 경우는 [1-1], 6 이하일 경우는 [1-2] 플러그인만 추가해주시면 됩니다.
  2. .adoc 파일에서 빌드, 스니펫 생성을 자동으로 구성해주는 의존성을 추가합니다.
    • Gradle 버전이 7 이상일 경우에는 해당 의존성을 추가하지 않아야 합니다.
  3. 테스트 코드에서 사용할 MockMVC 의존성을 추가합니다.
    ※ WebTestClient 혹은 REST Assured를 사용하실 분들은 그에 맞는 의존성을 추가해주시면 됩니다.
  4. generated snippets의 결과가 나오는 장소를 지정합니다.
  5. test 작업에 snippets directory를 출력으로 추가해줍니다.
  6. asciidoctor 작업에 대한 설정을 해줍니다.
  7. snippets directory를 입력으로 지정합니다.
  8. 문서가 생성되기 전에 테스트가 실행되도록 asciidoctor 작업이 test 작업에 의존하도록 지정합니다.
  9. 우리 프로젝트의 jar 파일 안에 자동으로 생성된 문서를 포함하고 싶을 때 사용합니다.
    1. jar가 build 되기 전에 문서가 생성되도록 설정합니다.
    2. 생성된 문서가 jar의 static/docs 디렉터리로 복사하도록 설정합니다.

 

MemberController

설정은 끝났습니다. 간단한 Controller를 만들어 보겠습니다.

REST Docs는 Controller 테스트 코드를 기반으로 문서를 만듭니다.
http://localhost:8080/members로 요청했을 때 멤버 리스트를 반환하는 간단한 API를 만듭니다.
@RestController
public class MemberController {

    @GetMapping("/members")
    public ResponseEntity<MemberResponse> getMemberList() {
        Member member1 = new Member(1L, "wangtak1@gmail.com", "왕탁이1");
        Member member2 = new Member(2L, "wangtak2@gmail.com", "왕탁이2");

        List<Member> members = new ArrayList<>();
        members.add(member1);
        members.add(member2);

        MemberResponse memberResponse = new MemberResponse(members, "멤버 리스트입니다.");

        return ResponseEntity.ok(memberResponse);
    }

    @Data
    public static class Member {
        private Long memberId;
        private String email;
        private String name;

        public Member(Long memberId, String email, String name) {
            this.memberId = memberId;
            this.email = email;
            this.name = name;
        }
    }

    @Data
    public static class MemberResponse {
        // 공부를 위해 Member 엔티티를 직접 response 하지만, 실무에서는 Dto 나 다른 응답 객체로 바꿔야 한다.
        private List<Member> members = new ArrayList<>();
        private String message;

        public MemberResponse(List<Member> members, String message) {
            this.members = members;
            this.message = message;
        }
    }
}
  • Json 형식
  • static class에는 @Getter가 필요합니다. [Getter 없이 사용하니 406 ERROR가 떴습니다.ㅠ]
    ※ @Data에 @Getter, @Setter가 포함되어 있습니다.
  • 배열을 직접 반환하는 것보다는 다른 객체로 덮어서 보내주는 것이 더 유연한 설계입니다.
MemberResponse 객체에서 List의 타입이 Member Entity인데, API를 설계할 때 Entity는 Request, Response 즉, API의 스펙에 관여하면 안 됩니다. 그래서 Dto라던지 API를 위한 요청, 응답 전용 객체를 사용해야 합니다.
ex) MemberDto, MemberRequest, MemberResponse

 

그럼 위 MemberController를 기반으로 Test 코드를 만들어보겠습니다.

Intellij를 사용하시면 MemberController에서 [Ctrl + Shift + T]를 누르시면 쉽게 테스트 코드를 만들 수 있습니다. [Windows 기준입니다! Mac을 쓰는 그날까지]

 

MemberControllerTest

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

    private MockMvc mockMvc;

    @BeforeEach
    public void setUp(WebApplicationContext webApplicationContext,
                      RestDocumentationContextProvider restDocumentation) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
                .apply(MockMvcRestDocumentation.documentationConfiguration(restDocumentation))
                .build();
    }

    @Test
    public void getMemberListTest() throws Exception {
        this.mockMvc.perform(get("/members").accept(MediaType.APPLICATION_JSON))
                .andDo(print())
                .andDo(document("Member-List", // 1
                        preprocessResponse(prettyPrint()), // 2
                        responseFields( // 3
                                subsectionWithPath("members") // 4
                                        .description("멤버 정보"), // 5
                                getDescription("message", "API 결과에 대한 메시지")) // 6
                ))
                .andExpect(status().isOk()); // 7
    }

    private FieldDescriptor getDescription(String name, String description) {
        return fieldWithPath(name)
                .description(description);
    }

}

Spring REST Docs에서 문서를 이쁘고 잘 만들 수 있도록 많은 기능을 지원하는데 이 글에서는 간략하게 설명만 하고 문서를 자동으로 만들어내는 1 cycle에 초점을 맞추겠습니다.

 

  1. generated-snippets의 디렉터리 이름을 지정해줍니다.
  2. response가 json인데 문서에 보기 좋도록 지정해줍니다. [이름만 봐도 prettyPrint() 임]
    (물론 request도 있습니다. 현재 API 스펙상 request가 없기 때문에 생략]
  3. response에 있는 각각의 필드들에 대한 설명할 수 있도록 합니다. 
  4. MemberResponse 객체에 Array 하나와 String 하나가 있었는데, Array는 위와 같이 변수 이름을 적어줍니다.
  5. 그리고 .description으로 체이닝 하여 설명을 적습니다.
  6. 필드에 대한 설명을 fieldWithParam(name).description(description)으로 체이닝 하여 적어줘야 하는데, getDescription이라는 커스텀 메서드를 만들어서 보기 쉽게 구현하였습니다.
    ※ 체이닝 하여 구현하셔도 문제없습니다!  
  7. API를 호출하고 난 후에 status가 어떤지 예상합니다.
    [/members에서는 ResponseEntity.ok(MemberResponse);로 했습니다. 그래서 status().isOk()]
위 코드를 따라 하다가 어? 자동 완성 안되는데? 하실 수 있습니다. 그런데 static import 된 영역이 꽤 있기 때문에 코드를 다 입력하면 IDE[IntelliJ]에서 Code Assistant가 활성화될 것입니다.

 

API 문서 만들기

getMemberListTest를 실행하여 테스트가 통과되면 [build > generated-snippets > Member-List]에 파일이 자동으로 만들어집니다.

※ 디렉터리 명이 Member-List인 이유는 위 getMemberListTest에서 1번에서 지정해줬기 때문입니다.

자동으로 생성된 파일 위치

 

위와 같이 자동으로 생성된 파일이 있다면 [src > docs > asciidoc]에 HTML을 만들기 위한 .adoc을 만들면 됩니다.

위 경로에 디렉터리가 만들어져있지 않다면 직접 만드셔야 됩니다.

직접 만든 디렉터리 예시

 

member.adoc

ifndef::snippets[]
:snippets: ./build/generated-snippets
endif::[]

= WangTak
:doctype: book
:toc: left
:sectnums:
:toclevels: 3
:source-highlighter: highlights

== Member
=== MemberList

HttpRequest
include::{snippets}/Member-List/http-request.adoc[]

ResponseFields
include::{snippets}/Member-List/response-fields.adoc[]

HttpResponse
include::{snippets}/Member-List/http-response.adoc[]

 

AsciiDoc을 처음 사용하실 경우 IntelliJ에서는 관련 Plugin을 설치할 수 있습니다.
[File > Settings > Plugins > [상단 탭에서 Marketplace 선택 후] > AsciiDoc 검색 > Install] 해주시면 아래 사진과 같이 Code Assistant와 작성한 문서의 예시를 바로 확인할 수 있습니다.

AsciiDoc Plugin 적용

 

member.adoc을 작성해준 후에 build 혹은 bootJar를 실행해주면 [build > docs > asciidoc] 경로에 보면 member.html이 자동으로 생성된 것을 확인할 수 있습니다.

자동으로 생성된 API 문서

 

해당 경로로 이동해서 member.html을 실행해보면 다음과 같이 깔끔한 API 문서가 만들어진 것을 확인할 수 있습니다.

 

member.html

member.html

 

반응형

'Spring' 카테고리의 다른 글

Spring AOP - AspectJ Pointcut Expression 하이라이팅  (0) 2022.03.16
API 문서 자동화 - Swagger  (0) 2021.12.03