API 문서화

InSeok·2022년 9월 22일
0

TIL

목록 보기
37/51

배운 내용


  • 클라이언트가 REST API 백엔드 애플리케이션에 요청을 전송하기 위해서 알아야 되는 요청 정보(요청 URL(또는 URI), request body, query parameter 등)를 문서로 잘 정리하는 것
  • API 문서 / API 스펙 : API 사용을 위한 어떤 정보가 담겨 있는 문서
  • API문서를 수기로 작성할 경우, 비효율적이며, API 문서에 추가된 기능을 빠뜨릴수도 있고, 클라이언트에게 제공된 API 정보와 수기로 작성한 API 문서의 정보가 다를수도 있다.

API 문서 자동화

SwaggerSpring Rest Docs
애터네이션 기반의 API 문서화 방식테스트 코드 기반의 API 문서화 방식
애플리케이션 코드에 문서화를 위한 애너테이션들이 포함된다.애플리케이션 코드에 문서화를 위한 정보들이 포함되지 않는다.
가독성 및 유지 보수성이 떨어진다.테스트 케이스의 실행이 “passed”여야 API 문서가 생성된다.
API 문서와 API 코드 간의 정보 불일치 문제가 발생할 수 있다.테스트 케이스를 반드시 작성해야된다.
API 툴로써의 기능을 활용할 수 있다.API 툴로써의 기능은 제공하지 않는다.

Spring Rest Docs

  • API 문서화의 대표적인 장점은 테스트 케이스에서 전송하는 API 문서 정보와 Controller에서 구현한 Request Body, Response Body, Query Parmeter 등의 정보가 하나라도 일치하지 않으면 테스트 케이스의 실행 결과가 “failed” 되면서 API 문서가 정상적으로 생성이 되지 않는다
  • 애플리케이션에 정의되어 있는 API 스펙 정보와 API 문서 정보의 불일치로 인해 발생하는 문제를 방지

Spring Rest Docs의 API 문서 생성 흐름

  • 테스트 코드 작성
    1. 슬라이스 테스트 코드 작성
      ⅰ. Controller에 대한 슬라이스 테스트 코드를 먼저 작성
    2. API 스펙 정보 코드 작성
      ⅰ. 슬라이스 테스트 코드 다음에 Controller에 정의 되어 있는 API 스펙 정보(Request Body, Response Body, Query Parameter 등)를 코드로 작성.
  • test 태스크(task) 실행
    1. 작성된 슬라이스 테스트 코드를 실행
      ⅰ. 일반적으로 Gradle의 빌드 태스크(task)중 하나인 test task를 실행 시켜서 API 문서 스니핏(snippet)을 일괄 생성
    2. 테스트 실행 결과가 “passed”이면 다음 작업을 진행하고, “failed”이면 문제를 해결하기 위해 테스트 케이스를 수정한 후, 다시 테스트를 진행해야 한다.
  • API 문서 스니핏(.adoc 파일) 생성
    1. 테스트 케이스의 테스트 실행 결과가 “passed”이면 테스트 코드에 포함된 API 스펙 정보 코드를 기반으로 API 문서 스니핏이 .adoc 확장자를 가진 파일로 생성됩니다.
  • API 문서 생성
  • API 문서를 HTML로 변환
    • HTML로 변환된 API 문서는 HTML 파일 자체를 공유할 수도 있고, URL을 통해 해당 HTML에 접속해서 확인가능

스니핏(snippet)은 일반적으로 코드의 일부 조각을 의미
테스트 케이스 하나 당 하나의 스니핏이 생성되며, 여러개의 스니핏을 모아서 하나의 API 문서를 생성

Spring Rest Docs 설정

build.gradle 설정


//.adoc 파일 확장자를 가지는 AsciiDoc 문서를 생성해주는 Asciidoctor를 사용하기 위한 플러그인을 추가
plugins {
	id "org.asciidoctor.jvm.convert" version "3.3.2"    
}

//ext 변수의 set() 메서드를 이용해서 API 문서 스니핏이 생성될 경로를 지정
ext {

	set('snippetsDir', file("build/generated-snippets"))
}

// AsciiDoctor에서 사용되는 의존 그룹을 지정
configurations {
	asciidoctorExtensions
}

dependencies {
//spring-restdocs-core와 spring-restdocs-mockmvc 의존 라이브러리가 추가
	testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
//spring-restdocs-asciidoctor 의존 라이브러리를 추가
	asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'
}

// :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
}

//: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 task가 실행되기 전에 :copyDocument task가 먼저 수행 되도록 합니다.
build {
	dependsOn copyDocument  
}

