REST API에 대해 알아본 지난 1편, 2편에 이은 마지막 3편이다.
이번 글에서는 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(애플리케이션 프로그래밍 인터페이스)란 - 개념, 기능, 장점
장점
장점은 아주 심플하다.
HTTP METHOD인 GET, POST, PUT, DELETE 네 개로 서버가 하는 대부분의 일을 설명 가능하다는 것이다.
특히 서버로부터 데이터를 가져오거나 서버의 데이터 일부를 업데이트하는 정도의 기본 데이터 중심 API인 경우에는 아주 충분하다.
그러한 간단한 한 예를 들어보자.
위 그림처럼 사용자에게 "지금 내 주변의 정보"를 제공하는 모바일 앱이 있다고 가정하면,
현재 위치를 통한 현재 날씨를 한 서버에서 가져오거나,
현재 위치 근방에서 벌어진 뉴스를 다른 서버에서 가져오거나,
현재 위치에서 가까운 음식점을 또 다른 서버에서 가져오는 등,
이러한 모바일 앱은 여러 서버에 여러 번 REST API 호출을 하는 "비즈니스 로직"을 갖고 있다.
이러한 예제는 모바일 앱이 두뇌의 역할을 맡고 API는 단순히 데이터를 제공하는 형태이다.
이러한 구조에서 REST API의 장점이 별 것 없어 보이지만, 'Simple is the Best'라는 말도 있듯이, 직관적이고 단순할수록 러닝 커브도 줄어들고 실제 적용했을 때에 문제점도 많이 없다.
단점 및 오해
하지만 위 예제는 오직 모바일 앱만이 비즈니스 로직을 제공하는 아주 단순한 경우로, 만약 앱과 서버가 독립적인 비즈니스 로직을 갖고 있는 형태의 모바일 앱이라면 어떨까?
데이터의 CRUD(Create, Read, Update, Delete) 측면에서 REST API를 통해 서버와 통신하는 동안 여러 문제점이 발생할 수 있다.
1. Schema나 Type 유효성의 부재
말이 좀 어려울 수 있지만,
모바일 앱이 서버에 무언가 데이터 요청을 했는데 해당 데이터가 없을 수 있거나(Schema의 부재),
요청한 데이터가 있지만 모바일 앱이 기대했던 Type과는 다른 Type의 데이터가 반환되는 경우다(Type 유효성의 부재).
Schema 부재에 대한 예를 들어보자.
모바일 은행 앱이 모든 사용자의 계좌 정보를 알기 위해 서버에 GET /accounts라고 요청할 수는 있지만, 서버에 실제로 저런 End-Point가 있으리라는 보장은 없다.
예전 API 버전에는 있었지만, 개발자들이 업데이트하면서 현재 버전에는 없는 경우도 있을 것이다.
만약 /accounts가 존재한다는 가정하에 GET /accounts/153와 같은 형태로 특정 계좌의 상세 정보를 요청했다면, 이 역시 실제로 id가 153인 계정 정보가 존재하는지는 확신할 수가 없다.
전체 계정 목록을 요청한 이후에 다른 사용자가 모바일 앱으로 해당 계정의 정보를 수정하거나 서버에서 삭제할 가능성이 있기 때문이다.
이번에는 Type 유효성 부재에 대한 예를 들어보자.
위 사진을 어떤 모바일 은행 앱의 API를 설계한 두 개발자의 API Return 방식이라고 가정하자.
두 개발자 모두 특정 id의 계좌 요청이 들어올 때 'amount'와 'curreny'라는 같은 정보를 return 하도록 설계했지만, 'amount'가 왼쪽 설계에서는 숫자이고 오른쪽 설계에서는 문자열로 들어오는 차이점이 있다.
물론 이런 일은 개발 과정에서 의사소통을 통해 하나로 통합할 테지만, 만약 실수로 그러지 못했다고 가정했을 때 굉장히 사소한 것으로 보이는 이러한 차이가 모바일 앱의 작동을 멈추게 할 수도 있다.
2. 데이터 변환
로직이 복잡한 모바일 앱에서 데이터 타입을 처리해야 하는 작업이 잦다면, 굉장히 성가실 뿐만 아니라 버그의 원인이 될 수도 있다.
HTTP와 JSON을 사용하는 REST API를 예로 들었을 때, 아래와 같은 flow로 네트워크를 통해 전송되는 데이터가 여러 변형을 거친다고 가정하자.
(1) Native 데이터 모델 정의
-> Native 코드로 데이터 모델을 정의한다.
(2) Serialize (Native 데이터 -> JSON)
-> 문자열, 숫자, 리스트, 딕셔너리 등만을 사용해서 Native 객체들을 JSON에 호환되는 표현으로 변환한다.
-> Native 데이터 Entity를 REST End-point가 요구하는 구조로 바꾼다.
(3) HTTP를 통해 전송
-> 데이터를 JSON 문자열로 Encoding 한 뒤, multipart HTTP 형태의 데이터로 다시 Encoding 하여 POST 요청을 보낸다.
(4) 서버에서 데이터를 Decoding
-> POST 데이터를 Decoding 한 뒤, JSON 문자열을 Decoding 한다.
(5) SQL DB에 데이터를 저장
-> 요청된 아이템을 Cherry-pick 하고, 서버 저장소에 매핑되는 객체를 생성한다.
-> 객체를 SQL DB에 저장할 수 있는 데이터로 변환한다.
이렇게 네트워크를 통해 전송하는 데이터는 형식과 타입이 여러 번 변경된다.
그리고 이러한 변환 과정에서 1번 단점도 발생할 수 있다.
3. Business Logic Wrapping
위의 단점들처럼 반복적으로 데이터를 변환하고 코드를 변환하는 과정뿐만 아니라, 언어 자체를 변경해야 하기도 한다.
위와 같은 예제에서 앱의 코드는 Java와 같은 Business Domain 언어를 사용하여 구현되었다고 가정하자.
그러한 방식은 앱의 목적과도 잘 부합하고, 구현 중인 비즈니스 로직을 구체적으로 처리하도록 설계된 다양하게 표현된 API이다.
특히, 사용 중인 언어의 최신 기능을 사용하도록 앱의 API를 설계한다면 앱을 가장 잘 설명하는 방향으로 데이터 Entity를 설계할 수도 있다.
하지만 이렇게 잘 설계된 시스템이 있음에도, 다시 CRUD 작업들로 표현할 수 있도록 모든 것을 다시 설계해야 한다.
즉, 요구사항을 잘 표현할 수 있도록 설계된 데이터 Entity를 다시 쪼개서 JSON 구조를 만들어야 한다.
이에 따라 잘 만들어둔 API의 표현력과 모델의 풍부함을 잃고, 단지 REST API의 단순함과 유연함만을 중심으로 비즈니스 로직을 Wrapping 하게 된다.
4. 항상 실패 가능성이 있는 네트워크 요청
우리가 생각하는 REST API는 위 그림의 상단 부분처럼 간단해 보이지만, 데이터를 주고받고 요청하는 모든 과정은 네트워크를 통해 이뤄지기 때문에 네트워크 연결이 일시적으로 끊어지거나, 웹 서버에서 문제가 발생하거나, 서버 애플리케이션에서 오류가 발생하는 등 여러 오류가 발생할 수 있다.
모바일 앱 단과 서버 단에서 모두 완벽한 통신 로직 코드를 구현하려면 발생 가능한 모든 Error에 대한 Handling 처리가 필요하다.
위 그림만 봤을 때는 그다지 복잡해 보이지 않을 수도 있지만, 한 번의 요청이 아닌 여러 요청, 심지어 종속적인 요청이 들어온다면 엄청난 복잡한 상황이 펼쳐질 수도 있다.
5. HTTP+JSON에 대한 오해
REST에 대한 잘못된 이해 중의 하나가 HTTP와 JSON을 쓰면 REST라고 부르는 경우가 많다고 한다.
하지만 REST에 대한 속성을 제대로 이해하고 디자인해야 제대로 된 REST 스타일이라고 부를 수 있다.
6. 표준 규약의 부재
REST는 표준이 없다.
그러다 보니 개발에 있어 이를 관리하기가 어렵다.
표준이 존재하고 그 표준을 따르게 되면 몇 가지 Spec에 맞춰 개발 프로세스나 패턴을 만들 수 있는데, REST는 그러한 표준이 없으니 REST 기반 시스템을 설계하려면 사용할 REST에 대한 자체 표준을 정해야 한다.
근래에 들어서는 YAML 등과 같이 REST에 대한 표준을 만들고자 하는 움직임은 있으나, JSON의 자유도를 제약하는 방향이기도 하고 YAML에 대한 Learning Curve가 다소 높기 때문에 그다지 확산되지는 않고 있다고 한다.
이런 표준의 부재에서 오는 관리의 문제점을 해결하려면 제대로 된 REST API 표준 가이드를 만들고, API 개발 전후로 문서(Spec)를 만들어서 리뷰하는 프로세스를 갖추면 된다.
7. RDBMS에 적용하기 어려움
RDBMS(관계형 DBMS)에서 하나의 Row는 하나의 Resource인 경우가 많으며, PK(Primary Key)가 여러 개로 묶여 하나의 PK가 되는 복합 Key 형태로 존재하는 경우가 대다수이다.
하지만 그러한 복합 Key는 "/"에 따라 계층 구조를 가지는 HTTP URI에서 매우 부자연스러운 표현이다.
예를 들어, 어떠한 RDBMS의 Table의 PK가 "세대주의 주민등록번호" + "사는 지역" + "성명"이라고 가정하자.
REST API에서는 이를 userinfo/{세대주 주민등록번호}/{사는 지역}/{성명} 식으로 표현할 수밖에 없는데 다소 이해하기가 어렵거나 이상해 보일 수 있다.
이를 해결하는 방안으로는 Google이 사용하는 REST 방식이 있는데, AK(Alternative Key)를 사용하는 것이다.
의미를 가지지 않지만 Unique 한 값을 AK로 잡아 사용하는 방식인데, DB에 AK 필드를 추가하는 것 자체가 전체적인 DB 설계에 대한 변경을 의미하고, 이는 REST를 위해 전체 시스템 아키텍처에 변화를 준다는 점에서 REST 사용 시 아키텍처 접근의 필요성을 의미한다.
그래서 최근에는 JSON Document를 그대로 넣을 수 있는 구조를 갖추고 있는 MongoDB 같은 Document based NoSQL를 사용하기도 한다.
하나의 Document를 하나의 REST Resource로 취급하면 되기 때문에 REST의 Resource 구조에 매핑하기 수월해진다.
그럼에도 불구하고..
이미 위 단점들에 대한 솔루션이 많이 구현되어 있다.
MSA(Micro Service Architecture)이나 데이터 기반 API는 REST를 통해 뛰어난 자동화 기능을 제공하므로 데이터 변환 등과 같은 반복적인 작업을 구현하는 부담을 줄여준다.
또한 Business Logic을 Wrapping 하기 위해 잘 설계된 End-Point는 특정 요청을 위해 많은 수의 요청을 해야 하는 것과 같은 문제를 해결하는 데에 큰 도움이 된다.
혹은 GET 요청의 매개 변수를 추가하거나, POST 요청의 JSON Payload(전송되는 데이터)를 커스텀하는 등 REST 요청을 보다 지능적으로 만든 작업들도 존재한다.
REST API 디자인 가이드
1. URI는 단순하고 직관적으로 설계
URI만 보고도 REST API를 직관적으로 이해할 수 있을 만큼 URI를 단순하고 직관적으로 설계해야 한다.
또한 Depth를 너무 깊게 들어가는 것보다 최대 2 Depth 정도로 간단하게 만드는 것이 이해하기 편하다.
ex) /accounts
ex) /accounts/153
그리고 URI에 Resource명을 기재할 때에는 동사보다는 명사를 사용하여, REST API는 그러한 명사형 리소스에 대한 행동을 정의하는 형태를 사용한다.
즉, HTTP METHOD에 의해 CRUD의 대상이 되는 리소스여야 한다.
예를 들면, accounts라는 리소스를 생성하라는 의미로 POST /accounts처럼 사용하는 것이 옳은 예이고,
POST /getAccounts 나 POST /setAccountsOwner 은 get과 set 등의 "행위"를 URI에 붙인 잘못된 예시들이다.
이보다는 GET /accounts나 POST /accounts/43/owner/James를 사용하는 것이 좋다.
추가로 가급적이면 리소스의 의미상 단수형 명사(account)보다는 복수형 명사(accounts)를 사용하는 것이 더 이해하기 쉽다.
2. Resource 간의 관계 표현 방식
리소스들 간에는 서로 연관 관계가 있을 수 있는데, 예를 들어 유저가 갖고 있는 애플 제품 목록이나 반려동물 등이다.
이러한 리소스 간의 관계를 표현하는 방법은 크게 두 가지가 있다.
(1) Sub Resource로 표현
유저가 갖고 있는 애플 제품 목록을 예로 들어 표현해보자.
/{Resource명}/{Resource ID}/{관계가 있는 다른 Resource명}의 형태(ex. GET /users/siahn95/appleDevices)일 것이다.
이처럼 siahn95라는 유저가 갖고 있는 애플 제품 목록을 리턴하는 방법이 있다.
(2) Sub Resource에 관계를 명시
만약 리소스 간의 관계가 복잡하게 얽혀있다면, 그러한 관계명을 명시적으로 표현하는 방법이다.
예를 들어, 유저가 "좋아하는" 애플 제품 목록을 이 방식으로 표현하자면 아래와 같다.
ex) GET /users/siahn95/likes/appleDevices
siahn95라는 사용자가 좋아하는 애플 제품 목록을 관계를 표현하여 리턴하는 방식이다.
위 (1), (2) 방법 중 어떤 것을 사용하더라도 문제는 없지만,
(1)의 방법은 일반적으로 소유(has)의 관계를 표현할 때 사용하며,
(2)의 방법은 리소스 간의 관계의 명이 애매하거나 구체적인 표현이 필요할 때 사용한다.
3. Error 처리
REST API에서 에러 처리의 기본은 HTTP Response Code를 사용하여 Response Body에 에러에 대한 상세 사항을 서술하는 것이 좋다.
먼저, 대표적인 IT 기업들의 API 서비스가 어떤 HTTP Response Code를 사용하는지 살펴보자.
Google의 경우 10개(200, 201, 304, 400, 401, 403, 404, 409, 410, 500),
Netflix의 경우 9개(200, 201, 304, 400, 401, 403, 404, 412, 500),
Digg의 경우 8개(200, 400, 401, 403, 404, 410, 500, 503)의 Response Code를 사용한다.
(참고: https://cloud.google.com/blog/products/api-management/restful-api-design-what-about-errors)
(Response Code 관련한 설명은 이전 글 하단부 참고, 혹은 위키백과 참고)
많은 Response Code를 사용하면 자세히 어떤 응답인지 알 수 있겠지만, 코드 관리가 어렵기 때문에 아래처럼 주요 몇 가지 Response Code를 사용하는 것을 권장한다.
- 200 (성공)
- 400 (Bad Request -> field validation 실패 시)
- 401 (Unauthorized -> API 인증, 인가 실패 시)
- 404 (Not found -> 해당 리소스가 없음)
- 500 (Internal Server Error -> 서버 에러)
다음으로는 에러 내용에 대한 상세 사항을 HTTP Response Body에 기술하여 해당 에러의 원인을 전달하는 것이 디버깅에 유리하다.
Twillo의 Error Message 형식의 경우 아래처럼 보여준다고 한다.
- HTTP Status Code : 401
- {"status":"401", "message":"Authenticate", "code":200003, "more info":"http://www.twillo.com/docs/errors/200003"}
이처럼 에러 코드 번호뿐만 아니라 해당 코드에 대한 에러 Docs 링크 또한 제공하는 것이 좋다.
에러에 대한 Document를 제공하는 것은 API에서 국한되는 것이 아니라 Oracle의 WebLogic 같은 잘 정의된 소프트웨어 제품의 경우에는 모두 제공하는데, 개발자에게 많은 정보를 제공하여 디버깅을 좀 더 쉽게 해 준다.
그렇다고 에러에 대한 상세 사항을 기술할 때, Stack 정보까지 포함하게 된다면 내부 코드와 프레임워크 구조를 외부에 노출하게 되므로 해커들에게 해킹을 할 수 있는 정보를 제공하기 때문에 굉장히 위험하다.
그래서 일반적인 서비스 구조에서는 에러 Stack 정보를 API Error Message에 포함시키지 않는 것이 바람직하나,
내부 개발 중이거나 디버깅 시에는 매우 유용하기 때문에 API 서비스를 개발하고 있다면 Server의 모드를 Prod와 Dev로 나누어 Dev인 경우에는 REST API의 에러 메시지에 Stack 정보를 포함해서 리턴하도록 하면 디버깅에 매우 유용하게 사용할 수 있다.
4. API Version 관리
Error 처리와 더불어 API 설계 시 중요한 것이 버전 관리이다.
이미 배포된 API의 경우, 해당 서비스는 계속 유지하면서 새로운 기능이 들어간 API를 배포할 때에는 하위 호환성을 보장해야 하기 때문에, 같은 API여도 버전에 따라 다른 기능을 제공해야 한다.
이러한 API 버전을 정의하는 방법에는 여러 가지가 있는데, 예시를 들어보면 크게 보자면 아래 두 가지가 있다.
- ?v={version}
- {Service Name}/{version}/{REST URI}
조대협 님의 경우 두 번째 형태로 아래처럼 정의하는 것을 권장하는데, 이는 서비스의 배포 모델과 관계가 있다고 한다.
- ex) api.server.com/account/v2.0/groups
account 서비스를 제공하는 위와 같은 Java 애플리케이션이 있다고 가정할 때, account.v1.0.war, account.v2.0.war과 같이 버전이 다른 War로 각각 배포하면 배포 binary를 관리할 수 있다.
또한, 앞 단에 서비스 명을 별도의 URL로 떼어 놓으면 향후 서비스가 확장되어도 account 서비스만 별도의 서버로 분리해서 배포가 가능하기 때문이다.
즉, 외부로 제공되는 URL은 api.server.com/account/v2.0/groups로 하나의 서버를 가리키지만, 내부적으로는 HAProxy 같은 Reverse Proxy를 이용하여 해당 URL을 account.server.com/v2.0/groups로 매핑을 할 수 있기 때문에 향후 서비스가 확장되어도 외부에 노출되는 URL에 대한 변경을 할 필요 없이 서버를 물리적으로 분리해내기 편리하기 때문이다.
5. Paging & Partial Response
만약 Client가 보낸 요청에 대한 응답이 100만 개의 데이터를 담고 있는 엄청난 사이즈의 list 형태라면, 이를 하나의 HTTP Response로 처리하는 것은 서버 성능과 네트워크 비용 등의 이슈가 발생하며 아주 비현실적이다.
그래서 이러한 대용량 데이터를 담고 있는 Response를 처리하기 위해 Paging과 Partial Response 기법이 필요하다.
먼저 Paging 처리 방법은 여러 스타일이 있다.
80번째 record부터 100번째 record까지 받는 API가 있다고 가정하면, 여러 IT 기업에서는 아래처럼 처리하고 있다.
Facebook API -> /record?offset=80&limit=20
Twitter API -> /record?page=4&rpp=20
(RPP는 Record Per Page의 준말로, page 당 record 수를 뜻한다,
rpp가 20이고 page가 4이면 80번째부터 100번째까지의 record이다)
LinkedIn API -> /record?start=80&count=20
구현하는 관점에서 보면 확실히 Facebook이나 LinkedIn 스타일이 직관적이기 때문에, 해당 스타일들을 사용하는 것을 권장한다고 한다.
두 번째로 알아볼 것은 Partial Response 처리 방법이다.
Client가 요청한 Resource에 대한 응답 메시지에서 굳이 모든 Field를 포함하지 않아도 되는 케이스들이 있다.
가령, Facebook의 피드는 User ID, 이름, 글 내용, 날짜, 좋아요 Count, 댓글, 사진 등 여러 정보를 갖는데, API를 요청하는 Client의 용도에 따라 몇 가지 필드만 필요한 경우가 있다.
이처럼 필드를 제한하게 되면 전체 응답의 양이 줄어들어 네트워크 대역폭(특히 모바일 환경에서)을 절약할 수 있고, 응답 메시지가 간소화되어 Parsing 등을 더욱 빠르게 진행 가능하다.
Partial Response 기능을 포함하고 있는 잘 디자인된 IT 기업들의 REST API 형태를 보면 다음과 같다.
Facebook API -> /siahn95/friends?fields=id,name
Google API -> ?fields=title,media:group(media:thumbnail)
(RPP는 Record Per Page의 준말로, page 당 record 수를 뜻한다,
rpp가 20이고 page가 4이면 80번째부터 100번째까지의 record이다)
LinkedIn API -> /people:(id,first-name,last-name,industry)
Facebook과 Google은 비슷한 접근 방법을 사용하는데, 특히 Google의 경우, group(media:thumbnail)처럼 JSON의 Sub-Object 개념을 지원한다.
LinkedIn의 경우, 가독성은 높지만 ":( )"로 구별하기 때문에 HTTP 프레임워크에서는 전체를 하나의 URL로 인식하고 :( 부분을 별도의 파라미터로 구별하지 않기 때문에 Parsing이 어렵다.
그래서 구현이 간단한 Facebook의 Partial Response를 권장한다고 한다.
6. 검색 (전역 검색 & 지역 검색)
사용자 등이 설정한 검색 조건은 일반적으로 HTTP Get의 Query String에 검색 조건을 정의하는데, 다른 Query String과 섞이는 위험이 있을 수 있다.
예를 들어, name=ahn이고 region=seoul인 사용자를 검색하기 위해 Query String만 사용하게 된다면 아래처럼 표현이 가능하다.
- /users?name=ahn®ion=seoul
그런데 만약 응답 결과가 대용량 데이터라면 아래처럼 Paging 처리를 추가할 수 있다.
- /users?name=ahn®ion=seoul&offset=30&limit=20
이렇게 되면 Paging 처리를 위해 추가된 offset과 limit이 검색 조건인지 Paging 조건인지 구분이 안 가게 된다.
그래서 Query 조건은 URLEncode를 사용하여 아래처럼 하나의 Query String으로 정의하는 것이 좋다.
- /user?q=name%3Dahn,region%3Dseoul&offset=30&limit=10
실제로는 "q= name=ahn,region=seoul"을 뜻하는 "q=name%3Dahn,region%3Dseoul"처럼 구분자 등을 통해 Query String을 작성하게 된다면, 우리가 원했던 검색 조건과 다른 Query String을 분리시킬 수 있다.
다음으로는 검색의 범위에 대해 고려할 필요가 있는데, 전체 리소스에 대한 검색을 하는 전역 검색과 특정 리소스에 대한 검색을 하는 지역 검색으로 나눌 수 있다.
예를 들어, 시스템에 users, cats, keyboards와 같은 리소스가 정의되어 있다고 하자.
이때 id=siahn95인 리소스에 대한 전역 검색과 지역 검색은 아래와 같다.
- 전역 검색 => /search?q=id%3Dsiahn95
- 지역 검색 => /users?q=id%3Dsiahn95
전역 검색의 경우 /search와 같은 전역 검색 URI를 사용하고, 지역 검색의 경우 리소스 명에 Query 조건을 붙이는 식으로 표현이 가능하다.
7. HATEOAS를 이용한 Link 처리
HATEOAS는 Hypermedia As The Engine Of Application State의 약어로,
Hypermedia의 특징을 이용하여 HTTP Response에 다음에 올 Action이나 관계되는 리소스에 대한 HTTP Link를 함께 리턴하는 것이다.
예를 들어, 앞서 설명한 Paging 처리의 경우 HTTP Response를 리턴하면서 전/후 페이지에 대한 링크를 아래처럼 제공 가능하다.
{
[
{
"id":"user1",
"name":"bob"
},
{
"id":"user2",
"name":"shawn"
}
],
"links":[
{
"rel":"pre_page",
"href":"http://xxx/users?offset=5&limit=5"
},
{
"rel":"next_page",
"href":"http://xxx/users?offset=10&limit=5"
}
]
}
혹은 연관된 리소스에 대한 디테일한 링크를 표시할 수도 있다.
{
"id":"user1",
"links":[
{
"rel":"friends",
"href":"http://xxx/users/shawn/friends"
},
]
}
이처럼 HATEOAS를 API에 적용하게 되면 자기 기술적(Self-Descriptive) 특성이 증대되어 API에 대한 가독성이 증가되는 장점을 갖게 된다.
하지만, Response 메시지 자체가 다른 Resource URI에 대해 의존성을 가지게 되기 때문에 구현이 다소 까다롭다는 단점이 있다.
최근에는 Spring과 같은 프레임워크 차원에서 HATEOAS를 지원하고도 있다.
(https://spring.io/projects/spring-hateoas)
8. 단일 API End-Point 활용
만약 API Server가 물리적으로 분리된 여러 서버에서 동작하고 있을 때, user.apiserver.com, car.apiserver.com과 같이 API가 서비스마다 URL이 분리되어 있으면 개발자가 사용하기 불편하다.
매번 다른 서버로 연결을 해야 하기도 하고, 중간에 방화벽이라도 존재한다면 일일이 해제해야 하기 때문이다.
따라서 API 서비스는 물리적으로 서버가 분리되어 있더라도 단일 URL을 사용하는 것이 좋은데,
4. API Version 관리에서 잠깐 봤던 것처럼 HAProxy나 nginx와 같은 Reverse Proxy를 사용하는 방법이 있다.
예를 들어, HAProxy를 앞 단에 세우고 api.apiserver.com이라는 단일 URL을 구축한 뒤에 HAProxy 설정에서 아래처럼 구현한다.
- api.apiserver.com/user는 user.apiserver.com으로 routing
- api.apiserver.com/car는 car.apiserver.com으로 routing
이렇게 세팅을 해놓으면,
향후 뒷 단에 API 서버가 확장이 되어도 API를 사용하는 Client 입장에서는 단일 End-Point만 보면 되고,
관리자 입장에서도 단일 End-Point를 통해 부하 분산 및 로그를 통한 모니터링이 가능하기 때문에 편리하며,
API에 대한 Routing을 Reverse Proxy를 이용함으로 좀 더 유연한 운용이 가능하게 된다.
REST API에 대해 알아보며 생각했던 것보다 훨씬 방대하고 깊은 내용들이 있다는 것을 알았다.
결국 실무나 프로젝트 등을 통해 경험 쌓는 것이 가장 좋아 보인다.
끝!