현재는 OAuthLogin API에 대해서만 문서화를 했습니다.
그러나 다른 API도 추가한다면 index.html 파일 하나만으로 좋은 문서를 만들기 어려울 것 같아 리팩토링을 하였습니다.
API 문서를 처음 만들어 어떤 구성이 좋을지 잘 모르겠어서 컬리 블로그 글을 참고하여 만들었습니다.
- REST Docs Test 코드 리팩토링
- 기존 코드에서 재사용성 향상시키기
- 요청 및 응답 명세서의 구성 개선
- index.adoc은 공통 요청 및 응답 명세
- API 문서를 스니펫으로 분리하기
- 분리된 API 문서를 통합
@ExtendWith(MockitoExtension.class)
@AutoConfigureRestDocs
public class RestDocsSetting {
protected MockMvc mockMvc;
protected ObjectMapper objectMapper = new ObjectMapper();
@MockBean
private JwtFilter jwtFilter;
@MockBean
private JwtExceptionFilter jwtExceptionFilter;
/* MockMvc Rest Docs 초기 설정 */
@BeforeEach
void setUp(@Autowired RestDocumentationContextProvider restDocumentation,
@Autowired WebApplicationContext webApplicationContext) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(restDocumentation)) // REST Docs의 기본 설정 적용
.build();
}
}
REST Docs를 사용하면 공통으로 필요한 사항들을 모아 RestDocsSetting
클래스를 구성하였습니다.
재사용성
과 확장성
을 높여 코드의 길이를 줄였습니다.
해당 코드 사진의 마지막 필드에 대한 줄에서 optional()
을 붙여 선택 여부를 표시하였습니다.
optional()
이 붙지 않은 필드들은 필수 사항입니다.
해당 사항을 어떻게 스니펫에서 표현하는지는 커스텀한 명세서에서 스니펫 코드의 주석을 봐주세요!
인터페이스에 정적 메소드로 여러 포맷들을 구현하였습니다.
Test 코드에 직접 작성하면 코드가 너무 길어져 정적 메소드로 만들었습니다.
여기서
key()
의 인수는 하나의 값으로 통일해주어야 커스텀한 스니펫에서 읽어올 수 있습니다.
또한 인터페이스에서 메소드 구현은Java8 이상
부터 됩니다!
attributes()
의 인수로 인터페이스에 구현해 놓은 포맷 메소드들을 가져와 호출해줍니다.
/src/test/
디렉토리에 resources/org/springframework/restdocs/templates/
디렉토리를 만들고, 아래 파일들을 각각 생성합니다.
위에서 추가한 필수/선택 사항 구분과 Format을 출력하기 위해 스니펫 커스텀하였습니다.
Request Headers
|===
|헤더명|타입|필수 여부|양식|설명
{{#headers}}
|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}} // 헤더명
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} // 요청 값 타입
|{{#tableCellContent}}{{^optional}}O{{/optional}}{{/tableCellContent}} // 필수 여부 (`optional()`이 붙지 않으면 "O" 출력)
|{{#tableCellContent}}{{#format}}{{.}}{{/format}}{{/tableCellContent}} // 양식 (양식을 따로 명시한 경우에만 출력)
|{{#tableCellContent}}{{description}}{{/tableCellContent}} // 설명
{{/headers}}
|===
Path Parameters
|===
|파라미터명|타입|필수 여부|양식|설명
{{#parameters}}
|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}}
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{^optional}}O{{/optional}}{{/tableCellContent}}
|{{#tableCellContent}}{{#format}}{{.}}{{/format}}{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/parameters}}
|===
MockMvcRequestBuilders.get()
은 /api/search/{chemId}
처럼 {chemId}
에 동적인 값을 넣는 것을 지원하지 않습니다.
Path Variable
을 문서화 하기 위해선RestDocumentationRequestBuilders.get()
으로 요청하여 테스트해야 합니다.
아래 예시를 봐주세요!
// MockMvcRequestBuilders의 get()이 아닌
ResultActions result = mockMvc.perform(
get("/api/search/{chemId}", chemId)
.header("Authorization", bearerToken)
);
// RestDocumentationRequestBuilders의 get()을 사용해야 합니다!
ResultActions result = mockMvc.perform(
RestDocumentationRequestBuilders.get("/api/search/{chemId}", chemId)
.header("Authorization", bearerToken)
);
Query Parameters
|===
|파라미터명|타입|필수 여부|양식|설명
{{#parameters}}
|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}} // 파라미터명
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} // 요청 값 타입
|{{#tableCellContent}}{{^optional}}O{{/optional}}{{/tableCellContent}} // 필수 여부 (`optional()`이 붙지 않으면 "O" 출력)
|{{#tableCellContent}}{{#format}}{{.}}{{/format}}{{/tableCellContent}} // 양식 (양식을 따로 명시한 경우에만 출력)
|{{#tableCellContent}}{{description}}{{/tableCellContent}} // 설명
{{/parameters}}
|===
Request Fields
|===
|필드명|타입|필수 여부|양식|설명
{{#fields}}
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} // 필드명
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} // 요청 값 타입
|{{#tableCellContent}}{{^optional}}O{{/optional}}{{/tableCellContent}} // 필수 여부 (`optional()`이 붙지 않으면 "O" 출력)
|{{#tableCellContent}}{{#format}}{{.}}{{/format}}{{/tableCellContent}} // 양식 (양식을 따로 명시한 경우에만 출력)
|{{#tableCellContent}}{{description}}{{/tableCellContent}} // 설명
{{/fields}}
|===
Response Fields
|===
|필드명|타입|설명
{{#fields}}
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} // 필드명
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} // 응답 값 타입
|{{#tableCellContent}}{{description}}{{/tableCellContent}} // 설명
{{/fields}}
|===
index.adoc
에 공통적인 사항을 명세하였습니다.
공통의 요청, 응답, 에러 코드를 작성하였습니다.
자세한 코드는 여기를 클릭해주세요.
저는 API 별로 문서를 나누어 관리할 것입니다.
그래서 /src/docs/asciidoc/
에 api 디렉토리를 추가했습니다.
api 디렉토리에는 각 API 문서 스니펫을 만들었습니다.
include::{docdir}/src/docs/asciidoc/api/login.adoc[]
index.adoc
에 위와 같은 코드를 원하는 위치에 적어주면 됩니다.
추가하려는 API 문서 스니펫의 경로를 적어주고 마지막에 []
를 붙이면 빌드 시 해당 문서를 포함하게 됩니다.
그러나 Intellij
에서는 위와 같은 경로를 인식하지 못해 코드 오류로 판단합니다.
그래서 개발 중일 때는 include::{docdir}/api/login.adoc[]
로 변경하여 하시면 됩니다.
{docdir}
을 현재 문서까지의 경로로 생각하여 이후 경로를 나타내주면 Intellij
에서는 오류를 발생하지 않아 미리보기 기능을 볼 수 있습니다.
마지막에 구성을 다 끝내고 빌드 하기 전에만 include::{docdir}/src/docs/asciidoc/api/login.adoc[]
와 같은 경로로 바꿔주면 됩니다!
자세한 문제 원인에 대해선 여기를 클릭해주세요.
아직 전부 완성된 것은 아니지만, gif로 결과를 첨부해 보았습니다.
화질이 좋지 못한 점 양해 부탁드립니다...
REST Docs
는 설정도 많고 Asciidoc
으로 문서를 구성하는게 번거롭긴 하다.
그러나 이 번거로운 처음만 넘긴다면, API 문서를 추가할 때마다 구성은 비슷하여 빠르게 만들 수 있게 되었다.
디자인 센스가 있지 않아서 나에겐 이 구성이 괜찮다고 느껴지지만, 다른 사람도 괜찮다고 느껴질지는 잘 모르겠다...
나중에 팀플을 할 때 이렇게 구성해보고 피드백을 받고 수정해야겠다!
Spring Rest Docs 적용 | 우아한 형제들 기술 블로그
Spring Rest Docs Guide | 컬리 기술 블로그
ch.1027님 블로그
공부하는 감자님 블로그