
클라이언트가 REST API 백엔드 애플리케이션에 요청을 전송하기 위해서 알아야 되는 요청 정보(요청 URL(또는 URI), request body, query parameter 등)를 문서로 잘 정리하는 것
API 문서 or API 스펙(사양, Specification)
- API 사용을 위한 어떤 정보가 담겨 있는 문서
@ApiOperation(value = "회원 정보 API", tags = {"Member Controller"})
@RestController
@RequestMapping("/v11/swagger/members")
@Validated
@Slf4j
public class MemberControllerSwaggerExample {
private final MemberService memberService;
private final MemberMapper mapper;
public MemberControllerSwaggerExample(MemberService memberService, MemberMapper mapper) {
this.memberService = memberService;
this.mapper = mapper;
}
@ApiOperation(value = "회원 정보 등록", notes = "회원 정보를 등록합니다.")
@ApiResponses(value = {
@ApiResponse(code = 201, message = "회원 등록 완료"),
@ApiResponse(code = 404, message = "Member not found")
})
@PostMapping
public ResponseEntity postMember(@Valid @RequestBody MemberDto.Post memberDto) {
Member member = mapper.memberPostToMember(memberDto);
member.setStamp(new Stamp()); // homework solution 추가
Member createdMember = memberService.createMember(member);
return new ResponseEntity<>(
new SingleResponseDto<>(mapper.memberToMemberResponse(createdMember)),
HttpStatus.CREATED);
}
@ApiOperation(value = "회원 정보 조회", notes = "회원 식별자(memberId)에 해당하는 회원을 조회합니다.")
@GetMapping("/{member-id}")
public ResponseEntity getMember(
@ApiParam(name = "member-id", value = "회원 식별자", example = "1") // (5)
@PathVariable("member-id") @Positive long memberId) {
Member member = memberService.findMember(memberId);
return new ResponseEntity<>(
new SingleResponseDto<>(mapper.memberToMemberResponse(member))
, HttpStatus.OK);
}
}
단점
장점
Spring Rest Docs
- REST API 문서를 자동으로 생성해 주는 Spring 하위 프로젝트
장점
단점
스니핏(snippet)
- 일부 조각을 의미
- 테스트 케이스 하나 당 하나의 스니핏이 생성
(여러개의 스니핏을 모아서 하나의 API 문서를 생성할 수 있다.)
plugins {
id 'org.springframework.boot' version '2.7.1'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id "org.asciidoctor.jvm.convert" version "3.3.2" // .adoc 파일 확장자를 가지는 AsciiDoc 문서를 생성해주는 Asciidoctor를 사용하기 위한 플러그인을 추가
id 'java'
}
group = 'com.codestates'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
ext { // API 문서 스니핏이 생성될 경로를 지정
set('snippetsDir', file("build/generated-snippets"))
}
configurations { // AsciiDoctor에서 사용되는 의존 그룹을 지정
asciidoctorExtensions
}
dependencies {
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' // spring-restdocs-core와 spring-restdocs-mockmvc 의존 라이브러리 추가
asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor' // asciidoctorExtensions 그룹에 spring-restdocs-asciidoctor 의존 라이브러리를 추가
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
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'
}
tasks.named('test') {// :test task 실행 시
outputs.dir snippetsDir
useJUnitPlatform() // 스니핏 디렉토리 경로 설정
}
tasks.named('asciidoctor') { // :asciidoctor task 실행 시
configurations "asciidoctorExtensions" // Asciidoctor 기능을 사용하기 위해 :asciidoctor task에 asciidoctorExtensions 을 설정
inputs.dir snippetsDir
dependsOn test
}
// :build 실행 전에 실행되는 task
task copyDocument(type: Copy) {
dependsOn asciidoctor // [:asciidoctor]가 실행된 후에 task가 실행 되도록 의존성을 설정
from file("${asciidoctor.outputDir}") // "build/docs/asciidoc/" 경로에 생성되는 index.html을 copy
into file("src/main/resources/static/docs") // 괄호안 경로에 복사한 index.html을 추가
}
build {
dependsOn copyDocument // :build task가 실행되기 전에 :copyDocument task가 먼저 수행 되도록 의존성 설정
}
// 애플리케이션 실행 파일이 생성하는 :bootJar task 설정
bootJar {
dependsOn copyDocument // :bootJar task 실행 전에 :copyDocument task가 실행 되도록 의존성을 설정
from ("${asciidoctor.outputDir}") {
into 'static/docs' // Asciidoctor 실행으로 생성되는 index.html 파일을 jar 파일 안에 추가
} // jar 파일에 index.html을 추가해 줌으로써 웹 브라우저에서 접속(http://localhost:8080/docs/index.html) 후, API 문서를 확인할 수 있게 된다.
}
src/docs/asciidoc/ 경로에 해당하는 디렉토리를 생성src/docs/asciidoc다)
@WebMvcTest(컨트롤러클래스명.class)
@MockBean(JpaMetamodelMappingContext.class)
@AutoConfigureRestDocs
@MockBean
ResultActions actions = request 전송
actions
.andExpect(status().isCreated()) // response에 대한 기대 값 검증
.andExpect(header().string("Location", is(startsWith("/v11/members/"))))
.andDo(document("post-member", // API 문서 스펙 정보 추가
getRequestPreProcessor(),
getResponsePreProcessor(),
requestFields(
List.of(
fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"),
fieldWithPath("name").type(JsonFieldType.STRING).description("이름"),
fieldWithPath("phone").type(JsonFieldType.STRING).description("휴대폰 번호")
)
),
responseHeaders(
headerWithName(HttpHeaders.LOCATION).description("Location header. 등록된 리소스의 URI")
)
));
(request 전송 방법은 이전에 한 실습 참고)
위와 같이 response 검증 후 andDo(document()) 메서드를 통해 API 문서를 생성한다.
@SpringBootTest 애너테이션은 @AutoConfigureMockMvc 과 함께 사용되어 Controller를 테스트 할 수 있다.@WebMvcTest 애너테이션은 Controller 테스트에 필요한 Bean만 ApplicationContext에 등록한다.= 커피 주문 애플리케이션 // API 문서의 제목
:sectnums:
:toc: left
:toclevels: 4
:toc-title: Table of Contents
:source-highlighter: prettify
We Won Jong <wjwee9@gmail.com> // API 문서를 생성한 사람의 정보
v1.0.0, 2022.04.08 // API 문서의 생성 날짜
// 테스트 케이스 실행을 통해 생성한 API 문서 스니핏을 사용하는 부분
***
== MemberController
=== 회원 등록
.curl-request
include::{snippets}/post-member/curl-request.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-headers
include::{snippets}/post-member/response-headers.adoc[]
=== 회원 정보 수정
.curl-request
include::{snippets}/patch-member/curl-request.adoc[]
.http-request
include::{snippets}/patch-member/http-request.adoc[]
.path-parameters
include::{snippets}/patch-member/path-parameters.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[]
include::{snippets}/스니핏 문서가 위치한 디렉토리/스니핏 문서파일명.adoc[]
.curl-request 에서 .은 하나의 스니핏 섹션 제목을 표현하기 위해 사용include는 Asciidoctor에서 사용하는 매크로(macro) 중 하나:: 은 매크로를 사용하기 위한 표기법{snippets}는 해당 스니핏이 생성되는 디폴트 경로를 의미

