그런
REST API로
괜찮은가

발표자 소개

  • 이응준입니다.
  • Viva Republica 라는 스타트업에서
  •      Toss 라는 금융서비스를 만들고 있습니다.

발표자 소개

  • 이응준입니다.
  • Viva Republica 라는 스타트업에서
  •      Toss 라는 금융서비스를 만들고 있습니다.

REST

REpresentational
State

Transfer

REST

WEB (1991)

Q: 어떻게 인터넷에서 정보를 공유할 것인가?

A: 정보들을 하이퍼텍스트로 연결한다.

표현 형식: HTML

식별자: URI

전송 방법: HTTP

 

HTTP/1.0 (1994-1996)

HTTP/1.0 (1994-1996)

해결책: HTTP Object Model

REST (1998)

REST (2000)

한편...

API

XML-RPC (1998)

by Microsoft
-> SOAP

Salesforce API (2000. 2)

SOAP

<?xml version="1.0" encoding="utf-8"?>   
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:urn="urn:enterprise.soap.sforce.com">
  <soapenv:Header>
     <urn:SessionHeader>
        <urn:sessionId><b>QwWsHJyTPW.1pd0_jXlNKOSU</b></urn:sessionId>
     </urn:SessionHeader>
  </soapenv:Header>
  <soapenv:Body>
        <urn:fieldList><b>Id, Name, Website</b></urn:fieldList>
        <urn:sObjectType><b>Account</b></urn:sObjectType>
        <!--Zero or more repetitions:-->
        <urn:ids><b>001D000000HS2Su</b></urn:ids>
        <urn:ids><b>001D000000HRzKD</b></urn:ids>
     </urn:retrieve>
  </soapenv:Body>
</soapenv:Envelope>
<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="fail">
	<err code="[error-code]" msg="[error-message]" />
</rsp>
<?xml version="1.0" encoding="utf-8" ?>
<s:Envelope xmlns:s="http://www.w3.org/2001/06/soap-envelope">
	<s:Body>
		<s:Fault>
			<faultcode>flickr.error.[error-code]</faultcode>
			<faultstring>[error-message]</faultstring>
			<faultactor>http://www.flickr.com/services/soap/</faultactor>
			<details>Please see http://www.flickr.com/services/docs/ for more details</details>
		</s:Fault>
	</s:Body>
</s:Envelope>

SOAP

  • 복잡하다
  • 규칙 많음
  • 어렵다

REST

  • 단순하다
  • 규칙 적음
  • 쉽다

SOAP과 REST의 느낌적인 비교

결국

결국

REST의 승리

그런데

Roy T. Fielding:

"No REST in CMIS"

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

Roy T. Fielding:

"s/REST API/HTTP API/"

뭐가 문제인 걸까요?

REST API

REST 아키텍쳐 스타일을 따르는 API

REST

분산 하이퍼미디어 시스템(예: 웹)을 위한

아키텍쳐 스타일

아키텍쳐 스타일

제약조건의 집합

  • client-server
  • stateless
  • cache
  • uniform interface
  • layered system
  • code-on-demand (optional)
  • identification of resources
  • manipulation of resources through representations
  • self-descriptive messages
  • hypermedia as the engine of application state (HATEOAS)

Self-descriptive message

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

Self-descriptive message

GET / HTTP/1.1

이 HTTP 요청 메시지는 뭔가 빠져있어서 self-descriptive 하지 못하다.

Self-descriptive message

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

목적지를 추가하면 이제 self-descriptive

Self-descriptive message

HTTP/1.1 200 OK

[ { "op": "remove", "path": "/a/b/c" } ]
HTTP/1.1 200 OK
Content-Type: application/json

[ { "op": "remove", "path": "/a/b/c" } ]
HTTP/1.1 200 OK
Content-Type: application/json-patch+json

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

HATEOAS

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

글 목록 보기
GET /articles

글 쓰기
GET /new-form

글 저장
POST /articles

목록 얻기
GET /articles

애플리케이션 상태의 전이

환영합니다!
[글 목록 보기]

제목: ...
제목: ...

제목: ...
[글 쓰기]

글이 생성되었습니다
[생성된 글 보기]

제목: ... 본문: ...
[목록 보기]

제목: ____
본문: ____
[글 저장]

