MSA Phase 7. API Document(1) - Spring Rest Docs

devty·2023년 10월 2일
1

MSA

목록 보기
10/14
post-thumbnail

서론

도입하게 된 계기

  • 기존에는 Spring Swagger를 사용하여 개발을 하였는데, 프로덕션 코드에 직접적인 영향을 미치기에 코드가 너무나도 지저분해지는 걸 느낄수 있었다.
    @RequestMapping("/users")
    @Api(value = "User Registration API", tags = {"User Registration"})
    public class UserRegisterController {
    
        ...
    
        @PostMapping("/register")
        @ApiOperation(value = "Register a new user", notes = "Register a new user with the provided details", response = ReturnObject.class)
        @ApiResponses({
                @ApiResponse(code = 200, message = "Successfully registered user"),
                @ApiResponse(code = 400, message = "Invalid input or data mismatch"),
                @ApiResponse(code = 500, message = "Internal server error")
        })
        public ResponseEntity<ReturnObject> registerUser(
                @ApiParam(value = "User registration details", required = true)
                @RequestBody RegisterUserRequest registerUserRequest
        ) {
            ...
        }
    }
    • 위와 같이 간단한 회원가입 로직인데 스웨거에 대한 코드가 많은 비중을 차지하고 있다.
    • 그리고 Swagger는 Postman처럼 API 동작을 테스트하는 용도에 더 특화되어 있는 것 같다.
  • 따라서 Spring Rest Docs를 사용하여 이러한 불편한 점을 개선하기 위해 도입을 할 예정이었다.

Swagger vs Rest Docs

항목SwaggerSpring REST Docs
설명API 스펙 작성, 테스트, 문서화 도구테스트 기반 API 문서화 도구
장점애노테이션 기반으로 자동으로 API 문서 생성문서는 실제 테스트를 기반으로 생성되므로 항상 최신 상태와 일치
스웨거 UI를 통해 API 테스트 가능테스트가 성공해야 문서가 생성되므로 문서의 정확성 보장
간단한 애노테이션 추가만으로 문서화 가능AsciiDoc 또는 Markdown을 사용하여 다른 문서와 통합 가능
단점코드와 문서 사이에 불일치 가능초기 설정이 약간 복잡할 수 있음
많은 애노테이션을 코드에 추가해야 함모든 API가 테스트되어야 함
적합한 경우빠른 프로토타이핑이 필요한 경우정확한 문서가 중요하고, 지속적인 통합(CI) 프로세스가 있는 경우
개발자가 직접 API를 테스트하고 싶은 경우테스트 커버리지와 문서의 일관성을 원하는 경우
  • 먼저 두개의 장단점을 비교해보았다.
  • 장점
    • Swagger는 Rest Docs에 비해 비교적 간단하고 자동적으로 생성된다는 장점이 있다.
    • Spring Rest Docs는 테스트 기반의 API 문서화라 항상 최신 상태를 유지하며, 테스트가 성공 되어야 문서가 생성되므로 문서의 정확성을 보장한다.
  • 단점
    • Swagger는 코드와 문서 사이에서 일치하지 않는 경우가 있고 위에서 말했듯이 프로덕션 코드에 많은 어노테이션을 추가해야한다.
    • Spring Rest Docs는 테스트 기반이라 테스트 코드를 필수적으로 만들어줘야하고 초기 설정이 어려울 수 있다.
  • 두개의 장단점을 비교 해보았는데 나는 앞전에 테스트 코드에 대해 많이 배우기도 하였고, 테스트 코드에 대한 신뢰성 및 최신화가 중요하다고 생각하여 Spring Rest Docs를 채택하였다.

본론

