백엔드/설계
REST 원칙과 REST API 설계 방법
eess
2024. 8. 3. 14:28
"그런 REST API로 괜찮은가"를 참고하여 정리한 내용입니다.
REST란?
- REpresentational State Transfer의 약자입니다.
- 웹 서비스 아키텍처의 스타일 중 하나로, 네트워크를 통해 클라이언트와 서버가 상호작용하는 방식을 정의하는 방법입니다.
- 인터넷에서 어떻게 정보를 공유할 것인지에 대한 해답으로 1991년 웹(Web)이 등장했습니다.
- 웹은 HTML이라는 형식으로 정보를 표현하고, 정보들에 대한 식별자로 URI를 선택하고, HTTP라는 프로토콜로 정보를 전송합니다.
- 2000년 Roy T. Fielding은 기존의 웹을 망가트리지 않고 HTTP를 개선할 수 있을지에 대한 논문을 발표했고, 이것이 REST 였습니다.
REST API란?
- REST API는 REST 아키텍처를 따르는 API로, RESTful API라고도 합니다.
- 한마디로 서버의 자원을 정의하고 자원에 대한 주소를 지정하는 방법이라고 할 수 있겠습니다.
- 엄밀히 말하자면, Roy T. Fielding이 정의한 REST 설계 원칙을 모두 지켜야만 REST를 따른다고 볼 수 있습니다.
REST 설계 원칙 6가지
1. Client-Server
- 네트워크가 클라이언트와 서버로 구성되어야 하며, 서로 완전히 독립적이어야 합니다.
- 클라이언트는 요청 리소스의 URI만 알면 되고, 서버는 HTTP를 통해 요청된 리소스를 전달만 하면 됩니다.
- 이벤트 기반 아키텍처는 REST가 아닙니다.
2. Stateless
- 클라이언트와 서버가 상태를 가지고 있지 않으며 서로의 상태를 추적하지 않습니다.
- 각 요청은 독립적으로 처리되며, 서버는 이전 요청의 컨텍스트를 저장하지 않습니다.
3. Cacheability
- 서버 응답은 캐싱 가능 여부를 표시해야 합니다.
- 클라이언트가 서버로부터 수신한 이전 응답을 저장하여 해당 데이터가 다시 필요할 때 캐시된 데이터를 사용할 수 있도록 하는 것입니다.
4. Layered System
- 클라이언트와 서버 외에도 여러 중개자가 있을 수 있습니다.
- 그러나 클라이언트, 서버가 최종 애플리케이션과 통신하는지 중개자와 통신하는지 알 수 없도록 설계해야 합니다.
5. Uniform Interface
- 클라이언트와 서버가 일관된 방식으로 통신해야 하며, 이로써 전체 시스템을 망가뜨리지 않고 각 부분을 변경할 수 있어야 합니다.
- Uniform Interface 원칙은 Identification of resources, Manipulation of resources through representations, self-descriptive messages, Hypermedia as the engine of application state(HATEOAS)의 4가지 제약 조건을 가지고 있습니다.
6. Code-On-Demand(optional)
- 일반적으로 정적 리소스를 전송하지만, 경우에 따라 서버가 클라이언트에서 실행시킬 수 있는 로직을 전송할 수 있습니다.
- 예를 들면, HTML <script> 태그에서 Javascript 파일을 가져와 실행하는 것입니다.
Uniform Interface
REST API를 설계할 때 가장 원칙대로 지켜지기 어려운 구성 요소입니다.
Uniform Interface 원칙에는 4가지 제약 조건이 있습니다.
1. Identification of resources
- 요청된 리소스는 URI로 식별 가능해야 합니다.
- 클라이언트는 서버에 저장된 리소스를 가져오기 위해 URI에 HTTP GET 요청을 보낼 수 있습니다.
2. Manipulation of resources through representations
- 전송한 표현을 통해 클라이언트가 리소스를 조작할 수 있어야 합니다.
- HTTP 메서드(GET, POST, PUT, DELETE)를 통해 리소스를 어떻게 조작할 것인지 표현할 수 있습니다.
3. Self-descriptive messages
- 각 메시지는 클라이언트와 서버가 요청 및 응답을 이해하는 데 필요한 모든 정보를 포함해야 합니다.
- 예를 들면 아래와 같은 HTTP 응답은 적절한 HTTP 메서드, 상태코드를 사용해서 리소스를 생성한다는 것을 알 수 있고, Content-Type 헤더를 포함하여 JSON 형식의 데이터라는 것을 알려줍니다.
HTTP/1.1 201 Created
Host: www.example.com
Content-Type: application/json
{
"id": 123,
"username": "johndoe",
"email": "johndoe@example.com",
"created_at": "2023-08-02T12:34:56Z"
}
4. Hypermedia as the engine of application state (HATEOAS)
- 클라이언트는 서버가 제공하는 하이퍼링크를 통해 애플리케이션 상태를 전이합니다.
- 서버는 클라이언트에게 가능한 다음 작업에 대한 링크를 제공하여, 클라이언트가 시스템을 탐색하고 사용할 수 있도록 합니다.
- 예를 들면 아래와 같은 HTTP 응답은 Link 헤더를 통해 연결된 다른 리소스의 링크를 알려주고 있습니다.
HTTP/1.1 200 OK
Content-Type: application/json
Link: </articles/1>; rel="previous", </articles/2>; rel="next";
{
"title": "첫 번째 게시글",
"content": "본문"
}
왜 Uniform Interface를 지켜야 할까?
- 서버와 클라이언트의 독립적 진화, 즉 서버의 기능이 변경되어도 클라이언트를 변경할 필요가 없도록 하기 위함입니다.
- REST를 지킨 대표적인 사례가 웹입니다.
- 웹 페이지를 변경했다고 웹 브라우저를 변경할 필요는 없고, 반대로 웹 브라우저를 변경했다고 웹 페이지를 변경할 필요가 없습니다.
- HTML, HTTP 명세가 바뀌어도 웹은 잘 동작합니다.
모든 제약 조건을 지켜서 REST API를 설계해야 할까?
- Roy T. Fielding은 시스템 전체를 통제할 수 없거나, 클라이언트와 서버의 독립적 진화(= 서로의 변경이 영향을 주지 않음)에 관심이 없다면 REST에 따지느라 시간을 낭비하지 말라고 합니다. (대신 그러면 엄밀히 말하면 REST는 아니라고 하네요 😅)
- 정리하자면, REST를 원칙대로 따를 것인지는 API를 설계하는 사람들이 스스로 판단해야 한다는 것입니다.
일반적인 REST API 설계 방법
Roy T. Fielding이 말하는 원칙대로의 REST API는 아니겠지만, REST API 설계의 Best Practice를 정리해보고자 합니다.
핵심은 HTTP 메서드와 URI를 조합해서 리소스를 요청하는 일관된 엔드 포인트를 정의해야 한다는 것입니다.
1. HTTP 메소드
서버 리소스에 대해 어떤 동작을 행하는지를 의미합니다.
GET
- 서버에 리소스를 요청하는 동작을 의미합니다.
- 요청 본문을 사용하지 않고, Path Variable 이나 Query String 을 사용할 것을 권장합니다.
- 명세에 GET 요청의 본문을 담으면 안 된다고 되어 있는 것은 아니지만, HTTP Client 구현체에 따라 전송이 안 될 수도 있습니다. [참고]
- Path Variable vs Query String
- 어떤 리소스를 식별하고 싶을 때는 Path Variable을 사용합니다.
GET /expenses/3427 - 리소스에 대해 검색, 정렬, 필터링, 페이지네이션을 하고 싶을 때는 Query String을 사용합니다.
GET /expenses?start-date=2024-08-01&end-date=2024-08-03
- 어떤 리소스를 식별하고 싶을 때는 Path Variable을 사용합니다.
- Path Variable vs Query String
POST
- 서버에 데이터를 전송할 때 사용하는데, 그 중에서도 리소스 생성을 요청하는 동작으로 주로 사용합니다.
- 요청 본문에 생성할 리소스의 데이터를 담아서 보냅니다.
- 멱등성을 가지지 않습니다. 즉, 여러 번의 요청은 서버 리소스에 각각 다른 영향을 미칩니다.
PUT
- 서버 리소스를 기존에 없으면 생성하고, 있으면 치환하는 동작을 의미합니다. (덮어쓰기 개념)
- 요청 본문에 치환할 데이터를 담아서 보냅니다.
- 멱등성을 가집니다. 즉, 여러 번 요청을 보내도 같은 효과를 보입니다.
PATCH
- 서버 리소스의 일부만 수정하는 동작을 의미합니다.
- 요청 본문에 치환할 데이터를 담아서 보냅니다.
- 멱등성을 가지지 않습니다. 즉, 여러 번의 요청은 서버 리소스에 각각 다른 영향을 미칩니다.
- 하지만 PATCH를 PUT과 같은 방식으로 사용하도록 설계한다면 멱등성을 가지게 할 수도 있습니다. [참고]
DELETE
- 서버 리소스를 삭제하는 동작을 의미합니다.
- GET과 마찬가지로 요청의 본문을 담으면 안 된다고 되어 있는 것은 아니지만, HTTP Client 구현체에 따라 전송이 안 될 수도 있습니다.
2. 리소스
동사 보다 명사
- URI에는 명사형의 리소스를 포함하고 리소스에 대한 행위를 표현하지 않는 것이 좋습니다. 이미 HTTP 메서드로 표현할 수 있으니까요!
단수 보다 복수
- 개별 리소스와 리소스의 집합을 구분하기 위해 복수형을 권장합니다.
밑줄(_)보다는 하이픈(-)
- 가독성을 위해 밑줄 보다는 하이픈을 사용하는 것이 좋습니다.
계층 관계를 나타낼 때는 슬래시(/)
- 슬래시를 사용하여 리소스의 계층 관계를 표현합니다.
GET /users/123/playlists
3. 검색 / 정렬 / 필터링 / 페이지네이션
보통 GET 메서드 URI에 Query String을 사용해 표현합니다.
- 검색 : GET /expenses?search=쇼핑
- 정렬 : GET /expenses?sort=amount_asc
- 필터링 : GET /expenses?category=food&start_date=2024-08-01&end_date=2024-08-03
- 페이지네이션 : GET /expenses?page=1&limit=10
나의 생각
REST 원칙 중 Uniform Interface를 아주 철저히 지켜서 설계하기는 어렵다고 보였습니다.
빠르게 개발해야 하는 상황에서는 모든 원칙을 준수하는 게 과연 실용적일까 싶기도 하고요.
일반적으로 알려진 REST API 디자인 가이드조차도 지키기 어려운 경우도 종종 보입니다.
결국은 표준을 따르려는 노력과 동시에, 협의를 통해 일관되고 예측 가능한 설계를 하는 것이 중요하다는 생각이 들었습니다. [참고]
참고
- https://www.ibm.com/kr-ko/topics/rest-apis
- https://codewords.recurse.com/issues/five/what-restful-actually-means
- https://roy.gbiv.com/untangled/
- https://restfulapi.net/
- https://wayhome25.github.io/etc/2017/11/26/restful-api-designing-guidelines/
- https://restfulapi.net/resource-naming/