생성된 글 보기
GET /articles/10

HATEOAS

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

<html>
<head></head>
<body><a href="/test">test</a></body>
</html>
HTTP/1.1 200 OK
Content-Type: application/json
Link: </articles/1>; rel="previous",
      </articles/3>; rel="next;
      
{
    "title": "The second article",
    "contents": "blah blah..."
}

왜 Uniform Interface?

독립적 진화

  • 웹 페이지를 변경했다고 웹 브라우저를 업데이트할 필요는 없다.
  • 웹 브라우저를 업데이트했다고 웹 페이지를 변경할 필요도 없다.
  • HTTP 명세가 변경되어도 웹은 잘 동작한다.
  • HTML 명세가 변경되어도 웹은 잘 동작한다.

이럴수도 있지만
동작은 합니다

이렇지는 않습니다

어떻게 한걸까요?

  • 놀라운 마법으로 한방에 해결 (x)
  • 피땀흘려 노력함 (o)

이 분들이

  • W3C Working groups
  • IETF Working groups
  • 웹 브라우저 개발자들
  • 웹 서버 개발자들

이 정도의 노력을 합니다

  • HTML5 첫 초안에서 권고안 나오는데까지 6년
  • HTTP/1.1 명세 개정판 작업하는데 7년

상호운용성(interoperability)에 대한 집착

  • Referer 오타지만 안 고침
  • charset 잘못 지은 이름이지만 안 고침
  • HTTP 상태 코드 418 포기함 (I'm a teapot)
  • HTTP/0.9 아직도 지원함 (크롬, 파이어폭스)

그런 노력이 없다면 웹도...

REST가 웹의 독립적 진화에 도움을 주었나

  • HTTP에 지속적으로 영향을 줌
  • Host 헤더 추가
  • 길이 제한을 다루는 방법이 명시 (414 URI Too Long 등)
  • URI에서 리소스의 정의가 추상적으로 변경됨: "식별하고자 하는 무언가"
  • 기타 HTTP와 URI에 많은 영향을 줌
  • HTTP/1.1 명세 최신판에서 REST에 대한 언급이 들어감
  • Reminder: Roy T. Fielding이 HTTP와 URI 명세의 저자 중 한명입니다

그럼 REST는 성공했는가

  • REST는 웹의 독립적 진화를 위해 만들어졌다
  • 웹은 독립적으로 진화하고 있다

성공!

그런데 REST API는?

  • REST API는 REST 아키텍쳐 스타일을 따라야한다.
  • 오늘날 스스로 REST API라고 하는 API들의 대부분이 REST 아키텍쳐 스타일을 따르지 않는다.

REST API도 저 제약조건들을 다 지켜야 하는건가?

그렇다고 합니다

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

SOAP

  • 복잡하다
  • 규칙 많음
  • 어렵다

REST

  • 단순하다
  • 규칙 적음
  • 쉽다

이런 줄 알았는데

SOAP

  • 복잡하다
  • 규칙 많음
  • 어렵다

REST

  • 단순하다
  • 규칙 적음
  • 쉽다 어렵다

오해였나봅니다

원격 API가 꼭 REST API여야 하는건가?

아니라고 합니다

REST emphasizes evolvability to sustain an uncontrollable system. If you think you have control over the system or aren’t interested in evolvability, don’t waste your time arguing about REST.

-- Roy T. Fielding

시스템 전체를 통제할 수 있다고 생각하거나, 진화에 관심이 없다면, REST에 대해 따지느라 시간을 낭비하지 마라

아니라고 합니다

그럼 이제 어떻게 할까?

  1. REST API를 구현하고 REST API라고 부른다.
  2. REST API 구현을 포기하고 HTTP API라고 부른다.
  3. REST API가 아니지만 REST API라고 부른다.

그럼 이제 어떻게 할까?

  1. REST API를 구현하고 REST API라고 부른다.
  2. REST API 구현을 포기하고 HTTP API라고 부른다.
  3. REST API가 아니지만 REST API라고 부른다. (현재 상태)

I am getting frustrated by the number of people calling any HTTP-based interface a REST API. ... Please try to adhere to them or choose some other buzzword for your API.

-- Roy T. Fielding

그럼 이제 어떻게 할까?

  1. REST API를 구현하고 REST API라고 부른다.
  2. REST API 구현을 포기하고 HTTP API라고 부른다.
  3. REST API가 아니지만 REST API라고 부른다. (현재 상태)

I am getting frustrated by the number of people calling any HTTP-based interface a REST API. ... Please try to adhere to them or choose some other buzzword for your API.

-- Roy T. Fielding

제발 제약조건을 따르던지 아니면 다른 단어를 써라

그럼 이제 어떻게 할까?

  1. REST API를 구현하고 REST API라고 부른다. (도전)
  2. REST API 구현을 포기하고 HTTP API라고 부른다.
  3. REST API가 아니지만 REST API라고 부른다. (현재 상태)

일단 왜 API는 REST가 잘 안되나

일반적인 웹과 비교를 해 봅시다

비교

흔한 웹 페이지 HTTP API
Protocol HTTP HTTP
커뮤니케이션 사람-기계 기계-기계
Media Type HTML JSON

비교

HTML JSON
Hyperlink 됨 (a 태그 등) 정의되어있지 않음
Self-descriptive 됨 (HTML 명세) 불완전*

* 문법 해석은 가능하지만, 의미를 해석하려면 별도로 문서가 (API 문서 등) 필요하다.

HTML

JSON

비교

GET /todos HTTP/1.1
Host: example.org

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

<html>
<body>
<a href="https://todos/1">회사 가기</a>
<a href="https://todos/2">집에 가기</a>
</body>
</html>
GET /todos HTTP/1.1
Host: example.org

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

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

HTML

비교

GET /todos HTTP/1.1
Host: example.org

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

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

1. 응답 메시지의 Content-Type을 보고 media type이 text/html 임을 확인한다.

2. HTTP 명세에 media type은 IANA에 등록되어있다고 하므로, IANA에서 text/html의 설명을 찾는다.

3. IANA에 따르면 text/html의 명세는 http://www.w3.org/TR/html 이므로 링크를 찾아가 명세를 해석한다.
4. 명세에 모든 태그의 해석방법이 구체적으로 나와있으므로 이를 해석하여 문서 저자가 사용자에게 해 주려고 했던 모든 일들을 해줄 수 있다.

Self-descriptive

SUCCESS

HTML

비교

GET /todos HTTP/1.1
Host: example.org

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

<html>
<body>
<a href="https://example.org/todos/1">회사 가기</a>
<a href="https://example.org/todos/2">집에 가기</a>
</body>
</html>

a 태그를 이용해 표현된 링크를 통해 다음 상태로 전이될 수 있으므로 HATEOAS를 만족한다.

HATEOAS

SUCCESS

JSON

비교

GET /todos HTTP/1.1
Host: example.org

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

[ 
  {"id": 1, "title": "회사 가기"},
  {"id": 2, "title": "집에 가기"}
]
  1. 응답 메시지의 Content-Type을 보고 media type이 application/json 임을 확인한다.
  2. HTTP 명세에 media type은 IANA에 등록되어있다고 하므로, IANA에서 application/json의 설명을 찾는다.
  3. IANA에 따르면 application/json의 명세는 draft-ietf-jsonbis-rfc7159bis-04 이므로 링크를 찾아가 명세를 해석한다.
  4. 명세에 json 문서를 파싱하는 방법이 명시되어있으므로 성공적으로 파싱에 성공한다. 그러나 "id"가 무엇을 의미하고, "title"이 무엇을 의미하는지 알 방법은 없다.

Self-descriptive

FAIL

JSON

비교

GET /todos HTTP/1.1
Host: example.org

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

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

다음 상태로 전이할 링크가 없다

HATEOAS

FAIL

그런데 Self-descriptive와 HATEOAS가 독립적 진화에 어떻게 도움이 될까요?

Self-descriptive

확장 가능한 커뮤니케이션

서버나 클라이언트가 변경되더라도 오고가는 메시지는 언제나 self-descriptive 하므로
언제나 해석이 가능하다.

HATEOAS

애플리케이션 상태 전이의 late binding

어디서 어디로 전이가 가능한지 미리 결정되지 않는다. 어떤 상태로 전이가 완료되고 나서야 그 다음 전이될 수 있는 상태가 결정된다.

쉽게 말해서: 링크는 동적으로 변경될 수 있다

그럼 REST API로 고쳐보자

JSON

Self-descriptive

GET /todos HTTP/1.1
Host: example.org

HTTP/1.1 200 OK
Content-Type: application/vnd.todos+json

[ 
  {"id": 1, "title": "회사 가기"},
  {"id": 2, "title": "집에 가기"}
]
  1. 미디어 타입을 하나 정의한다.
  2. 미디어 타입 문서를 작성한다. 이 문서에 "id"가 뭐고 "title"이 뭔지 의미를 정의한다.
  3. IANA에 미디어 타입을 등록한다. 이 때 만든 문서를 미디어 타입의 명세로 등록한다.
  4. 이제 이 메시지를 보는 사람은 명세를 찾아갈 수 있으므로 이 메시지의 의미를 온전히 해석할 수 있다.

방법1: Media type

SUCCESS

단점: 매번 media type을 정의해야한다

JSON

Self-descriptive

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": "집에 가기"}
]
  1. "id"가 뭐고 "title"이 뭔지 의미를 정의한 명세를 작성한다.
  2. Link 헤더에 profile relation으로 해당 명세를 링크한다.
  3. 이제 메시지를 보는 사람은 명세를 찾아갈 수 있으므로 이 문서의 의미를 온전히 해석할 수 있다.

