일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- findNavController
- Collections Function
- studywithme
- NumberPIcker
- gradle plugin
- layout_constrainedWidth
- todo
- Lifecycle
- BottomSheetDialogFragment
- 기기고유값
- multipart
- Room
- 화면 회전
- Load failed
- log
- Popup menu background color
- SSAID
- Android
- WorkManager
- Navigation
- DialogFragment
- 생명주기
- ThreeTen Backport
- layout_constrainedHeight
- DataBinding
- http
- kotlin
- json
- Retrofit2
- RecyclerView
- Today
- Total
chacha's
📜 REST API 본문
목차
그런 REST API로 괜찮은가? - naver D2 의 내용을 정리한 게시물입니다.
REST를 개발한 로이 필딩은 아래와 같이 말합니다.
"REST API must be hypertext-driven"
"REST API를 위한 최고의 버저닝 전략은 버저닝을 안 하는 것"
하지만 사람들이 말하는 REST API와 로이 필딩의 REST API는 좀 달랐습니다. 왜 그런걸까요?
📜 REST API란?
REST API란 REST 아키텍처 스타일을 따르는 API입니다.
▶ 그렇다면 REST는 무엇일까요? REST란 REpresentational State Transfer의 약자로 분산 하이퍼미디어 시스템(ex. 웹)을 위한 아키텍처 스타일입니다.
▶ 그럼 아키텍처 스타일은 뭘까요? 아키텍처 스타일은 제약조건의 집합을 의미합니다.
따라서 모든 제약 조건을 지켜야 REST를 따르는 API라고 할 수 있습니다.
REST 제약 조건들
REST 아키텍처 스타일은 아래와 같은 요소들로 구성됩니다.
- Client - Server
- Stateless
- Cache
- Uniform Interface
- Layerd System
- Code-On-Demand ( Optional ) : javascript와 같이 서버에서 코드를 클라이언트로 보내서 실행할 수 있어야 한다.
사실 현재 우리가 통신을 할 때 사용하는 HTTP 통신 규약만 잘 따라도 REST를 구성하는 여러 요소들( Client-Server / Stateless / Cache / Layerd System )을 잘 지킨다고 할 수 있습니다. 대체로 잘 지키지만 4️⃣ Uinform Interface를 만족하지 않아서 REST가 아니라고 말하는 경우가 많습니다.
Uniform Interface
Uniform Interface가 어떤 스타일인지 알아봅시다! Uniform Interface는 4가지 제약조건으로 이루어져 있습니다.
1️⃣ Identification of resources
: Resource가 URI로 식별되면 된다.
2️⃣ Manipulation of resources through representations
: representation 전송을 통해서 리소스를 조작해야 합니다. 리소스 생성, 수정, 삭제할 때 HTTP 메시지에 어떤 작업을 의미하는지 알려주는 표현을 전송해야 한다는 것입니다.
3️⃣ Self-descriptive messages
: 메시지는 스스로를 설명해야 합니다.
4️⃣ Hypermedia as the engine of application state (HATEOAS)
: 애플리케이션의 상태는 Hyperlink를 이용해 전이되어야 합니다.
여기서 1️⃣,2️⃣ 조건은 대체로 잘 만족하지만 REST API라고 알려진 거의 모든 것들이 3️⃣,4️⃣조건은 지키지 못하고 있습니다. 3️⃣,4️⃣ 조건에 대해 좀 더 자세히 알아봅시다.
3️⃣ Self-descriptive message
메시지는 스스로를 설명해야 합니다.
[1] 에서 클라이언트는 어떤 문법으로 작성된 것인지 모르기 때문에 해석을 할 수 없습니다. 그래서 [2] 와 같이 Content-Type
헤더를 작성하여 어떤 문법을 사용하여 해당 응답을 해석해야 할 지 알려줍니다. 하지만 아직도 Self-descriptive 하다고는 볼 수 없습니다!
대괄호, 중괄호, 큰따옴표의 의미가 무엇인지 알게 되어 해석을 할 수 있게 되었더라고, "op"가 무슨 뜻이고, "path"는 무엇을 의미하는지 해당 응답만으로 알 수 없습니다. 따라서 [3] 과 같이 json-patch+json이라는 미디어타입으로 정의된 메시지임을 알려줘야 합니다. 이를 통해 클라이언트는 json-patch 라는 명세를 찾아가서 이해하고 메시지를 온전히 해석할 수 있습니다.
이와 같이 Self-descriptive message라는 것은 메시지를 봤을 때 메시지의 내용을 온전히 해석 가능해야 한다는 것입니다. 하지만 오늘 날의 REST API의 상당 수는 미디어 타입에 이를 어떻게 해석해야 하는지 명시되어 있지 않습니다.
4️⃣ HATEOAS
위와 같이 동작하는 웹사이트가 있다고 가정합시다. 처음 홈페이지에 들어가서 화살표의 이동처럼 따라가며 동작하는 것을 애플리케이션의 상태를 전이한다라고 합니다. 이 상태 전이마다(화살표를 따라 이동할 때마다) 해당 페이지에 있던 링크를 따라가면서 전이했기 때문에 HATEOAS라고 합니다.
📖 HATEOAS를 만족하는 경우를 살펴봅시다!
HTML의 경우 HATEOAS를 만족합니다. 아래의 코드를 보면 <a></a>
태그를 통해서 하이퍼링크가 명시되어 있고 이 하이퍼링크를 통해서 다음 상태로 전이가 가능하기 때문입니다.
HTTP/1.1 200 OK
Content-Type: text/html
<html>
<head> </head>
<body> <a href="/test"> test </a> </body>
</html>
JSON의 경우에도 HATEOAS를 만족할 수 있습니다. 아래의 코드에서 Link
라는 헤더는 현재 리소스와 하이퍼링크로 연결되어 있는 다른 리소스를 가리킬 수 있는 기능을 제공합니다. 즉, 현재 게시물의 이전 게시물의 URI는 /articles/1이고 다음 게시물의 URI는 /articles/3 라는 것을 명시한 것입니다.
Link라는 헤더는 이미 표준으로 문서에 정의가 되어 있기 때문에, 이 메시지를 받은 사람은 메시지를 해석해서 상태가 어떻게 전이되는지 이해하고 하이퍼링크를 통해 다른 상태로 전이가 가능합니다.
따라서 아래의 코드는 HATEOAS를 만족합니다.
HTTP/1.1 200 OK
Content-Type: application/json
Link: </articles/1>; rel="previous",
</articles/3>; rel="next";
{
"title": "The second article",
"contents": "blah blah..."
}
🤔 왜 Uniform Interface를 사용해야 하죠?
이제 4가지 조건에 대해 알기는 하겠는데... 굳이 왜 이런 조건들을 지켜가며 Uniform Interface를 사용해야 할까요?
💡 독립적 진화
서버와 클라이언트가 각각 독립적으로 진화한다. =
서버의 기능이 변경되어도 클라이언트를 업데이트할 필요가 없다.
HTTP 1.0을 만들 당시 로이 필딩은 "How do I imporve HTTP without breaking the Web"라는 고민을 했다고 합니다. 이 고민의 결과 나온 게 REST입니다.
즉, REST의 목적은 "독립적인 진화"입니다. 이 독립적인 진화를 달성하기 위해서는 Uniform Interface가 반드시 만족되어야 합니다.
🔎 그렇다면 실제로 REST는 잘 지켜지고 있을까요?
REST를 아주 잘 지키고 있는 사례는 웹(WEB)입니다.
URI/그림/내용 등 웹 페이지가 변경됐다고 웹 브라우저를 업데이트할 필요는 없습니다.
크롬 등 웹 브라우저를 업데이트했다고 웹 페이지를 변경할 필요도 없습니다.
HTTP 명세가 변경되어도 웹은 잘 작동합니다. ( HTTP 1.0 -> HTTP 2.0 으로 변경되었어도 잘 됨 )
HTML 명세가 변경되어도 웹은 잘 작동합니다.
📱 우리가 잘 사용하는 모바일 앱(APP)은 어떨까요?
업데이트를 하지 않은 경우 앱을 사용하지 못하게 하여 사용자에게서 불만 섞인 목소리를 듣는 경우가 있습니다. 이런 경우 대체로, 서버의 기능이 변경되었는데 클라이언트 단에서 이를 지원해주는데 한계가 있어서 업데이트를 진행하는 것입니다. 문제는... 이런 경우가 빈번해서 사용자가 불편함을 느낀다는 것입니다.
이런 관점에서 봤을 때 모바일 앱은 엄밀히 말하면... REST 아키텍처 스타일을 따르고 있지 않다고 말할 수 있습니다.
REST가 웹의 [독립적 진화]에 도움을 주었는가?
네! HTTP에 지속적으로 영향을 주었습니다.
- Host 헤더 추가
- 길이 제한 다루는 방법 명시 (414 URI Too Long 등)
- URI에서 리소스의 정의가 추상적으로 변경 ( "문서의 위치" → "식별하고자 하는 무언가" 로 변경 )
- 기타 HTTP와 URI에 많은 영향을 줌
- HTTP/1.1 명세 최신판에서 REST에 대한 언급이 들어감 ( representaion의 개념도 있음 ) → 로이 필딩이 HTTP와 URI 명세 저자 중 1명이라서 당연히 영향을 줄 수밖에 없기도 합니다.
그렇다면 REST는 성공한 것일까요? REST는 웹의 독립적 진화를 위해 만들어졌고, 실제로 웹은 독립적으로 진화하고 있습니다. 따라서 REST는 성공했다고 할 수 있습니다.
그런데 REST API는요?? 위의 예시에서 보았듯이 모바일 앱을 구현할 때 우리는 REST API라고 부르며 개발을 하고 있지만, REST 아키텍처 스타일을 따르지 않습니다. 여기서 우리는 '혹시... REST API라 REST의 모든 제약 조건을 다 지켜야 하는 건 아닐까???' 라는 의문을 가질 수 있습니다. 이는 REST를 개발한 로이 필딩의 말을 통해 알 수 있습니다.
📌 놉!! 모든 제약 조건을 지켜야 합니다.
"An API that provides network-based access to resources via uniform interface of self-descriptive messages containing hypertext to indicate potential state transition might be part of an overall system that is a RESTful application." - Roy T. Fielding
: REST API라고 하는 것은 하이퍼텍스트를 포함한 Self-descriptive 메시지의 uniform interface를 통해 리소스에 접근하는 API
원격 API가 꼭 REST 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를 따르는 것이 좋습니다.
진짜 REST API를 구현해보자💨
그전에 먼저 일반 API는 REST가 잘 안되는지 웹과의 비교를 통해 알아봅시다❗
HTTP API는 웹페이지와 마찬가지로 HTTP 프로토콜을 사용하지만, 커뮤니케이션의 대상이 다릅니다. HTTP API는 사람이 아닌 기계가 해석을 합니다. 그렇다 보니 미디어타입이 다릅니다. JSON/XML 처럼 기계가 의미를 해석할 수 있는 포맷을 사용합니다. 그럼 문제의 원인이 Media Type이 아닐까요?
Self-descriptive 측면에서 봤을 때 JSON은 불완전합니다. 불완전하다는 것은 문법은 정의되어 있지만, 안에 들어가 있는 값(key-value)에 대한 정의는 되어 있지 않습니다. 즉, 문법 해석은 가능하지만 의미를 해석하기 위해서는 별도의 API 문서가 필요합니다.
HTML | JSON | |
Self-descriptive | 1. 응답 메시지의 Content-Type을 보고 미디어 타입이 text/html 확인 2. HTTP 명세에 미디어 타입은 IANA에 등록되어 있다고 하므로, IANA에서 text/html의 설명 찾음 3. IANA에 따르면 text/html 명세는 http://www.w3.org/TR/html 이므로 링크를 찾아가 명세를 해석 4. 명세에 모든 태그의 해석 방법이 있으므로 이를 해석하여 사용자에게 보여줄 수 있음 ⏩ Success |
1. 응답 메시지의 Content-Type을 보고 미디어 타입이 application/json 임을 확인 2. HTTP 명세에 미디어 타입은 IANA에 등록되어 있다고 하므로, IANA에서 application/json의 설명 찾음 3. IANA에 따르면 application/json 명세는 draft-ietf-jsonvis-rfc7159bis-04 이므로 링크를 찾아가 명세를 해석 4. 명세에 JSON 문서를 파싱하는 방법이 명시되어 있으므로 파싱을 성공한다. 그러나 "id"가 무엇을 의미하고, "title"이 무엇을 의미하는지 알 방법이 없음. ⏩ FAIL |
HATEOAS | a 태그를 이용해 표현된 링크를 통해 다음 상태로 전이될 수 있으므로 HATEOAS를 만족 | 다음 상태로 전이할 링크가 없습니다. |
그런데 self-descriptive, HATEOAS가 어떻게 독립적인 진화에 도움이 되는 거야?
📌 self-descriptive ▶ 확장 가능한 커뮤니케이션
서버나 클라이언트가 변경되더라도 오고 가는 메시지는 언제나 self-descriptive 하므로 언제나 해석이 가능하다.
📌 HATEOAS ▶ 애플리케이션 상태 전이의 late binding이 가능해진다.
어디서 어디로 전이가 가능한지 미리 결정되지 않습니다. 어떤 상태로 전이가 완료되고 나서야 그다음 전이될 수 있는 상태가 결정됩니다. 쉽게 말하면, 서버는 마음대로 링크를 동적으로 변경할 수 있다는 의미입니다.
🚀 이제 REST API로 바꿔봅시다!!
동영상으로... 대체하겠습니다.
📢 정리
- 오늘날 대부분의 REST API는 사실 REST를 따르지 않고 있습니다.
- REST의 제약조건 중에서 특히 Self-Descriptive와 HATEOAS를 잘 만족하지 못합니다.
- REST는 긴 시간에 걸쳐(수십년) 진화하는 웹 애플리케이션을 위한 것입니다.
- REST를 따를 것인지는 API를 설계하는 이들이 스스로 판단하여 결정해야 합니다.
- Self-Descriptive는 custom Media type이나 profile link relation 등으로 만족시킬 수 있습니다.
- HATEOAS는 HTTP 헤더나 본문에 링크를 담아 만족시킬 수 있습니다.
- REST를 따르지 않겠다면, "REST를 만족하지 않는 REST API"를 뭐라고 부를지 결정해야 할 것입니다. 그냥 HTTP API라고 부를 수 있고 그냥 이대로 REST API라고 부를 수도 있습니다.
END
'용어' 카테고리의 다른 글
Boiler Plate Code (0) | 2021.05.04 |
---|