Framework/Spring

[Spring] Rest Doc 활용하기

검은 까마귀 2023. 12. 13. 00:08

MSA 프로젝트를 진행하다가 API 문서에 대해서 팀원분이 Swagger를 쓰겠다고 했을때,
저는 안되는 이유에 대해서 설명을 해드렸습니다.

 

" 서비스가 쪼개질때마다 swagger 문서가 production에 생길텐데, 프론트 앤드 엔지니어 입장이라면 원하는 API문서를 찾을때까지 뒤지는건 생산성이 떨어진다." 

 

그러면서 API문서 구축하시는 분께 다른 방법이 있는지 찾아달라고 부탁드렸습니다.

(정 없다면 스프레드 시트로 작성해야죠.....!!)

 

아니다 다를까 MSA가 화두되고 있는 지금 다른 방법이 아예 없지는 않았습니다.

팀원분이 하루만에 방법을 찾아오셨더라고요

 

해당 방법은 아래와 같았습니다.

 

  1. 서비스별 Rest Docs을 생성한다.
  2. 생성된 Rest Doc을 Open API 형태의 Json 포맷으로 만다.
  3. API 문서 전용 인스턴스를 생성한다.
  4. 생성된 인스턴스에 swagger ui 컨테이너를 배포한다.
  5. swagger ui에 Open API json 파일을 밀어 넣는다.

잘게 나누면 5단계 큼직하게 나누면 3단계까지로 이루어져있더라고요

 

서비스별 Rest Docs을 생성한다.

 

 

1차적으로 여기서 걸리기 시작했습니다.

누구든 도전하지만! 아무나 쉽게 사용하지 못하는 Rest Docs 이죠

 

토이 프로젝트에서 빠른 구현과 배포가 생명인데
Rest Docs은 통합 테스트 코드가 작성되어야 API문서가 나오는 형태 입니다.

 

 

 

무튼..... 장단점이 있고 트레이드 오프 같은 생각이 들더라고요 그래서 현재 어떤 프로젝트를 진행하냐에 초점을 맞추었습니다.

어찌되었던 학습하고 배우는 프로젝트라고 다짐하고 참여했기도 하고 MSA에서는 어떤식으로 API문서를 관리하고 이런 이점을 몸소 체험해보고자 해당 방법을 채택했습니다.

 

최대한 공식문서를 활용해서 작성했습니다!


1. Response Mock 작성

// 예제 응답 데이터 설정
List<ReservationDTO.Response> exampleResponse = Arrays.asList(
                new ReservationDTO.Response(
                141, "123", date, "21:00:00", "23:00:00",
                attendeeDTO, subject, room,"123123", online, 156L));

 

먼저, 정상적인 Response 형태를 작성해줘야합니다. 그래야 API 호출하고 받은 바디의 데이터 부분과 비교할수 있겠죠?

 

2. When 작성

when(reservationService.get(anyString(), anyInt(), anyInt())).thenReturn(exampleResponse);

 

제가 작성한 코드가 정상적으로 동작하는지 서비스를 날렸을때 응답이 정확하게 돌아오는지 확인을 합니다.

 

3. REST Docs 필드 작성