방법2: Profile

SUCCESS

단점:

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

JSON

HATEOAS

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": "집에 가기"
  }
]

SUCCESS

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

data에 다양한 방법으로 하이퍼링크를 표현한다.

방법1: data로

JSON

HATEOAS

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"

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

data에 다양한 방법으로 하이퍼링크를 표현한다.

방법1: data로

SUCCESS

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

JSON

HATEOAS

data에 다양한 방법으로 하이퍼링크를 표현한다.

 

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

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

방법1: data로

SUCCESS

GET /todos HTTP/1.1
Host: example.org

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

{
  "data": [{
    "type": "todo",
    "id": "1",
    "attributes": { "title": "회사 가기" },
    "links": { "self": "http://example.com/todos/1" }
  }, {
    "type": "todo",
    "id": "2",
    "attributes": { "title": "회사 가기" },
    "links": { "self": "http://example.com/todos/2" }
  }]
}

단점: 기존 API를 많이 고쳐야한다. (침투적)

JSON

HATEOAS

POST /todos HTTP/1.1
Content-Type: application/json

{
    "title": "점심 약속"
}

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

Link, Location 등의 헤더로 링크를 표현한다.

방법2: HTTP 헤더로

SUCCESS

단점: 정의된 relation만 활용한다면 표현에 한계가 있다.

