[정리글] 그런 REST API로 괜찮은가

toto9602·2023년 10월 1일
1

제가 REST와 REST API라는 단어를 처음 접한 것은,
얄코와 노마드코더 영상 덕분이었던 것 같습니다!

그 중 얄코님 영상에서, "학술적으로 정확한 의미의 REST API"를 다루는 영상을 추천해 주셨었는데,
오늘은 그 추천을 따라 "그런 건 피자가 아니야!"라고 화내는 정통 이탈리안 주방장의 피자를 맛보러..!

이응준 연사님의 <그런 REST API로 괜찮은가> 발표 영상을 보고, 내용을 정리해 보려 합니다.

이하의 글은 발표 영상에 대한 내용 정리이기에, 모든 내용은 출처에 밝힌 강의 내용의 인용임을 밝힙니다.

출처 및 참고

출처 : [Naver D2] 그런 REST API로 괜찮은가

참고 : [얄팍한 코딩사전] REST API가 뭔가요?

REST의 정의

| Representational State Transfer

→ 정의만 보면, 무슨 뜻인지 이해하기 어렵다..

cf. 위키백과 정의

| a way of providing interoperability between compuuter systems on the Internet

==> 컴퓨터 시스템 간의 상호운용성을 제공하는 방법!

여전히 무슨 말인지 이해가 안됩니다..

이해하기 힘든 이 REST, 어쩌다 나왔는가?

WEB의 탄생 (1991)

| 1991년, 팀 버너스 리에 의해 World Wide Web이 탄생

WEB의 탄생과 함께 대두된 고민

| Q. 인터넷으로, 어떻게 정보 공유를 잘 할 수 있을까?


[ 팀 버너스 리의 답 ]

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

표현 형식 : HTML
식별자 : URI
정보 전송 방법 : HTTP 

HTTP/1.0과 Roy Fielding의 고민

→ REST의 창시자인 Roy T. Fielding은 HTTP/1.0 프로토콜 작업에 참여 중이었다.

→ 작업 이전부터, HTTP는 www의 전송 프로토콜로 이용 중이었고, 웹은 급속도로 성장하여 당시에는 이미 웹서버가 많았다..!

[ 기존의 명세를 정리하던 Roy Fielding의 고민 ]

기존 WEB과의 호환성!

| Q. 어떻게 하면 기존 WEB을 망가뜨리지 않고 HTTP를 진보시킬까?


[ Roy Fielding이 내린 결론 ]

| A. HTTP Object Model

→ 이 HTTP Object Model이, 4년 후에 REST라는 이름으로 발표된다!

→ 그리고, 그로부터 다시 2년 후, Roy Fielding은 이를 박사 논문으로 발표 (2000), 오늘날의 REST를 정의하는 논문이 된다!

그런 한편... API의 대두

XML-RPC(1998) → SOAP

| 1998년에, Microsoft가 원격으로 다른 시스템을 호출할 수 있는 XML-RPC를 만든다. (후에 SOAP라는 이름으로 바뀜)

Salesforce API(2000)

| Salesforce라는 회사에서 2000년 2월 공개한, 인터넷에서 거의 최초로 공개된 API

→ 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>QsWsHJyTPW.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>

→ 어떤 ID로 object 하나를 가져오는 데도 많은 양의 번잡한 코드가 필요했다.

→ 너무 복잡해서 인기가 없었음ㅠ

flickr API (2004.8)

→ SOAP 그리고, REST라는 형태로도 공개

→ 사람들이 보기엔 새로워 보였다.

[ SOAP ]

<?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>
      <faulcactor>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>

[ REST ]

<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="fail">
  <err code="[error-code]" msg="[error-message]" />
</rsp>

→ 거의 동일한 작업을 하는 코드인데, REST는 SOAP보다 훨씬 짧았다!

당시 사람들의 느낌적인 비교 ( SOAP VS REST )

[ SOAP ]

  • 복잡하다
  • 규칙이 많다
  • 어렵다..

[ REST ]

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

→ SOAP은 갈수록 인기 추락, REST는 인기 급상승!

→ 2006년, AWS가 자사 API 사용량의 85%가 REST임을 밝힌다.
→ 2010년, Salesforce조차 REST를 추가한다.

REST의 승리

→ www이 REST의 승리로 정착되는가... 싶었는데..!!