Spring Rest Docs 코드

  • build.gradle
    // Spring Rest Docs(1)
    plugins {
        id 'org.asciidoctor.jvm.convert' version '3.3.2'
    }
    
    // Spring Rest Docs(2)
    configurations {
        asciidoctorExt
    }
    
    // Spring Rest Docs(3)
    dependencies {
        asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
        testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
    }
    
    // Spring Rest Docs(4)
    ext {
        snippetsDir = file('build/generated-snippets')
    }
    
    tasks.named('test') {
        useJUnitPlatform()
        outputs.dir snippetsDir // Spring Rest Docs(5)
    }
    
    // Spring Rest Docs(6)
    asciidoctor {
        inputs.dir snippetsDir
        configurations 'asciidoctorExt'
        doFirst {
            delete 'src/main/resources/static/docs'
        }
    }
    
    // Spring Rest Docs(7)
    bootJar {
        dependsOn asciidoctor
        copy {
            from asciidoctor.outputDir
            into "src/main/resources/static/docs"
        }
    }
    • Spring Rest Docs(1) - Asciidoctor Plugin 추가
      • org.asciidoctor.jvm.convert 플러그인은 Asciidoctor를 사용하여 AsciiDoc 파일을 HTML, PDF 등 다양한 포맷으로 변환하는 작업을 지원한다.
      • Spring Rest Docs는 AsciiDoc을 통해 API 문서를 작성하고, 이 플러그인을 통해 해당 문서를 변환다.
    • Spring Rest Docs(2) - Configurations 설정
      • asciidoctorExt라는 새로운 의존성 구성을 정의합니다. 이 구성은 Asciidoctor 확장에 사용되는 라이브러리를 포함하는 데 사용된다.
    • Spring Rest Docs(3) - Dependencies 추가
      • spring-restdocs-asciidoctor: Spring Rest Docs를 AsciiDoc 포맷으로 작성하는 데 필요한 라이브러리이다.
      • spring-restdocs-mockmvc: MockMvc 기반의 Spring MVC 테스트를 위한 Spring Rest Docs 의존성이다.
    • Spring Rest Docs(4) - Snippets Directory 설정
      • snippetsDir 변수를 생성하여 Spring Rest Docs가 API 테스트 시 생성하는 코드 스니펫(snippets)의 저장 위치를 지정한다.
    • Spring Rest Docs(5) - Test Task 설정
      • 테스트가 실행될 때 생성되는 코드 스니펫을 snippetsDir에 저장하도록 설정한다.
    • Spring Rest Docs(6) - Asciidoctor Task 설정
      • Asciidoctor 플러그인을 사용하여 AsciiDoc 문서를 변환한다.
      • configurations 'asciidoctorExt'snippetsDir에 있는 코드 스니펫을 입력으로 사용하고, 해당 스니펫을 기반으로 API 문서를 생성한다.
      • 문서가 생성되기 전에 이전에 생성된 문서를 삭제한다.
    • Spring Rest Docs(7) - BootJar Task 설정
      • BootJar 작업은 Spring Boot 애플리케이션의 JAR 파일을 생성하는 작업이다.
      • Asciidoctor 작업을 완료한 후 실행되도록 의존성을 설정한다.
      • 생성된 API 문서를 JAR 파일의 src/main/resources/static/docs 디렉토리로 복사하여 애플리케이션이 실행될 때 API 문서에 접근할 수 있도록 한다.
  • UserRegisterController.java
    public class UserRegisterController {
    
        private final RegisterUserUseCase registerUserUseCase;
        private final UserResponseMapper userResponseMapper;
    
        @PostMapping("/register")
        public ResponseEntity<ReturnObject> registerUser(
                @RequestBody RegisterUserRequest registerUserRequest
        ) {
            RegisterUserCommand command = RegisterUserCommand.builder()
                    .username(registerUserRequest.getUsername())
                    // 등등
                    .build();
    
            User user = registerUserUseCase.registerUser(command);
            RegisterUserResponse response = userResponseMapper.mapToRegisterUserResponse(user);
    
    				ReturnObject returnObject = ReturnObject.builder()
                    .success(true)
                    .data(response)
                    .build();
            
    				return ResponseEntity.status(HttpStatus.OK).body(returnObject);
        }
    }
    • 예시로들 간단한 회원가입 API이다.
    • 우린 Spring Rest Docs를 만들기 위해 테스트 코드를 작성할 것 이다.
  • ControllerTest.java
    @Transactional
    @AutoConfigureMockMvc
    @AutoConfigureRestDocs
    @ActiveProfiles("test")
    @SpringBootTest(classes = UserServiceApplication.class)
    public abstract class ControllerTest {
    
        @Autowired
        protected MockMvc mockMvc;
    
    }
    • Spring Rest Docs를 만들기 위해 테스트 코드를 작성해야하는데 그 테스트 코드들에 대해 여러 어노테이션들을 붙여줘야한다.
    • 중복된 코드를 방지하기 위해 abstract로 클래스를 만들어 상속 받게 되는 개념이다.
    • 필수적인 두가지 어노테이션만 확인하겠다.
      • @AutoConfigureRestDocs → Spring Boot의 TestContext 프레임워크를 사용하여 Spring Rest Docs를 자동으로 구성한다.
      • @AutoConfigureMockMvc → Spring Rest Docs는 MockMvc를 통해 수행된 요청과 응답을 기반으로 문서 스니펫을 생성하기 때문에 MockMvc는 Spring Rest Docs 테스트에 필수적이다.
  • UserRegisterControllerDocumentTest.java
    @DisplayName("회원 가입 API 명세서")
    public class UserRegisterControllerDocumentTest extends ControllerTest {
    
        @Test
        @DisplayName("회원 가입 API 성공 시나리오 문서화")
        public void testRegisterUser() throws Exception {
            RegisterUserRequest request = new RegisterUserRequest(
                    "testuser111",
                    "test111!!!",
                    "test111!!!",
                    "testNickname",
                    "010-1234-5678",
                    "testuser@example.com"
            );
    
            mockMvc.perform(MockMvcRequestBuilders.post("/users/register")
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(new ObjectMapper().writeValueAsString(request)))
                    .andExpect(status().isOk())
                    .andDo(print())
                    .andDo(MockMvcRestDocumentation.document("user/register-user",
                            PayloadDocumentation.requestFields(
                                    PayloadDocumentation.fieldWithPath("username").description("User's username"),
                                    PayloadDocumentation.fieldWithPath("password").description("User's password"),
                                    PayloadDocumentation.fieldWithPath("confirmPassword").description("User's confirmed password"),
                                    PayloadDocumentation.fieldWithPath("nickname").description("User's nickname"),
                                    PayloadDocumentation.fieldWithPath("phone").description("User's phone number"),
                                    PayloadDocumentation.fieldWithPath("email").description("User's email address")
                            )
                            ,PayloadDocumentation.responseFields(
                                    PayloadDocumentation.fieldWithPath("success").description("Operation successful or not"),
                                    PayloadDocumentation.fieldWithPath("data").description("Registered user"),
                                    PayloadDocumentation.fieldWithPath("data.username").description("User's username"),
                                    PayloadDocumentation.fieldWithPath("data.nickname").description("User's nickname"),
                                    PayloadDocumentation.fieldWithPath("data.phone").description("User's phone number"),
                                    PayloadDocumentation.fieldWithPath("data.email").description("User's email address"),
                                    PayloadDocumentation.fieldWithPath("data.role").description("User's role"),
                                    PayloadDocumentation.fieldWithPath("errorCode").description("Error code if any").optional()
                            )
                    ));
        }
    }
    • 우리가 익히 알고 있는 테스트 코드이다.
    • 우리가 중점적으로 봐야할 것은 MockMvcRestDocumentation.document 부분이다.
      • 첫번째 매개변수인 "user/register-user" → 파일이 저장될 위치를 지정한다.
      • 두번째 매개변수인 PayloadDocumentation.requestFields → request 필드를 문서화하고 각 필드 경로와 설명을 적어둔다.
      • 세번째 매개변수인 PayloadDocumentation.responseFields → response 필드를 문서화하고 각 필드 경로와 설명을 적어둔다.
    • 여기서 Spring Rest Docs에 장점이 나온다.
      • 테스트 코드에 모든 Request, Response를 무조건 넣어줘야한다.
      • 하나라도 빠지게 되면 테스트를 성공하지 못하기에 API 명세서를 만들지 못하게 된다.
      • 또한 PayloadDocumentation.fieldWithPath("errorCode").description("Error code if any").optional() → 해당 errorCode에 대한 필드를 Response 받지 않고 있지만 무조건 넣어줘야하므로 .optional()을 넣어 해결해주면 된다.

