본문 바로가기
📨 Web/- HTTP

HATEOAS를 모르면 당신이 알고 있는 REST API는 REST API가 아니라고 장담할게요.

by Wonit 2021. 3. 24.

이 글은 그런 REST API 로 괜찮은가? 의 이응준 개발자님의 발표 자료에 여러 부분을 차용하였습니다.


REST API란 무엇일까

 

난 지금까지 REST API에 대해서 상당 부분을 오해하고 있었다.

 

REST API라고 함은 GET, POST, PUT, PATCH, DELETE등과 같은 HTTP 메서드를 URI를 통해서 잘 이용하는 것, 이를테면 정보 조회나 수정은 /user 이라는 하나의 URI에 다른 메서드를 이용, 이라고 알고 있었다.

 

하지만 그건 REST API의 아주 작은 일부분 중 하나였다.

 

더욱 신기한건 실력 좋은 많은 개발자들이 참여한 금융 결제원의 오픈 뱅킹 API에서도 REST API를 제대로 사용하지 않고 있다.

 

사실 REST API라고 하면 안되지만 REST API라고 하며 사용하고 있다.

 

 

해당 API는 입금 이체를 요청하는 Open API 인데, 응답 메시지를 확인해보자.

 

이건 REST API가 아니다.

 

왜 REST API가 아닌지는 아래에서 이야기를 하겠지만, REST API를 만든 로이 필딩은 이런 방식으로 API를 사용하며 REST 라고 할 것이라면 REST API라고 하지 말던가 HTTP API라고 명명해줄 것을 당부했었다고 한다.

 

여러분은 이 Response Message가 왜 REST API가 아닌지 글을 읽기 전 까지는 판단할 수 없었다.

 

(내가 그런 REST API 로 괜찮은가? 를 보지 않았을 때 처럼)

 

진정한 REST API가 무엇인지 볼 수 있는 식견을 여러분들에게 나눌테니 글을 다 읽고 진정한 REST API가 무엇인지 확인할 수 있는 기회가 되었으면 좋겠다.

REST API

잘 알다 싶이 API는 Application Programming Interface 란 뜻으로, 프로그래밍 내부에 대해서 몰라도 API 접점만 알고 있다면 우리는 해당 프로그램의 기능을 이용할 수 있게 된다.

 

이와 비슷하게 REST API는

REpresentational State Transfer

라는 뜻으로, 인터넷 상의 시스템 간 상호 운용성을 제공하는 방법 중 하나이다.

이번 글에서는 REST API에 대한 기본을 이야기하지 않으려 한다. HeeJeongKwon 님의 REST API 에서 자세히 확인할 수 있다. 참고하는 것도 좋을 것 같다.

인터넷 상의 시스템이라고 함은

  • client to client
  • client to server
  • server to server

로 해석할 수 있다.

 

이런 상호 시스템 운용성을 제공함으로 우리는 시스템 각각의 독립적인 진화를 달성했다.

 

이런 독립적인 진화는 웹 생태계에서 Client 만 개발하는 프론트 엔드라는 분야를 새롭게 만들었고, 또한 서버만 담당하는 백엔드 라는 분야로 고도화 되었다고 생각한다.

 

이런 REST API가 되기 위해서는 몇 가지 제약 조건들이 필요하다.

REST API의 제약 조건들

아래에 나오는 것들은 REST API의 필수 제약 조건이다.

 

만약 이들 중 하나라도 지켜지지 않는다면 이는 REST API라고 할 수 없다.

 

  • Clinet-Server
    • client는 server 에서 어떤 일을 수행하더라도 내부 작업을 알지 않아도 된다.
    • 이는 플랫폼의 이식성을 향상시킨다.
    • 클라이언트와 서버가 서로 독립적이라 별도의 진화가 가능하다.
  • Stateless
    • 클라이언트에서 서버로 각 요청에는 그 요청에 필요한 모든 정보가 포함되어야 한다.
  • Cache
    • 요청에 대한 응답 내의 데이터에 해당 요청은 캐시가 가능한지 불가능 한지 명시해야 한다.
    • 보통 HTTP Header에 cache-control 헤더를 이용한다.
  • Uniform Interface
    • 아래에서 더 자세한 설명을 하겠다.
    • 보통 많은 REST API라는 것들에서 지켜지지 않는다.
  • 등등 2가지가 더 있으나 생략한다.

여러 제약 조건들이 있는데, 사실 HTTP API만 사용하더라도 다른 제약 조건들은 잘 지켜진다고 할 수 있다.