Roy Fielding : "그런 건 REST가 아니야"

CMIS (2008)

| CMS를 위한 표준

→ EMC, IBM, MS 등이 작업
REST 바인딩을 지원했는데...


[ Roy T.Fielding ]

| "No REST in CMIS."

→ 이건 REST가 아니라고 이야기함..!

Microsoft REST API Guidelines (2016)

[ Microsoft REST API Gudelines 중 일부 ]

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

→ 사람들이 보기에는 합리적인 것 같았는데...


[ Roy T.Fielding ]

"이것도 REST 아니고, 그냥 HTTP API라고 불러야 한다."

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

위와 같은 내용을 언급함..!

→ 사람들이 알고 있던 REST와, 정작 REST를 만든 Roy T.Fielding의 이야기는 너무도 달랐다.

뭐가 문제인 걸까요?

→ REST API가 무엇인지, 따져보자!

REST API

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

그럼 REST는 뭘까?

| 분산 하이퍼미디어 시스템(예 : 웹)을 위한 아키텍처 스타일

그럼 아키텍처 스타일은 뭘까?

| 제약 조건의 집합

→ 제약 조건들을 모두!! 지켜야, REST를 따른다고 말할 수 있는 것

그럼 REST는 어떤 제약 조건을?

| Note : 사실 REST는, 아키텍처 스타일이자, 하이브리드 아키텍처 스타일이다.

→ 아키텍처 스타일이며, 동시에 아키텍처 스타일의 집합

REST를 구성하는 여섯 개의 아키텍처 스타일

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

→ 대체로 오늘날 REST API라 불리는 것들은 이것들을 잘 지키는 편이다~

[ 왜? ]

→ HTTP만 잘 따라도, 대체로 client-server, stateless, cache, layered system 정도는 잘 지킬 수 있게 되기 때문!

[ cf. code-on-demand란? ]
| 서버에서 코드를 클라이언트로 보내서, 실행할 수 있어야 한다.

→ javascript를 이야기하는 겁니다.

그렇다면, 남아있는 조건 하나는?

Uniform-interface : 오늘날의 REST API가 지키기 어려운 제약 조건

→ 남아있는 단 하나, uniform-interface 스타일을 잘 만족하지 못한다..!

Uniform Interface의 제약 조건

  • identification of resources
  • manipulation of resources through representations
  • self-descriptive messages
  • hypermedia as the engine of application state (HATEOAS)

[ Identification of Resources ]

| 리소스가 URI로 식별되면 된다!

[ manipulation of resources through representations ]

| 리소스를 만들거나, 업데이트하거나, 삭제할 때 HTTP 메시지에 표현을 담아, 전송해서 이를 달성해야 한다.

== 위 두 개 조건은, 전반적으로 잘 지켜지는 편!

→ 나머지 두 개가 문제입니다...

→ 나머지 두 제약 조건은, 거의 대부분의 자칭 REST API에서 지켜지지 못하고 있습니다.

  • self-descriptive messages
  • hypermedia as the engine of application state (HATEOAS)

Self-descriptive message

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

[ Example 1 ]

GET / HTTP/1.1

→ 이 메시지는 self-descriptive하지 못하다.

Why? → 목적지가 빠져있어서!


[ Example 1 수정 ]

GET / HTTP1.1
Host : www.example.org

목적지를 추가하면, self-descriptive


[ Example 2 ]

HTTP/1.1 200 OK

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

이 메시지도 self-descriptive하지 못하다.

Why? → 클라이언트가 이 메시지를 받아서 해석해야 하는데, 어떤 문법으로 작성된 건지 알 수 없음.

[ Example 2 수정 ]

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

Content-Type이 필요하다.

→ 이러면 문법을 이해해서, 응답값을 파싱할 수 있음.

| Q. 이러면, Example 2는 Self-descriptive한가요?

| A. 사실 아닙니다.

Why? → op 가 무슨 뜻인지, path가 무슨 뜻인지.. 등을 알 수 없음..!


[ Example 2는 이렇게 해야 완전하다 ]

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

→ 이미 존재하는, json-patch+json이라는 명세를 찾아가서, 메시지를 이해하고 해석하면
그제서야 올바르게 메시지의 의미를 이해할 수 있다.

오늘날의 REST API가 Self-descriptive하지 못한 이유

