May 22, 2021
REST API가 무엇인지 알아본다. 비바 리퍼블리카 이응준 개발자님이 Naver DEVIEW에서 강연하신 내용을 듣고 요약했으며, 일부 예제는 김영한님의 HTTP 강의를 참고했다. 해당 자료에 대한 링크는 포스트 하단에 명시했다.
REST(REpresentational State Transfer)는 로이 필딩(Roy Fielding)이 ‘어떻게하면 웹을 망가뜨리지 않으면서 HTTP를 개선할 수 있을까’에 대한 고민을 하며 제안한 “분산 하이퍼미디어 시스템(ex.웹)을 위한 아키텍쳐 스타일”이다. 2000년에 자신의 박사 논문 에서 REST를 처음으로 제안하였다.
즉 REST API란 이 REST 아키텍쳐 스타일을 따르는 API를 말한다.
아키텍쳐 스타일은 제약 조건의 집합이다. REST는 하이브리드 아키텍쳐라고도 불리는데, 이는 REST가 아키텍쳐 스타일의 집합이기 때문이다.
REST는 다음 여섯 가지의 아키텍쳐 스타일의 집합이다. 아래 여섯 가지 아키텍쳐 스타일을 모두 지켜야 RESTful하다고 할 수 있다.
HTTP만 잘 따라도 Uniform interface를 제외한 다른 제약 조건들은 대부분 만족시킬 수 있다.
대부분의 API들이 REST를 만족하지 못하는 것은 Uniform interface의 제약 조건을 만족하지 못하기 때문이다.
Uniform interface도 아키텍쳐 스타일이기 때문에 여러 제약 조건들로 이루어져있는데, 다음과 같다.
회원 정보 관리 API를 만든다고 해보자. 다음과 같은 요구사항이 정의되어 있다.
위 요구조건에 따른 API URI를 다음과 같이 설계했다.
위 API URI는 첫 번째 제약 조건을 만족시키는가? 즉 URI로 리소스 식별이 가능한가? 아니다.
위 예제에서 리소스는 ‘회원’이다. 회원을 조회하고 등록하고 수정하고 삭제하는게 리소스가 아니다. 위 설계의 문제점은 URI가 회원을 식별하는게 아니라 조회, 등록, 수정, 삭제같이 리소스에 대한 조작 행위를 식별하고 있다.
즉 위 설계를 identification of resources
제약 조건을 만족하도록 바꾸면 다음과 같다.
위 URI는 오로지 ‘회원’이라는 리소스만 식별하고 있다. /members는 회원 전체 집합을, 나머지는 {id}를 추가해서 단일 회원을 식별한다.
그런데 회원 조회, 등록, 수정, 삭제는 URI가 모두 같다. 리소스 조작(manipulation of resources)에 대한 정보는 HTTP 메서드를 이용한다.
HTTP 메서드를 통해 HTTP 메시지에 해당 리소스에 대해 어떤 조작을 하는지 명시한다.
회원 조회
GET /members/100 HTTP/1.1
Host: localhost:8080
회원 등록
POST /members HTTP/1.1
Content-Type: application/json
{
"username": "kim",
"age": 20
}
회원 수정
PATCH /member/100 HTTP/1.1
Content-Type: application/json
{
"age": 25
}
수정에는 PUT 또는 PATCH를 사용할 수 있는데, PUT의 경우 해당 리소스가 존재하지 않으면 새로 생성하고 리소스가 존재하면 리소스를 완전히 대치한다. 위 예시에서 만약 메서드만 PUT으로 바꾸면 /member/100은 username이 날아가고 age 정보만 남게된다.
회원 삭제
DELETE /member/100 HTTP/1.1
Host: localhost:8080
메시지는 스스로를 설명해야한다. 즉 HTTP 메시지만 보고도 해당 메시지가 무엇을 하려는건지 정확히 알 수 있어야한다.
다음과 같은 HTTP 요청 메시지가 있다고 해보자
GET / HTTP/1.1
위 메시지는 self-descriptive한가? 루트에 대한 정보를 조회하려는 것 같은데 어떤 루트에 대한 것인지 명시되어 있지 않다. 따라서 다음과 같이 Host 헤더를 추가해줘야 self-descriptive해진다.
GET / HTTP/1.1
Host: www.example.org
이제 이 메시지를 보면 ‘아 www.example.org의 루트 경로에 대한 정보를 조회하려는 것이구나’하고 해석할 수 있다.
또 다른 예로 다음과 같은 HTTP 응답 메시지가 있다고 해보자.
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" } ]
이제 메시지 바디의 정보가 json 타입인 걸 알 수 있으니 파싱이 가능하다. 그런데 이것만으로는 부족하다. json 메시지인건 알겠는데, “op”가 뭔지, “path”는 무얼 의미하는지 알 수 없다. 따라서 self-descriptive해지려면 정보를 더 추가해야한다.
HTTP/1.1 200 OK
Content-Type: application/json-patch+json
[ { "op": "remove", "path": "/a/b/c" } ]
이제 클라이언트는 json-patch+json이라는 명세를 찾아보고 해당 메시지를 정확하게 이해할 수 있다.
HATEOAS는 애플리케이션의 상태가 항상 Hyperlink를 통해서 전이되어야 한다는 제약조건이다.
HTML은 a tag의 하이퍼링크를 통해 다음 상태로 전이가 가능하다.
HTTP/1.1 200 OK
Content-Type: text/html
<html>
<head></head>
<body><a href="/test">test</a></body>
</html>
json으로도 HATEOAS를 만족할 수 있다.
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를 표현하고 있다. Link 헤더는 이미 표준으로 명세가 되어있기 때문에 이 메시지를 받은 클라이언트는 온전히 이 메시지를 해석해서 하이퍼링크를 타고 다른 상태로 전이할 수 있다.
앞서 말했듯이 6가지 아키텍쳐 스타일을 모두 따르는 API가 REST API이다. 근데 대부분의 HTTP API들이 자신이 REST API라고 말하면서도 Uniform interface의 self-descriptive messages와 HATEOAS 제약조건을 만족하지 못하고 있다.
그럼 꼭 저 두 제약 조건을 따라야하는가? 이를 위해 왜 self-descriptive messages와 HATEOAS가 필요한지 먼저 알아보자.
Uniform interface는 서버와 클라이언트의 독립적인 진화를 위해서 필요한 조건이다.
REST는 긴 시간에 걸쳐 진화하는 웹을 위해 만들어진 아키텍쳐이다.
REST API는 REST의 여섯 가지 아키텍쳐 스타일을 모두 만족하는 API를 말한다.
많은 API들이 REST를 따르고 있다고 말하지만 주로 Uniform interface의 self-descriptive messages와 HATEOAS를 만족하지 못하고 있다.
실제로 저 두가지 조건을 만족시키는 것에는 큰 비용이 따른다. 따라서 API 개발자들은 각자 상황에 따라서 REST를 따를지 말지를 판단해야 한다.
Naver DEVIEW, 이응준님 강연 - 그런 REST API로 괜찮은가?
이응준님 블로그 - 바쁜 개발자들을 위한 REST 논문 요약
김영한님 인프런 강의 - 모든 개발자를 위한 HTTP 웹 기본 지식