하지만 문제는 바로 Uniform Interface에서 발생한다.

Uniform Interface

Uniform Interface은 URL로 지정된 리소스에 대한 조작을 통일하고 한정된 인터페이스로 수행하는 아키텍쳐 스타일이다.

 

이런 Uniform Interface가 REST API를 구분 짓는 가장 큰 핵심이라고 생각한다.


Uniform Interface 에는 4가지 제약조건들이 존재하는데 하나씩 알아보자.

Uniform Interface의 4가지 제약 조건

  1. Resource-Based
  2. Manipluation Of Resources Through Representations
  3. Self-Descriptive Message
  4. Hypermedia As The Engine of Application State

나는 1번과 2번에만 관심을 가졌었다.

 

1번과 2번을 합쳐서 한 문장으로 하자면

URI로 지정한 리소스를 Http Method를 통해서 표현하고 구분한다!

이다.

 

아래 URL을 봐보자.

https://my-server.com/page?user=guest/menu=login

이 것은 예전에 HTML을 불러오는 방식이었다.

 

page의 파라미터로 guest 인지 user인지 admin 인지 구분하고 menu 파라미터를 통해서 어떤 메뉴인지 불러오는 방식이다.

 

하지만 REST API를 적용한다면

HTTP Method : get
https://my-server.com/user/login

를 통해서 간결하게 표현할 수 있다.


이렇게 된다면 짧은 URI를 통해서 여러 형태의 표현이 가능해지게 된다.


아래의 URL은 login 이라는 URI로 POST 요청을 보낸다는 뜻이 되는데, 이 것을 미루어 보았을 때 사용자 정보가 POST 요청이 되는 것이니 로그인을 할 것이라고 짐작 가능해지는 것 처럼 말이다.

 

HTTP Method : POST
https://my-server.com/user/login

진짜 문제인 3번과 4번을 알아보자.

이제 왜 제일 앞에서 나온 금융결제원의 오픈 API가 REST API가 아닌지 알 차례이다.

Self-Descriptive Message

말 그대로 메시지 스스로가 자신에 대한 설명을 할 수 있어야 한다.


즉, API 문서가 REST API 응답 본문에 존재해야 한다는 것이다.

 

물론 API 문서 전체를 응답에 넣는 것은 불가능 한다.

 

적어도 API 문서가 어디 있는지는 알려줘야 한다.

 

이를 달성하기 위한 방법은 아래에서 알아보도록 하자.

이렇게 되면 뭐가 좋을까?

 

이렇게 된다면 서버가 변해서 Response Data가 변경되었다고 가정하자.

 

그럼 클라이언트에서는 해당 API 문서를 통해서 어떤 데이터가 바뀌었는지 알 수 있게 된다.

Hypermedia As The Engine of Application State

이하 이것을 HATEOAS 라고 부른다.


HATEOAS가 말 하고자 하는 것은 한 줄로 표현하면 다음과 같다.

Hypermedia (링크)를 통해서 애플리케이션의 상태 전이가 가능해야 한다.

또한 Hypermedia (링크)에 자기 자신에 대해한 정보가 담겨야 한다.

상태 전이? 무슨 말일까?

 

예를 들어서 게시 글을 조회하는 URI가 있다고 가정 해보자.

GET https://my-server.com/article

그럼 여기서 해당 글을 조회한 사용자는 다음 행동으로 어떤 행동을 할까?

 

  • 다음 게시물 조회
  • 게시물을 내 피드에 저장
  • 댓글 달기

이런 행동들이 바로 상태 전이가 가능한 것들인데, 이 것들을 응답 본문에 넣어줘야 한다는 소리이다.

 

그런데 어떻게 넣을까?

 

바로 Hypermedia (링크)를 통해서 넣는다.

 

그럼 기존에 응답 메시지는 다음과 같은 형태로 변경된다.

{
  "data": {
    "id": 1000,
    "name": "게시글 1",
    "content": "HAL JSON을 이용한 예시 JSON",
    "self": "http://localhost:8080/api/article/1000", // 현재 api 주소
    "profile": "http://localhost:8080/docs#query-article", // 해당 api의 문서
    "next": "http://localhost:8080/api/article/1001", // 다음 article을 조회하는 URI
    "comment": "http://localhost:8080/api/article/comment", // article의 댓글 달기
    "save": "http://localhost:8080/api/feed/article/1000", // article을 내 피드로 저장
  },
}

