본 프로젝트 시리즈는 백기선님의 스프링 기반 REST API 개발을 참고하여 만든 과정이며,
본문은 네이버 컨퍼런스 d2에서 이응준님이 발표하신 영상을 보고 정리한 글이다.


시작

대학원생인 Roy T. Fielding은 박사 논문 발표로부터 시작됨
REpresentational State Transfer Application Programming Interface

첫 API가 등장하고 기존에 사용하던 SOAPREST의 비교가 시작되었다.

구분 SOAP REST
Complexity 복잡 단순
Rule 규칙 많음 규칙 적음
Usability 어려움 쉬움

점차 REST API 사용량이 증가함


그런데

2008년 나온 CMIS가 REST 바인딩을 지원했지만…

“No REST in CMIS”   -Roy T. Fielding-


2016년에 Microsoft는 아래와 같은 REST API Guidelines를 공개했지만…

  • uri는 https://{serviceRoot}/{collection}/{id} 형식이어야 한다.
  • GET, PUT, DELETE, POST, HEAD, PATCH, OPTIONS를 지원해야한다
  • API 버저닝은 Major.minor로 하고 uri에 버전 정보를 포함시킨다
  • 등등 …

“이건 그냥 HTTP API”   -Roy T. Fielding-


또 이런 말들을 하곤 했다.

“REST APIs must be hypertext-driven”
“REST API를 위한 최고의 버저닝 전략은 버저닝을 안 하는 것”


그래서 REST API가 뭘까

REST API는 아래와 같은 제약조건의 집합이라고 말하고 있다.

  • client-server
  • stateless
  • cache
  • uniform interface
  • layered system
  • code-on-demand (optional)

나머지는 대체로 만족하는데, uniform interface 중 2가지를 놓치는 경우가 많다고 한다.

  • Identification of resources[1] (잘 지켜짐)
  • manipulation of resources through representations[2] (잘 지켜짐)
  • self-descriptive message
  • hypermedia as the engine of application state (HATEOAS)

self-descriptive message

메시지는 스스로를 설명해야한다

1. HTTP 요청 메세지에는 목적지가 있어야 한다

GET / HTTP/1.1

이 HTTP를 본다면 루트를 얻어오는 GET method 구나 할 수 있지만, 이건 추측이지 self-descriptive하지는 않다.
따라서 아래처럼 추가해줘야한다.

GET / HTTP/1.1
Host: www.example.org

2. HTTP 응답 메세지 헤더에는 어떤 문법으로 작성된지 표기해야한다

HTTP/1.1 200 OK

[ { "op": "remove", "path": "/a/b/c"} ]

어떤 문법으로 작성된지 알 수 없기때문에 ,self-descriptive하지는 않다.
따라서 Content-Type[3]가 반드시 들어가야 한다.

HTTP/1.1 200 OK
Content-Type: application/json-patch+json

[ { "op": "remove", "path": "/a/b/c"} ]

확실히 해야할 점은, media type을 표기하여 꼭 이 응답 메세지만으로도 어떤 메세지인지 설명이 가능하여야 한다는 점이다.
여기서는 json patch로 해당 media type을 알리고 있다.


HATEOAS

애플리케이션의 상태는 hyperlink를 이용해 전이되어야 한다.

1. html 응답

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

<html>
<head></head>
<body><a href="/test">test</a></body>
</html>

이처럼 a태그를 통해 hyperlink가 나와있고, 그 다음 상태로 전이가 가능하기 때문에 HATEOAS를 만족함


2. json 응답

한 게시물을 json으로 표현 했을 때

HTTP/1.1 200 OK
Content-Type: application/json
Link: </articles/1>; rel: "previous",
</articles/3>; rel: "next",

{
"title": "The first Title",
"contetns": "abcde..."
}

이처럼 HTTP header 중 표준으로 나와 있는 Link를 활용했고, 그 다음 상태로 전이가 가능하기 때문에 HATEOAS를 만족함


왜 uniform interface 일까?

독립적인 진화

  • 서버와 클라이언트가 각각 독립적으로 진화한다.
  • 서버의 기능이 변경되어도 클라이언트 업데이트를 할 필요가 없다.

제약조건을 몇가지 빼먹은 REST API는 REST API인가?

지금까지 REST API를 지키지 않은 API들이 굉장히 많다.
그렇다면 몇가지 빼먹어도 괜찮지 않을까?

그렇지 않다.

Roy T. Fielding이 REST API를 아래와같이 지켜야된다고 말을 해버렸다.