Spring Rest Docs 테스트 코드 실행

  • 우리가 위에서 만든 테스트 코드를 실행하면 아래와 같은 Task들이 실행 된다.
    > Task :user-service:compileJava UP-TO-DATE
    > Task :user-service:processResources UP-TO-DATE
    > Task :user-service:classes UP-TO-DATE
    > Task :user-service:compileTestJava UP-TO-DATE
    > Task :user-service:processTestResources NO-SOURCE
    > Task :user-service:testClasses UP-TO-DATE
    > Task :user-service:test
    > Task :user-service:asciidoctor
    > Task :user-service:bootJar
    • 위 Task 중에서 맨 밑에 3개만 보면 된다.
  1. Task :user-service:test
    • tasks.named('test') 태스크가 호출된다.
    • 테스트 중 @RestDocsMockMvc를 사용하여 MockMvc 요청/응답을 캡쳐한다.
    • 이 때 생성된 스니펫들은 build/generated-snippets 디렉토리에 저장된다.
  2. Task :user-service:asciidoctor
    • 이 태스크는 build/generated-snippets에서 생성된 스니펫들을 사용하여 AsciiDoc 포맷의 문서를 생성한다.
    • 생성된 문서는 src/main/resources/static/docs 디렉토리에 저장된다.
  3. Task :user-service:bootJar
    • 생성된 Asciidoctor 문서를 Spring Boot JAR 파일 내부에 포함시킵니다.
    • 별도의 설정에 따라 src/main/resources/static/docs 디렉터리로 복사하고, 이 디렉터리는 Spring Boot 애플리케이션을 실행할 때 접근 가능한 경로가 됩니다.

