[스프링 기반 REST API 개발] 01. REST API 및 프로젝트 소개

hh·2023년 10월 19일
0

Spring

목록 보기
2/6
post-thumbnail

01. REST API 및 프로젝트 소개

먼저 APIApplication Programming Interface의 약어로 자바의 인터페이스나 웹으로 접근할 수 있는 웹 API인 REST API 등 여러 형태의 API가 존재한다.

그렇다면 REST API 란 무엇일까?

REST API

REST API 에 대해 알아보기 전에 먼저 REST 의 개념에 대해서 알아보자.

REST 는 로이 필딩(Roy Fielding)의 박사 논문에서 처음 등장한 말로, REpresentational State Transfer의 약자이다.

논문은 '어떻게 하면 인터넷 상의 서로 다른 시스템 간의 독립적인 진화를 보장할 수 있을까?'에 대한 내용이 담겨 있고, web을 깨트리지 않으면서 HTTP를 진화시키는 방법으로 네트워크 소프트웨어 아키텍처인 REST 가 제안된다.

따라서 REST 란 인터넷 상의 시스템 간의 상호 운용성 (interoperability)을 제공하는 방법 중 하나로, 시스템 제각각의 독립적인 진화를 보장하기 위한 방법을 의미한다.

이때 REST 아키텍처 스타일을 따르는 API가 바로 REST API 이다.


다음은 REST API 라고 부를 수 있으려면 따라야 하는 아키텍처 스타일이다.

REST 아키텍처 스타일

  • Client-Server
  • Stateless
  • Cache
  • Uniform Interface ⛔️
  • Layered System
  • Code-On-Demand (optional)

여기서 Uniform Interface 를 제외한 나머지 아키텍처 스타일은 HTTP를 사용하면 쉽게 달성할 수 있기 때문에 Uniform Interface 에 대해서 좀 더 자세히 살펴보자.


다음은 Uniform Interface 에 대한 4가지 가이드이다.

Uniform Interface

  1. Identification of resources
  2. manipulation of resources through representations
  3. self-descriptive message ⛔️
  4. HATEOAS
    (hypermedia as the engine of application state) ⛔️

오늘날 REST API 는 3번, 4번 가이드를 따르지 않기 때문에 인터넷 상의 시스템 제각각의 독립적인 진화가 보장되지 않는다.

두 문제를 조금 더 자세히 살펴보면 다음과 같다.

  • Self-descriptive message
    ▪︎ 메시지 스스로 메시지에 대한 설명이 가능해야 한다.
    ▪︎ 서버가 변해서 메시지가 변해도 클라이언트는 그 메시지를 보고 해석이 가능하다.
    → 서버가 메시지를 바꾸더라도 클라이언트는 메시지를 해석할 수 있는 정보가 메시지 안에 담겨 있기 때문에 바뀐 메시지에 대응이 가능하다.
    ▪︎ 확장 가능한 커뮤니케이션
  • HATEOAS
    ▪︎ 응답에 애플리케이션의 상태 변화가 가능한 하이퍼미디어 정보가 들어 있어야 한다.
    → 하이퍼미디어(링크)를 통해 애플리케이션 상태 변화가 가능해야 한다.
    ▪︎ Versioning 할 필요 없이 링크 정보를 동적으로 바꿀 수 있다.

즉, Self-descriptive messageHATEOAS 는 서로 간에 어떻게 소통을 할지에 대한 이야기이다.


앞서 이야기한 두 가지 문제를 해결하기 위한 방법은 무엇일까?

먼저, Self-descriptive message 는 다음 두 가지 방법으로 해결할 수 있다.

  1. 미디어 타입을 정의하여 IANA 에 등록하고, 그 미디어 타입을 리소스 리턴할 때 Content-Type 으로 사용한다.
  2. profile 링크 헤더를 추가한다.
    (브라우저들이 아직 스팩 지원을 잘 안 하기 때문에 대안으로 HAL 의 링크 데이터에 profile 링크를 추가할 수 있다.)