하이퍼텍스트를 포함한 self-descriptive한 메세지의 uniform interface를 통해 리소스에 접근하는 API 다.


원격 API가 꼭 REST API이어야 하는가?

Roy T. Fielding은 REST API가 아니여도 상관 없다고 말했다. 그리고 또 아래와같이 말했다.

시스템 전체를 통제 할 수 있다고 생각하거나, 진화에 관심이 없다면 REST를 따지는것에 신경쓰지마라

  • 본인이 클라이언트, 서버 모두를 컨트롤할 수 있고 통제가 가능하거나
  • 오랜 시간에 걸친 시스템을 설계히는 것에 관심이 없거나

REST API에 도전해보자!

먼저 REST가 잘 지켜지고 있는 HTML 과 잘 안지켜지고 있는 JSON 을 비교해보자

1. HTML

GET / HTTP/1.1
Host: www.example.org

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

<html>
<head></head>
<body>
<a href="/todos/1">집에 가기</a>
<a href="/todos/2">회사 가기</a>
</body>
</html>

self-descriptive

  • 응답 메세지의 Content-Type을 보고 media typetext/html임을 확인한다.
  • text/html은 IANA에 등록되어있고, https://w3.org/TR/html 명세에서 해석이 가능하다.
  • 명세에 보면 모든 태그의 해석방법이 구체적으로 나와있기때문에 self-descriptive하다고 말할 수 있다.

HATEOAS

  • a태그를 통해 다음 상태로 전이될 수 있기때문에 HATEOAS 또한 만족한다.

2. JSON

GET / HTTP/1.1
Host: www.example.org

HTTP/1.1 200 OK
Content-Type: application/json

{
{"id": 1, "title": "집에 가기"},
{"id": 2, "title": "회사 가기"},
}

self-descriptive

  • 응답 메세지의 Content-Type을 보고 media typeapplication/json임을 확인한다.
  • application/json은 IANA에 등록되어있고, 링크를 찾아가 명세에서 해석이 가능하다.
  • 명세에 보면 파싱하는 방법이 설명되어 있기때문에 파싱에 성공한다.
  • idtitle이 어떤 의미를 갖는지 알 수 없다. 따라서 self-descriptive를 만족하지 않는다.

HATEOAS

  • 다음 상태로 전이할 수 있는 링크가 없다.

JSON형태 API를 REST API로 고쳐보자!

1. self-descriptive

Media type 정의

  • 미디어 타입을 임의로 정의한다.
  • 미디어 타입 문서를 작성한다. 이 문서에 "id"가 뭐고, "title"이 뭔지 정의한다.
  • IANA에 미디어 타입을 등록한다.
  • 이 메세지를 보는 사람은 내가 IANA에 등록한 미디어 타입 명세를 보고 해석할 수 있다.

단점

  • 번거롭다

Profile 이용

GET / HTTP/1.1
Host: www.example.org
Link: <https://example.org/docs/todos>; rel="profile"

HTTP/1.1 200 OK
Content-Type: application/json

{
{"id": 1, "title": "집에 가기"},
{"id": 2, "title": "회사 가기"},
}
  • id, title이 뭔지 명세를 작성한 페이지를 만든다.
  • Link 헤더에 profile relation으로 해당 명세를 링크한다.

단점

  • 클라이언트가 Link 헤더(RFC 5988) profile(RFC6906)을 이해 해야한다.
  • Content negotiation을 할 수 없다.

2. HATEOAS

data 활용
다양한 방법으로 data에 하이퍼링크를 표현한다.
이미 JSON에 하이퍼링크를 표현하는 많은 미디어타입이 있고, 활용해도 좋다.

{
{"id": 1, "title": "집에 가기", "link": "https://example.org/todos/1"},
{"id": 2, "title": "회사 가기", "link": "https://example.org/todos/2"},
}

or

{
"links": {
"todo": "https://example.org/todos/{id}"
},
"data": [
{"id": 1, "title": "집에 가기"},
{"id": 2, "title": "회사 가기"}
]
}

단점

  • 침투성, 기존 API를 많이 수정해야한다.

HTTP 헤더 활용
Location과 Link 등의 헤더로 표현할 수 있다.

HTTP/1.1 204 No Content
Location: /todos/1
Link: </todos/>; rel="collection"

단점

  • 정의된 relation만 사용한다면 한계가 있을 수 있다.


  1. 리소스가 uri로 식별되어야함 ↩︎

  2. 리소스를 CUD 할 때, HTTP message에 잘 담아서 전송을 하면 됨 ↩︎

  3. Content-Type ↩︎