🙄 포스팅 계기
프로젝트 진행 중에 REST API를 작성하는 과정에서 제가 구현한 것들이
REST API 형식에서 벗어나 보인다.
라고 피드백이 있었습니다.피드백 과정에서
REST API를 어떻게 작성하는 것이 좋을까요?
라고 질문을 드렸고, 돌아온 답변과 제가 찾아본 자료들을 보니 너무 추상적인 느낌이 들었습니다.그래서 REST API를 알기 위해서 REST가 뭔지에 대한 제 확실한 정의가 있어야 더 나은 개발자가 될 수 있다고 판단해 포스팅이 시작하게 되었습니다.
위키백과의 REST(REpresentational State Transfer)의 정의
- REST a way of providing interoperability between computer systems on the internet
- 컴퓨터 시스템과의 상호 운용성을 제공합니다.
이러한 정의만으로는 이해하기 어려웠습니다.
🙋♂️접근법을 조금 달리, 기원과 배경을 이해하는 것이 전체 개념을 파악하는 데 도움이 될 것 같습니다!
그렇다면
REST는 어떤 계기로 나오게 되었을까요 ?
기원
- Q) 어떻게 인터넷에서 정보를 공유할까 ?
- A) 정보들을 하이퍼텍스트로 연결하자 !
표현 방식: HTML
- HTML이란 형식으로 정보를 표현하고
식별자: URI
- 정보들에 대한 식별자로 URI를 이용하고
전송 방법: HTTP
- 그 정보들을 전송하는 방법으로 HTTP 프로토콜을 이용한다.
Roy T.Fielding,
- "How do I improve HTTP without breaking the Web?"
Roy Fielding은 94년 HTTP/1.0 프로토콜 작업을 참여했습니다.
당시 HTTP 1.0 명세가 나오기 전에 이미 HTTP는 WEB에 전송 프로토콜로 이용되고 있었고, 당시 웹은 급속도로 성장하는 단계였습니다.
이 시점에서 Roy Fielding은 HTTP를 적립하고 명세의 기능을 더하고, 기존의 기능을 고치는 단계에 들어섭니다.
하지만 당시에 문제가 하나 있었었습니다.
HTTP 프로토콜을 고치게 되면 당연히 기존의 WEB과 호환성 문제를 피할 수 없다는 문제였죠.
Roy Fielding은 생각합니다.
어떻게하면 WEB을 망가뜨리지 않고 HTTP를 진보할 수 있을까 ?
Roy Fielding은 이러한 생각을 바탕으로 HTTP Object Model을 만들었습니다. 이 HTTP Object Model은 4년 이후
Microsoft Research에서 REST라는 이름으로 공개
가 됩니다.
Roy T. Fielding, Microsoft Research
Roy Fielding은 Microsoft Research 발표 2년 뒤, 박사 논문으로 REST를 발표하게 되고 이 논문이 오늘날 알고 있는 REST를 정의하는 논문입니다.
Roy T. Fielding,
- "Architectural Styles and the Design of Network-based Software Architectures"
- 아키텍처 스타일과 네트워크 기반 소프트웨어 아키텍처 설계
REST의 등장 배경을 알게 되었으니 API도 알아보겠습니다 !
1998년 Microsoft가 원격으로 다른 시스템의 메소드를 호출하는 XML-RPC라는 프로토콜을 만들고, 나중에 SOAP로 이름으로 바뀌게 됩니다.
이후 Salesforce라는 회사에서 API를 공개하는데 거의 최초의 API라고 볼 수 있다.
Salesforce API(2002.2) SOAP
사진의 API는 단순히 어떤 ID로 Object 하나를 가져오는 API의 요청 메시지인데,, 비효율적이고 복잡했습니다.
이러한 이유로 인기가 없었습니다🤦♂️
4년 후 flicker는 API(2004.8)를 여러가지 형태(SOAP, REST)로 만들었습니다.
SOAP 형식
REST 형식
두 개의 API 차이가 어떤가요 ?
REST 형식은 거의 같은 일을 SOAP 형식보다 코드가 훨씬 간결했습니다.
즉, SOAP는 복잡하고, 규칙이 많으며 어려웠지만
REST는 단순하고, 규칙이 적으며 쉬웠습니다.
이러한 이유로 두 형식의 API 사용량이 크게 차이가 나게 됩니다.
이렇게 WEB의 API가 REST로 정착이 되나 싶었는데 CMIS(2008)이 나왔습니다.
CMIS의 REST 바인딩 지원
을 본 Roy Fielding
은 "No REST in CMIS"
라고 말했습니다. 다른사람들에게는 충분히 REST로 보였지만 정작 REST를 만든 Roy Fielding은 REST가 아니라고 한거죠.
하지만 이것 역시
Roy Fielding은 REST API가 아닌 HTTP API
라고 해야한다. 라고 했습니다.
또한 Roy Fielding은 아래와 같이 말하였습니다.
사람들이 알고 있던 REST API와 REST를 만든 Roy Fielding이 말하는 REST가 너무 많이 다른 것 같습니다.
뭐가 문제인 걸까요 ?
REST API를 한번 자세히 알아봐야겠습니다 !
REST 아키텍쳐 스타일을 따르는
그렇다면 REST는 ?
아키텍쳐 스타일 ?
REST는 아키텍쳐 스타일이면서 동시에 하이브리드 아키텍쳐 스타일이라고 말합니다.
Why?
아키텍쳐 스타일과 동시에 아키텍쳐 스타일의 집합
구성 스타일
1. client-server
2. stateless
3. cache
4. uniform interface
5. layered system
6. code-on-demand (optional)
ㄴ> 서버에서 코드를 클라이언트로 보내서 실행할 수 있어야 한다. (Java Script)
대체로 오늘날 REST API는 위 구성 스타일을 잘 지키고 있습니다.
Why? HTTP만 잘 따라도 대체로 uniform interface를 제외한 구성은 잘 지킬 수 있습니다.
uniform interface 스타일의 구성 요소를 확인해볼까요 ?
Uniform interface의 제약조건은 4가지가 있는데
identification of resources
와manipulation of resources through representation
의 제약 조건은 잘 지켜지고 있습니다.
아래의 2가지는 거의 모든 REST API는 아래 두가지는 지키지 못하고 있습니다.
1. self-descriptive messages
메시지는 스스로를 설명해야 한다.
GET / HTTP/1.1
이 HTTP 요청 메시지는 뭔가 빠져있어서 self-descriptive 하지 못합니다.
Host: www.example.org
가 빠져있음.HTTP/1.1 200 OK [ { "op": "remove", "path": "/a/b/c"} ]
Content-Type: application/json
추가op
와 path
의 의미를 모릅니다. 즉 정보가 부족하기에 미디어 타입을 정의해줘야 합니다.Content-Type: application/json-patch+json
patch+json
라는 형식임을 명시함으로서 jsonPatch
라는 명세를 찾아가서 확인하고 해석하면 이 메시지의 의미를 이해할 수 있다.
self-descriptive를 만족하는 코드
HTTP/1.1 200 OK Content-Type: application/json-patch+json [ { "op": "remove", "path": "/a/b/c" } ]
self-descriptive messages는 메시지를 봤을 때 메시지의 내용으로 온전히 해석이 가능해야 합니다.
하지만 오늘날의 코드는 미디어 타입에 그냥 json이라고만 되어있지 이걸 어떻게 해석해야 하는가는 알기 어렵기 때문에 self-descriptive message의 제약조건은 지켜지지 않습니다.
2. HATEOAS(hypermedia as the engine of application state)
애플리케이션 상태의 전이
글 목록 보기
, 글 쓰기
, 글 저장
, 생성된 글 보기
, 목록 얻기
등의 과정들로 하이퍼링크를 통해 상태가 바뀌게 되는 것 입니다.HATEOAS Sample Code
HTTP/1.1 200 OK
Content-Type: text/html
<html>
<head></head>
<body><a href="/test">test</a></body>
</html>
HTML의 경우 HATEOAS를 만족하는데 a태그를 통해서 하이퍼링크가 나와있고, 이 하이퍼링크를 통해 그 다음 상태로 전이가 가능하기 때문이다.
HTTP/1.1 200 OK
Content-Type: application/json
Link:
</articles/1>; rel="previouse",
</articles/3>; rel="next";
{
"title": "The second article",
"contents": "bbb...bbb..."
}
json도 링크 헤더를 이용해 HATEOAS를 만족할 수 있다.
왜 Uniform Interface가 필요할까 ?
독립적 진화
독립적 진화란 ?
서버와 클라이언트가 각각 독립적으로 진화한다.
서버가 기능이 바뀌고, 기능이 새로 추가되고 기존 API가 변경되고, API가 삭제되고 URI가 바뀌고의 변경이 있을 때 Client는 업데이트 할 필요가 없다. 이것이 독립적인 진화 -> REST를 만들게 된 계기 "How do I improve HTTP without breaking the Web" --> 로이필딩이 HTTP 1.0을 만들 때 고민했던 것
즉, REST가 목적하는 바가 독립적 진화이다.
독립적 진화를 달성하기 위해서는 Uniform Interface가 반드시 만족이 돼야한다.
그렇다면 REST는 지켜지고 있는가 ?
--> 사례로 WEB이다. (Safari, chrome 등...)
단 (모바일로 접속 시) UI 페이지가 깨질 수 있지만 동작은 한다.
어떻게 가능할까 ? --> 엄청난...노력
상호운용성(interoperability)에 대한 집착
이런 노력이 없다면 웹도 아래처럼 될 수 있다
웹 서버가 브라우저 호환성을 고려하지않고 구현했을 때 발생할 수 있는 현상
만약 HTML명세나 hTTP 명세를 만드는 사람들이 상호운용성을 위한 노력을 하지 않았다면 위의 현상을 자주 발견할 수 있을 것이다.
REST가 웹의 독립적 진화에 도움을 주었을까 ?
이렇게 된 것은 HTTP나 URI를 만드는 사람들이 REST에 영향을 많이 받은 것도 있곘지만 그사람이 그사람이다.
로이필딩이 REST를 만들며 HTTP와 URI 명세의 저자 중 한명이기 때문에Reminder: Roy T. Fielding이 HTTP와 URI 명세의 저자 중 한명
그럼 REST는 성공했는가 ?
그런데 REST API는 ?
그렇다면 REST API도 저 제약 조건들을 다 지켜야 하는건가 ?
An API that provides network-based access to resources via a uniform interface of self-descriptive messages containing hypertext to indicate potential state transitions might be part of an overall system that is a RESTful application
-- Roy T. Fielding하이퍼텍스트를 포함한 self-descriptive한 메시지의 uniform interface를 통해 리소스에 접근하는 API
다시 SOAP과 REST를 보면...
SOAP는 복잡하고, 규칙이 많고, 어렵다.
REST는 단순하고 규칙이 적고, 쉽다.
고 알았는데,, 사실은 REST도 어렵다..
원격 API가 꼭 REST API여야 하는건가 ?
RST API가 아니여도 상관이 없다.
"REST emphasizes evolvability sustain on uncontrollable system. If you think you have control over the system or aren't interested in evolvability, don't waste your time arguing about REST. - Roy T. Fielding
시스템 전체를 통제할 수 있다고 생각하거나, 진화에 관심이 없다면 REST에 대해 따지느라 시간을 낭비하지 말자.
시스템 전체를 통제한다 ?
서버 개발자가 클라이언트 개발자를 통제할 수 있는 상황 & REST Clinet와 Server을 한명의 개발자가 다 만들 경우
진화에 관심이 없다 ?
최근 업데이트가 쌓이더라도 신경쓰지 않아도 된다. (진화하지 않는다.)
오랜 시간에 걸쳐 진화하는 시스템을 설계하고 싶을 경우 REST를 따라야한다.
그럼 이제 어떻게 할까 ?
"I am getting frustrated by the number of people calling any HTTP-base interface a REST API... Please try to adhere to them or choose some other buzzword for your API." - Roy T. Fielding
로이필딩 : 제약조건을 따르던지 아니면 다른 단어를 써라.
API는 왜 REST가 잘 안될까 ? 일반적인 웹과 비교를 해보자 !
흔한 웹 페이지 | HTTP API | |
---|---|---|
Protocol | HTTP | HTTP |
커뮤니케이션 | 사람ㆍ기계 | 기계ㆍ기계 |
Media Type | HTML | JSON |
HTTP API는 기계가 해석하기 때문에 Media Type이 다르다(JSON)
기계가 이해할 수 있는 format을 사용하게 된다.
문제의 원인이...Media Type이다 !
HTML | JSON | |
---|---|---|
Hyperlink | 됨 (a 태그 등) | 정의되어 있지 않음 |
Self-descriptive | 됨 (HTML 명세) | 불완전* |
JSON의 경우 문법 해석은 가능하지만 의미를 해석하려면 별도로 문서가(API 문서 등) 필요하다.
간단한 TodoList로 HTML과 JSON의 문서를 비교해보자 !
HTML을 Self-descriptive를 비교해보면
1. 응답 메시지의 Content-Type을 보고 media type이 text/html임을 확인한다.
2. HTTp 명세에 media type은 IANA에 등록되어있다고 하므로, IANA에서 text/html의 설명을 찾는다.
3. IANA에 따르면 text/html의 명세는 http://www.w3.org/TR/html이므로 링크를 찾아가 명세를 해석한다.
4. 명세에 모든 태그의 해석방법이 구체적으로 나와있으므로 이를 해석하여 문서 저자가 사용자에게 온전히 의미를 전달해 줄 수 있다.
HATEOAS로 보면 a 태그를 이용해 표현된 링크를 통해 다음 상태로 전이될 수 있으므로 HATEOAS를 만족한다.
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>
반면에 JSON은 Self-descriptive를 보면
1. 응답 메시지의 Content-Type을 보고 media type이 application/json 임을 확인한다.
2. HTTP 명세에 media type은 IANA에 등록되어 있어있다고 하므로, IANA에서 application/json의 설명을 찾는다.
3. IANA에 따르면 application/json의 명세는 draft-ietf-jsonbis-rfc7159bis-04 이므로 링크를 찾아가 명세를 해석한다.
4. 명세에 json 문서를 파싱하는 방법이 명시되어있으므로 성공적으로 파싱에 성공한다. 그러나 "id"가 무엇을 의미하고, "title"이 무엇을 의미하는지 알 방법은 없다. --> 온전한 해석에 실패하기 때문에 Self-descriptive가 되지 못한다.
HATEOAS 측면에서 볼 경우 다음 상태로 전이할 링크가 없다. --> HATEOAS도 실패하게 된다.
HTML은 HATEOAS와 Self-descriptive를 만족하지만
JSON은 둘 다 만족하지 못한다.
GET /todos HTTP/1.1
Host: example.org
HTTP/1.1 200 OK
Content-Type: application/json
{
{"id": 1, "title": "회사 가고 싶어요"},
{"id": 2, "title": "집에 안갈거예요"}
}
그런데 Self-descriptive와 HATEOAS가 독립적 진화에 어떻게 도움이 될까 ?
Self-descriptive는 확장 가능한 커뮤니케이션
HATEOAS 애플리케이션 상태 전이의 late binding
전이가 완료되고 나서야 그 다음 전이될 수 있는 상태가 결정된다. -> 어떤 링크를 따라서 그 페이지로 간다. 그 페이지에 가서 하이퍼링크들을 보고 나서야 확장이 된다. -> late binding이 되기 때문에 서버가 링크를 마음대로 동적으로 바꿀 수 있다. 따라서 동시적인 진화가 가능하다.
그럼 REST API로 바꿔보자
여러가지 방법이 있는데 첫 번째 Media type를 바꾸는 방법이다.
Content-Type: application/json
-> Content-Type: application/vnd.todos+json
단점 : 매번 media type을 정의해야한다.
다음 방법은 Profile을 쓴다.
1. "id"가 뭐고 "title"이 뭔지 의미를 정의한 명세를 작성한다.
2. Link 헤더에 profile relation으로 해당 명세를 링크한다.
3. 이제 메시지를 보는 사람은 명세를 찾아갈 수 있으므로 이 문서의 의미를 온전히 해석할 수 있다.
4.
Content-Type: application/json
의 밑에
Link: <https://example.org/docs/todos> rel="profile"
=> 이 방법
단점
1. 클라이언트가 Link 헤더(RFC5988)와 profile(RfC6906)을 이해해야한다.
2. Content negotiation을 할 수 없다.
- 클라이언트가 지원하지 못하는 상황을 서버가 알 수 없다. 미디어타입을 판단하는 것이 아닌 링크 헤더를 판단하기 때문에.
data로 data에 다양한 방법으로 하이퍼링크를 표현한다.
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": "https://example.org/todos/1", -> 이 부분
"title": "회사 가고 싶어요"
},
{
"Link": "https://example.org/todos/2", -> 이 부분
"title": "회사 가고 싶어요"
},
}
단점 : 이 링크가 어떻게 표현되는지 직접 정의해야한다.
data에 다양한 방법으로 하이퍼링크를 표현한다.
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": {
"todo" "https://example.org/todos/1", -> 이 부분
},
"data" [{
"id": 1,
"title": "회사 가고 싶어요"
},
"id": 2,
"title": "집에 가기 싫어요"
}]
}
단점 : 링크를 표현하는 방법을 직접 정의해야한다.
HATEOAS
data에 다양한 방법으로 하이퍼 링크를 표현한다.
JSON으로 하이퍼링크를 표현하는 방법을 정의한 명세들을 활용한다.
GET /todos HTTP/1.1
Host: example.org
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json -> 이 부분
Link: <https://example.org/docs/todos> rel="profile"
{
"data" [{
"Type": "todo",
"id": 1,
"attributes": { "title": "회사 가고 싶어요" },
"links": { "self": "http://example.com/todos/1" } -> 이 부분
}, {
"Type": "todo",
"id": 2,
"attributes": { "title": "열심히 할게요" },
"links": { "self": "http://example.com/todos/2" } -> 이 부분
},
}
단점: 기존 API를 많이 고쳐야한다. (침투적)
HATEOAS
HTTP 헤더로 Link, Location 등의 헤더로 링크를 표현한다.
POST /todos HTTP/1.1
Content-Type: application/json
{
"title" "점심 약속"
}
HTTP/1.1 204 No Content
Location: /todos/1 -> 이 부분
Link: </todos/>; rel="collection" -> 이 부분
단점: 정의된 relation만 활용한다면 표현에 한계가 있다.
결론 : HATEOAS는 data, 헤더 모두 활용하면 좋다.
종류 | 예 |
---|---|
uri | http//example.org/user/heondong |
uri reference (absolute) | /users/heondong |
uri reference (relative) | user |
uri template | /users/{username} |
다 괜찮음
NO
"A REST API should be entered with no prior knowledge beyond the initial URI (bookmark) and set of standardized media types that are appropriate for the intended audience (i.e., expected to be understood by any client that might use the API).
-- Roy. T. Fielding
의도한 저자가 이해할 수 있으면 상관 없다.
ex) 회사 내에서만 쓰는 API고 이 Media type을 모두 이해한다면 굳이 IANA에 등록할 필요는 없다.
그래도 IANA에 등록하면 좋다.
Media type을 누구나 쉽게 사용할 수 있게 되고, 이름 충돌을 피할 수 있으며, 등록이 별로 어렵지 않다(고 주장함)
오늘날 대부분의 "REST API"는 사실 REST를 따르지 않고 있다.
REST의 제약조건 중에서 특히 Self-descriptive와 HATEOAS를 잘 만족하지 못한다.
REST는 긴 시간에 걸쳐(수십년) 진화하는 웹 애플리케이션을 위한 것이다.
REST를 따를 것인지는 API를 설계하는 이들이 스스로 판단하여 결정해야한다.
REST를 따르지 않겠다면, "REST를 만족하지 않는 REST API"를 뭐라고 부를지 결정해야 할 것이다.
참고 자료