[REST] REST API

서정범·2023년 11월 23일
0

MSA

목록 보기
5/10
post-thumbnail

REST

REST: A way of providing interoperability between computer systems on the internet.

컴퓨터 시스템간에 상호운용성을 제공하는 것중 하나의 방법이 REST라고 합니다.

더 자세히 알아보기 위해서 어떻게 나오게 되었는지 역사를 따라가 봅시다.

역사

WEB(1991)

팀 버너스리는 어떻게 인터넷에서 정보를 공유할 수 있을까에 대해서 고민을 하였고 결론은 다음과 같았습니다.

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

  • 표현 형식: HTML
  • 식별자: URL
  • 전송 방법: HTTP

이렇게 설계함으로써 HTTP 프로토콜이 탄생하게 되는데,

HTTP/1.0(1994-1996)

Roy T.Fielding: "How do I improve HTTP without breaking the Web?"

이것이 무슨 말이냐면, HTTP/1.0의 설계가 시작되기 전에 이미 HTTP는 전세계의 World Wide Web에서 전송 프로토콜로써 사용되고 있었고, Web은 급속도로 성장하는 도중이였습니다.

HTTP를 정립하고 명세에 기능을 더하고 기존의 기능을 고쳐야 되는 상황에 놓이게 된 것이었죠.

이때 기존에 구축되어 있는 Web과 호환성의 문제를 피하기 어려울 수 있습니다.

여기서 나온 해결책은 HTTP Object Model입니다.

이것이 4년 후에는 REST라는 명칭으로 발표하게 됩니다.

로이 필딩은 2000년에 UC 어바인에서 "Architectural Styles and the Design of Network-based Software Architectures"라는 제목의 2000년 박사 학위 논문에 REST를 정의하였습니다.

한편으론,,

Microsoft에서는 XML-RPC를 1998년도에 만들게 되는데 이것은 원격으로 단일 메서드를 호출할 수 있는 프로토콜입니다. 이것은 나중에 SOAP이라는 이름으로 바뀌게 됩니다.

2000년 2월에는 Salesforce API가 공개됩니다.

이 당시에 SOAP을 사용해서 API를 만들었는데, 복잡하여 잘 사용하지 않았습니다.

4년 후에 flicker에서 API를 만들게 되는데 여러가지 형태로 만들었습니다. SOAP과 REST를 공개했는데, 여기서 SOAP보다 REST가 단순하고 규칙이 적게 보여서 사용자들에게 더 쉽게 보여졌습니다.

그래서 시간이 갈수록 사용자들은 SOAP API에서 REST API로 갈아탔고, 결국 2006년에 AWS가 자사 API의 사용량의 85%가 REST임을 밝혔습니다.

이렇게 REST로 정착될 것 같았지만 2008년에 CMIS라는 것이 나오게 됩니다.

CMIS (2008)

  • CMS를 위한 표준
  • EMC, IBM, Microsoft등이 함께 작업
  • REST 바인징 지원

하지만, 로이 필딩은 이것을 보고 이렇게 말합니다.

| "NO REST in CMIS"

다른 경우도 있는데, Microsoft에서 REST API Guildelines를 2016년에 발표하게 됩니다.

간단하게 보자면,

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

이것을 본 로이 필딩은 이렇게 남깁니다.

| "s/REST API/HTTP API/"

REST API가 아니다. 이것은 HTTP API라고 해야한다. 라는 의미입니다.

로이 필딩은 블로그에 아래 문장을 남겼다고 합니다.

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

사람들이 알고 있는 REST와 제작자인 로이 필딩이 말하는 REST는 너무 많이 다릅니다.

무엇이 문제인지 확인해 봅시다.

REST API

❗️REST API

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

여기서 REST는 무엇일까요?

❗️REST

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

❗️아키텍쳐 스타일

제약조건의 집합

즉, 제약조건을 모두 만족해야 REST를 만족했다고 할 수 있습니다.

사실 REST는 아키텍처 스타일이면서 하이브리드 아키텍처 스타일이라고 말하는데 그 이유는 아키텍처 스타일이면서 아키텍처 스타일의 집합이기 때문입니다.

REST를 구성하는 스타일

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

❓code-on-demand

서버에서 코드를 클라이언트에게 보내서 실행할 수 있어야 한다. -> JavaScript

Uniform Interface의 제약조건

  • identification of resources
    • 리소스가 URI로 식별되면 된다.
  • manipulation of resources through representations
    • representations를 통해서 리소스를 조작해야 한다.
  • self-descriptive message
  • hypermedia as the engine of application state(HATEOAS)

