API를 구성하고 공부하면서 제일 많이 들었던 단어를 떠올리라고 한다면 단연 REST라고 말할 수 있다. 오랜만에 REST에 대한 공부를 하다보니 HATEOAS라는 생소한 개념을 찾게되어 이에 대한 포스팅을 하려고 한다.
그렇다면 REST가 뭔지에 대해 먼저 이야기 할 필요가 있다. 해당 포스팅은 REST에 대한 내용은 아니므로 간단하게 짚고 넘어가도록 하겠다.
REST는 무엇일까??
REpresentationalStateTransfer의 줄임말로 컴퓨터 시스템 간의 상호운용성을 제공하기 위한 한가지 방법이라고 한다.. 모르겠다..
REST에 대한 이해도를 높이기위해 역사에 대해 알아보자.
REST는 Roy T.Fielding(이하 로이필딩)이라는 한 대학원생이 HTTP를 개발하면서 고안한 개념이다.
로이필딩은 HTTP/1.0를 작업하다가 고민이 생겼다. HTTP의 버전을 높이면서 기존의 WEB에 문제가 없도록(상호운용성이 보장되도록) 해야했다.
간단하게 생각해보자. 요리를 할 때, 머리카락이 음식에 들어가지 않으려면 어떻게 해야할까? 위생모를 착용하거나.. 머리를 밀거나 하는 식의 제약을 두면 이를 보장할 수 있다.
이와 같이 REST는 상호운용성을 지키기 위한 제약조건의 집합으로 세상에 소개되었다.
이 집합을 아키텍쳐 스타일이라고 부르는데 그 내용은 다음과 같다.
REST 아키텍쳐 스타일
위의 제약조건들은 서버와 클라이언트를 명확히 분리하여 개발하기 위한 특성들이다. 여기서 uniform interface라는 제약은 생소할 수 있는데 오늘의 주인공인 만큼 새 목차에서 알아보자.
REST의 제약들 중에서도 uniform interface는 서버와 클라이언트 간의 상호운용성을 유지하기 위한 제약이다. 이는 서버와 클라이언트의 독립적인 업데이트를 보장한다는 이야기와 같다.
굉장히 어지럽지만,, uniform interface도 제약조건의 집합을 가지고 있다. 이는 다음과 같다.
uniform interface
위의 요소들이 어떤 의미를 가지는지 HTTP에 적용해보자.
설명되지 않은 두가지는 별도의 목차로 알아보도록 하겠다.
메세지가 스스로를 설명할 수 있어야 한다. (메세지만 보고서 그 의미를 이해를 할 수 있어야한다.)
아래의 응답을 본다면 의미를 이해할 수 있을까??
{"id":1,"name":"제이슨"}
물론 유추는 할 수 있을 것이다. 하지만 정확히는 알 수 없다. 생각을 해보자면 흐름은 아래와 같을 것 이다.
id
라고 표기되어있는 key가 어떤 의미를 가지는지, name
은 또 사람의 이름인지 제품의 이름인지 알수가 없다.그렇다면 어떻게 작성하면 self-descriptive message가 될까?
1. HTTP/1.1 200 OK
2. Content-Type: application/json
3. {"id":1,"name":"제이슨"}
위와 같이 작성하면 1번과 2번의 경우, 해결된 것 처럼 보인다. 하지만 key, value가 각각 어떤 의미를 가지는지는 알 수 없다. 이는 다음의 두가지 방법으로 명세를 제공해 처리할 수 있을 것이다.
self-descriptive하게 메세지를 제공함으로서, 서버가 제공하는 내용을 클라이언트는 명확히 이해할 수 있고 변경이 있는 경우에도 어렵지 않게 적용할 수 있게 된다.
어플리케이션의 상태를 Hypermedia를 통해 전이 해야한다.
간단하게 말을하면 client의 상태를 링크를 통해서 전이하도록 처리해야한다는 의미이다. 예시를 보고 어떤점이 좋은지를 알아보자.
중요한 것은 API를 사용하는 클라이언트의 request의 흐름이다. API를 사용하는 클라이언트는 결국 서버가 제공하는 API들을 특정 흐름에 따라 사용을 하게된다.
주소록의 중복된 정보를 체크하고, 그 내용을 조회 혹은 삭제하는 API가 있다고 가정하고 그림 1의 예시를 보자.
그림 1. API request의 흐름
클라이언트는 위와 같은 흐름으로 API를 호출하게 될 것이다. 그렇다면 이 흐름을 전이라고 볼 수 있으며, 이 전이를 Hyper link로 표현한다면 HATEOAS라고 말을 할 수 있다는 의미이다.
API대해 설명을 하면, 그림 1의 POST /duplicate는 주소록의 중복된 데이터를 확인하고 중복 내용을 value로 저장하는 key를 반환한다. 해당 key를 다른 API들은 사용하게된다.
그림 1의 POST /duplicate의 응답을 HATEOAS로 바꾸어 보자. (기존 data는 ...로 생략, 응답의 key로 abcdefghifkey를 받는다고 가정)
"data": {
...
"self": "http://localhost:8080/duplicate"
"duplicate-list": "http://localhost:8080/members/abcdefghifkey
"delete-all": "http://localhost:8080/delete-all/members/abcdefghifkey
}
위와 같이 data외에 hyperlink로 다음 전이할 endpoints를 받는다면, 클라이언트는 응답의 duplicate-list
, delete-all
의 Hyper link를 선택해 전이를 할 수 있게 된다.
이를 통해 endpoints가 변경이 되어도, 동일한 기능을 제공받을 수 있으며 상호운용성을 보장할 수 있게된다.
다만 위의 예시도 규격이 아니기 때문에 완벽하다고 볼수는 없다. 이를 위한 규격인 HAL을 통해 변경해보자.
"data": {
...
}
"_links": {
"self": {
"href": "http://localhost:8080/duplicate"
}
"duplicate-list": {
"href": "http://localhost:8080/members/abcdefghifkey
}
"delete-all": {
"href": "http://localhost:8080/delete-all/members/abcdefghifkey
}
}
이제 HATEOAS를 만족하는 응답을 완성했다고 볼 수 있겠다.
위와 같이 uniform interface 제약을 고려하여 API를 구성한다면, 클라이언트와 서버의 독립적인 업데이트를 하기 유리한 환경을 만들 수 있다.
클라이언트와 서버를 모두 제어할 수 있는 환경에서는 필수가 되지 않겠지만, 적용을 고려해보는 것 자체로도 학습이 될 수 있는 개념이었다고 생각한다.
필자는 추후에 운영중인 API를 HTTP API보다 REST API에 가깝게 만들기 위해 적용을 해볼 계획이다.