HATEOAS 는 다음 두 가지 방법으로 해결이 가능하다.

  1. 데이터에 링크를 제공한다. (링크는 HAL 을 이용해서 정의한다.) ✅
  2. 링크 헤더나 Location을 제공한다.

본 강의에서는 체크 표시(✅)를 한 HAL 로 링크를 제공하는 방법을 사용해서 REST API를 개발한다.


REST API 예제

REST API가 아닌 것

다음은 네이버 CFR API 레퍼런스에서 확인할 수 있는 응답 예시이다.

먼저, 응답만 보고도 그 응답에 대한 해석이 불가능하기 때문에 REST API라고 부를 수 없다. 이때 응답 본문에 응답을 해석할 수 있는 정보가 담겨있는 문서의 링크를 걸면 Self-descriptive message 는 만족하게 되지만, HATEOAS 여전히 만족하지 못한다.

또한, 이 응답 이외에 다음 애플리케이션 상태로 전이할 수 있는 링크 정보가 없기 때문에 이 API는 REST API라고 부를 수 없다.

REST API인 것

다음은 GitHub Docs에서 확인할 수 있는 issue에 관한 API이다.


먼저, 헤더를 다 정의해 두고, 미디어 타입 정보를 IANA에 등록했다. 응답에 대한 미디어 타입을 정의하고, 그 미디어 타입에다가 각각의 응답에 들어올 메시지를 정의해 두어 Self-descriptive message 를 만족한다.

또한, 메시지 나름대로 응답을 받은 후 다음 상태로 얼마든지 전이할 수 있는 URL 정보가 많기 때문HATEOAS를 만족한다.

즉, REST API라고 부를 수 있다.

Event REST API

실습 예제로 이벤트 등록, 조회, 및 수정을 위한 4가지 API를 개발하고자 한다.

1️⃣   GET /api/events

  • 이벤트 목록 조회 REST API로, 로그인 안 한 상태와 로그인 한 상태에 보여지는 정보가 다르다.

  • 로그인을 안 한 상태에 응답에 보여줘야 할 데이터
    1) 이벤트 목록
    2) 링크(하이퍼미디어)
         ▪︎ self : 현재 본인이 요청한 URL
         ▪︎ profile : 이벤트 목록 조회 API 문서로 링크 (Self-descriptive message 충족)
         ▪︎ get-an-event : 이벤트 하나 조회하는 API 링크
         ▪︎ next : 다음 페이지 (optional)
         ▪︎ prev : 이전 페이지 (optional)
    - 문서는 스프링 Rest Docs로 만들 예정
    - 링크 정보는 스프링 HATEOAS로 만들 예정

  • 로그인을 한 상태에 응답에 보여줘야 할 데이터
    1) 이벤트 목록
    2) 링크(하이퍼미디어)
         ▪︎ self : 현재 본인이 요청한 URL
         ▪︎ profile : 이벤트 목록 조회 API 문서로 링크 (Self-descriptive message 충족)
         ▪︎ get-an-event : 이벤트 하나 조회하는 API 링크
         ▪︎ create-new-event : 이벤트를 생성할 수 있는 API 링크
         ▪︎ next : 다음 페이지 (optional)
         ▪︎ prev : 이전 페이지 (optional)
    - 로그인을 한 상태는 Bearer 헤더에 유효한 AccessToken이 들어 있는 경우이기 때문에 stateless 하다! (로그인 여부 판단 및 세션 정보 유지 X)

2️⃣   POST /api/events

  • 이벤트 생성

3️⃣   GET /api/events/{id}

  • 아이디에 해당하는 이벤트 하나 조회

4️⃣   PUT /api/events/{id}

  • 아이디에 해당하는 이벤트 수정

Postman & Restlet

Postman 또는 Restlet 을 이용해서 개발한 Event API를 사용해 볼 수 있다.

