클라이언트가 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 문서화를 자동화하기 위해 사용하는 메서드이지만 내용을 정렬하는 형식을 바꾼다는 차이점이 있다.