티스토리 뷰

DEVIEW 2017 세션 중 하나인 그런 REST API로 괜찮은가를 듣고 정리한 내용입니다.

REST API는 무엇인지, REST를 구성하는 6가지의 제약조건 중 오늘날 대부분의 REST API가 만족시키지 못한다는 self-descriptive messages와 HATEOAS란 무엇인지 중심으로 정리했습니다.

REST API?

  • REST 아키텍처 스타일을 따르는 API
    • REST : 분산 하이퍼미디어 시스템(웹)을 위한 아키텍처 스타일
    • 아키텍처 스타일 : 제약조건들의 집합
  • 즉, 제약조건을 모두 만족해야 REST하다고 말할 수 있다.

REST의 구현 원칙을 제대로 지키는 시스템 => RESTful

REST를 구성하는 스타일 (= 제약 조건)

  • client-server
  • stateless
  • cache
  • uniform interface
  • layerd system
  • code on demand (opional)

HTTP만 잘 지켜도 client-server, stateless, cache, layerd system 이 네가지는 만족할 수 있다. code on demand는 서버에서 코드를 클라이언트로 보내서 실행할 수 있어야 하는 javascript를 말한다. 이 조건은 optional이므로 반드시 만족시키지 않아도 된다.

참고 : 이 글에서는 self-descriptive messages와 HATEOAS 설명이 중심이기 때문에 나머지 제약조건에 대한 내용은 생략했다.

uniform interface의 제약조건

  • identification of resources : 리소스가 URI로 식별되면 된다.
  • manipulation of resources through representations : 메시지를 통한 리소스 조작
  • self-descriptive messages : 자기 서술적 메세지
  • hypermedia as the engine of application state(HATEOAS) : 애플리케이션 상태에 대한 엔진으로서의 하이퍼미디어

오늘날 REST API라 불리는 것들 중 대부분이 self-descriptive messagesHATEOAS를 만족시키지 못하고 있다.

self-descriptive messages

메시지 스스로 메시지에 대한 설명이 가능해야 한다.

GET / HTTP/1.1
  • 이 HTTP 요청 메시지는 목적지가 어딘지 모르기 때문에 self-descriptive하지 못하다.
GET / HTTP/1.1
HOST : www.example.org
  • www.example.org라는 목적지를 추가하면 self-descriptive하다.
GET / HTTP/1.1 200 OK
[{"op": "remove", "path" : "/a/b/c"}]
  • 클라이언트가 이 응답을 해석할 때 어떤 문법으로 작성된건지 모른다. => Content-Type 필요
GET / HTTP/1.1 200 OK
Content-Type : application/json
[{"op": "remove", "path" : "/a/b/c"}]
  • 클라이언트가 이 응답이 json 타입인 것을 알게 되었다.
  • 하지만 self-descriptive하다고는 할 수 없다. oppath가 어떤 의미인지 모르기 때문이다.

Content-Type : 엔티티 바디에 포함되는 오브젝트의 미디어 타입을 전달한다.

GET / HTTP/1.1 200 OK
Content-Type : application/json-patch+json
[{"op": "remove", "path" : "/a/b/c"}]
  • application/json-patch+json은 미디어 타입으로 정해져 있는 메시지다.
    • 명세를 찾아가서 메시지를 해석할 수 있기 때문에 self-descriptive하다.

참고 : JSON Patch

HATEOAS

클라이언트에 응답할 때 결과 데이터만 제공해주기보다 URI를 함께 제공해야 한다는 원칙이다.

HTTP/1.1 200 OK
Content-Type : text/html

<html>
<head></head>
<body><a href="/test">test</a></body>
</html>
  • a 태그,하이퍼링크를 통해서 상태 전이가 가능하기 때문에 HATEOAS를 만족한다.
