Notepad

스프링 부트 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 문서화 포털 구축

참고

profile

Notepad

@Apio

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!