Home [Spring] API 문서
Post
Cancel

[Spring] API 문서

API 문서화

  • API 문서화

    • 클라이언트가 REST API 애플리케이션에 요청을 전송하기 위해서 알아야 하는 요청정보(요청 URL, Request Body, Query Parameter 등)을 문서로 정리한것
    • API사용을 위한 정보가 담겨있는 문서를 API문서 또는 API 스펙(Specification, 사양)이라고 함
    • API문서화 방법
      • 개발자 수기 작성
      • 애플리케이션 빌드를 통한 API 문서 자동생성
  • API 문서화 필요이유

    • REST API기반의 백엔드 애플리케이션을 프론트엔드(클라이언트 측)에서 사용하기 위한 정보 제공
  • API 문서 생성 자동화 필요이유

    • API 문서를 수기로 작성하는 것은 굉장히 비효율적임
    • 클라이언트에게 제공한 API 정보와 수기로 작성한 정보가 다를 수 있음
    • 작업 시간 단축
    • 애플리케이션의 완성도 ↑
  • Spring Rest Docs vs 🔗Swagger

    • Swagger
      • API 요청 툴로써의 기능 사용 가능
      • API 문서를 만들기 위해 무수히 많은 애너테이션이 추가됨
      • 클라이언트 측에서 보기엔 편하지만 개발자 측에선 바람직하지 않게 보임
        • 기능 구현과 상관없는 애너테이션이 대량으로 추가되기 때문
      • API 엔드포인트를 위한 기능 구현 코드가 한 눈에 들어오지 않음
      • API 스펙 정보를 문자열로 입력하는 경우가 많음
    • Spring Rest Docs
      • Test 클래스에서만 API 문서를 위한 정보 코드를 작성하면 됨
      • Test 가 통과하지 않으면 API 문서가 생성되지 않음
      • 애플리케이션에 정의되어있는 API 스펙 정보와 API 문서 내의 정보가 일치하지 않으면 문서가 생성되지 않음
  • Spring Rest Docs

    • Controller의 슬라이스 테스트를 통해 테스트가 통과 되어야지만 API 문서가 정상적으로 만들어진다
    • 테스트를 중요하게 생각하는 개발자들에게 각광 받는 기술
    • Spring Rest Docs API 문서 생성 흐름
      • 슬라이드 테스트 코드 작성, API 스펙 정보 코드 작성 → test 태스크 실행 → 테스트 결과 → 실패 시, API 스펙 정보 코드 작성으로 되돌아감 → 성공 시, API 문서 스니핏 생성(.adoc) → API 문서 생성(.adoc) → API 문서를 HTML 로 변환