시나리오는 다음과 같다.

  1. (토근 없이) 이벤트 목록 조회
    GET http://localhost:8080/api/events
    • access token을 가지고 접근하지 않았기 때문에 create 안 보임
  2. access token 발급 받기 (사용자 A 로그인)
    POST http://localhost:8080/oauth/token
  3. (유효한 A 토큰 가지고) 이벤트 목록 조회
    GET http://localhost:8080/api/events
    • create event 보임
  4. (유효한 A 토큰 가지고) 이벤트 만들기
    POST http://localhost:8080/api/events
  5. (토큰 없이) 이벤트 조회
    GET http://localhost:8080/api/events/3
    • update 링크 안 보임
  6. (유효한 A 토큰 가지고) 이벤트 조회
    GET http://localhost:8080/api/events/3
    • update 링크 보임
  7. access token 발급 받기 (사용자 B 로그인)
    POST http://localhost:8080/oauth/token
  8. (유효한 B 토큰 가지고) 이벤트 조회
    GET http://localhost:8080/api/events/3
    • update 안 보임

스프링 부트 Project 만들기

자바 버전 11로 IntelliJ에서 프로젝트를 생성한다.

그리고 다음 7가지 의존성을 추가해야 한다.

  • Spring Web
  • Spring Data JPA
  • Spring HATEOAS
  • Spring REST Docs
  • H2 Database
  • PostgreSQL Driver
  • Lombok


프로젝트를 생성했다면, pom.xml에서 의존성 설정이 필요하다.

PostgreSQLH2 Database<scope> 부분을 변경한다.


우선 제대로 실행되는지 테스트해보기 위해서 pom.xml 에서 H2 Database<scope> 를 주석처리한 후 실행시켜 보자!

이때 변경된 사항을 반영하기 위해 노란색으로 표시한 'Load Maven Changes'를 클릭해야 한다.

애플리케이션을 실행시킨 후, http://localhost:8080 에 접속하면 아래와 같이 정상적으로 실행됨을 확인할 수 있다!

이벤트 도메인 구현

본격적으로 Event 생성 API를 구현하기 전에 Event 도메인을 구현해 보자.

먼저 events 라는 이름의 패키지를 생성하고,Event 클래스를 생성한다.

import java.time.LocalDateTime;

public class Event {

    private String name;
    private String description;
    private LocalDateTime beginEnrollmentDateTime; // 등록 시작 일시
    private LocalDateTime closeEnrollmentDateTime; // 등록 종료 일시
    private LocalDateTime beginEventDateTime; // 이벤트 시작 일시
    private LocalDateTime endEventDateTime; // 이벤트 종료 일시

    private String location; // (optional) 위치 : 이게 없으면 온라인 모임
    private int basePrice; // (optional)
    private int maxPrice; // (optional)
    private int limitOfEnrollment;

    private Integer id; // 식별자
    private boolean offline; // 오프라인 여부
    private boolean free; // 이 모임이 유료인지 무료인지
    private EventStatus eventStatus;
}

💡 밑줄 치고 [opt + enter] 누르면 자동 생성

EventStatus는 enum 타입 이기 때문에 같은 패키지 안에 EventStatus.java 파일을 생성하여 다음과 같이 작성한다.

package GDSC.ewha.springrestapi.events;

public enum EventStatus {

    DRAFT, PUBLISHED, BEGAN_ENROLLMENT;

}

이번에는 test class 를 생성해 보자.

💡 [cmd + shift + T] : test class 생성 및 test로 이동
💡 [ctrl + shift + R] : 테스트 메소드 실행

// EventTest.java
package GDSC.ewha.springrestapi.events;

import org.junit.Test;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

public class EventTest {
    @Test
    public void builder() { // 빌더가 있는지 확인
        Event event = Event.builder()
                .name("Inflearn Spring REST API")
                .description("REST API development with Spring")
                .build();
        assertThat(event).isNotNull();
    }