Self-Descriptive: 메시지의 내용으로 온전히 해석이 가능해야 한다.

&arr; 오늘날의 REST API는 대부분 media type을 Json으로 사용
&arr; 어떻게 해석해야 하는가-를 대부분 메시지만으로 알 수 없다.

HATEOAS

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

[ 애플리케이션 상태의 전이란? ]

< Example >

  1. GET /articles 로 게시글 목록 조회
  2. GET /new-form을 통해 글 쓰기 페이지 진입
  3. POST /articles로 게시글 저장
  4. GET /articles/10으로, 생성한 게시글 조회
  5. GET /articles로 다시 글 목록 조회

→ 위와 같이 이동하는 각 단계를, 상태의 전이라고 함!

→ 이 상태의 전이마다, 해당 페이지에서 접근 가능한 링크를 통했기 때문에, HATEOAS!

[ HTML은 HATEOAS를 만족한다 ]

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

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

a 태그를 통해서 하이퍼링크가 나와있고, 이 하이퍼링크를 통해 다음 상태로 전이가 가능하기 때문!

[ JSON으로 HATEOAS를 만족하려면...? ]

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

Link라는 헤더가, 해당 리소스와 하이퍼링크로 연결된 다른 리소스를 가리킬 수 있는 기능 제공

→ 이전 아티클과, 다음 아티클의 정보를 표현하고 있다.

→ 또한 이 정보는, Link 헤더의 표준 정보가 나와있으므로
메시지를 보는 사람이 온전히 해석해서, 하이퍼링크를 통해 다음 상태로 전이가 가능하다!

== HATEOAS를 만족!

Uniform-interface, 왜 필요한가?

독립적 진화

→ 서버와 클라이언트가 각각 독립적으로 진화.

→ 서버의 기능이 변경되어도, 클라이언트를 업데이트할 필요가 없다!

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

== 그리고, 이것이 곧 REST를 만들게 된 계기이기도 하다.

HTTP/1.0을 만들던 Roy Fielding의 고민

Q. "How do I improve HTTP without breaking the WEB."

→ "내가 HTTP를 고치면 웹이 깨질 것 같은데.. 어떻게 해결하지?"

REST의 목적 : 독립적 진화

→ REST의 목적은 독립적 진화이다.
→ 이를 위해서는 Uniform-Interface를 반드시 만족해야 함!
→ 이를 만족하지 못한다면, REST라 부를 수 없다.

REST는 실제로 지켜지고 있는가?

웹의 사례

| 웹페이지들은 REST를 매우매우 잘 만족함.

  • 웹 페이지를 변경했다고 웹 브라우저를 업데이트할 필요는 없다.
  • 웹 브라우저를 업데이트했다고 웹 페이지를 변경할 필요도 업삳.
  • HTTP 명세가 변경되어도 웹은 잘 동작한다.
  • HTML 명세가 변경되어도 웹은 잘 동작한다.
    (5.0까지만 지원되는 브라우저여도, 5.1로 만들어진 웹사이트 구동 가능)

모바일 앱은?

→ 강제 업데이트가 필요한 경우가 있음.
→ 서버 기능이 변경되는데, 클라이언트가 이를 지원하는 데 한계가 있는 경우 등

→ 웹에서는, '당신의 웹 브라우저는 더 이상 안된다..' 같은 일은 잘 없음.

| 모바일 앱 클라이언트와 서버가 REST 아키텍처 스타일을 따르고 있지 않다..

웹은, 어떻게 한 걸까요?

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

노력하신 분들

  • W3C Working groups → HTML을 만듦
  • IETF Working groups → HTTP 를 만듦
  • 웹 브라우저 개발자들
  • 웹 서버 개발자들

어떤 노력을 했을까

  • HTML 5 첫 초안에서 권고안이 나오는 데까지 6년
  • HTTP/1.1 명세 개정판 작업하는 데 7년..
  • RFC2616 명세는 1999년에 나왔고, 15년 뒤에야 그 개정판이 나옴.

→ 7년 동안, 기능 추가는 전혀 하지 않고, 문서 다듬는 데만 시간을 할애한 셈..!

하위 호환성을 절대로 깨뜨리지 않기 위함이었다.

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

1. Referer 오타지만 안 고침