//애플리케이션 실행 파일이 생성하는 :bootJar task 설정
bootJar {
	dependsOn copyDocument   //:bootJar task 실행 전에 :copyDocument task가 실행 되도록 의존성을 설정
	from ("${asciidoctor.outputDir}") {  //Asciidoctor 실행으로 생성되는 index.html 파일을 jar 파일 안에 추가 -> 써 웹 브라우저에서 접속(http://localhost:8080/docs/index.html) 후, API 문서를 확인가능
		into 'static/docs'     
	}
}

API 문서 스니핏을 사용하기 위한 템플릿(또는 source 파일) 생성

  • API 문서 스니핏이 생성 되었을 때 이 스니핏을 사용해서 최종 API 문서로 만들어 주는 템플릿 문서(index.adoc)를 생성해야한다.
  • Gradle 기반 프로젝트에서는 아래 경로에 해당하는 디렉토리를 생성해주어야 한다.
    • src/docs/asciidoc/
  • src/docs/asciidoc/디렉토리 내에 비어있는 템플릿 문서(index.adoc)를 생성해주면 된다.
@WebMvcTest(MemberController.class)   // (1)
@MockBean(JpaMetamodelMappingContext.class)   // (2)
@AutoConfigureRestDocs    // Spring Rest Docs에 대한 자동 구성
public class MemberControllerRestDocsTest {
    @Autowired
    private MockMvc mockMvc;  

    @MockBean
	  // (5) 테스트 대상 Controller 클래스가 의존하는 객체를 Mock Bean 객체로 주입 받기

