RESTful API Design 하기

오늘 페이스북에 누군가가 올린  “RESTFul is dead… ” 라고 시작하는 자극적인 글이 보여 눈길이 갔다. 뭔가 새로운 방식이라도 나왔나? 하는 마음에 읽어봤는데.. 2014년도 10월 경에 InfoQ 에 올라왔던 글에 대한 개인적인 생각을 올려놓으신 듯 하다. ( 다시 찾아보니 찾을 수가 없음 )

원글은 : https://www.infoq.com/articles/web-api-rest

여튼, 그래서 나는 RESTful API 설계에 대해 어떤 생각을 가지고 있는지 잠깐 고민하는 시간을 가질 수 있게 되었다.

사실 원문위 주요 내용은 real restful way 를 구현하기 위해 RESTful  API 을 설계하면서 고려해야 할 사항과, 피해야 하는 사항들을 몇가지 예를 들어 설명하고 그 중, Programtic 방식이란 것에 대해서 설명하고 추천하는 내용인 것 같다.

사실 나도 RESTful API를 설계하면서 지키려고 노력하는 나만의 원칙이 몇가지 있다.

  1. 리소스와 행위를 명시적이고 직관적으로 분리하기.
    • 리소스는 URI로 표현되는데 리소그가 가르키는 것은 명사(noun)이어야 한다.
      ( http://mysite.com/books/1)
    • 행위는 Httpd Method 로 표현하고, POST(생성), PUT(기존 entity 전체를 변경), DELETE(삭제), PATCH(기존 entity의 일부 상태만 변경), GET(조회) 을 분명한 목적으로 사용한다.
  2.  Message 는 Header 와 Body 를 명확하게 분리해서 사용 ( 메시지 추상화를 포함한..)
    • Entity 에 대한 내용은 body에 담고, 어플리케이션 서버가 행동할 판단의 근거가 되는 컨트롤 정보는 header에 담는다.( API의 버전 정보, 응답받고자 하는 MIME 타입 등..)
    • 여기에서 header 와 body는 httpd header 와 http body 로 나눌수도 있고, http body에 들어가는 json 구조를 분리할수도 있다. 어떤 환경에서는 요청이 지나는 hop 중에 하나가 http header 정보를 다 지우고 자기의 정보로 채우는 것도 있어서 그런 상황이라면 http header에 중요한 컨트롤 정보를 담는것이 위험할 수도 있다.
  3. API 버전을 관리한다.(되도록 하위 호환성을 확보한다)
    • 어찌 보면 당연한 얘기겠지만, 영원불멸한 것은 없고 환경은 항상 변하기 때문에 API 에 signature도 변경될 수 있다.
    • 내가 hosting 하고 있는 서비스에 디펜던시를 가지고 있는 모든 시스템이 나의 변경계획에 따라 모두 같이 변화할 수 없기 때문에 내가 특정 API를 변경할 때에는 반드시 하위호환성을 보장해야 한다. ( 이렇게 하다보면 결과적으로 불용자원-더 이상 사용하지 않는 API- 을 어떠한 기준으로 판단해서 제거해야 하는지에 대한 고민도 뒤따라온다.)
  4. 서버와 front client가 같은 방식을 사용해서 요청하도록 한다.
    • 예를 들어 브라우저는 form submit으로 보내고, 서버에서는 json  형태로 보내는 식으로 분리하지 말고, front client 와 서버 모두가 form 으로 보내거나,  json 으로 보내든 방식은 하나로 통일한다.
    • 당연한 얘기지만 이게 지켜지지 않고 있는 환경을 많이 봤다.
      당연한 얘기라고 생각했지만 환경마다 사람마다 선호하는 방식이 있겠다는 생각이 든다. 스프링을 사용한다면 스프링에서 요청 Entity 의 속성을 자동 바인딩 해주니 사실 이 부분은 form 과 json 모두를 그 환경에서 편한 방식으로 사용해도 괜찮을 것 같다는 생각이 든다.
    • 하지만 위 2번 항목에서 JSON으로 메시지 구조를 추상화 한다면 JSON 방식으로 통합하는 것도 고려해야 할 문제다.
  5. API 명세 문서는 항상 자동으로 현행화 할 수 있는 방안을 마련해 놓는다.
    • 프로젝트를 빌드할 때나, 혹은 주기적으로 batch 스케줄러를 돌리는 방법을 활용하던 API 명세에 대한 문서는 항상 자동으로 현행화 상태를 유지할 수 있게 한다. 이걸 사람이 수작업으로 하게 한다면 거의 100% 현행화 되지 않고, 곧 쓸모없는 문서가 되어 버린다.
    • 문서는 API release 용과, 개발 staging 용이 구분되어서 관리되어야 한다.

이 외에도 여러가지 원칙들이 있겠지만, 나는 적어도 위 5가지는 API 설계를 하면서 가급적 최 우선적으로 지키려고 노력하는 항목들이다.

위에서 언급한 InfoQ 의 아티클에서 제시하는 내용중 위 5가지와 겹치는 부분도 있지만, 중요하게 언급하고 있는 내용은 이른바 “Progmatic Rest” 방식이다.

예를 들어 아래 “ORDER”라는 서비스에 대한 응답 메시지의 형태를 보자.

<pre><code><?xml version="1.0" encoding="UTF-8"?>
<serviceDescription xml:base="http://om.example.com">
   		<link rel="all" href="/orders/" />
   		<link rel="received" href="/orders/received/" />
   		<link rel="accepted" href="/orders/accepted/" />
   		<link rel="rejected" href="/orders/rejected/" />
   		<link rel="cancelled" href="/orders/cancelled/" />
   		<link rel="fulfilled" href="http://om.archive.com/orders/" />
   		<link rel="cancellations" href="/cancellations/" />
   		<link rel="reports" href="/reports/" />
</serviceDescription>
   { "serviceDescription" : {
      "base": ",
      "links": [
         { "rel": "all", "href": "/orders/" },
         { "rel": "received", "href": "/orders/received/" },
         { "rel": "accepted", "href": "/orders/accepted/" },
         { "rel": "rejected", "href": "/orders/rejected/" },
         { "rel": "cancelled", "href": "/orders/cancelled/" },
         { "rel": "fulfilled", "href": "/orders/fulfilled/" },
         { "rel": "cancellations", "href": "/cancellations/" },
         { "rel": "reports", "href": "/reports/" }
      ]
      }
}</code></pre>

응답형태가 매우 복잡하게 보이지만,  해당 서비스가 어떤 resource를 제공하는지 그 목록을 리소스 URI가 포함된 서비스 문서 ( 웹 서비스의 홈페이지 같은..)로  제공하자는 것이다. 이렇게 하면 Client는 특정 resource 주소를 알기 위해 사전에 다른 웹 서비스를 호출하거나, 클라이언트 코드에 리소스 URI에 대한 하드코딩을 하지 않아도 되고, 결론적으로 서비스 제공자가 어떤 이유에 의해서건 provider 입장에서 서비스 URI 변경에 대한 부담이 적어진다는 이야기 인것 같다.

하지만, 저런 형태의 서비스 description 을 제공한다고 해도 과연 URI 를 직접 하드코딩 하는 경우가 없어질지 의문이다.  아니, 모든 클라이언트가 URI를 하드코딩하지 않는다는 확식이 있으니  API URI를 내 임의로 바꿀 수 있을까? 엄청 통제가 잘 되고 커뮤니케이션이 잘 이루어 지고 있는 조직이라면 어쩌면 가능할 수도 있겠지만, 여태까지 내가 경험했던 모든 조직에서는 사실 불가능하다고 생각이 든다.오히려 저런 서비스 description 페이지가 현행화 되지 않아 혼란을 주는 경우가 생기지는 않을까?

특정 서비스(리소스)의 위치(URI)를 동적으로 제공할 필요가 있거나, 특정 기준(예를 들어 버전)에 따라 라우팅을 해야할 필요가 있다면,  API Gateway Pattern 을 사용하거나,  NetFlix 의 Eureka 같은 제품을 이용한 Service Registry Pattern 이용해서 서비스를 관리하는 것도 고려해 볼 만하다.