→ 25년 전에 발생한 오타지만, 고치지 않음..!
→ 고치는 순간, 웹이 깨집니다.

2. charset은 잘못 지은 이름이지만, 안 고침

accept-charset 헤더, media type에 charset 관련 등

→ 원래 encoding이라고 이름 지어야 하는데, 당시 이름 짓던 사람들이 encoding 관련 개념을 알고 있지 않아, charset과 같은 줄 알고 charset이라고 이름 지음

3. HTTP 상태 코드 416(I'm a teapot) 포기함

→ 416번 상태 코드를 추가해야 하는데, 이전에 만우절 때 만든 이상한 스펙이 있었던 것..
→ 엄밀히는 HTTP가 아니고, HTCPC이기에 사실 무시해도 되었지만 이미 수많은 서버 구현들이 (e.g node js, golang) HTTP가 아닌데도 이를 HTTP 상태 코드로 구현해 버림.

→ 처음에는 HTTP 의장이 프로젝트마다 돌면서, 416 지원을 제거해야 된다는 이슈를 올렸는데, 맹비난을 얻고 포기

→ 416번은 HTTP 상태 코드에서 영구 결번으로 만드는 draft가 나와 있다.

| Q. 왜 이렇게까지...?

| A. 이런 구현체들이 실제로 세상에 존재하고, 이런 구현체들과의 상호운용성도 지원해야, 웹이 깨지지 않기 때문에..!

4. HTTP/0.9 아직도 지원함 (크롬, 파이어폭스)

→ 크롬에서 HTTP/0.9는 빼도 되지 않나-하는 이야기 나왔었지만 결국 실패

→ HTTP/1.0이 1996년에 나왔는데, 그 이전에 나온 HTTP/0.9에 대한 지원을 포기하지 못하는 것.

→ 빼 봤더니, 몇몇 일부 경우에서 깨지는 경우가 발견됨..!

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

| Netscape 6.0은 지원하지 않습니다.

→ 웹서버가 브라우저 호환성을 고려하지 않았을 때 발생하는 일

→ HTML 명세, HTTP 명세, 웹서버 구현체, 웹 브라우저 구현체 개발자들이 노력해도 애플리케이션 개발자의 약간의 잘못으로 발생 가능..

→ HTML 명세, HTTP 명세 개발자분들이 노력해 주지 않았다면, 우리는 이런 메시지를 일상다반사로 보고 살아야 했을 지도 모릅니다..!

Q. REST는 실제로 웹의 독립적 진화에 도움을 주었나

| A. 실제로 도움을 주었습니다!

0. REST는 HTTP에 지속적으로 영향을 주었음.

1. Host 헤더 추가

2. 길이 제한을 다루는 방법이 명시 (414 URI Too Long 등)
→ REST 아키텍처를 만들면서, Host 헤더의 필요성에 확신을 갖게 되었고, 추가되었음.

3. URI에서 리소스의 정의가 추상적으로 변경됨 : "문서의 위치" -> "식별하고자 하는 무언가"

4. 기타 HTTP와 URI에 많은 영향을 줌

5. HTTP/1.1 명세 최신판에서 REST에 대한 언급이 들어감.

→ HTTP/1.1에는 Representation 개념이 정의되어 있음.
→ 이 Representation 개념은 REST에서 온 것이다-라며 REST 박사학위 논문 링크가 걸려 있다고 함.

→ HTTP, URI를 만드는 사람들이 REST에 감명을 받았기 때문도 있겠지만-
사실 그 사람이 그 사람이다 ㅇㅅㅇ

→ Reminder : Roy T.Fielding이 REST를 만들었으며, 동시에 HTTP와 URI 명세의 저자 중 한 명~!
→ 저자가 옳다고 생각한 방향대로, 만든 것.

그럼 REST는 성공했는가

| REST는 웹의 독립적 진화를 위해 만들어졌다.

| 웹은, 현재 독립적으로 진화하고 있다.

→ REST는 성공!!

그런데 REST API는?

| REST API는 REST 아키텍처 스타일을 따라야 한다.

| 오늘날, 스스로 REST API라고 하는 API들은 대부분 REST 아키텍처 스타일을 따르지 않는다..

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

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

[ Roy Fielding이 그렇다고 합니다 ]

"An API that provides network-based access to resources via a uniform interface of self-descriptive messages containing hypertext to indicate potential state transitions might be part of an overall system that is a RESTful application"
-- Roy T. Fielding

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

→ Roy Fielding은 명백하게, REST API가 저 제약 조건들을 다 지켜야 한다고 말했다.

이런 줄 알았는데...

[ SOAP ]

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

[ REST ]

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

→ 사실 REST도 어렵다ㅠㅠ

어려운 제약조건에도, 우리의 원격 API가 꼭 REST API여야 할까?

Roy T.Fielding "꼭 그렇지는 않습니다."

"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에 대해 따지느라 시간을 낭비하지 마라. "

[ 시스템 전체의 통제 ? ]

ex1) 회사에서 일을 하는데, 내가 서버 개발자이지만 클라이언트 개발자에게 이래라 저래라 이야기할 수 있는 상황

