다니고있는 스타트업에 프론트엔드 인턴으로 막 들어갔을 때 개발팀 직원분들 대다수가 코로나에 걸리셨다. 혼자 코드를 보면서 낑낑대며 프로젝트 구조를 파악하고 있던 찰나에 같은 팀의 팀장님(모모님)이 세미나 발표를 하는게 어떻냐고 제안을 해주셨다. 입사한지 1~2주라 부담됐지만 결국 동기님(달코님)과 함께 REST API에 대해서 발표를 하게 됐다. 그래서 이 글은 동기님과 열심히 발표를 준비하며 정리한 글이다. 보안상의 이유로 생략, 변형한 부분도 있는점 참고바란다.
REST는 Representational State Transfer의 약자로 WWW(World Wide Web)과 같은 분산 하이퍼미디어 시스템을 위한 소프트웨어 아키텍쳐 스타일이다.
위의 정의만 봐서는 REST가 무엇인지 와닿기 힘들다. 그래서 REST를 구성하는 각 단어의 뜻을 하나씩 살펴보며 정의를 이해해보자.
우선, REST에서는 기본 데이터 표현을 자원(resource)라고 한다.
문서나 이미지, 시간 서비스, 다른 자원의 모음 또는 “사람"과 같은 가상이 아닌 객체 등 이름을 지정할 수 있는 모든 정보는 자원이 될 수 있다.
/programmers
/programmers/{name}
...
REST에서 state(상태)란 자원(resource)의 상태를 의미한다.
특정 시간의 자원의 상태를 반영하는 정보를 자원의 표현(representation) 이라고 한다.
Representation
: 기계가 읽을 수 있는 자원의 현재 상태의 표현
HTTP/1.1 200 OK
Content-Length: 6 // representation metadata
Content-Type: text/plain // representation metadata
Content-Language: en // representation metadata
hello // representation data
REST에서 전송(transfer)이란 이 representation의 전송을 의미한다.
예를 들어, 탈잉 홈페이지를 보고 있던 사용자가 탈잉 vod페이지로 가능 링크를 클릭하면, 웹 브라우저는 vod페이지를 렌더링해서 보여줄 것이다. 링크를 클릭함으로써 웹 애플리케이션의 상태가 홈페이지에서 vod페이지로 변경된 것이다.
즉, 서버에서 클라이언트로 representation을 전송(transfer)함으로써 웹 애플리케이션의 상태가 변경된 것이다.
이러한 전송은 주로 HTTP를 통해 JSON(Javascript Object Notation), HTML, XML, Python, PHP 또는 일반 텍스트 등의 형식 중 하나로 전달된다. 이 중 JSON은 언어에 구애받지 않고, 사람과 기계가 모두 읽을 수 있기 때문에 가장 일반적으로 사용되는 파일 형식이다.
정리하자면, REpresentational State Transfer는
다시 맨 처음 정의로 돌아가보자
“REST는 Representational State Transfer 의 약자”로 WWW(World Wide Web)과 같은 분산 하이퍼미디어 시스템을 위한 소프트웨어 아키텍쳐 스타일이다.
지금까지 REST의 의미를 살펴보았다.
이어서 REST는 아키텍쳐 스타일의 한 종류이기 때문에 이에 대해 정하고 있는 제약 조건들이 있다. 이러한 조건들을 충족시키는 시스템과 API에 대한 개념을 먼저 살펴보도록 한다.
REST는 기본적으로 웹의 기존 기술과 HTTP 프로토콜을 그대로 활용하기 때문에 웹의 장점을 최대한 활용할 수 있는 아키텍처 스타일이다. 이런 REST 형식을 따른 시스템을 RESTful 이라고 부른다.
REST 아키텍처 스타일을 따르는 Web API를 REST API라고 한다.
REST는 HTTP 표준을 기반으로 구현하므로, HTTP를 지원하는 프로그램 언어로 클라이언트, 서버를 구현할 수 있다. 즉, REST API를 제작하면 델파이 클라이언트 뿐 아니라, 자바, C#, 웹 등을 이용해 클라이언트를 제작할 수 있다.
웹에 존재하는 모든 자원(이미지, 동영상, DB 등)에 HTTP URI를 부여해 자원을 명시하고, HTTP Method (POST, GET, PUT, DELETE 등)을 통해 해당 자원에 대한 CRUD operation을 적용하는 방법론이다.
REST는 6가지 제약 조건이 있으며, 이를 잘 지킬 경우 웹서비스는 RESTful하게 된다.
REST의 대부분의 조건들은 HTTP API만 사용하더라도 만족시킬 수 있다.
단, Uniform Interface 조건을 만족시키는 것이 까다롭다. 따라서 REST API를 구분 짓는 가장 큰 핵심이라고 생각한다.
Uniform Interface 역시 아키텍처 스타일이므로 그에 따른 제약 조건들이 있다. 다음은 Uniform Interface에 대한 4가지 가이드이다.
/vod/view/42373
GET /orders/12345
Accept: text/plain
GET /orders/12345
Accept: application/json
예: 게시글을 조회 했다면 여기서의 상태 저이는 다음과 같다.
- 다음 게시물 조회
- 게시물 내 피드에 저장
- 댓글 달기
예시를 통해 HATEOAS를 통한 이점을 살펴보자
{
"links": [
{
"rel": "detail",
"href": "http://server/api/items/12345"
},
{
"rel": "order",
"method": "post",
"href": "http://server/api/items/order"
}
]
}
detail
과 order
로 이동할 수 있음을 알 수 있다.detail
을 응답 받기 위한 URL을 저장하지 않고 href
값을 얻어와 호출함으로써 API 엔드포인트의 변화로부터 자유로워진다.rel
의 이름으로 URI를 사용하기 때문에 URI 수정이 발생하더라도 클라이언트 사읻는 수정이 필요하지 않다.이 중 self-descriptive message와 HATEOAS의 경우 실제 코드에서 어떤 방식으로 해당 조건을 충족시킬 수 있는지 살펴보자
요청 예시
// self-descriptive X
GET/HTTP/1,1
// self-descriptive O
GET/HTTP/1.1
Host: www.example.orgz // 목적지 추가
응답 예시 1
// self-descriptive X
HTTP/1.1 200 OK
[{ "op": "remove", "path": "/a/b/c" }]
// self-descriptive X
HTTP/1.1 200 OK
Content-Type: application/json
[{ "op": "remove", "path": "/a/b/c" }]
// self-descriptive O
HTTP/1.1 200 OK
**Content-Type: application/json-patch+json** // 어떤 문법과 명세로 작성되었는지 추가
[{ "op": "remove", "path": "/a/b/c" }]
💡 json-patch란? [http://jsonpatch.com/](http://jsonpatch.com/)
응답 예시 2
HTTP/1.1 200 OK
Content-Type: application/vnd.todos+json
[
{"id: 1, "title": "회사 가기"},
{"id: 2, "title": "집에 가기"}
]
id
와 title
이 무엇을 의미하는지 정의한다.응답 예시 3
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": "집에 가기"
},
]
id
와 title
이 뭔지 의미를 정의한 명세를 작성한다.HATEOAS를 달성하기 위해 우리는 HAL JSON을 이용할 수 있다.
Hypertext Application Language으로 JSON, XML 코드 내의 외부 리소스에 대한 링크를 추가하기 위한 특별한 데이터 타입이다.
HAL은 두 개의 타입을 갖으며, HAL 타입을 이용하면 쉽게 HATEOAS를 달성할 수 있다.
다음의 예시가 REST API 조건들에 부합하는지 살펴보고, 부합하기 위해서는 어떻게 변경되어야 할지 예시를 통해 비교해보자
HATEOAS 달성 실패
HTTP/1.1 200 OK
Content-Type: application/json
{
"Content": "너무 감사합니다. 코로나때문에 비대면으로 전환되서 너무 아쉬웠어요. ㅠㅠ"
"Id": "956065",
"Name": "aaa",
"likes: "0",
...
}
HAL을 이용하여 HATEOAS 달성 성공
HTTP/1.1 200 OK
Content-Type: application/hal+json
{
"data": { // HAL JSON의 리소스 필드
"Content": "너무 감사합니다. 코로나때문에 비대면으로 전환되서 너무 아쉬웠어요. ㅠㅠ"
"Id": "956065",
"Name": "aaa",
"likes: "0",
...
},
"_links": { // HAL JSON의 링크 필드
"self": { // 현재 api 주소
"href": "주소?page=2"
},
"prev": { // 이전 리뷰 페이지 api 주소
"href": "주소?page=1"
},
"next": { // 다음 리뷰 페이지 api 주소
"href": "주소?page=3"
},
},
}
사실 REST 제약 조건을 만족하면서, 특히 Uniform Interface의 조건들을 모두 충족하면서 API를 작성하는 것은 굉장히 까다로우며 많은 비용이 요구된다. 또한 실제로 REST의 제약 조건을 다 지키는 서비스 역시 드물다.
API에 대한 문법과 명세를 설명하고 있는 문서를 제공함으로써 uniform interface의 self descriptive를 만족시킬 수 있다. 여기에 custom media type이나 profile link relation 등을 추가하여 해당 링크를 통해 바로 api 문서에 접근할 수 있도록 수정한다면 보다 완벽하게 조건을 충족시킬 수 있다.
URI에는 리소스만 명시하고 CRUD에 적절한 HTTP 메서드를 사용함으로써 리소스와 행위를 분리시킨다.
review를 리소스로 예시를 들면 API의 작성 예시는 다음과 같다.
올바른 예시
잘못된 예시
잘못된 예시를 보면 URI에 리소스만 있는 것이 아니라 행위까지 명시돼있다.
이런 경우는 보통 HTTP 메서드가 적절하게 사용되지 못하고 있기 때문인데, 주로 GET과 POST 두 가지만 사용하는 경우 혹은 둘 중 무엇을 사용해도 상관 없는 경우이다.
self descriptive와 HATEOAS를 달성하기 힘든 경우가 대다수이며 지키려면 구조 설계부터 작업이 진행되어야 하는 경우가 많다. 따라서 모든 REST 제약 조건을 만족할 수 없는 경우, 최소한 적절한 HTTP Method를 사용하여 API를 설계하도록 하자
세미나를 준비하기 전에는 REST API를 대략적으로만 알고 있었다. 사실 사내의 많은 개발자들 앞에서 세미나를 준비하면서 약간 부담스러운 것도 있었다. 하지만 동기님(달코님)과 힘을 합쳐 열심히 준비하여 REST API에 보다 자세히 알 수 있게 됐다. 무엇보다 이전까지 내가 알고 있던 REST API는 REST API가 아니었으며 HTTP API에 더 가깝다라는 것을 알게 됐다. 이번 세미나를 통해서 학습에 대한 깊이의 중요성에 대해서도 깨달을 수 있었다.
[Spring] RESTful의 HATEOAS 관련 내용 정리 - RESTful 하려면 어떤 조건들이 필요할까?