REST는 위키백과의 정의에 따르면 다음과 같다.
a way of providing interoperability between computer systems on the internet
-Wikipedia-
interoperability
: 상호운영성하지만 이렇게만 봐서는 정확히 어떤 의미인지 알기 힘들다. 먼저 REST의 역사를 살펴보면서 어떻게 REST가 만들어졌는지부터 REST라는 것이 정확히 무엇인지 알아보자.
1991년 WEB이 나왔다. 여기서 어떻게 인터넷에서 정보를 공유할 지가 문제였다. 이를 해결하는 방법으로 모든 정보를 하이퍼텍스트로 연결했다.
데이터를 표현하는 방법은 HTML 형식으로 했으며, 이를 식별하기 위한 방법으로 URI를 사용했다. 그리고 이러한 정보를 통신하는 방법이 HTTP였다.
How do i improve HTTP without breaking the Web? -Roy T. Fielding-
HTTP/1.0이 나오기 이전부터 HTTP는 Web에서 전송 방법으로 사용하고 있었고, 전세계에는 수 많은 Web 서버가 존재하고 있었다. 이 시점에서 로이 필딩은 HTTP 명세를 만들고 기능을 추가하는 작업을 해야 했다. 로이 필딩은 만약 HTTP를 변경한다면 기존의 HTTP를 사용하던 Web에 영향이 미칠 것으로 생각했다. 이를 해결하기 위해 만든 것이 HTTP Object Model 이었다. 그리고 이것이 4년 후 1998년 REST라는 이름으로 발표되고, 바로 2년 후 로이 필딩의 박사 논문으로 제출된다.
인터넷이 발전하면서 API 역시 급속도로 생겨나기 시작했다. 그 중 Microsoft는 원격에서 메서드를 호출할 수 있는 XML-RPC(1998)을 만든다. 이는 후에 SOAP이라는 이름으로 바뀐다. 그리고 2000년 2월 Salesforce는 SOAP을 이용하여 최초로 인터넷에 Salesforce API를 공개한다.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:enterprise.soap.sforce.com" xmlns:urn1="urn:sobject.enterprise.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<urn:SessionHeader>
<urn:sessionId>[sessionId retrieved from the login() call]</urn:sessionId>
</urn:SessionHeader>
</soapenv:Header>
<soapenv:Body>
<urn:create>
<urn:sObjects xsi:type="urn1:Account">
<Name>Sample Inbound Account One</Name>
</urn:sObjects>
</urn:create>
</soapenv:Body>
</soapenv:Envelope>
위 예제는 Salesforce API의 모습이며, 이는 매우 복잡하다. 4년 후 2004년 8월 Flickr API가 나왔고 이는 SOAP과 REST를 사용한 두 가지 API를 만들었다. 아래는 그 예제이다.
<s:Envelope
xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/1999/XMLSchema"
>
<s:Body>
<x:FlickrRequest xmlns:x="urn:flickr">
<method>flickr.test.echo</method>
<name>value</name>
</x:FlickrRequest>
</s:Body>
</s:Envelope>
https://www.flickr.com/services/rest/?method=flickr.test.echo&name=value
위는 같은 기능을 하는 Flickr API를 SOAP과 REST 두 형식으로 나타낸 것이다. 한 눈에 봐도 REST가 훨씬 간단해보인다. SOAP는 REST에 비해 복잡하고, 규칙이 많고, 어려워 보인다. 그에 반해 REST는 단순해보인다. 그 결과 REST는 빠른 속도로 발전하게 되었고 다음과 같은 결과를 만들었다.
http://{serviceRoot}/{collection}/{id}
형식이어야 한다.위는 마이크로소프트에서 만든 REST API 가이드라인이다. 이를 본 로이 필딩은 해당 가이드라인은 REST API가 아닌 HTTP API라고 해야한다고 말했다. 그리고 REST API를 위한 최고의 버저닝 전략은 버져닝 자체를 하지 않는 것이라고 덧붙였다. 그렇다면 무엇이 문제인 것일까?
REST API는 REST 아키텍처 스타일을 따르는 API 를 말한다.
따라서 REST 제약조건을 모두 만족한 API가 REST API이다.
Server는 자원을 가지고 있고, client는 자원을 요청하는 입장이다.
이처럼 서버와 클라이언트 각각의 역할이 명확히 구분되므로 서로간의 의존성이 줄어들게 된다.
REST의 가장 큰 특징 중 하나는 HTTP 웹 표준을 그대로 사용하고 있다. 따라서 HTTP가 stateless protocol이므로, REST 역시 stateless 특징을 갖고 있다. 무상태는 서버에서 클라이언트의 세션과 쿠기와 같은 context를 저장하지 않으므로 구현이 단순해진다. 그리고 서버는 각각의 요청을 완전히 독립된 요청으로 인식하고 처리할 수 있으므로 처리 방식에 일관성을 부여할 수 있다. 이는 서버에 부담을 줄이고, 서비스의 자유도를 높일 수 있다.
REST는 HTTP 웹 표준을 사용하므로 기존 웹에서 사용하는 인프라를 그대로 사용할 수 있다. 따라서 HTTP가 가진 캐시 처리 기능을 그대로 적용가능할 수 있다. HTTP 표준에서 사용하는 Last-Modified 태그나 E-Tag를 이용하면 캐싱 구현이 가능하다. 캐시 사용으로 많은 양의 요청을 효율적으로 처리할 수 있고 응답 시간이 빨라지는 등 서버의 자원을 효율적으로 사용할 수 있다.
REST 서버는 다중 계층으로 구성될 수 있으며 보안, 로드 밸런싱, 암호화 계층을 추가해 구조상의 유연성을 둘 수 있다. 또한 PROXY, 게이트웨이 같은 네트워크 기반의 중간매체를 사용할 수도 있다.
Code-On-Demand는 REST 서버에서 클라이언트로 코드를 보낸 후 클라이언트는 이 코드를 수행할 수 있어야 한다. 이 코드는 현재 대부분 사용하고 있는 javascript를 말한다. Code-On-Demand는 반드시 충족할 필요는 없다.
Uniform Interface는 URI로 지정한 리소스에 대한 조작을 통일되고 한정적인 인터페이스로 수행하는 아키텍처 스타일을 말한다. 즉, 통일된 Interface를 통해 어떠한 기술(Python, PHP, C#, Ruby, Java)이든 플랫폼(iOS, Android, Mac, Windows)이든 상관없이 사용할 수 있다. REST를 구성하는 제약조건 중 Uniform Interface를 제외한 조건들은 대부분 HTTP 특징과 거의 동일하기 때문에 개발자가 신경쓰지 않아도 충분히 잘 지켜진다. 하지만 Uniform Interface는 아키텍쳐 스타일로서 여러 제약조건이 있는데, REST API는 대부분 이를 지키지 못한다. 그래서 위에서 살펴봤듯이, REST가 아니라는 말이 계속 나오고 있고 이는 현재도 마찬가지이다.
위의 네 가지 조건 중에서 대부분 지켜지지 않는 것은 아래의 두 가지 조건이다. 거의 대부분 API가 이 두 조건을 지키지 못하고 있다.
Self-Descriptive Messages는 서버와 클라이언트 사이에서 주고 받는 메시지는 스스로를 설명해야한다는 의미이다. 즉, 메시지만 보고도 어떤 기능을 수행하는지 알 수 있어야 한다는 것이다. 아래의 예제를 보자.
GET / HTTP/1.1
이 요청 메시지는 목적지가 빠져있어 Self-Descriptive Messages 조건을 만족하지 못한다. 이를 만족하려면 아래와 같이 목적지 링크를 담고 있어야 한다.
GET / HTTP/1.1
Host: www.example.org
다음은 응답 메시지이다.
HTTP/1.1 200 OK
[ {op: "remove", path: "a/b/c" } ]
이 응답 메시지는 본문에 JSON 형식의 데이터를 담고 있다. 하지만 클라이언트에서 이 메시지만 보고서는 해당 본문이 어떤 데이터 형식인지 알 수 없다.
HTTP/1.1 200 OK
Content-Type: application/json
[ {op: "remove", path: "a/b/c" } ]
위와 같이 내용의 타입을 선언해야한다. 그래야 아래 본문을 해석할 수 있다. 하지만 여기서도 Self-Descriptive Messages 조건을 만족하지 못한다. 왜냐하면 op
, path
가 어떤 의미인지 알 수 없기 때문이다.
HTTP/1.1 200 OK
Content-Type: application/json-patch+json
[ {op: "remove", path: "a/b/c" } ]
이를 해결하는 방법 역시 알맞은 Content-Type을 설정하는 것이다. 위 Content-Type의 명세를 찾아서 이를 해석해야 한다. Self-Descriptive Messages 조건은 메시지를 봤을 때 온전히 해석이 가능해야한다. 오늘날의 API는 단순히 JSON이라고만 적혀있어 메시지만 보고서는 완전히 해석할 수 없는 경우가 대부분이다.
HATEOAS는 애플리케이션의 상태는 Hyperlink를 이용해 전이되어야한다는 의미이다.
위 그림에서 []
가 하이퍼링크라고 생각하자. 따라서 페이지마다 이러한 링크를 클릭하면 다른 페이지로 이동하며, 이를 애플리케이션 상태가 전이된다고 말한다. 이 구현은 HTML로 되어 있으며 대부분의 HTML은 HATEOAS를 만족한다. 간단하게 HATEOAS를 만족하는 HTML 예제 코드를 보자.
HTTP/1.1 200 OK
Content-Type: text/html
<html>
<head></head>
<body><a herf="/test">test</a></body>
</html>
JSON도 HATEOAS를 만족시킬 수 있는 방법이 존재한다.
HTTP/1.1 200 OK
Content-Type: application/json
Link: </articles/1>; rel="previous",
</articles/3>; rel="nest";
{
"title": "The second article",
"contents": "blah blah..."
}
위는 이전 페이지와 다음 페이지의 링크가 메시지를 통해 드러나 있고, Link는 JSON 표준에 등록되어 있다.
Web은 REST를 잘 적용했지만 API는 왜 되지 않을까? 그 이유에 대해 살펴보자.
흔한 웹 페이지 | HTTP API | |
---|---|---|
Protocol | HTTP | HTTP |
커뮤니케이션 | 사람-기계 | 기계-기계 |
Media Type | HTML | JSON |
위 표를 보면 웹 페이지와 HTTP API 사이에서는 미디어 타입만 다르다. 그러면 이 미디어 타입을 비교해보자.
HTML | JSON | |
---|---|---|
Hyperlink | 됨(a tag 등) | 정의되어 있지 않음 |
Self-Descriptive | 됨(HTML 명세) | 불완전 |
Self-Descriptive가 불완전한 이유는 JSON이 파싱은 가능하지만 파싱된 안의 데이터는 해석할 수 없기 때문이다. 그래서 이를 위한 문서를 따로 만들어야한다.
GET /todos HTTP/1.1
Host: example.org
HTTP/1.1 200 OK
Content-Type: text/html
<html>
<body>
<a href="https://todos/1">회사 가기</a>
<a href="https://todos/2">집에 가기</a>
</body>
</html>
GET /todos HTTP/1.1
Host: example.org
HTTP/1.1 200 OK
Content-Type: application/json
[
{"id": 1, "title": "회사 가기"},
{"id": 2, "title": "집에 가기"}
]
GET /todos HTTP/1.1
Host: example.org
HTTP/1.1 200 OK
Content-Type: application/vnd.todos+json
[
{"id": 1, "title": "회사 가기"},
{"id": 2, "title": "집에 가기"}
]
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": "집에 가기"}
]
단순히 링크를 추가하면 된다.
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"
[
{
"link": "http://example.org/todos/1",
"title": "회사 가기"
},
{
"link": "http://example.org/todos/2",
"title": "집에 가기"
}
]
Uniform Interface가 필요한 이유는 독립적인 진화때문이다. 이는 서버와 클라이언트가 각각 독립적으로 진화한다는 뜻이다. 서버의 기능이 변경되더라도 예를 들어 API의 기능이 추가되거나 삭제되어도 클라이언트에게는 전혀 영향을 미치지 않는다는 것이다. 즉 클라이언트를 업데이트할 필요가 없다. 이러한 이유가 로이 필딩이 REST를 만든 이유이다.
웹에서는 Uniform Interface를 매우 잘지키고 있다. 이는 다음과 같은 이유가 있다.
물론 이러한 변경때문에 웹 UI가 살짝 깨질 수는 있다. 하지만 최소한의 기능 동작은 한다. 하지만 모바일 앱은 웹과 다르다. 항상 업데이트가 필요하다. 심지어 서버가 변경되었을 때 클라이언트에서 필수적으로 변경되어야 하는 부분이 있어 강제로 업데이트를 시도할 때도 있다. 이러한 모바일 앱은 REST하지 않다고 볼 수 있다.
간단히 말하면 수 많은 사람의 노력에 의해 지켜질 수 있었다. HTML5 초안에서 권고안이 나오는데 6년이 걸렸고, HTTP/1.1 명세 개정판을 작업하는데 7년이 걸렸다. 이만큼 노력하고 있다. 7년동안 기능이 하나도 추가안되고 문서만 변경했다. 이러한 노력은 하위 호환성을 유지하기 위함이다. 즉, 상호운영성(interoperability)을 지키기 위한 집착이다. 이러한 집착때문에 다음과 같은 일이 있었다.
만약 이러한 노력이 없었다면 웹에서도 모바일 앱과 같이 다음과 같은 에러를 매우 자주 볼 수 있었을 것이다.
Netscape 6.0은 지원하지 않습니다.
좋은 내용 감사합니다!!