우측 상단의 [Gradle] 탭을 클릭한 후, :bootJar 또는 :build task 명령을 더블 클릭
http://localhost:8080/docs/index.html를 입력
이 과정에서 Gradle오류가 발생해 고생했다.
드디어 Spring Rest Docs를 이용해서 API 문서를 생성할 준비 완료!!
위 과정 간단 정리
Controller 테스트를 위한 테스트 케이스 실행으로 생성된 API 문서 스니핏은 템플릿 문서에 포함해서 사용할 수 있다.
애플리케이션 빌드를 통해 템플릿 문서를 HTML 파일로 변환할 수 있다.
변환된 HTML 파일을 ‘src/main/resources/static/docs/’ 디렉토리에 위치 시키면 웹 브라우저로 API 문서를 확인할 수 있다.
Asciidoc
- Spring Rest Docs를 통해 생성되는 텍스트 기반 문서 포맷
= 커피 주문 애플리케이션 // (1)
:sectnums: // (2)
:toc: left // (3)
:toclevels: 4 // (4)
:toc-title: Table of Contents // (5)
:source-highlighter: prettify // (6)
We Won Jong <wjwee9@gmail.com> // API 문서를 생성한 사람의 정보
v1.0.0, 2023.03.09 // API 문서의 생성 날짜
(1) 문서의 제목을 작성하기 위해서는 =를 추가하면 된다. ====와 같이 =의 개수가 늘어날 수록 글자는 작아진다.
(2) 목차에서 각 섹션에 넘버링을 해주기 위해서는 :sectnums: 를 추가하면 된다.
(3) :toc: 는 목차를 문서의 어느 위치에 구성할 것인지를 설정한다. 위 예시에서는 문서의 왼쪽정렬로 목차가 표시되도록 left를 지정했다.
(4) :toclevels: 은 목차에 표시할 제목의 level을 지정한다. 위 예시에서는 4로 지정했기 때문에 ==== 까지의 제목만 목차에 표시된다.
(5) :toc-title: 은 목차의 제목을 지정할 수 있다.
(6) :source-highlighter: 문서에 표시되는 소스 코드 하일라이터를 지정한다. (위 예시에서는 prettify로 지정)

