스프링 부트 API 서버 구축
외부 시스템을 위한 인터페이스인 API 서버 만들기.
학습 내용
JSON 기반 웹 서비스 구축
스프링 REST Docs를 활용한 API 문서화
스프링 부트로 만든 API 포털에서 다양한 API 제공
스프링 HATEOAS를 사용한 하이퍼미디어 활용
API 포털에 하이퍼미디어 링크 추가
HTTP 웹 서비스 구축
가장 단순한 API 서버는 쿼리를 실행하고 결과를 반환함
- 예전에는 XML이나 기술 스택에 따른 바이너리 데이터를 반환해주는 서버가 일반적
- 최근에는 JSON(Java Script Object Notation)을 반환해주는 게 일반적
코드 작성
- API 컨트롤러 정의 ("ApiItemController")
@RestController
@RequiredArgsConstructor
public class ApiItemController {
private final ItemRepository repository;
}- 모든 상품을 반환하는 API ("GET /api/items")
@GetMapping("/api/items")
public Flux<Item> findAll() {
return this.repository.findAll();
}- 한 개의 Item을 조회하는 API ("GET /api/item/{id}")
@GetMapping("/api/items/{id}")
public Mono<Item> findOne(@PathVariable String id) {
return this.repository.findById(id);
}- 새 Item을 생성하는 API ("POST /api/items")
@PostMapping("/api/items")
public Mono<ResponseEntity<?>> addNewItem(@RequestBody Mono<Item> item) {
return item.flatMap(s -> this.repository.save(s))
.map(savedItem -> ResponseEntity.created(URI.create("/api/items/" + savedItem.getId())).body(savedItem));
}- 기존에 존재하는 Item 객체 교체 API ("PUT /api/items/{id}")
@PutMapping("/api/items/{id}")
public Mono<ResponseEntity<?>> updateItem(@RequestBody Mono<Item> item, @PathVariable String id) {
return item.map(content -> new Item(id, content.getName(), content.getDescription(), content.getPrice()))
.flatMap(this.repository::save)
.map(ResponseEntity::ok);
}멱등(idempotent)
- 어떤 연산을 여러 번 적용하더라도 결괏값이 달라지지 않는 성질을 말함
- 멱등한 연산 : GET(자원 조회), PUT(자원 Upsert), DELETE(자원 삭제)
- 멱등하지 않는 연산: POST(새로운 자원 추가), PATCH(자원 부분 변경)
API 포털 생성
웹 서비스를 출시한 후에는 사용자에게 사용법을 알려줘야 함
- 가장 좋은 방법은 API 포털을 만들고 사용자에게 필요한 정보를 제공하는 것
스프링 REST Docs
스프링 REST Dosc을 추가하면 사용자가 직접 사용해볼 수 있는 API 예제를 포함해서 API 문서를 쉽게 만들어 낼 수 있음
아스키 닥터(Asciidoctor) 문서화 도구를 사용해서 세부 내용도 쉽게 문서로 만들 수 있음
- 아스키 닥터: 아스키 독 표준을 루비 언어로 구현한 프로젝트
코드 작성
- API 문서화를 위한 asciidoc 사용 설정 추가
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.3</version>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html</backend>
<doctype>book</doctype>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-asciidoctor</artifactId>
<version>${spring-restdocs.version}</version>
</dependency>
</dependencies>
</plugin>- API 포털 도입부 작성
= 스프링 부트 실전 활용 마스터
웹 서비스를 출시하면 개발자들에게 사용법을 알려줘야 합니다.
스프링 레스트 독 덕분에 테스트 케이스에서 서비스의 모든 상호 작용을 추출하고 읽기 좋은 문서를 자동으로 만들 수 있으며, +
IDE를 통해 아주 쉽게 작업을 수행할 수 있습니다.
다음 요청을 실행하면:- spring-restdocs-webtestclient 의존관계 추가
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-webtestclient</artifactId>
<scope>test</scope>
</dependency>- 자동 생성된 문서를 정적 웹 콘텐츠 디렉터리로 복사
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.outputDirectory}/static/docs</outputDirectory>
<resources>
<resource>
<directory>${project.build.directory}/generated-docs</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>- REST Dosc를 사용하기 위한 테스트 클래스 설정
@WebFluxTest(controllers = ApiItemController.class)
@AutoConfigureRestDocs
class ApiItemControllerDocumentationTest {
@Autowired
private WebTestClient webTestClient;
@MockBean
InventoryService service;
@MockBean
ItemRepository repository;
// ...생략...
}- 문서를 자동으로 생성하는 첫 번째 테스트 케이스 작성
@Test
void findingAllItems() {
when(repository.findAll()).thenReturn(Flux.just(new Item("item-1", "Alf alarm clock", "nothing I really need", 19.99)));
this.webTestClient.get().uri("/api/items")
.exchange()
.expectStatus().isOk()
.expectBody()
.consumeWith(document("findAll", preprocessResponse(prettyPrint())));
}- 새 객체 추가 테스트 및 문서화
@Test
void postNewItem() {
when(repository.save(any())).thenReturn(
Mono.just(new Item("Alf alarm clock", "nothing important", 19.19)));
this.webTestClient.post().uri("/api/items")
.bodyValue(new Item("Alf alarm clock", "nothing important", 19.19))
.exchange()
.expectStatus().isCreated()
.expectBody()
.consumeWith(document("post-new-item", preprocessResponse(prettyPrint())));
}- API 포털 문서 생성
./mvnw clean prepare-package- 생성된 문서 조각 확인
├─findAll
│ curl-request.adoc
│ http-request.adoc
│ http-response.adoc
│ httpie-request.adoc
│ request-body.adoc
│ response-body.adoc
│
└─post-new-item
curl-request.adoc
http-request.adoc
http-response.adoc
httpie-request.adoc
request-body.adoc
response-body.adoc- 문서 조각을 모아서 구성한 API 문서
스프링 부트 실전 활용 마스터
웹 서비스를 출시하면 개발자들에게 사용법을 알려줘야 합니다.
스프링 레스트 독 덕분에 테스트 케이스에서 서비스의 모든 상호 작용을 추출하고 읽기 좋은 문서를 자동으로 만들 수 있으며, +
IDE를 통해 아주 쉽게 작업을 수행할 수 있습니다.
다음 요청을 실행하면:
include::{snippets}/findAll/curl-request.adoc[]
`ApiItemController`는 다음과 같은 응답을 반환합니다.
include::{snippets}/findAll/response-body.adoc[]
HTTPie를 사용하시나요? 다음 명령을 실행해보세요.
include::{snippets}/findAll/httpie-request.adoc[]
동일한 응답 본문이 반환됩니다. curl과 HTTPie 중 좋아하는 것을 사용하시면 됩니다.스프링 REST Dosc 제공 전처리기
prettyPrint()
- 요청이나 응답 메시지에 줄 바꿈, 들여 쓰기 등 적용
removeHeaders(String... headerNames)
- 표시하지 않을 헤더 이름 지정
- 스프링의 HttpHeaders 유틸 클래스에 표준 헤더 이름이 상수로 등록돼 있으므로 함께 사용하면 편리함
removeMatchingHeaders(String... headerNamePatterns)
- 표시하지 않을 헤더를 정규 표현식으로 지정
maskLinks()
- href 링크 항목 내용을 '...'로 대체
- HAL(Hypertext Application Language)을 적용할 때 API 문서에 하드 코딩된 URI 대신 링크를 통해 API 사용을 독려하기 위해 URI 링크를 '...'로 대체한다.
maskLinks(String mask)
- href 항목을 대체할 문자열 명시
replacePattern(Pattern pattern, string replacement)
- 정규 표현식에 매칭 되는 문자열을 주어진 문자열로 대체
modifyParameters()
- 평문형 API(fluent API)를 사용해서 요청 파라미터 추가, 변경, 제거
modifyUris()
- 평문형 API를 사용해서 로컬 환경에서 테스트할 때 API 문서에 표시되는 URI 지정
custom 전처리기
OperationPreprocessor인터페이스를 구현해서 전처리기를 직접 만들어서 사용할 수 있음OperationPreprocessorAdapter추상 클래스를 상속해서 필요한 부분만 오버라이드 하는 것이 더 편함
API 진화 반영
API는 시간이 지남에 따라 진화함
장자끄 뒤브레의 세 가지 API 변경 유형
- 매듭(knot) : 모든 API 사용자가 단 하나의 버전에 묶여 있다. API가 변경되면 모든 사용자도 함께 변경을 반영해야 하므로 엄청난 여파를 몰고 온다.
- 점대점(point-to-point) : 사용자마다 별도의 API 서버를 통해 API를 제공한다. 사용자별로 적절한 시점에 API를 변경할 수 있다.
- 호환성 버저닝(compatible versioning) : 모든 사용자가 호환 가능한 하나의 API 서비스 버전을 사용한다.
뒤브레의 연구 결과 그래프
매듭 방식
- 비용이 급속도로 증가함
- 변경의 양이 적더라도 영향이 큼
- API 사용자는 변경된 API에 포함된 기능을 사용하든 사용하지 않든 무조건 강제적으로 업그레이드해야 함
점대점 방식
- 매듭 방식과 비교하면 상당히 많은 비용을 줄일 수 있지만 여전히 적지 않은 비용이 소요되고 증가 속도도 가파름
- API 사용자에게 미치는 영향은 줄어들지만, 여러 버전을 유지 관리해야 하므로 서버 개발팀의 비용이 많이 늘어남
호환성 방식
- 비용 규모도 적고 증가 속도도 완만함
- 동일한 API에 대해 기존 사용자도 그대로 사용할 수 있고 새로 추가된 기능도 사용할 수 있게 해 줌
- API 사용자의 상황에 맞춰 가장 적합할 때 업그레이드하면 됨
- 여러 버전을 관리할 필요가 없으므로 부담이 적음
하위 호환성을 유지하는 호환성 방식 서비스 만드는 방법
- 하이퍼미디어를 적용해서 만들 수 있음
하이퍼미디어 기반 웹 서비스 구축
하이퍼미디어의 가치
API에 행동 유도성 추가
정리
- 원격 접근을 통해 시스템을 변경하는 API 생성
- 스프링 REST Dosc을 사용해서 API 문서화 포털을 만드는 테스트 작성
- HAL 기반 링크 정보를 포함하는 하이퍼미디어 제공 컨트롤러 작성
- 링크 정보 및 관계 세부 정보를 추가해서 문서화 테스트 보완
- 행동 유도성 소개 및 HAL-FORMS 형식 데이터와 데이터 템플릿 제공
- 아스키 독 문서 조각을 합쳐서 API 문서화 포털 구축
참고
- "스프링 부트 실전 활용 마스터", 그렉 턴키스트 저(원서: "Hacking with Spring Boot 2.3 - Reactive Edition")
- 소스 코드
'dev > Spring' 카테고리의 다른 글
| [정리] 스프링 부트 실전 활용 마스터(8) (0) | 2022.08.04 |
|---|---|
| [정리] 스프링 부트 실전 활용 마스터(7) (0) | 2022.07.14 |
| [정리] 스프링 부트 실전 활용 마스터(5) (0) | 2022.06.23 |
| [정리] 스프링 부트 실전 활용 마스터(4) (0) | 2022.06.16 |
| [정리] 스프링 부트 실전 활용 마스터(3) (0) | 2022.05.26 |