지난 글에서 API에 대해 알아봤다.
API의 연장선으로,
최근 백엔드 개발에서 필수 요소인 REST API에 대해 알아보려고 한다.
이번 글에서는 REST API 개념을 최초로 제시한 Roy Fielding의 논문을 살펴보며,
REST 구조가 나온 배경에 대해 알아본다.
REST API 시리즈의 글들은 아래 사이트들에서 정보를 얻었다.
- CHAPTER 5 Representational State Transfer (REST)
- REST API(RESTful API, 레스트풀 API)란? 구현 및 사용법
- REST API의 이해와 설계 #2-API 설계 가이드
- 골치아픈 REST API에서 벗어나 효율적인 모바일 네트워크를 구성하는 방법
- [Network] REST란? REST API란? RESTful이란?
- REST API
- SOAP REST 차이, 두 방식의 가장 큰 차이점은?
- API(애플리케이션 프로그래밍 인터페이스)란 - 개념, 기능, 장점
REST 아키텍처에 적용되는 6가지 제한 조건
Web(HTTP)의 창시자 중 한 사람인 Roy Fielding은 당시의 아키텍처가 웹의 본래 설계의 우수성을 많이 사용하지 못하고 있다고 판단했다.
하여 2000년에 논문을 하나 발표하는데, 웹의 장점을 최대한 활용할 수 있는 네트워크 기반의 아키텍처를 소개한다.
그것이 바로 REST, Representational State Transfer이다.
이 논문에서는 REST라는 아키텍처를 만들기까지의 과정을 적어놨는데, 그대로 따라가 보려고 한다.
0. Starting with the Null Style
아키텍처를 디자인할 때에는 크게 두 가지 관점이 있다.
첫 번째는, 진짜 아무것도 없는 빈 곳에서 시작하여 최종적으로 원하는 시스템의 조건을 충족시킬 때까지 아키텍처를 만드는 것이다.
REST API는 두 번째 관점을 사용하는데,
부가적인 것들이 없는 기본적인 시스템(Null)에서 시작하여 제약(contraint)을 추가하면서 최종적으로 원하는 시스템으로 만드는 것이다.
첫 번째 관점은 창의성을 중시하는 반면,
두 번째 관점은 제약과 시스템 context에 대한 이해를 중시한다.
1. Client-Server
처음으로 추가할 제약은 Client-Server 아키텍처이다.
아키텍처를 단순화시키고 작은 단위로 분리(decouple)함으로써
Client-Server의 각 파트가 독립적으로 개선될 수 있도록 해주는 것이다.
다시 말해,
사용자가 시스템과 상호작용하는 UI(User Interface)와 데이터 저장하는 것을 분리함으로써,
이식성(portability)을 높여 다양한 플랫폼에서 사용 가능하게 하고 서버를 단순화시켜 Scale을 높이기도 쉬워진다.
그러나 Web에서 이러한 것이 가장 중요한 이유는 Client와 Server의 분리를 통해 구성 요소를 독립적으로 발전시킬 수 있게 되고,
그로 인해 여러 조직의 Internet-Scale급 요구 사항을 지원할 수 있게 된다.
2. Stateless
다음으로는 Client와 Server 간의 상호 작용에 제약을 추가할 것이다.
바로 Client와 Server 간의 소통은 Stateless이어야만 한다는 것이다.
Client로부터 Server로 요청(Request)을 보낼 때,
각 요청에는 해당 요청을 이해하기 위한 필수적인 모든 정보를 포함하고 있어야 하고, Server에 저장되어 있는 어떠한 context라도 사용하면 안 된다.
즉, 각 요청 간 Client의 Context가 Server에 저장되어서는 안 된다.
그렇기 때문에 Session State는 Client에 의해서만 관리된다.
이러한 제약을 통해 아래 세 가지 특성을 끌어낼 수 있다.
(1) Visibility(가시성)
Monitoring 시스템이 요청마다의 특성을 파악할 때, 하나의 Request만 보면 된다.
(2) Reliability(신뢰성)
부분적으로 무언가 오류가 발생해도 복구가 쉬워진다.
(3) Scalability(확장성)
요청마다 상태(State)를 서버에 저장하지 않아도 되기 때문에 서버를 구성하고 있는 요소들이 서버 리소스를 빠르게 해제할 수 있고,
더 나아가 서버가 요청 간의 리소스 사용량을 관리할 필요도 없기 때문에 구현(implementation)도 단순화한다.
하지만 Stateless 제약에도 Trade-Off가 존재한다.
(1) 네트워크 퍼포먼스 감소
데이터가 서버의 Shared Context에 저장되지 않기 때문에,
만약 일련의 요청들의 데이터가 중복되더라도 모두 별개의 요청으로 처리하기 때문에 네트워크 퍼포먼스가 감소되는 단점이 있다.
(2) Server 측 제어 감소
또한, 애플리케이션 상태(State)를 Server가 아닌 Client 측에서 관리하게 된다면,
여러 Client 버전이 존재할 경우에 올바르게 구현된 버전에 의존하게 되기 때문에 일관된(consistent) 애플리케이션 동작에 대한 Server 측의 제어가 감소하게 된다.
3. Cache
Stateless를 추가하며 네트워크 이슈가 발생해버렸다.
이를 해결하려면 위 그림과 같은 Client-Cache-Stateless-Server 스타일로 아키텍처를 바꿔주면 되는데,
쉽게 말해 Client 단에 Cache 제약을 더하면 된다.
Cache 제약을 사용한다는 의미는 결국 Client의 Cache 기능을 사용한다는 말과 같은데,
이를 사용하려면 Server에서 Client로 보내는 응답(Request) 내의 데이터들이 캐싱이 가능한지 아닌지에 대한 정보가 있어야 한다.
만약 응답 내의 데이터를 캐싱할 수 있다면, Client의 Cache에서는 해당 데이터를 캐싱해두고 이후에 똑같은 응답(Response)을 받게 될 똑같은 요청(Request)을 하게 될 경우, 캐싱해둔 데이터를 재사용할 수 있게 된다.
이렇게 Cache 제약을 추가하게 되면, 일부 상호 작용(Interaction)을 부분적으로 또는 완전히 제거하여 일련의 상호 작용의 평균 대기 시간을 줄일 수 있게 된다.
이로 인해 효율성(Efficiency)과 확장성(Scalability), 그리고 사용자가 느끼는 성능이 향상될 수 있지만,
이 또한 등가교환의 법칙이 존재한다.
Client의 Cache에 있는 오래된 데이터가 Server로 요청(Request)을 보냈을 때 받게 될 응답(Response) 데이터와 크게 다르게 된다면 안정성(Reliability)이 감소하게 된다.
즉, Cache 내의 데이터가 오래되어 실제로 요청을 보내보면 받게 될 응답의 데이터와는 달라질 수도 있다는 이야기다.
그런데 우리가 여기까지 만들어 본 아키텍처인 Client-Cache-Stateless-Server 구조는 사실, 아래 그림과 같은 초기 웹 아키텍처이다.
1994년 이전까지는 인터넷을 통해 오고 가는 데이터들 대부분이 정적인 데이터였기 때문에 위와 같은 디자인이 가능했다.
또, Client와 Server가 의사소통하기 위해 사용되는 프로토콜의 경우, Non-Shared Cache에 대해서만 아주 기초적인 지원만 해줬을 뿐, 모든 리소스에 대한 인터페이스를 제공해주지는 않았다.
그래서 유닉스와 윈도우에서 사용 가능한, 모듈 방식의 클라이언트 사이드 웹 API인 CERN libwww라는 것을 추가로 사용하기도 했다.
하여튼, 지금의 웹 아키텍처 디자인은 우리가 지금까지 알아본 Client-Cache-Stateless-Server 구조를 이미 예~~전에 뛰어넘었다는 것이다.
정적인 데이터들 뿐만 아니라 동적인 데이터도 교환할 수 있게 되었고, Proxy나 Shared Cache 같은 Client와 Server 중간에 위치하는 component도 생겨났다.
그렇기 때문에 지금까지 살펴본 제약들은 과거의 웹 구조를 형성하고 있었다면, 이후부터 살펴볼 제약들은 현대 웹 구조를 형성하고 있는 것들이라 생각할 수 있다.
4. Uniform Interface
요게 REST 아키텍처의 핵심이다.
다른 네트워크 기반의 아키텍처 스타일과는 다르게 component들 사이의 uniform interface를 강조한다.
Uniform Interface라고 거창하게 써놨지만, 별 거 없다.
말 그대로 통일화된 인터페이스를 제공한다는 것이다.
소프트웨어 공학에는 Generality라는 원칙이 존재하는데, 이미 만들어진 것에 대해서는 생각할 필요가 없고 단지 Reuse 하기만 하면 된다는 의미이다(참고 링크).
Component interface에도 그러한 Generality 원칙을 적용함으로, 전체 시스템 구조가 단순화되어 상호 작용 간의 가시성(visibility)이 향상된다.
또한 구현(Implementation)은, 서비스를 제공하는 곳과 분리되어 독립적인 발전 가능성(evolvabilty)을 장려한다.
하지만 역시 trade-off가 존재한다.
모든 것을 통일화하고 균일화하다 보니, 정보가 애플리케이션이 필요로 하는 세부적인 형태가 아닌 표준화된(standardized) 형태로 전송되어 효율성(efficiency)이 떨어진다.
그래서 REST 인터페이스는 대규모 Hypermedia 데이터 전송에 효율적으로 설계되어 일반적인 웹의 경우에 최적화되지만,
다른 형태의 아키텍처 상호 작용에는 최적이 아닌 인터페이스가 된다.
이러한 Uniform Interface를 위해, Component들의 behavior에 대한 아래와 같은 아키텍처 제한들이 추가로 요구되기도 한다.
- Identification of Resources
- Manipulation of Resources through Representations
- Self-descriptive Messages
- Hypermedia as the Engine of Application State
5. Layered System
이제 Internet 규모의 요구사항을 위해 아키텍처를 더욱 향상할 필요가 있다.
그러기 위해 위 그림과 같은 Layered System 제약을 추가하는데,
예를 들면, 각 component는 상호 작용하는 계층 너머를 직접 볼 수가 없도록 제한하며,
그럼 Client는 보통 대상 Server에 직접 연결되었는지, 또는 중간 Server를 통해 연결되었는지를 알 수 없다.
이렇게 component들이 본인들이 속한 단일 layer에 대해서만 데이터를 공유하도록 만듦으로써,
전반적인 시스템의 복잡성(complexity)을 줄이고 독립성을 높이며 보안적인 부분도 강화할 수 있다.
또한 계층을 사용하여 legacy 서비스를 캡슐화(encapsulate)하고 legacy client로부터 새로운 서비스를 보호하기도 하며,
자주 사용하지 않는 기능을 shared intermediary(중개자)로 이동시켜 component들을 단순화할 수도 있다.
이러한 중개자(intermediary)는 여러 네트워크 및 프로세서 사이에서 Load Balancing(로드 밸런싱)을 가능하게 하기 때문에 시스템 확장성(scalability)을 개선할 수도 있다.
하지만, Layered System의 가장 큰 단점은 데이터를 처리할 때 Overhead와 Latency(지연)가 증가하여 사용자가 느끼는 퍼포먼스의 하락을 가져올 수도 있지만, Cache 제약 조건을 사용하는 네트워크 기반 시스템에서는 중개자(intermediary)의 Shared Caching(공유 캐시)의 이점으로 이 단점을 상쇄시킬 수 있기는 하다.
6. Code-On-Demand
드디어 마지막 제약 조건인 Code-On-Demand이다.
이 조건을 통해 REST를 사용하면, Applet이나 Script 형태의 코드를 다운로드하고 실행하여 Client 기능을 확장시킬 수 있게 된다.
이는 Client가 사전에 구현해야 하는 기능의 수를 줄여 Client를 단순화하고, 배포 후에 그러한 기능 다운로드를 허용하면 시스템의 확장성 또한 향상된다.
말이 좀 어렵지만,
Java Applet이나 JavaScript의 제공을 통해 Server가 Client가 실행시킬 수 있는 로직을 전송하여 기능을 확장한다는 것이다.
하지만 그만큼 가시성(visibility)이 낮아지기도 하며, 이 제약 조건은 유일하게 선택적인 조건이다.
요약 및 결론
지금까지 알아본 것처럼 REST 구조는 여러 네트워크 아키텍처 제약으로 구성되어있다.
여기서 '네트워크 아키텍처 제약'이란, 자원을 정의하고 자원에 대한 주소를 지정하는 방법 전반을 일컫는다.
간단한 의미로는, WEB 상의 자료를 HTTP 위에서 SOPA이나 Cookie를 통한 Session Tracking 같은 별도의 전송 계층 없이 전송하기 위한 아주 간단한 인터페이스를 말한다.
그리고 이러한 Fielding의 REST 원리를 따르는 시스템을 RESTful이란 용어로 지칭되기도 한다
REST 인터페이스의 원칙에 대한 가이드
1. 자원의 식별
WEB 기반의 REST 시스템에서의 URI처럼, 요청 내에 기술된 개별 자원을 식별할 수 있어야 한다.
자원 그 자체는 Client가 받는 문서와는 개념적으로 분리되어 있는데,
예를 들어, Server는 DB 내부의 자료를 직접 전송하는 대신에 DB 레코드를 HTML, XML, JSON 등의 형식으로 전송하는 것이다.
2. 메시지를 통한 리소스의 조작
Client가 어떤 자원을 지칭하는 메시지와 특정 메타데이터만 가지고 있다면, 이것으로 Server 상의 해당 자원을 변경 및 삭제가 가능하다.
3. 자기서술적 메시지
각 메시지는 자신을 어떻게 처리해야 하는지에 대해 충분히 정보를 포함해야 한다.
예를 들어, MIME type과 같은 인터넷 미디어 타입을 전달한다면, 메세지 안에는 어떤 parser를 이용해야 하는지에 대한 정보도 포함해야 한다.
즉, Client는 미디어 타입만으로도 어떻게 그 내용을 처리해야할 지 알 수 있어야 한다는 것이다.
한 예시로, "application/xml"이라는 미디어 타입은 실제 내용을 다운로드 받지 않으면 그 메시지만 가지고는 무엇을 해야할지에 대해 충분히 알 수 없기 때문에, 메시지를 이해하기 위해 그 내용까지 살펴봐야 한다.
이러한 메시지는 "자기서술적"이 아니다.
4. 애플리케이션의 상태에 대한 엔진으로서 하이퍼미디어
충분한 Context 속에서의 URI를 제공해주는 Hypertext Link처럼, 만약 Client가 관련된 리소스에 접근하기를 원한다면, return되는 지시자에서 구별될 수 있어야 한다.
REST의 주요한 목표
(1) 구성 요소 상호작용의 규모 확장성
(Scalability of component interactions)
(2) 인터페이스의 범용성
(Generality of interfaces)
(3) 구성 요소의 독립적인 배포
(Independent deployment of components)
(4) 중간적 구성요소를 이용해 응답 지연 감소, 보안을 강화, 레거시 시스템을 인캡슐레이션
(Intermediary components to reduce latency, enforce security and encapsulate legacy systems)
글이 또 이렇게 길어질 줄 상상도 못 했다..
다음 글에서는 REST API에 대해 본격적으로 알아볼 예정이다.
끝!