adoc 문서화

  • .adoc 파일은 텍스트 편집기나 IDE에서 쉽게 로컬에서 미리 볼 수 있다.
  • 인텔리제이와 같은 일부 IDE는 Asciidoc 플러그인을 지원하며, 문서를 편집하는 동안 실시간으로 미리볼 수 있게 해줍니다.
    = User API문서
    :doctype:book
    :icons:font
    :source-highlighter:highlightjs
    :toc:left
    :toclevels:3
    
    ==회원 가입 API
    
    === Request
    include::{snippets}/user/register-user/request-fields.adoc[]
    === Request
    include::{snippets}/user/register-user/http-request.adoc[]
    === Response
    include::{snippets}/user/register-user/http-response.adoc[]
    • 기본적인 것들을 제외하고 나머지만 설명하겠다.
    • include::{snippets}/user/register-user/request-fields.adoc[] → 위에서 생성한 파일인 Request Fiedls를 adoc에 include 시켜준다.
    • 나머지도 HTTP Request, HTTP Response를 adoc에 include 시켜준다.
  • 문서화 된 adoc 파일을 인텔리제이에서 확인해보면 아래와 같다.

결론

후기

  • 원래는 엑셀도 많이 사용하였고 Swagger도 많이 사용하였는데 테스트 기반으로 신뢰성을 바탕하여 API 문서를 만드니 처음 도입하긴 어려웠지만 그래도 뿌듯한 경험이었다.
  • 근데 아직도..UI가 너무 익숙지 않다…
    • Swagger를 사용할 땐 너무나 예쁜 UI였는데 그게 너무나도 아쉽다.
profile
지나가는 개발자

0개의 댓글