이게 바로 Uniform Interface 제약 조건을 만족 시키는 REST API라고 할 수 있다.

하지만!!! 아직 완벽하지는 않다. HAL JSON을 이용하기 전 까지

우선 이렇게 하면 뭐가 좋을지를 생각해보자.

이렇게 되면 뭐가 좋을까?

  1. API 버전을 명세하지 않아도 된다.
  2. 링크 정보를 동적으로 바꿀 수 있다.
  3. 링크를 통해서 상태 전이가 쉽게 가능하다.

위와 같은 방식으로 데이터를 담아 클라이언트에게 보낸다면 클라이언트는 해당 링크를 참조하는 방식으로, JPA에서 객체 그래프 탐색을 하는 것 처럼 API 그래프 탐색이 가능해진다.

 

그럼 링크에 대한 정보가 바뀌더라도 클라이언트에선 일일이 대응하지 않아도 된다.

 

그럼 어떻게 이걸 완벽하게 할까?

 

바로 HAL JSON을 이용한다.

HAL

Hypertext Application Language 으로 JSON, XMl 코드 내의 외부 리소스에 대한 링크를 추가하기 위한 특별한 데이터 타입이다.

 

HAL은 두 개의 타입을 갖는다.

  1. application/hal+json
  2. application/hal+xml

이 HAL 타입을 이용한다면 쉽게 HATEOAS를 달성할 수 있다.

 

HAL 타입에서는 두 가지의 특징만 이용하면 된다.

리소스와 링크

  • 리소스 : 일반적인 data 필드에 해당한다.
  • 링크 : 하이퍼미디어로 보통 _self 필드가 링크 필드가 된다.

그냥 JSON 응답 본문을 한 번 보는게 빠를 것 같다.

{
  "data": { // HAL JSON의 리소스 필드
    "id": 1000,
    "name": "게시글 1",
    "content": "HAL JSON을 이용한 예시 JSON"
  },
  "_links": { // HAL JSON의 링크 필드
    "self": {
      "href": "http://localhost:8080/api/article/1000" // 현재 api 주소
    },
    "profile": {
      "href": "http://localhost:8080/docs#query-article" // 해당 api의 문서
    },
    "next": {
      "href": "http://localhost:8080/api/article/1001" // article 의 다음 api 주소
    },
    "prev": {
      "href": "http://localhost:8080/api/article/999" // article의 이전 api 주소
    }
  }
}

그럼 이제 진정한 REST API를 만족시켰다고 할 수 있다.


다시 앞으로 돌아가서 왜 금융결제원의 API는 REST API가 아닐까?

스스로 답해볼 수 있을 것 같다.

 

댓글8

  • 뽀삐 2021.11.28 22:24

    글 잘 보았습니다. 카카오 develop REST API도 HATEOAS를 지키지 않았네요.
    아직 4단계까지 지원하는 REST API보단 흔히 사용하는 2단계까지의 REST API를 선호하는 것 같습니다.
    답글

    • Favicon of https://wonit.tistory.com BlogIcon Wonit 2022.03.14 22:16 신고

      왜 사용하지 않는지 알것 같네요 ㅎㅎ.. 하지만 어떤 API 에서는 잘 지키고 있고, 어떤 API 는 지키지 않고 있고, trade-off 를 잘 따져 적합한 스펙을 가져가시나봅니다

  • Favicon of https://thalals.tistory.com BlogIcon 민돌v 2022.03.11 01:43 신고

    정말 좋은글이네요 한눈에 이해했습니다 감사합니다!
    답글

  • Favicon of https://katastrophe.tistory.com BlogIcon 김까따 2022.06.18 16:28 신고

    부트 공부를 하다가 뜬금 api 서버개발 파트랑 hateoas 때문에 찾아보다가 뇌정지 왔는데 이해되고 갑니다..!!
    답글

  • Favicon of https://magical52.tistory.com BlogIcon 한량人 2022.06.29 13:47 신고

    rest api에 대해서 저도 처음 이해할 때 "REST API라고 함은 GET, POST, PUT, PATCH, DELETE등과 같은 HTTP 메서드를 URI를 통해서 잘 이용하는 것, 이를테면 정보 조회나 수정은 /user 이라는 하나의 URI에 다른 메서드를 이용, 이라고 알고 있었다." 라는 개념으로 알고 있었는데 이글을 통해서 좀더 rest api에 대해서 알게되었습니다. 감사합니다
    답글