ex2) 내가 클라이언트, 서버 다 개발하는 상황

[ 진화에 관심이 없다 ? ]

ex1) 최근 10일 동안 하루도 빠짐없이 업데이트하네.. 업데이트 좀 작작 해라!
→ 이런 말을 들어도 상관이 없는 경우~

| REST는 시간과 비용이 든다.

→ 반드시 진화를 달성해야 할 목표로 삼을 필요는 없다!

→ 다만, 오랜 시간에 걸쳐 진화하는 시스템을 설계하고자 한다면, REST에 관심을 갖자.

그럼 이제 어떻게 할까?

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

이러면 Roy Fielding이 싫어하십니다..ㅠㅠ

"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번을 도전해 보려고 합니다.

일단, 왜 API는 REST가 잘 안되는지 알아보자

일반적인 웹과의 비교

흔한 웹 페이지HTTP API
ProtocolHTTPHTTP
커뮤니케이션사람 - 기계기계 - 기계
Media TypeHTMLJSON

→ HTTP API는 JSON 미디어 타입을 사용하고, 이 부분이 차이!

→ 문제의 원인은 media type이다?
일반적인 웹과 비교를 해 봅시다.

HTML VS JSON

HTMLJSON
Hyperlink된다 (a 태그 등)정의되어 있지 않음
Self-descriptive된다 (HTML 명세가 존재)불완전

[ Self-Descriptive 추가 설명 ]

→ HTML은 명세가 있고, 명세를 보면 사용 가능한 모든 태그가 정의되어 있으므로 의미를 이해할 수 있다.

→ JSON도 array, object 등을 어떻게 해석하라!라는 정도는 정의되어 있다.
→ 다만 JSON은 object 안에 들어갈 수 있는 key, value의 의미 등을 파악할 수 없음.

→ 즉, 문법 해석은 가능하나, 의미를 해석하려면 별도로 문서가 (API 문서 등) 필요하다.
== 불완전