Spring Rest Docs 설정

  • build.gradle 설정 ```java plugins { … // .adoc 파일 확장자를 가지는 AsciiDoc 문서 생성하는 Asciidoctor를 사용하기 위한 플러그인 id “org.asciidoctor.jvm.convert” version “3.3.2” id ‘java’ }

group = ‘com.codestates’ version = ‘0.0.1-SNAPSHOT’ sourceCompatibility = ‘11’

repositories { mavenCentral() }

// ext 변수의 set()메서드를 사용하여 API 문서 스니핏 생성 경로 지정 ext { set(‘snippetsDir’, file(“build/generated-snippets”)) }

// AsciiDoctor에서 사용되는 의존 그룹 지정 // :asciidoctor task 실행 시 지정한 ‘asciidoctorExtensions’ 라는 그룹을 지정 configurations { asciidoctorExtensions }

dependencies { // spring-restdocs-core와 spring-restdocs-mockmvc 의존 라이브러리 추가 testImplementation ‘org.springframework.restdocs:spring-restdocs-mockmvc’

1
2
3
4
5
6
7
8
9
10
11
// spring-restdocs-asciidoctor 의존 라이브러리 추가
// configurations 에서 지정한 그룹에 의존 라이브러리가 포함됨
asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'

...

implementation 'org.mapstruct:mapstruct:1.5.1.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.1.Final'
implementation 'org.springframework.boot:spring-boot-starter-mail'

implementation 'com.google.code.gson:gson'   }

// :test task 실행 시, API 문서 생성 스니핏 디렉토리 경로 지정 tasks.named(‘test’) { outputs.dir snippetsDir useJUnitPlatform() }

// :asciidoctor task 실행 시, Asciidoctor 기능 사용 위한 :asciidoctor task에 asciidoctorExtensions 설정 tasks.named(‘asciidoctor’) { configurations “asciidoctorExtensions” inputs.dir snippetsDir dependsOn test }

// :build task 실행 전 실행되는 task // :copyDocument task 수행 시 // index.html파일이 src/main/resources/static/docs 에 copy됨 // copy된 index.html 파일은 API 문서를 파일 형태로 외부에 제공하기 위한 용도로 사용 가능 task copyDocument(type: Copy) { dependsOn asciidoctor // :asciidoctor task 실행 후 task실행되도록 의존성 설정 from file(“${asciidoctor.outputDir}”) // build/docs/asciidoc/ 경로에 생성되는 index.html을 copy into file(“src/main/resources/static/docs”) // src/main/resources/static/docs 경로에 index.html 추가 }

build { dependsOn copyDocument // :build task 실행 전 :copyDocument task 가 먼저 수행되도록 함 }

// :bootJar task 설정. 웹브라우저에서 API문서 확인 위한 용도 bootJar { // :bootJar task 실행 전 :copyDocument task 실행되도록 의존성 설정 dependsOn copyDocument // Asciidoctor 실행으로 생성되는 index.html파일을 jar 파일 안에 추가 // 웹 브라우저에서 접속(http://localhost:8080/docs/index.html) 후, API문서 확인 가능 from (“${asciidoctor.outputDir}”) { into ‘static/docs’ } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
  - [🔗Gradle](https://docs.gradle.org/current/userguide/userguide.html)
  - [🔗ext 변수](https://docs.gradle.org/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html)



### <span style='color: #2D3748; background-color: #ffdce0'>Spring Rest Docs 적용</span>

  ```java
  @WebMvcTest(MemberController.class)
  @MockBean(JpaMetamodelMappingContext.class)
  @AutoConfigureRestDocs
  public class MemberControllerRestDocsTest {
    @Autowired
    private MockMvc mockMvc;

    // MemberService의 Mock Bean 주입
    // 테스트 케이스에서의 가짜 메서드 호출 시 사용(Stubbing)
    @MockBean
    private MemberService memberService;

    // MemberMapper의 Mock Bean 주입
    // 테스트 케이스에서의 가짜 메서드 호출 시 사용(Stubbing)
    @MockBean
    private MemberMapper mapper;

    @Autowired
    private Gson gson;

    @Test
    public void postMemberTest() throws Exception {
        // postMember() 핸들러 메서드에 전송하는 request body
        // given
        MemberDto.Post post = new MemberDto.Post("hgd@gmail.com", "홍길동", "010-1234-5678");
        String content = gson.toJson(post);

        // postMember() 핸들러 메서드가 응답으로 전송하는 response body
        MemberDto.response responseDto =
                new MemberDto.response(1L,
                        "hgd@gmail.com",
                        "홍길동",
                        "010-1234-5678",
                        Member.MemberStatus.MEMBER_ACTIVE,
                        new Stamp());
                        
        // mockito
        // 주입받은 mock 객체를 사용하여 stubbing
        given(mapper.memberPostToMember(Mockito.any(MemberDto.Post.class))).willReturn(new Member());
        given(memberService.createMember(Mockito.any(Member.class))).willReturn(new Member());
        given(mapper.memberToMemberResponse(Mockito.any(Member.class))).willReturn(responseDto);

        // 슬라이스 테스트
        // MockMvc의 perform()메서드로 PATCH 요청 전송
        ResultActions actions =
                mockMvc.perform(
                        post("/v11/members/{member-id}")
                                .accept(MediaType.APPLICATION_JSON)
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(content)
                );

        // then
        actions
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.data.memberId").value(patch.getMemberId()))
                .andExpect(jsonPath("$.data.email").value(post.getEmail()))
                .andExpect(jsonPath("$.data.name").value(post.getName()))
                .andExpect(jsonPath("$.data.phone").value(post.getPhone()))
                /// andDo(document... 부터 API 문서 생성 위한 코드
                .andDo(document(       // API 스펙 정보를 전달받아 실질적 문서화 작업을 수행하는 핵심 메서드
                        "patch-member",     // API 문서 스니핏의 식별자
                        getRequestPreProcessor(),      
                        getResponsePreProcessor(),
                        pathParameters(             
                                parameterWithName("member-id").description("회원 식별자")
                        ),
                        requestFields(             
                                List.of(
                                        fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"), 
                                        fieldWithPath("name").type(JsonFieldType.STRING).description("이름"),
                                        fieldWithPath("phone").type(JsonFieldType.STRING).description("휴대폰 번호")
                                )
                        ),
                        responseFields(        
                                List.of(
                                        fieldWithPath("data").type(JsonFieldType.OBJECT).description("결과 데이터"),
                                        fieldWithPath("data.memberId").type(JsonFieldType.NUMBER).description("회원 식별자"),
                                        fieldWithPath("data.email").type(JsonFieldType.STRING).description("이메일"),
                                        fieldWithPath("data.name").type(JsonFieldType.STRING).description("이름"),
                                        fieldWithPath("data.phone").type(JsonFieldType.STRING).description("휴대폰 번호"),
                                        fieldWithPath("data.memberStatus").type(JsonFieldType.STRING).description("회원 상태"),
                                        fieldWithPath("data.stamp").type(JsonFieldType.NUMBER).description("스탬프 갯수")
                                )
                        )
                ));
    }
  }
  • pathParameters 사용 시 주의

    • MockMvcBuilers가 아닌 RestDocumentationRequestBuilders 사용해야함
      • RestDocumentationRequestBuilders 사용 시 URI를 매개변수로 받으면 urlTemplates를 찾을 수 없어 URI를 String 타입으로 만들거나 해야함
      • 🔗에러로그 링크
This post is licensed under CC BY 4.0 by the author.