API 설계 원칙

모든 API 는 크게 두가지 종류로 구분할 수 있다. 하나는 “정보를 조회”하는 목적의 API 이고, 하나는 “정보를 변경(생성)”하는 API 이다. 전자는 Query  method라고 하고, 후자는 Command method라고 부른다.

API를 설계할 때 이 두 가지 기준에 따라 적용되어야 하는 원칙(principle)같은 것이 있다. 당연하겠지만 Query method는 조회 결과에 해당하는 리턴값이 존재하겠지만, Command  method는 리턴값이 없는 void 타입으로 정의하자는 것이다. (HTTP (Rest) API 호출에서 Http Status 값은 해당 API의 리턴값으로 취급하지 않는다.)

 

Command method의 리턴값이 void가 아니게 되면 당장에는 큰 문제가 없을지도 모르지만, 향후에 성능이슈로 인해 해당 API를 비동기로 전환하게 되면 API를 사용하는 측에서는 그 변경에 대한 영향이 만만치 않다. 특히 많은 서비스로 분리되어 있는  MSA환경에서는 비동기로 API를 전환하고 기존 API 를 deprecate시키지 못하고 계속 끌고 가야 하는 상황이 생기게 된다.

 

그렇다면 void 타입의 Command method API를 호출하고 그 결과가 필요할 때는 어떻게 해야 할까?

 

첫 번째로 생각할 수 있는 방법은 callback 방식이다.  Requestor가 요청하는 command  message에 처리 결과를 받을 callback address 를 포함해서 요청하고, replier가 해당 요청을 처리한 후 callback address 에 결과를 push 해 주는 방법이다.

 

두 번째 방법은, EIP에서 제시하는 Request & Reply Pattern을 사용하는 것이다. ( http://www.enterpriseintegrationpatterns.com/patterns/messaging/RequestReply.html ) 간단하게 말하면 요청과 응답을 모두 message event로 전달하는 방식이다. 이 방법을 조금 확장하면 requestor 와 replier가 모두 publisher, subscriber 가 되는 EDA기반의 CQS 패턴을 생각해 볼 수 있게 된다. Publisher 는 subscriber가 누구이건 관심없이 이벤트를 발행하고, 관심있는 subscriber가 이를 구독해 가되, 자신에게 필요한 이벤트를 필터링 할 수 있는 Pipe & Filter 패턴을 함께 사용하는 것이다.

 

자칫하면 오히려 시스템의 복잡성이 커질 것 같기는 하지만, 관심영역을 잘 분리해 낼 수 있다면 효과적으로 단순함을 유지시킬 수도 있을 것 같다.

 

지금 사내의 복잡한 monolithic legacy 시스템을 micro service 로 전환하는 작업을 하고 있는데 아키텍처 스타일로 여러가지를 고민하고 있다. 이벤트 기반의 비동기 방식을 큰 줄기로 가져가고 싶은데 구세력의 저항이 만만치 않다. ㅋㅋ

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 이용해서 서비스를 관리하는 것도 고려해 볼 만하다.

Single Responsebility Principle

오늘 DZONE에서 온 뉴스레터에 SRP(Single Responsibility Principle) 에 대한 언급이 있었는데 개인적으로는 조금 신선한 내용이었습니다.
아티클에서는 SRP 의 Responsibility 가 무엇이냐를 얘기하고 있는거 같은데 그 내용중, 내가 구현한 Class나 Method가 SRP를 준수하고 있는지 알 수 있는 방법을 소개하고 있습니다.
클래스나 매서드가 SRP를 준수하고 있는지 알려면 그 클래스나 매서드에 이름을 붙여보라고 얘기하고 있는데, 간결하게 이름을 붙이기 쉽다면 그 클래스나 매서드는 SRP를 준수하고 있을 가능성이 높고 그렇지 않다면 담당하고 있는 기능이 복잡해서 그럴것이라는 의견입니다.
어찌보면 당연한 얘기처럼 들리기도 하지만, 이렇게 작명행위를 통해 내가 지금 설계의 원칙을 잘 유지하고 있는지 여부를 쉽게 확인할 수 있겠다는 접근은 저에게는 신선하네요. 🙂

 

원본링크