HATEOAS

data, 헤더 모두 활용하면 좋습니다

몇가지 궁금점

Hyperlink는 반드시 uri여야 하는건 아닌가?

종류
uri https://toss.im/users/eungjun
uri reference (absolute) /users/eungjun
uri reference (relative) eungjun
uri template /users/{username}

다 괜찮음

Media type 등록은 필수인가?

  • NO
  • 하지만 하면 좋다

Media type을 IANA에 등록하기

  • 누구나 쉽게 사용할 수 있게 된다
  • 이름 충돌을 피할 수 있다
  • 등록이 별로 어렵지 않다(고 주장함)

정리

정리

  • 오늘날 대부분의 "REST API"는 사실 REST를 따르지 않고 있다.
  • REST의 제약조건 중에서 특히  Self-descriptiveHATEOAS를 잘 만족하지 못한다.
  • REST는 긴 시간에 걸쳐(수십년) 진화하는 웹 애플리케이션을 위한 것이다.
  • REST를 따를 것인지는 API를 설계하는 이들이 스스로 판단하여 결정해야한다.
  • REST를 따르겠다면, Self-descriptiveHATEOAS를 만족시켜야한다.
    • Self-descriptive는 custom  media type이나 profile link relation 등으로 만족시킬 수 있다.
    • HATEOAS는 HTTP 헤더나 본문에 링크를 담아 만족시킬 수 있다.
  • REST를 따르지 않겠다면, "REST를 만족하지 않는 REST API"를 뭐라고 부를지 결정해야 할 것이다.
    • HTTP API라고 부를 수도 있고
    • 그냥 이대로 REST API라고 부를 수도 있다. (roy가 싫어합니다)

rest

By EungJun Yi