[ 비교 : 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>

| Q. Self-Descriptive한가?

| A. YES.

  1. 응답 메시지의 Content-Type을 보고 media type이 text/html임을 확인
    → 얘는 HTTP 메시지니까, HTTP 명세를 보고 확인한다.
  2. HTTP 명세에 media type은 IANA에 등록되어 있다고 하므로, IANA에서 text/html의 설명을 찾는다.
  3. IANA에 따르면 text/html의 명세는 http://www.w3.org/TR/html 이므로 링크를 찾아가 명세를 해석한다.
  4. 명세에 모든 태그의 해석방법이 구체적으로 나와있으므로 문서를 해석할 수 있다.

→ 메시지의 힌트만을 단서로 온전히 해석이 가능


| Q. HATEOAS를 만족하는가?

| A. YES

  • a 태그에 하이퍼링크가 있고, 이걸 클릭하면 다음 페이지로 간다.
  • a 태그를 이용해 표현된 링크를 통해, 다음 상태로 전이될 수 있으므로 HATEOAS 만족!

[ 비교 : JSON ]

GET /todos HTTP/1.1
Host : example.org

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

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

| Q. Self-Descriptive한가?

| A. NO.

  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"이 무엇을 의미하는지 알 방법은 없다.

| Q. HATEOAS를 만족하는가?

| A. NO.

  • 다음 상태로 전이할 링크가 존재하지 않는다.

결론 : HTML은 REST를 성공했고, JSON은 만족하지 못했다.

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

Self-descriptive

| 확장 가능한 커뮤니케이션을 가능하게 한다.

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

→ 서버가 어떻게 변하든 항상 메시지 해석이 가능하니까, 서버가 계속 변할 수 있다.

HATEOAS

| 애플리케이션 상태 전이의 late binding을 가능하게 한다.

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

[ 쉽게 말하면 ]

→ 어떤 링크를 따라 어떤 페이지로 왔을 때, 그 페이지의 하이퍼링크를 알고 나서야, 다음 전이 가능한 상태가 확정된다 == late binding

→ 즉, 링크가 동적으로 변경될 수 있다!
→ 서버가 링크를 바꾼다고 해도, 클라이언트의 동작에 문제가 없다.
→ 따라서, 언제나 서버를 마음대로 바꿀 수 있다.

| 그리고 이것이 곧, 독립적 진화!

그렇다면, REST API로 고쳐보자

Self-descriptive를 만족하도록

[ 방법 1 : Media Type의 수정 ]

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

[ 방법 1의 단점 ]

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

[ 방법 2의 단점 ]
1. 클라이언트가 Link 헤더(RFC 5988)와 profile(RFC 6906)을 이해해야 한다.
2. Content-Negotiation을 할 수 없다.
→ 방법 1.은 Media Type을 사용해서, 클라이언트가 이를 지원하지 못하면 서버가 인지할 수 있음.
→ 방법 2.는 미디어 타입이 아니라 Link 헤더로만 판단해서 불가능함.

HATEOAS를 만족하도록

[ 방법 1 :data(본문)에 링크 추가 ]

  • 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", // link 추가
        "title": "회사 가기",
     },
     {
     	"link": "https://example.org/todos/2", // link 추가
        "title": "집에 가기",
     }
]

[ 방법 1의 단점 ]

  • 링크를 표현하는 방법을 직접 정의해야 한다.
    → Link 헤더로 profile 문서에 정의하거나
    → Media Type을 정의하거나

[ cf. URI 탬플릿 ]

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

[ URI 탬플릿의 단점 ]

  • 역시, 링크를 표현하는 방법을 직접 정의해야 한다.

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

  • JSON API
  • HAL
  • UBER
  • Siren
  • Collection+json
  • .......
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": "https://example.com/todos/1"}
        }, {
        "type" : "todo",
        "id" : "2",
        "attributes": {"title":"집에 가기"}
        "links": {"self": "https://example.com/todos/2"}
        }]
}

[ JSON으로 하이퍼링크를 표현하는 방법을 정의한 명세 활용의 단점 ]

  • 기존 API를 많이 고쳐야 한다 (침투적)


[ 방법 2 : HTTP 헤더의 활용 ]

  • Link나 Location 등의 헤더로 하이퍼링크를 표현한다.
POST /todos HTTP1.1
Content-Type: application/json

{
	"title": "점심 약속"
    }
    
HTTP/1.1 204 No Content
Location: /todos/1 // Location 추가
Link </todos/>; rel="collection"

[ 방법 2의 단점 ]

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


[ HATEOAS 결론 ]

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

몇 가지 궁금한 점

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

| A. 그렇진 않습니다.

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

→ 하이퍼링크라는 것만 포함되면 다 괜찮음.

Media type 등록은 필수인가?

| A. 그렇진 않습니다.

"A REST API should be entered with no prior knowledge beyond the initial URI(bookmark) and set of standardized media atypes that are appropriate for the intended audience (i.e., expected to be understood by any client that might use the API)."
-- Roy T.Fielding

→ 의도한 audience가 이해할 수만 있으면 상관없다.

ex) 회사 안에서만 쓰고, 회사 구성원들이 그걸 다 이해하고 있다면 굳이 IANA에 등록할 필요는 없다.

[ 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 Fielding님이 싫어합니다..)

cf) MS도 REST를 HTTP로 고치는 Pull-request을 받은 적 있는데, 그냥 무시했다.

"우리는 REST가 틀렸다고 한 걸 알지만, 그냥 REST라고 부를 거임 ㅇㅅㅇ"

→ 스스로 결정할 수만 있다면, 사실 어떻게 해도 상관은 없습니다.

profile
주니어 백엔드 개발자입니다! 조용한 시간에 읽고 쓰는 것을 좋아합니다 :)

0개의 댓글

관련 채용 정보