API 문서화란 클라이언트가 REST API 백엔드 애플리케이션에 요청을 전송하기 위해서 알아야 되는 요청 정보(요청 URL 또는 URI, request body, query parameter 등)를 문서로 잘 정리하는 것을 의미한다.
▶️ API 요청을 위해 필요한 정보를 문서로 정리해야 하는 이유는 무엇인가?
백엔드에서 만든 REST API 기반의 애플리케이션을 클라이언트 쪽에서 사용하려면 API 사용을 위한 정보가 필요하기 때문이다.
API 사용을 위한 정보가 담겨 있는 문서를 API 문서 또는 API 스펙(사양, Specification)이라고 한다.
▶️ API 문서를 만드는 방법
🥑 개발자가 수기로 작성 ➡️ 비효율적!, 기능을 빠뜨릴 수 있음!
🥑 애플리케이션 빌드를 통해 API 문서를 자동으로 생성
➤ Spring Rest Docs vs Swagger
▶️ Swagger의 API 문서화 방식
🍋 Swagger는 API 문서 자동화 오픈 소스이다.
🍋Java 기반의 애플리케이션에서는 전통적으로 Swagger를 많이 사용해왔다.
🍋많은 양의 애너테이션이 추가된다. ➡️ 가독성이 떨어짐
🍋코드가 길어지면 그만큼 API 문서화를 위한 코드도 길어진다. ➡️ 유지보수성이 떨어짐
🍋Controller 뿐만 아니라 DTO 클래스에도 애너테이션을 일일이 추가해주어야 한다.
🍋Swagger 기반의 API 문서는 [Excute] 라는 버튼하나로 Controller에 요청을 전송할 수 있다. ➡️ API 요청 툴로서 기능을 사용할 수 있다.
▶️ Spring Rest Docs의 API 문서화 방식
🍑 애플리케이션 기능 구현과 관련된 코드에 API 문서 생성을 위한 애너테이션과 같은 정보를 추가하지 않는다.
🍑Controller 테스트 클래스에 API 문서화를 위한 정보가 추가된다.
🍑테스트 케이스의 실행결과가 “passed”로 만들지 않으면 API 문서 생성이 완료되지 않는다. ➡️ API 스펙 정보와 API 문서 정보의 불일치로 생길 수 있는 문제 방지
🍑테스트 케이스에서 전송하는 API 문서 정보와 Controller에서 구현한 request body, response body, query parameter 등의 정보가 하나라도 일치하지 않으면 테스트 케이스 실행 결과가 “failed” 되면서 API 문서가 정상적으로 생성이 되지 않는다.
🍑Swagger 처럼 API 호출할 수 있는 툴의 역할은 하지 못한다.
❑ Spring Rest Docs
➤ Spring Rest Docs 란?
Rest API 문서를 자동으로 생성해주는 Spring 하위 프로젝트이다.
➤ Spring Rest Docs 의 API 문서 생성 흐름
1. 테스트 코드 작성
🍒 슬라이스 테스트 코드 작성
🍒API 스펙 정보(Request Body, Response Body, Query Parameter 등) 코드 작성
2. test task 실행
🍒작성된 슬라이스 테스트 코드를 실행한다.
🍒하나의 테스트 클래스를 실행시켜도 되지만 일반적으로 Gradle의 build task 중 하나인 test task 를 실행시켜서 API 문서 snippet(스니핏*)을 일괄 생성한다.
🍒테스트 실행 결과가 “passed” 이면 다음 작업을 진행한다.
🍒실행결과가 “failed” 일 경우 문제를 해결하기 위해 테스트 케이스를 수정한 뒤 다시 테스트를 진행한다.
※ 스니핏(snippet)
일반적으로 코드의 조각을 의미한다.
여기선 문서의 일부 조각을 의미한다.
테스트 케이스 하나당 하나의 스니핏이 생성되며, 여러개의 스니핏을 모아서 하나의 API 문서를 생성할 수 있다.
3. API 문서 스니핏(.adoc 파일) 생성
🍒 API 스펙 정보 코드를 기반으로 API 문서 스니핏이 .adoc 확장자를 가진 파일로 생성된다.
4. API 문서 생성
🍒 생성된 API 문서 스니핏을 모아서 하나의 API 문서로 생성한다.
5. API 문서를 HTML로 변환
🍒 생성된 API 문서를 HTML 파일로 변환한다.
🍒 HTML로 변환된 API 문서는 HTML 파일 자체를 공유해도 되고 URL을 통해 해당 HTML에 접속해서 확인할 수 있다.
🍉 Hamcrest 는 JUnit 기반의 단위 테스트에서 사용할 수 있는 Assertion Framework 이다.
🍉 JUnit에서 Assertion을 위한 다양한 메서드를 지원한다.
▶️ Hamcrest를 사용하는 이유
🍉 Assertion을 위한 매쳐(Matcher)가 자연스러운 문장으로 이어져서 가독성이 향상된다.
🍉 테스트 실패 메세지를 이해하기 쉽다.
🍉 다양한 Matcher를 제공한다.
➤ JUnit Assertion ➡️ Hamcrest Assertion 적용하기
🍇 JUnit Assertion 사용시
public class HelloJUnitTest {
@DisplayName("Hello JUnit Test")
@Test
public void assertionTest() {
String expected = "Hello, World!";
String actual = "Hello, JUnit";
assertEquals(expected, actual);
}
}
콘솔 출력 결과
expected: <Hello, World!> but was: <Hello, JUnit>
Expected :Hello, World!
Actual :Hello, JUnit
🍇 Hamcrest의 Matcher 사용시
public class HelloHamcrestTest {
@DisplayName("Hello Junit Test using hamcrest")
@Test
public void assertionTest1() {
String expected = "Hello, World!";
String actual = "Hello, Hamcrest";
assertThat(actual, is(equalTo(expected)));
}
}
콘솔 출력 결과
Expected: is "Hello, World!"
but: was "Hello, Hamcrest"
Expected :Hello, World!
Actual :Hello, Hamcrest
▶️ assertThat(actual, is(equalTo(expected)));
JUnit의 assertEquals(expected, actual) 와 비슷하지만 더 쉽다.
▶️ JUnit의 Assertion vs Hamcrest Assertion 테스트 결과
Hamcrest의 Matcher를 사용하면 사람이 읽기 편한 자연스러운 Assertion 문장을 구성할 수 있다. ➡️ 가독성이 많이 높아진다.
-> TransactionDriverControlImpl (물리적인 트랜잭션을 제어하기 위한 로컬 트랜잭션 드라이버 구현 객체) 를 얻은 후 구현 메서드인 commit()을 다시 호출한다.
-> TransactionDriverControlImpl 에서 JDBC Connection의 액세스 방법을 제공하는 JdbcResourceTransaction의 구현객체인 AbstractLogicalConnectionImplementor 의 commit()을 호출한다. (여기까지가 Hibernate ORM에서의 영역)
-> 물리적인 JDBC Connection을 통해 데이터베이스와 인터랙션하기 위해서 JDBC API의 구현체인 JdbcConnection 영역으로 이동한다.
-> (여기부터 JDBC API의 구현체인 H2의 영역) 데이터베이스에 commit 명령을 준비(prepareCommand)한 후, 명령을 실행(executeUpdate)한다.
-> Command 클래스에서 메서드 실행된다.
데이터베이스에 commit 명령을 전달하기 위해 commitIfNonTransactional 메서드 호출한다.
commitIfNonTransactional() 메서드 내부에서 auto commit 여부를 체크한 후
데이터베이스 세션에 해당하는 Session 객체를 통해 commit 명령을 수행한다.
만약에 commitIfNonTransactional() 수행 과정에서 예외가 발생하면 rollback을 수행한다.