this.mockMvc.perform(get("/reservations")// 여기서 your-endpoint는 API 엔드포인트입니다.
                        .param("month", "12")
                        .param("week", "3"))
                .andExpect(status().isOk())
                .andDo(document("get-reservation",
                        requestParameters(
                                parameterWithName("month").description("월"),
                                parameterWithName("week").description("주")),
                        responseFields(
                                fieldWithPath("[].reservationId").description("예약 ID"),
                                fieldWithPath("[].title").description("제목"),
                                fieldWithPath("[].date").description("날짜"),
                                fieldWithPath("[].startTime").description("시작 시간"),
                                fieldWithPath("[].endTime").description("종료 시간"),
                                fieldWithPath("[].attendeeList").description("참석자 목록"),
                                fieldWithPath("[].attendeeList[].email").description("참석자의 이메일"),
                                fieldWithPath("[].attendeeList[].name").description("참석자의 이름"),
                                fieldWithPath("[].attendeeList[].isHost").description("참석자가 호스트인지 여부"),
                                fieldWithPath("[].attendeeList[].isWriter").description("참석자가 작성자인지 여부"),
                                fieldWithPath("[].attendeeList[].status").description("참석자의 상태"),
                                fieldWithPath("[].subject").description("주제에 대한 정보"),
                                fieldWithPath("[].subject.id").description("주제의 ID"),
                                fieldWithPath("[].subject.name").description("주제의 이름"),
                                fieldWithPath("[].room").description("회의실 정보"),
                                fieldWithPath("[].room.id").description("회의실 ID"),
                                fieldWithPath("[].room.name").description("회의실 이름"),
                                fieldWithPath("[].room.location").description("회의실 위치"),
                                fieldWithPath("[].room.capacity").description("회의실 수용 인원"),
                                fieldWithPath("[].room.note").description("회의실에 대한 비고"),
                                fieldWithPath("[].room.facility").description("회의실 시설"),
                                fieldWithPath("[].note").description("일반 비고"),
                                fieldWithPath("[].online").description("온라인 회의 관련 정보"),
                                fieldWithPath("[].online.id").description("온라인 회의 ID"),
                                fieldWithPath("[].online.isOnline").description("온라인 여부"),
                                fieldWithPath("[].online.onlineUrl").description("온라인 회의 URL"),
                                fieldWithPath("[].minutesId").description("회의록 ID")
                        )));
    }

 

흠.... 이 부분을 작성하면서 오류 코드가 제일 많이 나왔습니다.

발생했던 오류는 response를 이렇게 받았습니다.

  fieldWithPath("minutesId").description("회의록 ID")

 

그런데 payload를 찾을 수 없다고 나오더라고요...

그래서 보니깐 List에 한번 감싸서 json형식으로 전달을 받았습니다.

  fieldWithPath("[].minutesId").description("회의록 ID")

 

코드에 []를 앞에 추가하여 "리스트형식에서 minutesId 객체만 받겠다"고 선언을 하니 

payload를 정상적으로 가져왔습니다!!

 

코드를 쓰면서 엔티티가 너무 많으니깐 코드를 작성하는데 어려움? 보다는 귀찮음이 가장 크더라고요

혹시 저런 케이스에서 노가다 안하고 작성할 수 있는 방법이 있으면 공유 부탁드릴게요~

 

오랜만에 쓰는 글인데 즐겁게 읽어주셔서 감사합니다.

좋은 피드백은 언제나 환영해요

 

차후에 해당 Rest Docs 문서들을 Open API 형태로 이식하는지 블로그 포스팅을 하겠습니다

 

 

 

https://docs.spring.io/spring-restdocs/docs/current/reference/htmlsingle/

 

Spring REST Docs

Document RESTful services by combining hand-written documentation with auto-generated snippets produced with Spring MVC Test or WebTestClient.

docs.spring.io

https://techblog.woowahan.com/2597/

 

Spring Rest Docs 적용 | 우아한형제들 기술블로그

{{item.name}} 안녕하세요? 우아한형제들에서 정산시스템을 개발하고 있는 이호진입니다. 지금부터 정산시스템 API 문서를 wiki 에서 Spring Rest Docs 로 전환한 이야기를 해보려고 합니다. 1. 전환하는

techblog.woowahan.com

 

반응형

'Framework > Spring' 카테고리의 다른 글

[Spring] DB 접근 방법  (0) 2024.04.10
[Spring] Open API 활용하기  (0) 2023.12.15
[Spring] Spring MVC 패턴의 LifeCycle  (0) 2023.10.26
[Spring] JPA의 Persistence  (0) 2022.04.11
[Spring] Gradle 1편  (0) 2022.04.04