Life/컨퍼런스

[REST API] 그런 REST API로 괜찮은가?

샤아이인 2022. 4. 17. 14:06

naver D2 에서 발표를 보고 정리한 글 입니다. 문제될시 삭제하도록 하겠습니다!

다음 영상에 대한 정리를 간략하게 나마 해보았습니다!

 

1. 글을 쓰게 된 이유

그동안 REST에 대하여 명확하게 인식하지 못하고 무분별 하게 사용하고 있다는 느낌을 받았다.

따라서 관련된 영상중 가장 감명깊게 봤던 위 영상을 정리겸 복습하고 싶어 이 글을 작성하였다.

 

1) REST 란?

REST는 REpresentational State Transfer의 약자이다.

하지만 위 글만 보고 이해할 수 있는 사람은.... 아마 창시자 정도??

 

이에대한 부가적인 설명으로, 발표 영상에서는 다음과 같이 상호운용성 에 대하여 강조해 주셨다.

a way of providing interoperability between computer systems on the Internet.

상호운용성(Interoperability)이란 하나의 시스템이 동일 또는 이기종의 다른 시스템과 아무런 제약이 없이 서로 호환되어 사용할 수 있는 성질을 말한다.

 

아직 불명확 할 것 이다... 좀더 뒤를 읽어보자!

 

2) REST API 란?

  • REST API: REST 아키텍처 스타일을 따르는 API
  • REST: 분산 하이퍼미디어 시스템(ex. 웹)을 위한 아키텍처 스타일
  • 아키텍처 스타일: 제약 조건의 집합

즉, REST에서 정의한 제약 조건을 모두 지켜야 REST를 따른다고 말할 수 있다는 것이다.

이렇게 제약 조건은 모두 만족할때, REST Full 하다고 부른다.

 

사실 REST는 아키텍처 스타일이면서 하이브리드 아키텍처 스타일이라고 말한다. 왜냐하면 아키텍처 스타일이면서, 동시에 아키텍처 스타일의 집합이기 때문이다.

 

3) REST 아키텍쳐 스타일

다음 목록이 REST를 만족하기 위해 지켜야 할 제약조건이다.

  • Client-Server
  • Stateless
  • Cache
  • Uniform Interface
  • Layered System
  • Code-On-Demand (optional)

생각해보면, HTTP만 잘 따라도 Client-Server, Stateless, Cache, Layered System은 다 지키고 있다.

(Layered System 이란, 서버가 클라이언트가 모르는 사이에 API서버에 여러 계층(인증, 암호화, 로드밸런싱) 을 추가하여 유연하게 개발할 수 있다.)

Code-on-Demand는 서버에서 코드를 클라이언트로 보내서 실행하는 것 이다, 즉 JavaScript를 의미한다. 이 조건은 필수는 아니다.

 

이중 로이필딩이 REST가 아니라고 말하는 것들은 Uniform Interface를 만족하지 못하는 것이 대부분이였다.

그럼 Uniform Interface이 뭐길래 지키기가 힘든것 일까?

 

4) Uniform Interface

  • Identification of resources
  • manipulation of resources through represenations
  • Self-descriptive messages
  • Hypermedia as the engine of application state(HATEOAS)

 

Identification of resources은 URI로 리소스가 식별하는 것 이다.

Manipulation of resources through representations는 representation 전송을 통해서 리소스를 조작해야된댜는 것이다.

즉, 리소스를 만들거나 삭제, 수정할 때 http 메시지에 그 표현을 전송해야된다는 것이다.

 

위 2가지 조건은 대부분 잘 지켜지고 있다.

하지만 문제는 아래 2개이다. 이 2가지는 사실 우리가 REST API라고 부르는 거의 모든 것들은 지키지 못하고 있다.

 

4-1 Self-descriptive messages

아래와 같은 메시지가 있다고 해보자

GET / HTTP/1.1

단순히 루트를 얻어오는 GET 요청이다. 이 HTTP 요청 메시지는 뭔가 빠져 있어서 Self-descriptive 하지 못하다.