위의 두개는 많은 API들이 잘 지키고 있지만 아래 두개는 오늘날 대부분의 REST API라고 지칭하는 것들이 지키고 있지 않은 것들입니다.

Self-descriptive message

이것은 "메시지는 스스로를 설명해야한다"라는 의미입니다.

예를 들어 다음과 같은 메시지가 있다고 해봅시다.

GET / HTTP/1.1

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

"목적지"가 빠져있습니다.

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

"목적지"를 추가하면 이제 self-descriptive합니다.

또 다른 예시를 보자면 다음과 같은 응답이 있다고 해봅시다.

HTTP/1.1 200 OK

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

이것 또한 self-descriptive 하지 못하다고 말하는데 그 이유는 이것이 어떤 문법으로 작성된 것인지 모르기 때문에 해석을 못하기 때문에 다음과 같이 해줘야 합니다.

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

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

이제 self-descriptive 하냐고 물었을 때 아니라고 합니다.

이것을 보고 "op"가 무엇을 의미하고, "path"가 무엇을 의미하는지 알 수가 없습니다. 다음과 같이 해줘야 완전해 진다고 합니다.

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

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

json-patch 명세를 찾아가서 메시지를 해석하면 그떄서야 비로소 올바르게 메시지의 의미를 이해할 수 있게 됩니다.

HATEOAS

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

다음과 같은 애플리케이션이 있다고 해봅시다.

이러한 형태로 되어 있는 것을 애플리케이션 상태를 전이한다고 합니다.

상태 전이마다 해당 페이지에 있던 링크를 따라가면서 HATEOAS가 된다고 합니다.

말 그대로 Hyperlink를 통한 전이가 된 것입니다.

HTML 같은 경우에는 HATEOAS를 만족하게 되는데,

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

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

a 태그를 통해서 하이퍼링크가 나와있고, 이 링크를 통해서 그 다음 상태로 전이가 가능하기 때문에 만족한다고 합니다.

그렇다면 json으로는 어떻게 표현할 수 있을까요?

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..."
}

Link라는 헤더가 있는데, 이것이 바로 이 리소스와 하이퍼링크로 연결되어 있는 다른 리소스를 가르킬 수 있는 기능을 제공해줍니다.

이 정보는 Link 헤더가 이미 표준으로 문서화 되어 있기 때문에 이 메시지를 보낸 사람이 온전히 해석해서 이해할 수 있기 때문에 이것 또한 HATEOAS를 만족할 수 있는 것입니다.

Uniform Interface

왜 필요한 것일까요?

그것은 독립적 진화 때문이라고 합니다.

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

그렇다면 실제로 이것이 잘 지켜지고 있는가? REST가 잘 지켜지고 있는가?

이것을 알 수 있는 것은 바로 "WEB"입니다.

웹(WEB)

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

웹 브라우저의 경우 옛날 버전에서는 UI가 좀 깨질 수도 있지만 동작은 됩니다.

웹에서는 웹 사이트가 바뀌었다고 해서 웹 브라우저를 업데이트 해야하거나 옛날 버전은 호환되지 않는다거나 하는 일이 거의 일어나지 않습니다.

하지만 모바일 앱에서는 빈번하게 일어나죠.

이것은 엄밀하게 말하면 모바일 앱 클라이언트서버REST로 통신하고 있지 않다는 것(REST 아키텍처 스타일을 따르고 있지 않다는 것)을 의미합니다.

웹에서는 어떻게 이것이 가능하게 되었을까요?

W3C Working group에서 html을 만들고, IEFT Working groups에서 http를 만들고, 웹 서버/브라우저 개발자들이 많은 토론을 하며 노력을 합니다.

HTML5 첫 초안에서 권고안이 나오는데 무려 6년, HTTP/1.1 명세 개정판 작업하는데 7년이 걸렸다. HTTP/1.1에 새로운 기능이 추가 되었는가? 아닙니다. 단지, 문서를 다듬기만 하는데 7년이 걸렸다. 절대로 하위 호환을 깨드리지 않기 위해 정말 깊은 토론의 결과였던 것입니다.

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

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

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

그렇다면 웹이 계속해서 진화할 수 있도록 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는 위에서 언급했던 모든 제약조건을 전부 지킬 필요가 있을까?"에 대한 부분입니다.

Roy T. Fielding은 그렇다고 합니다.

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

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

Roy T. Fielding은 이에 대해서 아니라고 말합니다.

