aeFactory
[네트워크] REST, REST API, RESTful 본문
REST가 뭘까?
REST는 Reprenentational State Transfer의 약자로 사실 약자만 보고는 직관적으로 어떻게 하는게 REST인지 알아보기는 쉽지 않습니다. 본 글의 4장 Uniform Interface까지 설명하면 풀이가 가능합니다. REST는 HTTP를 사용하기 위한 아키텍쳐 쳐의 제약 조건입니다. 따라서 제약 조건을 하나씩 살펴보면서 왜 이런 제약 조건들이 붙었나, 사용했을 때 어떤 장점이 있나 살펴보겠습니다.
API와 REST API, HTTP API
일단 REST API와 HTTP API를 구분할 필요가 있습니다. 일단 API(Application Programming Interface)는 이름에서 알 수 있듯 Application 간의 서비스를 주고 받을 때 서로 통신할 계약을 의미합니다. 어떤 A 애플리케이션이 B 애플리케이션의 기능을 이용하고 싶을 때 A 애플리케이션이 B 애플리케이션의 모든 코드를 알 필요는 없습니다. 쉽게 말하면 B 애플리케이션이 제공하는 B 애플리케이션에게 요청할 수 있는 함수를 알고있다가 A 애플리케이션이 이를 호출하면 B 애플리케이션이 결과만 던져주면 됩니다. 이때 B 애플리케이션이 제공한 함수들을 API라고 할 수 있습니다. 그럼 REST API란? REST하게 서비스를 주고 받을 수 있도록 잘 설계된 API입니다. 그럼 HTTP API란? HTTP 표준을 잘 지켜서 서비스를 주고 받을 수 있도록 설계된 API라는 것입니다.
REST API의 제약 조건
1) Client - Server 구조
REST한 API는 서비스 통신을 주고 받을 주체들이 Client와 Server여야 합니다. Client는 사용자에 관련된 처리를 담당하는 역할, 서버는 API와 로직을 처리하는 역할로 확실하게 구분되어 있어야 합니다.
2) Stateless
REST한 API는 서버에 어떤 작업을 요청할 때 상태 정보를 기억하지 않아야 합니다. 서버 입장에서는 요청이 들어왔을 때 어떤 Client가 보냈는지, 그 Client가 이전엔 어떤 요청을 했는지, 결과값은 어땠는지 등등 어떠한 정보도 유지하고 있지 않습니다. 그러한 상태 정보 유지가 없어도 요청을 처리할 수 있도록 API를 설계해야합니다.
이렇게 설계하게되면 어떤 Client가 요청하더라도 어떤 Server든 동일한 처리를 할 수 있는 장점이 있습니다. 따라서 Server 입장에서는 Server 환경을 분산하는 등 자원 관리에 유리하며 Client 입장에서는 내가 기존에 어떤 Server에 연결하고 요청했는지와 관계 없이 언제나 요청 결과를 받아볼 수 있게 됩니다.
3) cache
Caching이 가능해야합니다. 예를 들면, 값 변화 없이 HTTP GET Method를 사용할 경우 Server에서 값 변경 여부를 판단하여 변화 없을 시 캐싱된 값을 이용하여 요청을 API 요청을 발생시키지 않아도 되기 때문에 부담을 줄일 수 있습니다.
4) Layered System
시스템이 계층화되어 있어야합니다. Client는 Server의 계층이 어떻게 되어있든 Server API만 호출하고 어느 서버에 연결되었는지 알 수 없습니다. 중간에 시스템 확장성이나 부하 관리를 위한 계층에 있는 서버를 통해 Server API에 접속해 있든 API Server에 직접 접근하든 Client는 신경 쓸 필요가 없습니다.
5) Uniform Interface
전체 구조를 파악할 수 있고 일반화된 인터페이스를 제공해야합니다. 잘 와닿지 않습니다. 심지어 해당 제약 조건은 REST의 제창자 Roy Fielding이 말하길 거의 대부분 제대로 지켜지지 않고 있다고 합니다. 거의 모든 Web Service를 제공하는 기업에서 목 놓아 REST를 외치고 있는데도 지켜지지 않고 있다니 얼마나 복잡하고 지키기 어려운 제약조건인지 4장에서 Uniform Interface의 조건을 살펴보겠습니다.
6) Code On Demand (선택)
서버가 제공한 코드를 클라이언트가 그대로 실행할 수 있어야 합니다. 평소처럼 정적 데이터나 파일이 응답으로 오는 것이 아니라 client가 바로 실행할 수 있는 코드를 보내 이를 실행하는 것을 Code On Demand라고 합니다. 대표적으로 javascript가 있습니다. 해당 제약 조건은 선택적으로 지켜도되고 지키지 않아도 된다고 합니다.
Uniform Interface
해당 제약 사항은 가장 잘 지켜지지 않고 있다고 Roy Fielding이 언급한 바 있습니다. Uniform Interface의 제약 조건 안에는 4가지의 세부 제약 조건이 있습니다. 각각의 제약 조건을 살펴보도록 하겠습니다.
1) Identification of resources
첫 번째 제약 조건인 자원의 식별입니다. 자원은 생성된 후, 변하거나 삭제될 수 있습니다. 이 때 이 자원을 식별하기 위해서는 자원의 생성시에 부여된 고유의 식별자를 가져 자원이 변경되더라도 자원의 식별이 가능하도록 해야합니다. 이를 HTTP에서 URI에 자원 고유의 식별자를 넣어 구분할 수 있도록 해야 합니다.
요청 | 자원이 식별되지 않은 URI |
회원 목록 조회 | /get-member-list |
회원 등록 폼 | /get-creat-member-form |
회원 등록 | /creat-member |
회원 조회 | /get-member-by-name |
회원 수정 폼 | /get-update-member-form |
회원 수정 | /update-member |
회원 삭제 | /delete-member |
" 캬 너무 잘 설계하지 않았나요? " 라고 하면 유언이 될 수도 있습니다(어떻게 유언이 캬 ㅋㅋ). 해당 URI 어디에도 자원을 식별할 수 없습니다. 일단 자원이 명시되어 있지도 않습니다. REST API를 설계할 때 중요한 점을 꼽으라면 해당 요청 사항의 자원에 해당하는게 뭔지 파악하는 것입니다. 해당 요청의 자원은 "회원"입니다. 식별 되어야할 것은 명령어들이 아니기 때문에 REST하게 이를 고치면 다음과 같습니다.
요청 | 자원이 식별되지 않은 URI | REST한 URI |
회원 목록 조회 | /get-member-list | /members |
회원 등록 폼 | /get-creat-member-form | /members/new |
회원 등록 | /creat-member | /members/new |
회원 조회 | /get-member-by-name | /members/{id} |
회원 수정 폼 | /get-update-member-form | /members/{id}/edit |
회원 수정 | /update-member | /members/{id}/edit |
회원 삭제 | /delete-member | /members/{id} |
" (아까 그 캬): 그럼 회원 등록 폼이랑 회원 등록, 회원 조회와 회원 삭제, 회원 수정 폼과 회원 수정은 어떻게 구분하나요? " 일단 URI의 역할은 자원을 구분하는 것입니다. 이 URI들은 REST에 의하면 표현 방식으로 구분될 수 있습니다. 이는 두 번째 제약 조건에서 살펴보겠습니다.
2) Manipulation of resources through representations
표현을 통한 자원의 조작. 이 제약 조건이 위의 이름이 같은 URI를 REST에서 구분해내는 방식입니다. HTTP에선 HTTP Mothod가 해당 역할을 할 수 있습니다.
요청 | REST한 URI | HTTP Method |
회원 목록 조회 | /members | GET |
회원 등록 폼 | /members/new | GET |
회원 등록 | /members/new | POST |
회원 조회 | /members/{id} | GET |
회원 수정 폼 | /members/{id}/edit | GET |
회원 수정 | /members/{id}/edit | POST |
회원 삭제 | /members/{id} | POST |
위와 같이 HTTP Mothod를 통해 같은 자원을 다루는 요청 사항을 표현에 의해 구분할 수 있습니다.
1) 자원의 식별과 2) 표현을 통한 자유의 조작은 REST를 표방하는 곳에서는 모두 잘 지키고 있다고 하는데 아래 3), 4) 조건이 잘 지켜지지 않고 있다고 합니다.
3) Self-descriptive message
메시지는 메시지만 보고도 이해할 수 있어야한다. 즉 메시지가 메시지 스스로 설명하는 부분을 포함해야 한다는 것입니다. 알려만 주면 모두 이해할 수 있는 기계한테 우리 응답을 보여주면 해석해낼 수 있는지 생각해봅시다. 일단 위의 요청 사항 중 회원 조회를 하는 상황을 설정하겠습니다.
회원의 식별자는 10이고 회원의 이름은 "은하비"이고 요청에 대한 응답은 성공했다고 가정해보겠습니다.
{"id":10,name:"은하비"}
이렇게 응답이 오면 기계는 해석할 수 있을까요? 아닙니다. 기계는 이 문자들의 나열이 HTTP 응답인지조차 파악하지 못 했습니다.
HTTP/1.1
{id:"10",name:"은하비"}
HTTP 응답임과 버전을 추가주었습니다. 기계는 해석할 수 있을까요? 안됩니다. 해당 HTTP 응답의 상태가 성공적인 응답인지 아닌지 상태를 알 수 없습니다.
HTTP/1.1 200 OK
{id:"10",name:"은하비"}
이러면 알 수 있을까요? 아직도 아닙니다. 해당 응답은 json문법으로 쓰여진걸 휴리스틱한 우리는 알지만 기계는 아직 알 수 없습니다. 응답이 json인 것을 알려주겠습니다.
HTTP/1.1 200 OK
Content-Type: application/json
{id:"10",name:"은하비"}
해치웠나? 아직도 아닙니다. json 문법을 알게된 기계는 id라는 필드의 값이 10, name이라는 필드의 값이 "은하비"임을 해석해냈지만 id가 뭔지 name이 뭘 설명하는지 알 수 없습니다.
이럴 때 사용하는 방법이 2가지 있는데, 그 중 첫 번째는 미디어 타입을 직접 정의하는 방법입니다.
위 링크에 가면 직접 Media Type을 정의하고 등록할 수 있습니다. 그 Media Type 명세에 필드의 값이 각각 어떤 것을 의미하는지 정의에 적을 수 있으므로 이제야 기계가 이해할 수 있습니다.
두 번째가 주로 사용되는 방법인데, 이미 구현되어있는 Media Type을 이용하는 것입니다. 많이 사용되는 Media Type은 HAL(Hypertext Application Language) 타입이 있습니다.
4) HATEOAS(Hypermedia As The Engine of Applcation State)
애플리케이션의 상태 전이는 하이퍼링크로 이루어져야 한다. 라는 제약 조건입니다. 위의 회원 조회를 예로 들면 식별자가 10인 회원을 조회하는 HTTP 응답에서는 회원을 조회했으니 해당 회원을 수정할 수도, 좀 더 세부적인 회원 정보를 요청할 수도 또는 조회된 회원을 삭제할 수도 있을 것이다. 이렇게 현재 상태에서 전이할 수 있는 링크를 포함하는 것이 HATEOAS 제약 조건입니다. 이렇게되면 어떤 API 요청에서도 모든 전이 가능한 상태를 탐색할 수 있게됩니다.