위 요청이 www.example.org 라는 도메인으로 간다라는 목적지가 빠져있기 때문에 이런 경우는 Self-descriptive하지 않다고 한다.

목적지를 다음과 같이 추가해보자!

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

 

또 이런 것도 생각해볼 수 있다. 200 응답 메시지이며, JSON 본문이 있다.

HTTP/1.1 200 OK
[ { "op": "save", "path": "/api/book/save" } ]

이렇게 되면 당연히 Self-descriptive 하지 않는데, 그 이유는 이걸 Client가 해석하려고 하면, 어떤 문법으로 작성된 것인지 모르기 때문에 해석에 실패한다. 그렇기 때문에 Content-Type 헤더가 반드시 들어가야한다.

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

[ { "op": "save", "path": "/api/book/save" } ]

Content-Type 헤더에서 대괄호, 중괄호, 큰따옴표의 의미가 뭔지 알게 되어, 파싱이 가능하여 문법을 해석할 수 있게 된다.

 

그렇다면 이제 Self-descriptive하다고 볼 수 있는가? 아니다...

 

해석은 했지만 아직 op값은 무엇인지?, path가 무엇인지 알수가 없다

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

[ { "op": "save", "path": "/api/book/save" } ]

이렇게 명시를 하면 완전해진다.

이 응답은 json-patch+json이라는 미디어 타입으로 정의된 메시지이기 때문에 json-patch라는 명세를 찾아가서 이해한 다음, 이 메시지를 해석을 하면 그제서야 올바르게 메시지의 의미를 이해할 수 있게 된다.

 

이처럼 Self-descriptive message라는 것은 메시지를 봤을 때 메시지의 내용으로 온전히 해석이 다 가능해야된다는 것이다.

  • 메시지 스스로 메시지에 대한 설명이 가능해야 한다.
  • 서버가 변해서 메시지가 변해도 클라이언트는 그 메시지를 보고 해석이 가능하다.
  • 확장 가능한 커뮤니케이션

 

4-2 HATEOAS

애플리케이션의 상태는 HyperLink를 통해 상태가 전이된다. 다음과 같이 간단한 애플리케이션이 있다고 해보자.

루트 홈페이지 → 글 목록 보기 GET → 글 쓰기 GET → 글 저장 POST → 생성된 글 보기 GET → 목록 보기 GET → ...

이렇게 상태를 전이하는 것을 애플리케이션 상태 전이라고 하고, 이 상태 전이마다 항상 해당 페이지에 있던 링크를 따라가면서 전이했기 때문에 HATEOAS라고 할 수 있다.

 

그래서 html 같은 경우를 보면 HATEOAS를 대부분 만족할수가 있다. 다음 html 을 봐보자!

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

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

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

 

문제는... Json일때 이다.

 

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라는 헤더가 있는데, 이것이 바로 이 리소스와 하이퍼링크로 연결되어 있는 다른 리소스를 가르킬 수 있는 기능을 제공해준다.

이전의 게시물의 URI가 /articles/1이고, 다음 게시물의 URI가 /articles/3에 있다는 정보를 표현해주고 있다.

이를 통해 다른 상태로 상태전이가 가능해진다!

 

5) 왜 Uniform Interface?

왜 이러한 Uniform Interface가 필요한걸 일까? 가장 중요한 것 은 독립적인 진화상호 운용성이다.

5-1. 독립적 진화

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

이를 통해 서버의 기능이 바뀌어도 클라이언트가 바뀌지 않아도 된다.

로이필딩은 초반에 http 1.0을 만들 당시 고민했던 것이, "웹을 망가뜨리지 않고 어떻게 수정할 것인가?"에 대한 결과가 REST였다고 했다.

따라서 REST가 목적하는 바가 독립적인 진화이다.

이를 달성하기 위해서는 Unifrom Interface가 필수적이기기에, 이를 만족하지 못하면 REST라고 부를 수 없는 것이다.