"REST emphasizes evolvability sustain on 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 API를 어떻게 받아들이면 되는 것일까요?

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

3번에 대해서는 Roy T. Fielding이 다음과 같이 말하면서 싫어합니다.

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

그래서 1번을 시도해보고자 합니다.

일단 왜 API는 REST가 잘 안되나

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

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

커뮤니케이션 부분이 조금 다른데, HTTP API는 사람이 아닌 기계가 해석을 합니다. 그러다보니깐 미디어 타입이 다를 수 밖에 없습니다.

그렇다면 미디어 타입을 비교를 해봅시다.

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

여기서 Self-descriptive를 보면 HTML은 되는 이유가 명세에 보면 모든 태그의 정의가 다 되어 있습니다.

JSON은 Object안에 들어갈 수 있는 Key-Value에 대한 정의는 아무런 것도 정의되어 있지 않습니다. 단, 문법만 정의되어 있을 뿐입니다.

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

예시를 한번 봐봅시다.

HTML 예시

GET /todos HTTP/1.1
Host: example.org

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

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

이 예시가 Self-descriptive하는지 따져봅시다.

Self-descriptive

  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. 명세에 모든 태그의 해석 방법이 있으므로 이를 해석, 사용자에게 보여줄 수 있습니다.
  5. 온전히 해석 가능하므로, Success

HATEOAS

  1. a 태그를 이용해 표현된 링크를 통해 다음 상태로 전이될 수 있음.
  2. 따라서, 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": "집에 가기"}
]

Self-descriptive

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

HATEOAS

  1. 다음 상태로 전이할 링크가 없습니다.
  2. 따라서, FAIL

Self-descriptive와 HATEOAS가 독립적 진화에 어떻게 도움이 되나?

Self-descriptive확장 가능한 커뮤니케이션을 가능하게 합니다.

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

HATEOAS는 애플리케이션 상태 전이의 late binding을 가능하게 해줍니다.

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

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

좀 더 알기 쉽게 풀어보자면, 링크를 주고 받기 때문에 서버가 링크를 바꾼다고 해도 클라이언트 동작에는 문제가 없습니다. 왜냐하면 수정된 링크를 받고 따라가면 되기 때문입니다.

링크를 따라 페이지 이동을 성공적으로 한 후에야 다음 전이될 수 있는 상태를 보고 결정할 수 있는 것이 late binding입니다.

late binding이 되기 때문에 서버가 동적으로 링크를 바꿀 수 있고 독립적인 진화가 가능한 것입니다.

이것을 토대로 REST API를 수정해 봅시다.

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

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

단점

  • 매번 media type을 정의해야한다.
  1. Profile
GET /todos HTTP/1.1
Host: example.org

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

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

단점

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

HATEOAS

  1. data로
GET /todos HTTP/1.1
Host: example.org

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

[
	{
		"link": "https://exmplae.org/todos/1, 
		"title": "회사 가기"
	},
	{
		"link": "https://exmplae.org/todos/2, 
		"title": "회사 가기"
	},
]
GET /todos HTTP/1.1
Host: example.org

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

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

단점

  • 링크를 표현하는 방법을 직접 정의해야 합니다.
  1. JSON으로 하이퍼링크를 표현하는 방법을 정의한 명세들을 활용
GET /todos HTTP/1.1
Host: example.org

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

{
	"data": [{
		"type": "todo",
		"id": 1, 
		"attributes": {"title": "회사 가기"},
	"links" : { "self": "https://example.org/todos/1"
	},{
		"type": "todo",
		"id": 2, 
		"attributes": {"title": "집에 가기"},
	"links" : { "self": "https://example.org/todos/2"
	}]
}
  • JSON API
  • HAL
  • UBER
  • Siren
  • Collection+json
  • ...

단점

  • 기존 API를 많이 고쳐야한다. (침투적)
  1. HTTP 헤더로
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 등을 활용한다.

단점

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

결론적으로, 정의를 해보자면 HATEOAS는 data, 헤더 모두 활용하면 좋습니다.

Media type 등록은 필수인가?

NO입니다.

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

사용하고 있는 회사 즉, 사내에서 이해하고 사용하는데 지장이 없다면 IANA에 등록하지 않아도 상관은 없다고 하네요.

하지만, 장점도 있습니다.

장점

  • 사전에 알고 있지 않은 사람도 누구나 쉽게 사용할 수 있게 됨
  • 이름 충돌을 피할 수 있음
  • 등록이 별로 어렵지 않다(고 주장)

정리

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

참고한 사이트

profile
개발정리블로그

0개의 댓글