HTTP/1.1 200 OK
Content-Type : application/json
Link : </articles/1>; rel="previous",
       </articles/3>; rel="next";
{
    "title" : "The second article"
    "contens" : "blah blah..."
}
<html>
<head></head>
<body><a href="/test">test</a></body>
</html>
  • 링크 헤더(Link)는 표준으로 문서가 나와있다.
    • 이 메시지를 보는 사람이 온전히 해석해서 이 하이퍼링크를 타고 다른 상태로 전이가 가능하다. 때문에 HATEOAS를 만족한다.

왜 uniform interface?

  • 독립적인 진화를 하기 위해서

독립적 진화

  • 서버와 클라이언트가 각각 독립적으로 진화한다.
  • 서버의 기능이 변경되어도 클라이언트를 업데이트할 필요가 없다.
  • REST를 만들게 된 계기 : "How do I improve HTTP without breaking the web"

uniform interface를 만족시키지 못하면 REST하지 못하다.

self-descriptive messages와 HATEOAS가 독립적 진화에 어떻게 도움이 될까?

self-descriptive messages

  • 확장 가능한 커뮤니케이션
  • 서버나 클라이언트가 변경되더라도 오고 가는 메시지는 언제나 self-descriptive하므로 언제나 해석이 가능하다.

HATEOAS

  • 애플리케이션 상태 전이의 late binding
  • 어디서 어디로 전이가 가능한지 미리 결정되지 않는다. 어떤 상태로 전이가 완료되고 나서야 그 다음 전이될 수 있는 상태가 결정된다.
  • 쉽게 말해서, 링크는 동적으로 변경될 수 있다. 서버 링크가 바뀌면 클라이언트는 바뀐 링크만 보고 따라가기만 하면 된다.

REST API로 만들어보자

self-descriptive

1. Media Type

GET /todos HTTP/1.1
Host : example.org

HTTP/1.1 200 OK
Content-Type : application/vnd.todos+json
Link : <https://example.org/docs/todos>; rel=""profile"

[
    {"id": 1, "title": "회사 가기"}
    {"id": 2, "title": "집에 가기"}
]
  • 미디어 타입 하나 정의한다.
    • ex) application/vnd.todos+json
  • 미디어 타입 문서를 작성한다. 이 문서에 id, title의 의미를 정의한다.
  • IANA에 미디어 타입을 등록한다. 이 때 만든 문서를 미디어 타입의 명세로 등록한다.

단점은 매번 Media Type을 정의해야 한다는 것이다.

2. Profile

GET /todos HTTP/1.1
Host : example.org

HTTP/1.1 200 OK
Content-Type : application/json
Link : <https://example.org/docs/todos>; rel=""profile"

[
    {"id": 1, "title": "회사 가기"}
    {"id": 2, "title": "집에 가기"}
]
  • id, title의 의미를 정의한 명세를 작성한다.
  • Link 헤더에 profile relation으로 해당 명세를 링크한다.
  • 메시지를 보는 사람은 명세를 찾을 수 있으므로 이 문서의 의미를 온전히 해석할 수 있다.

이 방법의 단점은 클라이언트가 Link 헤더(RFC 5988)와 profile(RFC6906)을 이해해야 한다. 그리고 Content negotiation을 할 수 없다.

HATEOAS

1. data

GET /todos HTTP/1.1
Host : example.org

HTTP/1.1 200 OK
Content-Type : application/json
Link : <https://example.org/docs/todos>; rel=""profile"

{
    {
        "link": "https://example.org/todos/1"
        "title": "회사 가기"
    },
    {
        "link": "https://example.org/todos/2"
        "title": "집에 가기"
    }
}
  • data에 다양한 방법으로 하이퍼링크를 표현한다.

단점은 링크를 표현하는 방법을 직접 정의해야 한다는 것이다.

2. JSON으로 하이퍼링크를 표현하는 방법을 정의한 명세를 활용

  • JSON API
  • HAL
  • UBER
  • Siren
  • Collection+json

참고자료

댓글