우리는 종종 우리가 만든 API를 REST의 원칙의 일부만 지키더라도 REST API, RESTful API라고 부르는 경우들이 종종 있다.
그렇다면 REST API, RESTEful API라고 하기 위해선 어떤 원칙들을 지켜야할까?
이번 포스팅에서는 우리의 API가 정말 RESTful한지, RESTful하려면 어떻게 해야하는지 한번 알아볼 예정이다.
REST란?
REST란 "REpresentational State Transfer"의 약자로
자원의 표현(이름)으로 구분해 자원의 상태(정보)를 전송하는 것을 의미한다.
REST의 탄생 배경
팀버너스리는 "어떻게 하면 인터넷에 정보를 쉽게 공유할 수 있을까?"로 시작하여
1991년 모든 정보들을 하이퍼텍스트로 연결하는 시스템인 WWW(World Wide Web)을 만들게 된다.
이 WWW는 목적에 맞게 다음과 같은 기본 요소와 함께 개발되었다.
- HTML (HyperText Markup Language): 문서 작성을 위한 마크업 언어
- HTTP 0.9 (HyperText Transfer Protocol): 데이터 전송 프로토콜
- URL (Uniform Resource Locator): 웹 자원 위치 지정
이후 웹은 폭발적으로 성장하게 됐고 이와 더불어
웹이 폭발적으로 성장하면서 서버의 부하가 급증해 클라이언트와 서버가 강하게 결합되어 독립적인 진화가 어려웠고
다양한 클라이언트(브라우저, 모바일 등) 등장하는 등의 이유로 새로운 HTTP 프로토콜에 새로운 기능 추가가 필요해졌다.
하지만 기존의 폭팔적인 인기로 이미 전 세계의 웹 서버들이 사용하고 있던 HTTP 프로토콜 버전을 바꾸는 것은 힘든 일이다.
로이 필딩은 이러한 문제를 해결하고자 2000년 웹의 장점을 극대화할 수 있는 아키텍처 스타일인 REST를 제시한다.
초기에는 관심을 받지 못하고 마이크로소프트의 SOAP API 아키텍처와 많이 비교되었고
이후 SOAP는 복잡하고, 어렵다는 등의 이유로 대기업들이 REST를 선택하게 되면서 현재는 거의 REST를 사용하게 되었다.
https://ics.uci.edu/~fielding/pubs/dissertation/top.htm
이 링크에서 REST 논문 내용과 로이 필딩이란 사람에 대해서도 확인할 수 있다.
REST 아키텍처 조건
REST 아키텍처에 적용되는 조건은 다음과 같은 6가지 조건들이 있다.
Client-Server
클라이언트는 UI/UX에 집중
서버는 비즈니스 로직 처리와 데이터 저장에 집중과 같이
클라이언트와 서버는 서로 분리되어야 하며 서로 의존성이 없어야 한다.
얻을 수 있는 효과
- 서버는 비즈니스 로직 단순화, 확장성 향상
- 클라이언트는 UI/UX 집중, 다양한 플랫폼 개발 용이
- 독립적 개발과 배포 가능
Stateless
서버는 클라이언트의 상태 정보를 따로 저장하지 않는다.
모든 요청은 필요한 모든 정보를 담고 있어야 하며
각 요청은 이전 요청과 독립적이다.
얻을 수 있는 효과
서버의 확정성과 신뢰성 향상
Cacheable
HTTP 웹 표준을 그대로 사용하기 때문에 기존 기능들을 그대로 사용할 수 있다.
따라서 캐시 헤더를 활용하여 캐싱 적용 가능하다.
요청에 대한 응답 내의 데이터가 암묵적으로 또는 명시적으로
캐시 가능 또는 캐시 불가능으로 표시되어야 한다.
얻을 수 있는 효과
서버 부하 감소와 응답 시간 단축
Layered System
클라이언트는 직접 연결된 계층하고만 상호작용하고
중간 계층의 존재를 클라이언트가 알 필요가 없다.
각 계층은 바로 인접한 계층만 알면 된다.
얻을 수 있는 효과
- 중간 계층(로드 밸런서, 프록시 등) 추가 가능
- 시스템 확장성과 보안성 향상
Uniform Interface
자원의 식별
모든 자원은 상태가 변화한다.
따라서 상태 변화에 영향을 받지 않는 고유한 식별자(URI)로 식별할 수 있어야한다.
// 좋은 예시
GET /users/123
GET /orders/456
// 나쁜 예시
GET /api/getUserById?id=123
GET /api/findOrder?orderId=456
이를 통해 리소스 중심의 설계를 통한 일관성을 확보할 수 있다.
표현을 통한 자원 조작
자원은 서버에 저장된 실제 데이터/개체이다.
표현은 특정 시점에 리소스의 상태를 반영한 정보이다.
클라이언트는 리소스의 표현(Representation)을 받고 조작할 수 있어야 한다.
같은 리소스도 여러 가지 형태(HTML, XML, JSON)로 표현 가능하다.
// JSON 표현 예시
// 1. 클라이언트가 리소스의 표현을 요청
GET /users/123
Accept: application/json
// 2. 서버가 리소스의 표현을 전송
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "123",
"name": "kaki",
"email": "kaki@example.com"
}
// 3. 클라이언트가 응답받은 표현을 수정해 서버에 요청
PUT /users/123
Content-Type: application/json
{
"name": "hyun",
"email": "hyun@example.com"
}
// 4. 서버는 받은 표현으로 실제 리소스를 수정해 응답
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "123",
"name": "hyun",
"email": "hyun@example.com"
}
이러한 방식으로 클라이언트는 실제 리소스에 직접 접근하지 않고, 표현을 통해 간접적으로 리소스를 조작할 수 있다.
이는 캡슐화와 보안성을 높이고, 클라이언트와 서버의 독립성을 보장한다.
자기 서술적 메시지 (Self-descriptive Messages)
API를 통해 전송되는 내용은 별도의 문서 없이도
스스로가 자신을 어떻게 처리해야 하는지 표현할 수 있어야 한다.
즉, 요청/응답 메시지만 보고도 해당 메시지를 이해할 수 있어야 한다.
HATEOAS (Hypermedia as the Engine of Application State)
클라이언트 요청에 대해 서버가 현재 상황에서 할 수 있는 작업들의 URI를 함께 전달한다.
일반적인 우리의 API 응답은 다음과 같다.
{
"orderId": "12345",
"status": "배송준비중",
"totalAmount": 50000
}
이 방식에서는 클라이언트의 요청에 대한 응답으로 json 데이터에 필요한 값만을 보내
클라이언트에서 이 데이터를 가지고 view 페이지를 구성한다.
즉, 한 View에서 주문 취소, 배송 조회등의 기능들을 사용할 수 있게 하려면
클라이언트 개발자가 "주문 취소는 /orders/{id}/cancel로 요청해야 한다"는 것을 API 문서를 보고 코드에 직접 작성해야할 것이다.
여기에 Hateoas를 적용하면 다음과 같이 응답할 수 있다.
{
"orderId": "12345",
"status": "배송준비중",
"totalAmount": 50000,
"links": {
"cancel": "/orders/12345/cancel",
"update": "/orders/12345/update",
"payment": "/orders/12345/payment"
}
}
Hateoas 방식에서는 서버가 현재 상황에서 할 수 있는 작업들의 URI를 함께 전달한다.
클라이언트는 단순히 응답에 포함된 링크를 사용해 사용자가 원하는 페이지로 이동하게 끔 호출하게 끔 페이지를 구성하면된다.
만약 주문 상태가 '배송완료'라면 서버는 cancel 링크를 아예 포함하지 않을 것이고 클라이언트는 자연스럽게 취소가 불가능하다는 것을 알 수 있다.
이는 마치 클라이언트가 모든 URI을 다 알고 있지 않더라도 서버로 부터 받은 링크를 통해 현재 어떠한 행위가 가능한지 알 수 있다.
이렇게 하면 서버가 URL 구조를 변경하더라도 클라이언트 코드를 수정할 필요가 없어지며
현재 상태에서 수행 가능한 작업만 표시할 수 있다는 장점이 있다.
하지만 서버측에서는 서버의 비즈니스 로직 복잡도가 크게 증가할 수 있다.
각각의 API 응답마다 해당 시점에 클라이언트가 수행할 수 있는 모든 가능한 작업들의 URI를 응답에 추가해줘야하는데
예를 들어, 주문 시스템에서는 다음과 같은 복잡한 로직이 필요할 수 있다.
public OrderResponse getOrderDetails(Long orderId) {
Order order = orderRepository.findById(orderId);
Map<String, String> links = new HashMap<>();
// 주문 상태별 가능한 작업 확인
if (order.getStatus() == OrderStatus.PENDING) {
links.put("cancel", "/orders/" + orderId + "/cancel");
links.put("payment", "/orders/" + orderId + "/payment");
} else if (order.getStatus() == OrderStatus.PAID) {
// 배송 시작 전인지 확인
if (!deliveryService.isStarted(orderId)) {
links.put("updateShipping", "/orders/" + orderId + "/shipping");
}
// 환불 가능 기간인지 확인
if (refundService.isRefundable(order)) {
links.put("refund", "/orders/" + orderId + "/refund");
}
}
// 사용자 권한별 추가 작업 확인
if (userService.isAdmin(getCurrentUser())) {
links.put("forceCancel", "/orders/" + orderId + "/admin/cancel");
}
return new OrderResponse(order, links);
}
링크를 응답에 포함하는 것이 비즈니스의 핵심 로직도 아닌데 부가적인 부분으로 인해 가독성이 상당히 떨어지고
코드의 유지보수를 어렵게 만들고, 새로운 기능이나 규칙이 추가될 때마다 로직이 더욱 복잡해질 수 있다.
우리의 API는 REST, RESTful 한가?
REST 원칙의 일부를 따르면 REST API, 철저히 지키면 RESTful API라고 많이 부르는 것 같다.
사실 다 달라서 정확한 개념은 없는 것 같다.
하지만 REST 창시자인 로이 필딩의 말에 따르면 REST API, RESTful API든 결국 REST 원칙을 잘 지키지 않으면 그냥 API라고 해석된다.
REST 원칙 중 Uniform Interface의 Hateoas 원칙을 지키고 있는가?
지키고 있고 그 외의 조건도 만족한다면 RESTful API라고 할 수 있을 것 같다.
아니라면 HTTP API? 우리만의 API? 라고 할 수 있지 않을까
중요한점은 로이 필딩이 정의한 REST는 원칙은 REST API를 구현하는 방법에 대한 절대적인 표준이 아닌 방법이라는 것이다.
절대적인 표준이 없기 때문에 REST API의 가장 큰 단점인 공식적인 표준이 존재하지 않아 제각각 구현이 다르다는 점이 있다.
때문에 로이 필딩이 싫어하실 수도 있겠지만...
절대적인 표준은 아니기에 어떤식으로 API를 부르던 크게 문제는 되지 않는다고 생각해
회사마다의 REST 가이드를 제시하는 것이 아닌가 싶다.
꼭 REST의 규칙을 다 지켜야할까?
REST 규칙을 다 지켜가면서 까지 얻을 수 있는 장점이 많다면 RESTful한 API를 설계해 사용하는 것이 좋겠지만
위에서 봤던 Hateos의 단점 예시 처럼 RESTful한 API가 절대적으로 좋은 것은 아니라고 생각한다.
분명 엄격하게 지켜야할 필요가 있다고 생각하는 client-server, 무상태성 원칙들이 있지만
서비스마다, 팀마다 추구하는 가치, 방식이 다르니 REST의 모든 원칙을 100% 준수하는 것보다는
시스템의 요구사항, 개발 생산성, 유지보수성 등을 종합적으로 고려하여
적절한 수준의 REST API를 구현하는 것이 더 현실적인 접근 방식일 수 있다고 생각한다.
참고자료
'◼ CS 기초 지식 > [개발상식]' 카테고리의 다른 글
[OOP] 정적 팩토리 메서드를 왜 사용하는가? 어떤 상황에 사용하는게 좋을까? (생성자와 차이) (10) | 2023.10.30 |
---|---|
[OOP] 디미터 법칙에 대한 오해와 디미터 법칙에 대해 (30) | 2023.10.26 |
JWT란? 장단점과 Session 인증 방식과의 차이점을 알아보자. (13) | 2023.10.12 |
[비동기 통신] Ajax와 Axios의 차이점? (0) | 2023.07.23 |
YAML이란? (JSON, XML과 비교) (0) | 2023.05.22 |