Study

API Versioning 전략

Hyunec 2021. 4. 14. 16:59

API Versioning은 불특정 다수의 컨슈머가 접속하는 OpenAPI를 지원하기 위해 필수적인 요소 중 하나로, 설령 컨슈머가 한 곳이라고 하더라도 원활한 적용과 장애 시간 최소화를 기대할 수 있습니다. (물론 훌륭한 배포 전략을 준비하면 더 좋습니다.)

아래는 일상 속 사물이 알려주는 웹 API 디자인 을 읽고 해결한 고민과 경험을 기록해봤습니다.

브레이킹 체인지 - 호환성을 지원하지 않게 되는 변경 (특히 하위 호환성)
컨슈머 - API 를 사용하는 클라이언트 (프론트)  
프로바이더 - API를 제공하는 서버 (백엔드)
 

일상 속 사물이 알려주는 웹 API 디자인 - YES24

웹 API는 새로운 서비스나 앱을 만들 때 기존에 존재하던 서비스가 제공하는 기능을 활용할 수 있도록 해준다. 굳이 기존 서비스에 대한 자세한 소스 코드를 알지 않더라도 개발자가 만드는 프로

www.yes24.com

 

Versioning 전략

API를 제공하다 보면 브레이킹 체인지를 만나는 것은 필연적입니다.
다만 운영 방법에 따라 컨슈머의 인지를 돕고, 프로바이더는 더 적은 변화의 개발을 기대할 수 있습니다.

  v1 v2
경로 GET /v1/transfers
Host: api.bankingcompany.com
GET /v2/transfers
Host: api.bankingcompany.com
도메인 GET /transfers
Host: api.bankingcompany.com
GET /transfers
Host: apiv2.bankingcompany.com
쿼리 파라미터 GET /transfers?version=1
Host: api.bankingcompany.com
GET /transfers?version=2
Host: api.bankingcompany.com
커스텀 헤더 GET /transfers
Host: api.bankingcompany.com
Version: 1
GET /transfers
Host: api.bankingcompany.com
Version: 2
콘텐츠 네고시에이션 GET /transfers
Host: api.bankingcompany.com
Accept: application/vnd.bank.1+json
GET /transfers
Host: api.bankingcompany.com
Accept: application/vnd.bank.2+json
컨슈머의 설정 GET /transfers
Host: api.bankingcompany.com
Authorization: Bearer 4R57TD78
GET /transfers
Host: api.bankingcompany.com
Authorization: Bearer 4R57TD78

 

선호하지 않는 버저닝 전략

도메인 전략과 쿼리 파라미터 전략은 가장 선호하지 않는 전략입니다.

  1. 도메인 버저닝 전략은 작은 변경에도 도메인이라는 비교적 큰 단위의 변경이 필요합니다.
  2. 쿼리 파라미터 버저닝 전략은 패스와 파라미터의 역할을 침범한다고 생각합니다.

커스텀 헤드 전략과 콘텐츠 네고시에이션은 좀 더 RESTful 한 설계가 가능하기에 좋아 보였습니다.
하지만 RESTful 함보다는 컨슈머 친화적인 설계를 지향하기에 선택하지 않았습니다.

  1. 컨슈머의 사용이 어렵고, 무엇보다 변경을 인지하는 것이 어렵습니다.
    • (드물지만) 헤더 사용이 제한된 컨슈머가 존재할 수 있습니다.
  2. 내부용으로만 사용된다면 충분히 좋은 선택이라고 생각합니다.

컨슈머의 설정 전략은 컨슈머 친화적이지만 프로바이더에서 고려할 것이 너무 많습니다.

 

경로 버저닝 전략

저는 경로 전략을 추천합니다.
그리고 API 사용에 대한 철학을 포함하여 단점을 보완합니다.

  1. 의미 있는 에러 메시지를 구현합니다.
    • Deprecate 된, 또는 예정을 알려줄 수 있습니다.
    • 응답 코드를 활용하는 것도 좋습니다. (301 Moved Permanently, 302 Found)
    • HATEOAS를 구현하는 경우 link를 고려합니다.
  2. 버저닝은 리소스 단위로 관리합니다.
    • 비록 일부 API는 브레이킹 체인지가 없다고 하더라도, 컨슈머의 직관적인 인지를 돕습니다.
  3. 하위 호환성을 고려합니다.
  4. 낱개의 필드보다는 네이밍 된 객체를 사용합니다.
    • null 인 객체/필드는 내려보내지 않음으로써 컨슈머가 옵셔널 처리를 할 수 있도록 유도합니다.
  5. 단순 조회성 API 라면 요건 변경에 따른 필드는 추가하되 기존의 필드도 남겨두는 것을 고려합니다.
    • (비록 response의 크기는 커지지만) 일부 값의 부재로 생기는 전체 에러를 방지하고, 나머지는 정상적으로 보여줄 수 있습니다.
    • 단 컨슈머가 응답으로 추가적인 연산을 하는 경우는 안됩니다. 오히려 의도하지 않은 문제가 발생하고 추적이 더 어려워집니다.
  6. 변경이 잦은 리소스는 분리를 고려합니다.
    • 변경이 적은 핵심 리소스와 변경이 많은 유동적인 리소스로 분리합니다. 또는 재설계합니다.
  7. 컨슈머에게 친절한 API를 만듭니다.
    • 컨슈머가 넣어야 하는 param에 디폴트 값을 넣어줍니다.
    • 하위 리소스가 유일할 경우 id를 생략합니다.
      • ex) 영화에는 별점이 1개만 존재합니다.
      • Good - PUT /movie/{movieId}/rating
      • Bad - PUT /movie/{movieId}/rating/{ratingId}

후기

혹자는 원칙과 기술에 입각한 설계와 개발만이 서비스의 수명을 길게 해 준다고 말합니다.
하지만 발전하는 기술 앞에 저는 항상 비숙련자이고, 컨슈머의 숙련도는 예측할 수 없습니다. 그리고 우리의 시간은 항상 부족하며 요건은 계속해서 변하기에 완벽함을 보증하는 것은 불가능합니다.

때문에 최선을 학습하되 현재의 상황에 맞는 선택으로 트레이드오프 해야 한다고 생각합니다.