    @Test
    public void postMemberTest() throws Exception {
        // given
        // (6) 테스트 데이터

        // (7) Mock 객체를 이용한 Stubbing

        // when
        ResultActions actions =
                mockMvc.perform(
                     // (8) request 전송
                );

        // then
        actions
                .andExpect(// (9) response에 대한 기대 값 검증)
                .andDo(document(  // (10) API 문서 스펙 정보 추가
                          "post-member",     // (9-1) API 문서 스니핏의 식별자 역할
																				//문서 스니핏은 post-member 디렉토리 하위에 생성
                        getRequestPreProcessor(),      // (9-2) 문서 영역을 전처리
                        getResponsePreProcessor(),     // (9-3) 문서 영역을 전처리
												pathParameters(              // URL의 path variable의 정보
                              parameterWithName("member-id").description("회원 식별자")
                        ),

                        requestFields(      // (9-4) 문서로 표현될 request body를 의미
//파라미터로 전달되는 List<FieldDescriptor> 의 원소인 FieldDescriptor 객체가 request body에 포함된 데이터를 표현
                                List.of(
//request body를 JSON 포맷으로 표현 했을 때, 하나의 프로퍼티를 의미하는 FieldDescriptor
fieldWithPath("memberId").type(JsonFieldType.NUMBER).description("회원 식별자").
ignored(),  // request body에 매핑되지 않는 정보로 ignored()를 추가해서 API 스펙 정보에서 제외
      fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"), // (9-5)
   fieldWithPath("name").type(JsonFieldType.STRING).description("이름").optional(),
//optional()을 추가해서 API 스펙 정보에서 필수가 아닌 선택 정보로 설정
      fieldWithPath("phone").type(JsonFieldType.STRING).description("휴대폰 번호")
                                )
                        ),
                        responseFields(     // (9-6) 문서로 표현될 response body를 의미
                                List.of(
         fieldWithPath("data").type(JsonFieldType.OBJECT).description("결과 데이터"),
          fieldWithPath("data.memberId").type(JsonFieldType.NUMBER).description("회원 식별자"), // (9-7)
     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("스탬프 갯수")
                 ));
    }
}

@WebMvcTest

  • Controller를 테스트 하기 위한 전용 애너테이션
  • 괄호 안에는 테스트 대상 Controller 클래스를 지정
  • Controller에서 의존하고 있는 객체가 있다면 해당 객체에 대해서 Mock 객체를 사용하여 의존성을 일일이 제거해 주어야 한다.
  • @SpringBootTest는 데이터베이스까지 요청 프로세스가 이어지는 통합 테스트에 주로 사용되고, @WebMvcTestController를 위한 슬라이스 테스트에 주로 사용

@MockBean

  • JPA에서 사용하는 Bean 들을 Mock 객체로 주입해주는 설정
  • 최상위 패키지 경로에 있는 xxxxxxxApplication 클래스에@EnableJpaAuditing
    애너테이션을 추가해야한다.
// 전처리작업에 사용
// preprocessRequest(prettyPrint()) 는 문서에 표시되는 JSON 포맷의 request body를 예쁘게 표현
public interface ApiDocumentUtils {
   static OperationRequestPreprocessor getRequestPreProcessor() {
       return preprocessRequest(prettyPrint());
    }

    static OperationResponsePreprocessor getResponsePreProcessor() {
        return preprocessResponse(prettyPrint());
    }
}

스니핏을 이용한 API 문서화

= 커피 주문 애플리케이션    // (1) API 문서의 제목
// API 문서의 목차와 관련된 내용
:sectnums:
:toc: left
:toclevels: 4
:toc-title: Table of Contents
:source-highlighter: prettify

Hwang Jung Sik <jungsik.hwang@codestates.com>   // (2) API 문서를 생성한 이의 정보

v1.0.0, 2022.04.08    // (3) API 문서의 생성 날짜

// (4) 우리가 테스트 케이스 실행을 통해 생성한 API 문서 스니핏을 사용하는 부분
***
== MemberController
=== 회원 등록
.curl-request
include::{snippets}/post-member/curl-request.adoc[]
// include::{snippets}/스니핏 문서가 위치한 디렉토리/스니핏 문서파일명.adoc[]

.http-request
include::{snippets}/post-member/http-request.adoc[]

.request-fields
include::{snippets}/post-member/request-fields.adoc[]

.http-response
include::{snippets}/post-member/http-response.adoc[]

.response-fields
include::{snippets}/post-member/response-fields.adoc[]

=== 회원 정보 수정
.curl-request
include::{snippets}/patch-member/curl-request.adoc[]

.http-request
include::{snippets}/patch-member/http-request.adoc[]

.request-fields
include::{snippets}/patch-member/request-fields.adoc[]

.http-response
include::{snippets}/patch-member/http-response.adoc[]

.response-fields
include::{snippets}/patch-member/response-fields.adoc[]

**템플릿 문서를 HTML 파일로 변환**

  • [Gradle] 탭을 클릭한 후, :bootJar또는 :buildtask 명령을 더블 클릭
  • 정상적으로 빌드가 종료되면, src/main/resources/static/docs’ 디렉토리에 index.adoc 파일을 이용해 변환된 index.html 파일이 생성된다.
  • [http://localhost:8080/docs/index.html](http://localhost:8080/docs/index.html) 로 확인

**Asciidoc**

  • Spring Rest Docs를 통해 생성되는 텍스트 기반 문서 포맷
  • 기술 문서 작성을 위해 설계된 가벼운 마크업 언어

Asciidoc 목차 구성

  1. , = : 문서의 제목을 작성. =의 개수가 늘어날 수록 글자는 작아진다.

  2. :sectnums: : 목차에서 각 섹션에 넘버링

  3. :toc: 는 목차를 문서의 어느 위치에 구성할 것인지를 설정.

  4. :toclevels: 은 목차에 표시할 제목의 level을 지정합니다.

    • 4로 지정하면, ==== 까지의 제목만 목차에 표시
  5. :toc-title: 은 목차의 제목을 지정

  6. :source-highlighter: 문서에 표시되는 소스 코드 하일라이터를 지정. ex)prettify

  7. *** 는 단락을 구분 지을 수 있는 수평선을 추가

  8. CAUTION:을 사용해서 경고 문구를 추가(NOTE:, TIP: , IMPORTANT: , WARNING:등을 사용가능)

  9. 문단의 제목 다음에 한 라인을 띄우고 한 칸 들여쓰기의 문단을 작성하면 박스 문단을 사용가능

**URL Scheme 자동 인식**

**이미지 추가**

  • image::를 사용해서 추가

**Asciidoctor**

  • AsciiDoc 포맷의 문서를 파싱해서 HTML 5, 매뉴얼 페이지, PDF 및 EPUB 3 등의 문서를 생성하는 툴

**문서 스니핏을 템플릿 문서에 포함 시키기**

  • 템플릿 문서에 포함된 스닛핏은 애플리케이션 빌드 타임에 내부적으로 Asciidoctor가index.adocindex.html로 변환 후, 특정 디렉토리(src/main/resources/static/docs)에 생성
.curl-request       // (1)
include::{snippets}/post-member/http-request.adoc[]
  1. .은 하나의 스니핏 섹션 제목을 표현하기 위해 사용

  2. include는 Asciidoctor에서 사용하는 매크로, 스니핏을 템플릿 문서에 포함할 때 사용

    ::은 매크로를 사용하기 위한 표기법, {snippets}는 해당 스니핏이 생성되는 디폴트 경로

profile
백엔드 개발자

0개의 댓글