    @Test
    public void javaBean() {
        // Given
        String name = "Event";
        String description = "Spring";

        // When
        Event event = new Event();
        event.setName(name);
        event.setDescription(description);

        // Then
        assertThat(event.getName()).isEqualTo(name);
        assertThat(event.getDescription()).isEqualTo(description);
    }
}

여기서 builder() 같은 메소드는 Lombok Annotation을 사용하면 메소드를 따로 생성하지 않아도 클래스를 컴파일할 때 추가적으로 코드가 생성된다.

Lombok 이란 멤버 변수에 값을 설정하는 생성자 등을 자동으로 생성해 주는 라이브러리이다!


Java Bean 스펙은 기본 생성자가 있어야 하고, 모든 필드에 대해서 Getter와 Setter가 있어야 한다.

이걸 Lombok Annotation 을 써서 구현해 보자.
Event 클래스 위에 다음과 같은 어노테이션을 붙여주기만 하면 된다.

import lombok.*;

@Builder @AllArgsConstructor @NoArgsConstructor
@Getter @Setter @EqualsAndHashCode(of = "id")

각 어노테이션을 자세히 살펴보면 다음과 같다.

  • @Getter, @Setter : Getter와 Setter를 자동으로 생성함
  • @EqualsAndHashCode(of = "id") : 구현할 때 모든 필드를 기본적으로 다 사용하는데, 이때 엔티티 간에 연관관계가 있어 서로 상호참조하게 된다면 stack overflow가 발생할 수 있기 때문에 id 값만 가지고 equals와 hashcode를 비교하도록 함
  • @Builder : 이름으로 값을 설정하기 때문에 내가 입력하는 값이 무엇인지 알 수 있음
  • @NoArgsConstructor, @AllArgsConstructor: 기본 생성자와 모든 argument를 가지고 있는 생성자를 둘 다 만들기 위해서 사용함

이벤트 비즈니스 로직

Event 생성 API는 다음의 값을 입력으로 받는다.

  1. name : 이벤트 이름
  2. description : 설명
  3. beginEnrollmentDateTime : 이벤트 등록 시작 시간
  4. closeEnrollmentDateTime : 이벤트 등록 종료 시간
  5. beginEventDateTime : 이벤트 시작 일시
  6. endEventDateTime : 이벤트 종료 일시
  7. location : optional, 이벤트 위치 (장소가 없으면 온라인 이벤트)
  8. basePrice : optional, 모임 등록비
  9. maxPrice : optional, 모임 등록비
  10. limitOfEnrollment : 최대 참가 인원

optional한 값이지만 basePricemaxPrice 의 경우의 수와 각각의 로직은 다음과 같다.

basePricemaxPrice
0100선착순 등록
00무료
1000무제한 경매 (높은 금액 낸 사람이 등록)
100200항상 basePrice < maxPrice, 제한가 선착순 등록

결과값은 다음과 같다.

  • id : 이벤트 id (이벤트를 고유하게 식별할 수 있는 식별자)
  • name
  • ...
  • eventStatus : 기본적으로 DRAFT 상태 (다른 유저들한테 이 이벤트에 대한 정보가 보이지는 않는 상태, 본인에게만 보임)
  • offline : location이 있으면 offline
  • free
  • _links : 여러 HATEOAS 정보들
       ▪︎  profile : 메시지 자체에 대한 정보를 담고 있는 문서의 링크 (self-descriptive message 충족)
       ▪︎  self
       ▪︎  publish : 생성한 이벤트를 조회할 수 있는 링크
       ▪︎  ...

IntelliJ 명령어 정리 (macOS)

💡 [cmd + shift + T] : test class 생성 및 test로 이동
💡 [ctrl + shift + R] : 테스트 메소드 실행
💡 [ctrl + opt + O] : optimize import, 사용하지 않는 import문 제거
💡 [alt + cmd + V] : 리팩토링


인프런 백기선님의 스프링 기반 REST API 개발을 기반으로 작성했습니다.

profile
EWHA CSE 21

0개의 댓글