CafeM0ca

좋은 API 디자인 본문

Programming/Go

좋은 API 디자인

M0ca 2021. 3. 26. 13:08
반응형

좋은 API 디자인하기

REST나 RPC 중 하나를 선택하는 것은 환경에 달려있다.

이미 사용하고 있는 방식이 있다면 그 방식으로 나가는게 좋다.

RESTful API

REpresentational State Transfer(표현적 상태 전송)의 약자로 컴포넌트간 상호작용의 확장성, 범용적인 인터페이스, 컴포넌트의 독립적인 배포를 강조하며 응답 지연시간 감소, 보안 강화, 레거시 시스템의 캡슐화를 위한 중간 컴포넌트 역시 강조한다.

이와 같은 원칙을 준수하는 API를 사용하면 RESTful이다.

URI(Uniform Resource Identifiers:일관된 리소스 식별자)

API에 접근하는 방법.

URI와 URL의 차이점은 없다.

URL은 네트워크 위치로 리소스를 식별하는 URI다.

리소스 전체를 기술했다면 URI,URL은 동일하게 사용할 수 있다.

URN은 namespace 체계로 urn:isbn:n-nn-nnnnnn-n의 형식이다. isbn은 namespace 식별자다.

URI 경로 설계

경로는 document, collection, store, controller로 구분된다.

collection

collection은 개별 문서에 접근하기 위한 매개변수로 구분되는 여러 리소스가 들어있는 하나의 디렉터리다.

GET /cats -> 모든 고양이 컬렉션

GET /cats/1 -> 1번 고양이 하나

collection을 정의할 때 항상 복수명사를 사용해야한다.

document

데이터베이스의 row와 비슷한 하나의 객체를 가리키는 리소스다.

하나의 document는 하위 document 또는 collection과 같은 child resources를 가질 수 있다.

GET /cats/1 -> 1번 고양이 하나

GET /cats/1/kittens -> 1번 고양이의 모든 새끼 고양이들

GET /cats/1/kittens/1 1번 고양이의 1번 새끼 고양이

controller

controller resource는 프로시저와 비슷하지만 리소스를 표준 CRUD 기능에 매핑 할 수 없는 경우에 사용된다.

컨트롤러의 이름은 하위 리소스가 없는 URI 경로의 마지막 segment이다.

POST /cats/1/feed -> 1번 고양이에게 먹이주기

POST /cats/1/feed?food-fish -> 1번 고양이에게 물고기를 먹이로 주기

accept-encoding -gzip, release

REST 엔드 포인트는 가능한 경우 gzip과 defalte 인코딩을 항상 지원해야 한다.

compress/gzip 패키지를 사용하면 io.Writer를 wrapping한 ioWriteCloser를 구현하고 있는 Writer 인터페이스를 만들 수 있다.

이 인터페이스는 매개변수로 받은 writer에 gzip 압축을 사용해 데이터를 쓴다.

표준 응답 해더

모든 서비스는 아래 해더를 리턴해야 한다.

  • Date: 요청이 처리된 날짜
  • Content-Type: reponse의 content-type
  • Content-Encoding: gzip or deflate(무손실 압축)
  • X-Request-ID/X-Correlation-ID: 다운스트림 서비스(서버에서 클라이언트로 보내는 요청)를 호출 할 때 클라이언트에서 요청을추가할 수 있다. 운영 환경에서 실행 중인 서비스의 버그를 수정하려고 할 때, 하나의 트랜잭션 ID로 모든 요청을 그룹화하면 매우 유용함. 각 다운스트림 호출의 연관 ID를 주고 받는, 서로 연결도니 여러 개의 마이크로서비스를 구축할 때 표준 작업 방식을 설정하면 Kibana 또는 다른 로그 쿼리 도구에서 로그를 조회해 볼 수 있고 한 번의 트랜잭션으로 요청을 그룹화할 수 있다.
  • X-Request-ID: f058ebd6-02f7-4d3f-942e-904344e8cde

에러 리턴

MS API 가이드 라인을 참조하자

https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#51-errors

자바스크립트에서 API에 접근하기

웹 브라우저는 샌드박스 메커니즘을 구현한다.

