지난 글에서 REST 구조에 대해 알아봤다.
이번 글에서는 본격적으로 REST API의 개념, 특징, 설계 규칙 등에 대해 알아보려고 한다.
REST와 API 각각에 대한 개념에 대해서는 이번 글에서는 다루지 않을 예정이니 REST는 위에 걸어놓은 링크, API는 이전에 작성했던 글을 읽고 오면 좋다.
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 API란?
RESTful API라고도 하는 REST API는 우리가 이전 글에서 알아보았던 REST 아키텍처의 제약 조건을 준수하는 애플리케이션 프로그래밍 인터페이스를 뜻한다.
이러한 RESTful API를 통해 요청자가 어떠한 요청을 하면, 응답자 측은 요청을 받은 리소스 상태에 대한 표현을 RESTful API를 통해 요청자에게 다시 전송한다.
이 정보 또는 표현은 JSON(Javascript Object Notation), HTML, XLT 또는 일반 text와 같은 형식으로 전송된다.
이 중, JSON은 Javascript라는 이름이 들어가는 것과 다르게 사용 언어와 상관없이 인간과 머신 모두 읽을 수 있기 때문에 가장 널리 사용된다.
정리하자면,
(1) Resource를 제대로 정의하고,
(2) 이에 대한 CRUD를 HTTP METHOD(POST/GET/POST/DELET)로 사용하며,
(3) 에러 코드에 대해 HTTP Response Code를 사용해야 한다.
API가 RESTful로 간주되기 위한 기준
아래와 같은 총 6가지 기준이 있다.
읽어보고 '어, 어디서 봤는데?'라는 생각이 든다면 이전 글을 잘 이해하고 숙지하고 있는 것이다.
1. Client-Server Architecture
Client, Server 및 Resource로 구성되어 있으며, 요청이 HTTP를 통해 관리되어야 한다.
Server는 API 제공을 하고, Client는 사용자 인증에 관련된 일들을 관리한다.
자원이 있는 쪽을 Server, 자원을 요청하는 쪽이 Client가 된다.
이렇게 나누면 의존성이 줄어들기 때문에 역할이 확실하게 구분되어 개발해야 할 내용들이 명확해진다.
2. Stateless Client-Server Communication
요청 간에 Client 정보가 저장되지 않으며, 각 요청이 분리되어 있고 서로 연결되어 있지 않는다.
즉, 서버는 각 요청을 완전히 다른 것으로 인식하고 처리한다.
이렇게 상태 정보를 Server 쪽에 저장하지 않으면 각 API Server는 들어오는 요청을 메시지로만 처리하면 되며, Session이나 Cookie 같은 Context 정보를 신경 쓸 필요가 없기 때문에 구현이 단순해진다.
3. Cache
Client-Server 상호 작용을 간소화하는 Cache 기능이 가능해야 한다.
REST는 HTTP의 기존 WEB 표준을 그대로 사용하기 때문에 HTTP가 가진 Caching 기능 적용이 가능하다.
일반적인 서비스 시스템에서 60%~80%가량의 트랜잭션이 Select와 같은 조회성 트랜잭션인 것을 감안하면,
HTTP의 리소스들을 Web Cache Server 등에 캐싱하는 것은 용량이나 성능 면에서 많은 장점을 갖고 올 수 있다.
HTTP 표준에서 사용하는 "Last-Modified" 태그나 E-Tag를 이용하면 쉽게 캐싱을 구현할 수 있다.
예를 들어, 위처럼 Client가 HTTP GET을 "Last-Modified" 값과 함께 보냈을 때, 콘텐츠에 변화가 없으면 REST Component는 "304 Not Modified"를 리턴하고 Client는 자체 캐시에 저장된 값을 사용하게 된다.
이런 식으로 캐시를 사용하게 되면 네트워크 응답 시간뿐만 아니라, REST Component가 위치한 Server에 트랜잭션을 발생시키지 않기 때문에, 전체 응답 시간과 성능 그리고 Server의 자원 사용률을 비약적으로 향상할 수 있다.
4. Uniform Interface
정보가 표준 형식으로 전송되도록 하기 위한 구성 요소(Component) 간 통합 인터페이스이다.
HTTP 표준만 따른다면 어떤 언어나 플랫폼에서 사용이 가능하다는 의미이다.
예를 들어, HTTP + JSON 형태로 REST API를 정의했다면,
안드로이드 플랫폼이든, iOS 플랫폼이든, C, Java, Python이든 특정 언어나 기술에 종속받지 않고 HTTP와 JSON을 사용 가능한 모든 플랫폼에서 사용이 가능한 느슨한 결합(Loosely Coupling) 형태의 구조이다.
이러한 통합 인터페이스에 필요한 것은 아래 4가지다.
(1) 요청된 리소스가 식별 가능하며, Client에 전송된 표현과 분리되어야 한다.
(2) 수신한 표현을 통해 Client가 리소스를 조작 가능해야 한다(이렇게 할 수 있는 충분한 정보가 표현에 포함되어 있기 때문).
(3) Client에 반환되는 자기 기술적(Self-Descriptive) 메시지에 Client가 정보를 어떻게 처리해야 할지 설명하는 정보가 충분히 포함되어야 한다. 즉, REST API 메시지만 보고도 쉽게 이해할 수 있어야 한다.
(4) Hypermedia(하이퍼미디어) -> Client가 리소스에 접근한 후 Hyperlink(하이퍼링크)를 사용해 현재 수행 가능한 기타 모든 작업을 찾을 수 있어야 한다.
5. Layered System
요청된 정보를 검색하는 데 관련된 서버(보안, 로드 밸런싱 등을 담당)의 각 유형을 Client가 볼 수 없는 계층 구조로 체계화하는 계층화된 시스템이어야 한다.
즉, Client는 REST API 서버만 호출한다.
6. Code-On-Demand(선택 사항)
Server가 요청을 받으면 Client로 실행 가능한 코드를 전송하여 Client 기능을 확장할 수 있는 기능이다.
REST API 구성
그럼 이제 본격적으로 REST API에 대해 알아보자.
REST API를 사용하기 위해서는 아래 세 가지 구성 요소가 필요하다.
(1) 자원(Resource) : URI
(2) 행위(Verb) : HTTP METHOD
(3) 표현(Representations) : 메시지
즉, URI를 통해 자원을 표시하고
HTTP METHOD를 이용하여 해당 자원의 행위를 정해주며
그 결과를 받는 것을 말한다.
예를 들어, "헬스 운동 중 이름이 벤치 프레스라는 종목을 생성한다"라는 호출이 있을 때,
"종목(헬스 운동)"은 생성되는 자원(Resource),
"생성한다"라는 행위(Verb),
"이름이 벤치 프레스라는 종목"은 표현(Representations)이 된다.
맛보기로 어떻게 REST 형태로 표현하는지 보자면 아래와 같다.
# HTTP POST == '생성한다'는 의미를 갖는 행위
# http://myweb/fitness == 생성하고자 하는 대상이 되는 자원이 표현되는 형태(URI)
# 생성하고자 하는 운동 종목의 디테일한 내용은 JSON 형식을 이용하여 표현됨
HTTP POST, http://myweb/fitness/
{
"exercise": {
"name": "Bench Press"
}
}
각각에 대해 좀 더 자세히 알아보며 설계 규칙까지 같이 알아보자.
1. URI는 정보의 자원을 표현해야 한다
URI는 자원(Resource)을 표현하는데 중점을 두어야 하기 때문에 행위에 대한 표현이 들어가면 안 된다.
즉, 해당 자원을 읽거나 만들거나 수정하거나 삭제하는 등 URI에 동사 표현이 들어가면 안 된다.
그래서 자원의 이름은 동사가 아닌 명사를 사용해야 한다.
ex) GET /users/123
다른 예를 들어, "회원 중 id 1번을 삭제"라는 API를 GET /users/delete/1과 같이 쓰면 안 되고 DELETE /users/1처럼 수정해야 한다.
물론 모든 명령이 이런 명사형으로 정의가 가능한 것은 아니다.
만약 '사용자가 좋아하는 디바이스 목록'을 표현해야 할 경우에는, GET /users/{userid}/likes/devices 와 같은 형태로 사용할 수도 있다.
그래도 되도록이면 리소스 기반의 명사 형태로 정의를 하는 것이 REST 형태의 디자인이 된다.
몇 가지 옳은 예와 옳지 않은 예를 추가로 남겨둔다.
GET /users/show/1 -> (X)
GET /users/1 -> (O)
GET /members/insert/2 -> (X)
POST /members/2 -> (O)
2. 자원에 대한 행위는 HTTP METHOD로 표현한다
그럼 그러한 자원에 대한 행위는 뭘로 표현할까?
바로 HTTP METHOD를 이용한다.
HTTP METHOD에는 GET, POST, PUT, DELETE 등이 있다.
흔히 CRUD(Create, Read, Update, Delete)라고 부르는 데이터베이스 용어가 있는데, C는 POST, R은 GET, U는 PUT, D는 DELETE와 같다.
ex)
GET /users/123 == 유저 ID가 123인 유저의 정보를 가져오기
DELETE /users/123 == 유저 ID가 123인 유저의 정보를 삭제하기
POST /users/123 == 유저 ID가 123인 유저 생성
여기서 한 가지 더 중요한 개념을 짚고 넘어갈 건데, 멱등성(冪等性)이라고도 불리는 Idempotent이다.
거듭제곱을 뜻하는 멱(冪)과 같다는 의미를 가지는 등(等)이 합쳐져, 연산을 여러 번 적용하더라도 결과가 달라지지 않는 성질을 의미한다.
멱등성을 알아보는 간단한 예를 들자면,
x라는 정수 값을 갖고 있는 변수를 더하는 행위인 x++은 연산을 적용할 때마다 결과가 달라지기 때문에 Idempotent 하지 않다고 하지만(멱등성이 없음),
x=3과 같은 대입 연산은 반복적으로 수행해도 Idempotent 하다(멱등성이 있음).
하여튼, 이 개념을 왜 꺼냈냐 하면 HTTP Method에도 이러한 멱등성 여부를 따질 수 있기 때문이다.
'그게 뭐 어때서!'하고 생각할 수도 있지만, Idempotent 하지 않는 HTTP Method를 사용하는 트랜잭션에 대해서는 각별한 주의가 필요하기 때문이다.
예를 들어, 게시물 조회를 하는 API가 있다고 가정하자.
조회할 때마다 조회수를 +1 해주는 연산을 수행하기 때문에 이 Method는 Idempotent 하지 않고, 만약 조회하다가 실패했을 때는 이미 올라가버린 조회수를 -1 해줘야 한다.
그럼 위에서 본 HTTP METHOD와 멱등성의 개념을 다시 생각해보면, 딱 하나의 HTTP METHOD가 Idempotent 하지 않은 것을 알 수 있다.
바로 리소스를 추가하는 연산인 POST이다.
3. 슬래시(/)는 계층 관계를 나타내는 데 사용한다
Windows의 파일 탐색기를 생각하면 된다.
ex)
http://hellchang.com/users/benchpress
http://hellchang.com/users/deadlift
4. URI 마지막은 슬래시(/)를 사용하면 안 된다
ex)
http://hellchang.com/users/benchpress/ [X]
http://hellchang.com/users/deadlift [O]
5. 언더바(_) 혹은 밑줄 대신 하이픈(-)을, 대문자 대신 소문자를 사용한다
불가피하게 긴 URI를 사용하게 될 경우, 언더바나 밑줄 대신 하이픈을 사용하여 가독성을 높인다.
언더바나 밑줄은 보기에도 어렵고 문자가 가려지기도 하기 때문이다.
또한 URI는 대소문자를 구분하기 때문에 대소문자가 달라지면 각각을 다른 자원으로 인식한다.
그래서 소문자만 사용하는 것이 좋다.
6. 파일 확장자는 URI에 포함하지 않는다
REST API에서는 메시지 body 내용의 포맷을 나타내기 위한 .exe, .txt 등의 파일 확장자를 URI 안에 포함시키지 않는다.
대신 Accept header를 사용한다.
ex) GET /users/soccer/345/photo HTTP/1.1 HOST: restapi.example.com Accept: image/jpg
REST Anti Pattern
지금까지 REST API에 대한 개념과 전체적인 특징에 대해 알아봤다.
이제는 REST 디자인 시, 하지 말아야 할 안티 패턴에 대해 알아보자.
1. GET / POST를 이용한 터널링
먼저 터널링이라는 용어에 대해 간단하게 알아보자면, 용어 그대로 터널을 뚫어 데이터를 조작한다는 것이다.
GET을 이용한 터널링의 한 예를 보자.
GET http://myweb/users?method=update&id=terry
METHOD의 실제 동작은 리소스를 업데이트하는 내용인데, HTTP PUT을 사용하지 않고 GET의 쿼리 파라미터로 method=update라고 넘겨서 이 메서드가 수정 메서드임을 명시했다.
HTTP METHOD 사상을 따르지 않았기 때문에 REST라고 부를 수도 없고, Web Cache 인프라 등의 사용도 불가능하기 때문에 대단히 좋지 않은 디자인이다.
또 많이 사용하는 좋지 않은 터널링 예는 아래와 같은 POST를 이용한 터널링이다.
Create(Insert) 성 명령이 아닌데도 불구하고, JSON Body에 명령을 넘기는 형태이다.
POST http://myweb/users/
{
"getuser": {
"id": "terry",
}
}
위 API는 특정 사용자 정보를 가지고 오는데, GET이 아닌 POST를 이용해서 만든 나쁜 예이다.
2. Self-Descriptive 속성을 사용하지 않음
앞서 API가 RESTful 하기 위한 조건 중 Uniform Interface 항목의 Self-Descriptive(자기 서술성) 속성을 사용하여, REST URI와 METHOD, 그리고 쉽게 정의된 메시지 포맷에 의해 쉽게 API를 이해할 수 있어야 한다.
이러한 Self-Descriptive를 위반하는 가장 대표적인 사례가 바로 전에 알아본 GET / POST를 이용한 터널링 구조다.
3. HTTP Response Code를 사용하지 않음
많이들 하는 실수 중 하나가 HTTP Response Code를 충실하게 따르지 않고, 성공은 200, 실패는 500과 같이 1~2개의 Code만 사용하는 경우이다.
심한 경우에는 에러가 발생해도 HTTP Response Code 200으로 정의하고 별도의 에러 메시지를 200 Response Code와 함께 보내기도 하는데, 이는 REST 디자인 사상에도 어긋남은 물론이고 Self-Descriptive에도 어긋난다.
HTTP 응답 코드
HTTP METHOD를 사용하는 만큼, 응답 코드에 대해서도 숙지해두면 좋다.
2xx -> 성공
200 : Client의 요청을 정상적으로 수행
201 : Client가 어떠한 리소스 생성을 요청, 해당 리소스가 성공적으로 생성됨(POST를 통한 리소스 생성 작업 시)
204 : 요청은 성공, but 응답할 콘텐츠가 없음
3xx -> 리다이렉션(Redirection)
301 : Client가 요청한 리소스에 대한 URI가 영구적으로 변경되었을 때 사용(응답 시 Location Header에 변경된 URI를 적어줘야 함)
302 : 301과 같으나, 임시적으로 주소가 바뀌었을 경우 사용
304 : 이전에 방문했을 때의 요청 결과와 다르지 않을 경우 사용, 캐시 된 페이지를 그대로 사용
307 : 임시 페이지로 redirect
4xx -> Client 오류
400 : Client의 요청이 부적절할 경우 사용
401 : Client가 인증되지 않은 상태(ex. 로그인을 하지 않음)에서 보호된 리소스를 요청했을 때 사용
403 : 유저 인증 상태와 관계없이 응답하고 싶지 않은 리소스(ex. admin 페이지)를 Client가 요청했을 때 사용(403보다는 400이나 404 사용을 권고, 403 자체가 리소스가 존재한다는 뜻이기 때문)
404 : 찾을 수 없는 페이지, 주소를 잘못 입력했을 때 사용, 403 대신 사용 가능(해커들의 공격을 방지하고자 페이지가 없는 것처럼 위장)
405 : Client가 요청한 리소스에서는 사용 불가능한 METHOD를 이용했을 경우 사용
408 : 요청 시간이 초과됨
409 : Server가 요청을 처리하는 과정에서 충돌이 발생한 경우(ex. 회원가입 중 중복된 ID인 경우)
410 : 영구적으로 사용할 수 없는 페이지
5xx -> Server 오류
501 : 해당 요청을 처리하는 기능이 만들어지지 않음
502 : Server로 가는 요청이 중간에서 유실된 경우
503 : Server가 터졌거나 유지 보수 중(이때 유지 보수 중이라는 것을 알려주는 페이지로 전송해주는 것이 좋음)
504 : Server 게이트웨이에 문제가 생겨 시간 초과가 된 경우
505 : HTTP version이 달라 요청이 처리할 수 없음
어우 시간을 너무 많이 잡아먹는데..
다음 글에서는 REST API의 단점과 디자인 가이드에 대해 알아본다.