들여쓰기 = 박스 문단
*** = 단락을 구분 지울 수 있는 수평선 추가

경고 문구 추가 = CAUTION:, NOTE: , TIP: , IMPORTANT: , WARNING: 등

URL Scheme = http, https, ftp, irc, mailto, wjwee9@gmail.com
(위와 같은 URL Scheme는 Asciidoc에서 자동으로 인식하여 링크가 설정 된다.)
이미지 추가 = image::
image::https://velog.velcdn.com/images/wish17/post/fa5f2b25-161f-491f-8f4b-d589a3d42861/image.png[spring]
Asciidoctor
- Asciidoctor는 AsciiDoc 포맷의 문서를 파싱해서 HTML 5, 매뉴얼 페이지, PDF 및 EPUB 3 등의 문서를 생성하는 툴
- Spring Rest Docs에서는 Asciidoc 포맷의 문서를 HTML 파일로 변환하기 위해 내부적으로 Asciidoctor를 사용한다.
위와 같은 방법들로 템플릿(index.adoc) 문서를 다 작성한 뒤 디폴트 디렉토리 주소 src/main/resources/static/docs에 비어있는 텍스트 파일 index.html을 생성해 두고 위에서 언급한 Gradle task 명령(build) 실행을 하면 자동 생성 된다.
이전 실습까지는 API문서를 만들기 위한 andDo(document())메서드를 사용하지 않았기 때문에 MockMvcRequestBuilders 클래스의 get(), post(), patch()를 사용해도 상관 없었다.
키포인트!!!
하지만
andDo(document())메서드를 사용하기 위해서는RestDocumentationRequestBuilders클래스의get(),post(),patch()를 사용해야 한다.
// getRequestPreProcessor()
// getResponsePreProcessor()
.andDo(document(
"delete-member",
getRequestPreProcessor(),
getResponsePreProcessor(),
pathParameters(
parameterWithName("member-id").description("회원 식별자")
)
));
// preprocessRequest(prettyPrint())
// preprocessResponse(prettyPrint())
.andDo(document(
"delete-member",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
pathParameters(
parameterWithName("member-id").description("회원 식별자")
)
));
위와 같이 preprocess 메서드를 이용해 문서화 정렬방법을 바꿀 수 있다.
결국 둘 다 api 문서화를 자동화하기 위해 사용하는 메서드이지만 내용을 정렬하는 형식을 바꾼다는 차이점이 있다.