사용자 데이터의 수정 및 검색을 허용하는 API와 이 API에 대한 인터페이스를 제공하는 웹사이트가 있을 수 있다. 브라우저가 "동일 출저 정책"(same-origin policy")을 구현하지 않은 상태에서 사용자가 자신의 세션에서 로그아웃하지 않는다고 가정하면, 악성 페이지가 사용자에게 알리지 않고 API에 요청을 보내고 수정할 수 있다.

이 보안 문제를 해결하며 마이크로서비스가 이 접근을 허용할 수 있는 구현 방법이 2개 있다.

  1. JSONP: 패딩된 JSON
  2. CORS: Cross-Origin Resource Sharding (서로 다른 도메인 사이의 리소스 공유)

JSONP (CORS 쓰자)

거의 해킹에 가까운 방식이며 CORS 표준을 구현하지 않은 대부분의 브라우저에서 구현되며 GET 요청만 가능하고 서드파티서버에 대한 요청에서는 XMLHTTPRequest는 차단되고 HTML 스크립트 요소에 대한 제한은 없다는 문제점을 이용해 작동한다.

JSONP 요청은 브라우저의 DOM에 <script src="...\\"> 요소를 삽입하는데, 이 때 src의 대상은 API의 URI가 된다.

이 컴포넌트는 JSON 데이터를 매개 변수로 쓰는 함수의 호출을 리턴하며, 페이지가 로드되면 함수가 실행되면서 데이터를 콜백 함수로 전달한다.

JSONP 형태로 반환될 데이터에 대한 요청을 나타내기 위해 일반적으로 callback=functionName이라는 매개변수가 URI에 추가된다.

한 가지 주의해야 할 것은 리턴할 Content-Type 헤더다. JSON을 리턴하는 것이 아니므로 더이상 application/json을 리턴하지 않는다. 실제로는 JS를 리턴하므로 Content-Type 헤더를 적절히 설정해야 한다.

CORS

책 지필 시점으로 지난 5년(지금을부터 9년) 이후에 출시된 브라우저나 모바일 브라우저를 사용한다면 CORS만으로 충분하다.

CORS는 브라우저에서 서로 다른 도메인 간의 요청을 표준화하는 W3C의 제안이다.

한 서버가 스크립트가 로드되는 다른 도메인의 출저를 포함한 다음과 같은 헤더를 리턴하면, 브라우저는 서버를 신뢰하고 두 사이트 간의 요청을 허용한다.

Access-Control-Allow-Origin: origin.com

Go에서 이를 구현하는 것은 매우 간단하며 이것을 전역적으로 관리하는 미들웨어를 만들 수 있다.

RPC API

REST가 HTTP를 전송 계층(5계층)으로 사용해야 하지만, RPC는 이런 제약이 없고 HTTP를 통해 RPC 호출을 보낼 수 있고 원하는 경우 좀 더 가벼운 TCP/UDP 소켓 사용 가능하다.

RPC는 HTTP를 사용하지 않기 때문에 대기 시간이 짧아지고 JSON, XML이 아닌 (바이너리 메시지 형식을 구현메시지 크기가 작기 때문에 속도가 빠르고 성능이 좋다.

RPC를 싫어하는 사람들은 클라이언트-서버간에 발생할 수 있는 긴밀한 결함을 언급한다.

서버에서 규약을 변경하면 모든 클라이언트를 변경해야 한다. 현대의 많은 RPC 구현에서는 이 점은 큰 문제가 아니며 RESTful API와 비교해서 더 문제가 될 만한 점도 없다.

Protocol Buffer와 같은 최신 구현은 객체를 현명하게 마샬링하고 사소한 차이가 있다고 해도 에러를 발생시키지 않는다.

RPC의 장점중 하나는 사용자를 위한 클라이언트를 신속하게 생성할 수 있다는 것이다. 이로 인해 전송 방식 및 메시지 타입을 추상화할 수 있으며, 인터페이스에 의존할 수 있게 한다.

사용자에게 제공되는 클라이언트의 최신 버전만 사용하면

  1. Thrif에서 Protocol Buffer로 변경하는 것과 같은 애플리케이션의 하부 구현 사항을 변경할 수 있다.
  2. 버전 관리를 사용하면 REST를 사용하는 것과 동일하게 이전 버전과의 호환성을 유지할 수 있다.

RPC 메시지 프레임워크

최근에는 더 이상 클라이언트와 서버 양쪽의 인터페이스를 동일하게 구현하지 않는다.

Gob

Gob 형식은 Go 프로세스 사이의 통신을 용이하게 하기 위해 특별히 고안됐으며 Protocol Buffer 같은 것 보다 사용하기 쉬우면서 좀 더 효율적일 수 있는 것을 만들려는 아이디어에서 설계됨.

다른 언어 사이에서 통신에서는 추가 비용이 발생

https://golang.org/pkg/encoding/gob/

Thrift

페북에서 2007년 공개하고 현재는 아파치 소프트웨어 재단에서 관리

주요 목표

  • 단순함 : 직관적이고 친숙하며 불필요한 의존성 없이 작성
  • 투명성: 다른 언어에서 일반적인 관용구는 그대로 준수
  • 일관성: 간결함. 개별 언어에 대한 기능은 확장 기능에 추가
  • 성능: 성능먼저 중시. 우아함은 나중에

https://thrift.apache.org

Protocol Buffer

생성자로 10개 이상의 언어에 대해 클라이언트 및 서비스 stub을 읽고 생성할 수 있는 DSL(Domain Specific Language)을 제공하는 방식을 취한다.

자세한 내용은 여기를 통해 이해하자 https://jeong-pro.tistory.com/190

https://developers.google.com/protocol-bufferes

JSON-RPC

RPC용 객체를 표현하는 표준 방식으로 JSON을 사용하려는 시도로 개발됨

Thrift 및 Protocol Buffer와 달리 JSON-RPC는 메시지 직렬화의 표준을 설정함.

http://www.jsonrpc.org/specificaton

API 버전 관리

주요 변경사항은 아래와 같고. 주요 변경사항이 생기면 API 버전 번호를 증가시킨다.

  • API나 API 매개 변수의 제거 또는 이름 변경
  • API 매개 변수의 타입 변경(예를 들어, 정수에서 문자열로)
  • 응답 코드, 에러 코드 또는 실패 규약의 변경
  • 기존 API의 동작 변경

주요 변경사항이 아닌 것은 다음과 같다.

  • 리턴되는 엔티티에 매개 변수 추가
  • 새로운 엔드 포인트나 기능의 추가
  • 버그 수정 또는 주요 변경사항 목록에 포함되지 않는 기타 유지 관리

semantic 버전 관리

마이크로서비스는 메이저 버전 관리 체계를 구현해야 한다.

마이너 버전은 0.x대를 사용하는데, 마이너 버전은 별로 신경쓰지 않아도 된다.

메이저 버전을 주로 신경쓰는대는 2가지 이유가 있다.

  1. URI의 가독성이 높아지며 마침표가 네트워크 위치(도메인orIP)의 구분 기호로만 사용된다. RPC API를 사용하면 마침표가 API, VERSION, METHOD를 구분하는 데만 사용돼 가독성이 높아진다.
  2. API 버전 관리를 통해 중요한 변화가 일어났으며 클라이언트의 기능을 영향을 미친다는 것을 유추할 수 있다.

REST API의 버전 관리 형식

방식을 정했으면 고객에게 일관되고 멋진 경험을 제공하는 것이 중요하다.

RPC API 버전 관리 형식

버전을 처리하는 가장 좋은 방법은 핸들러의 namespace를 사용하는 것이다.

Go의 기본 패키지에는 핸들러에 Greet.v1.HelloWorld와 같이 이름을 붙일 수 있는 기능이 있다.

RPC의 이름 지정

HTTP API는 GET, POST, DELETE 등을 사용하여 다양한 동작을 구분할 수 있다.

RPC는 이게 불가능하므로 Go 코드 내에서 메서드를 작성하는 것과 같은 방식으로 생각해야한다.

GET /v1/users

앞의 코드는 아래의 RPC 메서드로 작성할 수 있다.

Users.v1.users

GET /v1/users/123434

또는

Users.v1.User

하위 collection의 경우 표현에서 의미 유추가 쉽진 않지만 RESTful API에서는 다음과 같이 수행할 수 있다.

GET /v1/usrs/12343/permission/1232

RPC에서는

Permissions.v1.Permission

메서드 이름 또한 API가 수행할 동작으 ㄹ내포해야 한다. HTTP 동사를 사용할 수 없기 때문에 사용자를 삭제할 수 있는 메서드가 있다면 delete 메서드를 호출해야한다.

Delete /v1/users/1234123

위 의 방식을 RPC로

Users.v1.DeleteUser

객체 타입 표준화

JSON, JSON-RPC를 사용하는 경우, 사용자가 트랜잭션의 반대편에서 객체를 어떻게 처리하는지를 고려해야한다.

protocol buffer나 thrift는 기본 타입을 사용하면 되지만 JSON과 같은 경우는 클라이언트 사용자가 쉽게 역직렬화 할 수 있도록 ISO 표준을 이용하자.

날짜

Date를 반환할 때는 항상 DataLiteral 타입을 사용해야하며 ISO8601Literal을 사용하는 것이 좋다.

{"date": "2016-07-14T16:00Z"}

좀 더 형식을 갖춘 StructuredDateLiteral은 문자열을 리턴하지 않고 kind 및 value의 두 가지 속성이 포함된 엔티티를 리턴.

{"date" : {"kind": "U", "value": 1471186826}}

kind의 값은 아래와 같다

  • C : CLR
  • E : ECMAScript. 1970년 1월 1일 자정 이후 경과한 밀리초 수
  • I : ISO 8601
  • U : UNIX 시간
  • 기타 여러가지는 찾아보자

기간

Duration은 ISO8601에 따라 직렬화되며 아래 형식으로 표현

P[n]Y[n]M[n]DT[n]H[n]M[n]S

간격

ISO8601의 일부로 interval을 받거나 보내는 경우 사용

https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#113-json-serialization-of-dates-and-times

API 문서화

  • Swagger
  • API Blueprint
  • RAML

RPC 기반 API

RPC API에서는 규약(contract)가 곧 문서화 라는 주장이 있다.

코드가 곧 문서로 작동할 수 있게 깔끔하게 작성하고 주석을 달아 부족한 부분을 채우자.

 

 

 

Go 언어를 활용한 마이크로서비스 개발(잭 닉슨 저)를 정리한 내용입니다.

반응형

'Programming > Go' 카테고리의 다른 글

[Go] Go channel 데이터 파이프라인  (2) 2021.04.05
[Go] net/http routing  (0) 2021.03.26
[Go] Go 프로젝트 개발 환경 구축  (0) 2021.01.24
[Go] JSON encode/decode  (0) 2021.01.09
[Go] A Tour of Go Exercise: Web Crawler  (0) 2020.12.09
Comments