RESTful API와 REST는 다릅니다. (RESTful API 톺아보기)
Mar 12, 2023
Table Of Content
- RESTful API를 귀에 딱지가 앉게 들었고, 무엇인지 어렴풋이 알지만 정확하게 알고 싶은 독자
- REST와 RESTful API의 차이점이 무엇인지 모르는 독자
API(Application Programming Interface)는 두 개 이상의 컴퓨터 프로그램에서 서로 통신하기 위한 인터페이스를 말합니다.
예를 들어, 카카오맵에서 특정 장소를 검색하는 기능이나 네이버 검색을 통해 책 정보를 가져오는 기능을 사용하고 싶을 때
어떻게 검색어를 전달해야하는지
, 정보가 어떤 식으로 전달되는지
에 대해 정의된 규칙이 바로 API입니다.
출처: KAKAO 로컬 API 문서
RESTful API란 REST 아키텍처를 기반으로 만들어진 API를 말합니다. RESTful API는 흔히 REST API로 많이 쓰이기 때문에 REST와 RESTful API를 같은 것으로 이해하는 경우가 많습니다.
하지만
- REST는 네트워크에서 통신을 할 때 어떤 규칙을 따라야 하는지를 정의한 아키텍처,
- RESTful API란 REST 아키텍쳐의 조건을 따라서 설계한 API
를 의미합니다.
처음 REST 아키텍쳐를 정의한 Roy Fielding은 HTTP와 URI의 기준을 만드는데 참여했었기 때문에, HTTP와 URI의 장점(캐싱, 식별, HTTP Verb 등)을 가장 극대화하기 위해 만든 아키텍쳐가 바로 REST입니다.
Roy Fielding은 본인의 논문에서 다음과 같이 REST의 조건을 정의합니다.
유저와의 상호작용을 처리하는 클라이언트와 데이터에 대한 로직을 관리하는 서버는 서로 독립적이어야 합니다. 예를 들어서 과일 가게 웹사이트에서 유저가 과일 리스트의 정보를 가져오는 버튼이 있다고 합니다. 이 버튼을 누르면, 유저와의 상호작용을 처리하는 클라이언트는 서버에 과일 리스트의 정보를 요청(request)하고, 서버는 과일 리스트의 정보를 클라이언트에게 응답(response)합니다.
여기서 주목할 것은 클라이언트와 서버는 서로 독립적이라는 것입니다. 즉, 클라이언트에서 버튼의 색이 바뀐다던지 클릭할 때 나타나는 시각 효과 자체가 바뀌더라도, 서버에 아무 영향을 주지 않습니다. 역으로 서버에서 과일 리스트의 정보를 가져오는 로직이 바뀌더라도, 클라이언트에 아무 영향을 주지 않습니다.
인터페이스 일관성은 RESTful system에서 가장 중요한 부분입니다. 아래 4가지 규칙들로 단순하고 구분된 인터페이스를 만들 수 있습니다.
-
Resource identification in requests(각각의 요청에 대한 리소스 식별)
각각의 리소스에 대한 요청은 명확히 구분되어야 합니다. Web Service의 경우에는 주로 URI를 사용해서 각각의 리소스를 식별합니다.# 유저 정보 가져오기 GET /users # 책에 대한 정보 수정하기 PUT /books/:id
-
Manipulation of resources through representations(표현형을 통한 리소스 조작)
클라이언트에서 서버에 요청을 보낼 때, 요청에 대한 정보를 표현형으로 전달합니다. 표현형으로 전달한다는 말은 클라이언트가 요청을 전달할 때metadata
를 통해 JSON, XML, HTML 등 특정 표현형으로 데이터를 받을지 결정하고, 표현형으로 데이터를 전달합니다.즉, 클라이언트가 요청할 때 서버에서 데이터베이스를 조작하는
SQL
을 직접 쓰지 않고 데이터를 원하는 방식(표현형)으로 주고 받을 수 있다는 것을 의미합니다.# 유저 정보 가져오기 GET /users Accept: application/json #클라이언트가 JSON으로 데이터를 받을 것을 요청
-
Self-descriptive messages(자기 설명적인 메시지)
각각의 요청의 식별형(Identification)은 그것만 보고도 서버가 어떤 기능을 수행할건지 알 수 있어야 한다는 것입니다. 예를 들어GET /users
라는 요청은 서버에게 유저 정보를 가져오라는 요청이고,POST /users
라는 요청은 서버에게 유저 정보를 생성하라는 요청입니다.TASK METHOD URI Search for users GET /users Delete an existing user DELETE /users/{user_id} Update an existing comment PUT /comments/{comment_id} create a comment POST /comments -
Hypermedia as the Engine of Application State
4가지 규칙들 중 가장 어려운 개념인 HATEOAS(Hypermedia as the Engine of Application State)는 웹 서비스의 예를 들어서 설명하겠습니다.웹 서비스에서는 웹 페이지가 앱 상태(Application State)에 해당합니다. 웹 서비스에서 앱 상태를 표시하는 하이퍼미디어(Hypermedia)는 하이퍼링크를 의미합니다. 즉, 하이퍼 링크를 통해 다른 웹페이지로 이동하듯, 하이퍼미디어는 각각의 앱상태를 표현합니다.
REST한 아키텍처에서는 클라이언트가 서버에게 요청을 보낼 때, 서버는 요청에 대한 응답으로 그 뒤에 요청할 내용에 대한 하이퍼링크를 함께 전달하여, API에 대한 문서를 더 찾아보지 않고도 서버에게 필요한 요청을 할 수 있도록 합니다.
GET /articles HTTP/1.1 200 OK { "links": { "self": "http://example.com/articles", "next": "http://example.com/articles?page[offset]=2", "last": "http://example.com/articles?page[offset]=10" }, "data": [ { "type": "articles", "id": "1", "attributes": { "title": "JSON API paints my bikeshed!" }, "relationships": { "author": { "links": { "self": "http://example.com/articles/1/relationships/author", "related": "http://example.com/articles/1/author" }, }, "comments": { "links": { "self": "http://example.com/articles/1/relationships/comments", "related": "http://example.com/articles/1/comments" }, "links": { "self": "http://example.com/articles/1" } }, [ ... ] ] }
상태(state)란 요청에 있어서 이전 요청의 정보(세션)를 따로 저장하지 않는 것을 말합니다. 이에 관해 이해하기 위해 Stateful한 통신과 Stateless한 통신의 예를 들어보면 다음과 같습니다.
Stateful한 통신
- 클라이언트(이하 클): 안녕하세요. 여기서는 어떤 과일들을 파나요?
- 서버(이하 서): 저희는 빨간 사과, 파란 사과, 바나나, 오렌지를 팔고 있습니다. 무엇을 드릴까요?
- 클: 그러면 사과 주세요.
- 서: 네, 어떤 사과로 드릴까요? (사과를 주문했다는 것을 기억한다.)
- 클: 빨간걸로요.
- 서: 네, 빨간 사과 몇 개를 드릴까요? (빨간 사과를 주문했다는 것을 기억한다.)
- 클: 2개만 주세요.
- 서: 네, 빨간 사과 2개 드리겠습니다. (빨간 사과를 주문했다는 것을 기억하고 2개를 준다.)
Stateless한 통신
-
클: 안녕하세요. 여기서는 어떤 과일들을 파나요?
-
서: 저희는 빨간 사과, 파란 사과, 바나나, 오렌지를 팔고 있습니다. 무엇을 드릴까요?
-
클: 그러면 사과 주세요.
-
서: 네, 어떤 사과로 드릴까요? (아무것도 기억하지 않는다.)
-
클: 빨간걸로요.
-
서: 네, 어떤 빨간걸 말씀하시나요?
-
클: 빨간 사과를 주세요.
-
서: 네, 빨간 사과 몇 개를 드릴까요? (아무것도 기억하지 않는다.)
-
클: 2개만 주세요.
-
서: 어떤걸 2개만 드릴까요?
-
클: 빨간 사과를 2개만 주세요.
-
서: 네, 빨간 사과 2개 드리겠습니다.
후자의 경우 클라이언트의 이전 요청에 대한 정보를 기억하지 않기 때문에 클라이언트가 이전 요청에 대한 정보를 서버에게 전달해야 합니다.
그렇기 때문에 Stateless한 통신은 굉장히 비효율적으로 보입니다. 하지만 과일가게에 손님이 많아져서 서버가 여러대 띄워져있는 상황에 Stateful한 통신을 해야된다고 생각해봅시다.
Stateful한 통신(서버가 여러대인 상황)
- 클: 안녕하세요. 여기서는 어떤 과일들을 파나요?
- 서1: 저희는 빨간 사과, 파란 사과, 바나나, 오렌지를 팔고 있습니다. 무엇을 드릴까요?
- 클: 그러면 사과 주세요.
- 서1: 네, 어떤 사과로 드릴까요? (사과를 주문했다는 것을 기억한다.)
- 클: 빨간걸로요.
- 서1: 네, 빨간 사과 몇 개를 드릴까요? (빨간 사과를 2개 주문했다는 것을 기억한다.)
- 클: 2개만 주세요. (서1이 하는 일이 많아져서 서2가 대신 주문을 받는다.)
- 서2: 어떤걸 2개만 드릴까요?(2개를 주문했다는 것을 기억한다.)
- 클: 사과 주세요.
- 서2: 네, 어떤 사과를 2개만 드릴까요? (사과를 2개 주문한 것을 기억한다.)
클라이언트는 응답을 캐싱(미래 같은 요청이 올 것을 대비하여 응답을 저장하는 것)하여, 성능을 높일 수 있어야 합니다. 그렇기 위해 서버에서 응답을 줄 때는 캐시를 할 수 있는지 할 수 없는지 알려줘서 잘못된 정보가 캐싱되는 일이 없도록 합니다.
단순화해서 클라이언트와 서버로 나누지만, 클라이언트와 서버 사이에는 여러 계층이 존재할 수 있습니다. 예를 들어 서버가 여러대 있을 때 요청을 분산시켜주는 Load Balancer가 있을 수 있고, API를 요청하는 대상의 권한을 확인하는 Security Layer가 있을 수 있습니다. 각각의 Layer는 분리되어서 독립적으로 동작할 수 있어야 합니다.
위에서 얘기한 클라이언트와 서버의 예시처럼 Security Layer의 변화가 다른 Layer들에게 영향을 주지 않아야 합니다. 그래서 가장 좋은 계층화는 계층이 추가되어도 다른 계층에서는 그것이 추가된지 모르고, 똑같이 동작하는 것입니다.
REST 아키텍처의 조건을 통해
- API 대용량의 부하를 처리하기 위한 서비스를 처리하는 웹 서비스를 만들 수 있고(Scalability),
- 역할에 따라 서버를 분리하여 서비스를 운영할 수 있고(Decoupling),
- API를 간단하게 관리할 수 있습니다(Simplicity).
Richardson Maturity Model은 Leonard Richardson이 만든 개념으로 Web API가 얼마나 RESTful한지 판단하기 위한 모델입니다.
이 모델을 통해 RESTful한 API를 만들기 위한 조건을 알 수 있습니다.
가장 기본 단계로 전혀 RESTful하지 않은 API를 의미합니다. 이 단계에서는 단일의 엔드포인트로 모든 요청을 처리합니다.
URI | HTTP VERB | OPERATION |
---|---|---|
/doingWork | POST | retrieve/add customers, remove/update fruit |
첫 번째 단계는 URI는 리소스 별로 구분되지만 HTTP 메소드는 하나만 사용되는 단계입니다. 리소스란 서버에 존재하는 데이터를 의미합니다. 하나의 엔드포인트로 하나의 리소스를 표현합니다.
예를 들어 과일 가게 웹서비스에서는 고객
, 과일
을 리소스로 표현할 수 있습니다.
URI | HTTP VERB | OPERATION |
---|---|---|
/gettingCustomers | POST | retrieve customers |
/addingCustomers | POST | add new customer |
/removingFruits | POST | remove an existing fruit |
/updatingFruits | POST | update information of an existing fruit |
두 번째 단계는 URI는 리소스 별로 구분되고 HTTP 메소드는 GET
, POST
, PUT
, DELETE
등을 사용하는 단계입니다.
URI | HTTP VERB | OPERATION |
---|---|---|
/customers | GET | retrieve customers |
/customers | POST | add new customer |
/fruits/{id} | DELETE | remove an existing fruit |
/fruits/{id} | PUT | update information of an existing fruit |
마지막 단계는 URI는 리소스 별로 구분되고 HTTP 메소드는 GET
, POST
, PUT
, DELETE
등을 사용하고,
HATEOAS(Hypermedia as the Engine of Application State)를 지원하기 위해 응답에 링크 정보를 포함하는 단계입니다.
Request:
GET /customers/1 HTTP/1.1
Response:
HTTP/1.1 200 OK
{
customerId: 1,
name: "John",
address: "Seoul",
orders: [
{
orderId: 1,
orderDate: "2020-01-01",
href: "https://api.example.com/orders/1",
orderItems: [
{
itemId: 1,
itemName: "Apple",
quantity: 10
},
{
itemId: 2,
itemName: "Banana",
quantity: 5
}
]
},
{
orderId: 2,
orderDate: "2020-01-02",
href: "https://api.example.com/orders/2",
orderItems: [
{
itemId: 3,
itemName: "Orange",
quantity: 3
}
]
}
],
}
RESTful한 API란 REST architecture를 따르는 API를 의미합니다. 그렇기 때문에 RESTful한 API에 대한 "공식적인" 기준은 존재하지 않습니다. Richardson Maturity Model을 통해 RESTful한 API를 만들기 위한 조건을 알 수 있는데, 대부분의 RESTful한 API는 Level 2까지만 만족하는 경우가 많습니다. 그래서 엄밀하게는 해당 API들은 RESTful한 API라고 할 수 없습니다. 👀
하지만 REST architecture 또한 scalability
, performance
, reliability
, maintainability
등을 위해 만들어진
아키텍쳐이기 때문에 개발하려는 서비스의 특성을 이해하고 해당 목적에 맞게 조건을 취사선택하여 디자인할 수도 있습니다.
RESTful API에 대한 이해에 도움이 되었으면 좋겠습니다. 이제 RESTful API를 REST라고 부르는 사람들과 논쟁이 가능해졌습니다.
긴 글 읽어주셔서 감사합니다. 🙇