API 변경

버전 번호 없는 GraphQL 비추천 처리

4분 분량

REST API는 /v1/ 옆에 /v2/를 발행하고 호출자가 자기 속도로 이동하게 둘 수 있다. GraphQL은 하나의 엔드포인트에 하나의 스키마를 가지며, 작년 빌드의 모바일 앱과 오늘 아침 배포된 내부 대시보드를 포함한 모든 클라이언트가 같은 그래프를 조회한다. 포크할 URL이 없다. 필드를 비추천 처리한다는 것은 모두가 이미 의존하고 있는 스키마 안에서 그것을 그 자리에 비추천으로 표시한다는 뜻이며, 이는 규율을 REST와 다르게 만든다. 호출자에게 무언가가 사라질 것이라고 말하는 근본적인 문제는 API 비추천 처리가 일반적으로 다루는 것과 같은데도 그렇다.

올릴 버전이 없다면 GraphQL은 필드를 비추천으로 어떻게 표시하는가

필드에 직접 적용되는 @deprecated 지시어로:

type Product {
  price: Float @deprecated(reason: "Use priceV2 for multi-currency support.")
  priceV2: Money
}

필드는 계속 조회 가능하다. 사라지지 않고, 404를 반환하지 않고, 동작을 바꾸지 않는다. 그저 GraphiQL, Apollo Studio, 스키마 린터 같은 대부분의 GraphQL 도구가 스키마를 둘러보거나 그것에 대해 쿼리를 작성하는 누구에게나 보여줄 기계가 읽을 수 있는 메모를 담고 있을 뿐이다. 이것이 메커니즘의 전부다. 별도의 비추천 처리 엔드포인트도, 헤더도, 스펙이 요구하는 동반 문서도 없으며, 이는 매력이자 함정이다. 지시어는 추가하기 쉽고 무시하기도 쉽다. 클라이언트가 그것을 보도록 강제하는 것이 아무것도 없기 때문이다.

누군가 비추천 처리 이유를 실제로 보기는 하는가

스키마를 직접 사용하는 사람, 즉 인트로스펙션이나 스키마를 인식하는 편집기를 통해 사용하는 사람만 본다. 그리고 이는 API 체인지로그의 일반적인 독자보다 작은 청중이다. 여섯 달 전 쿼리에 맞춰 만들어진 모바일 앱은 이미 그 쿼리를 바이너리에 구워 넣었다. 누군가 새 필드로 앱을 다시 만들고 업데이트를 내놓을 때까지, 비추천이든 아니든 계속 price를 요청하고 계속 응답을 받을 것이다. 지시어는 새 코드를 작성하는 개발자에게 예전 필드를 쓰지 말라고 말한다. 이미 배포되어 실행 중인 클라이언트에게는 아무것도 하지 않는다.

메커니즘누구에게 닿는가
@deprecated 지시어스키마를 둘러보거나 새 쿼리를 작성하는 개발자
스키마 린터의 CI 실패하나를 돌린다면 클라이언트 코드베이스를 소유한 팀
체인지로그 항목린터 없는 클라이언트 팀을 포함해 그것을 읽는 누구든
아무것도 없음 (필드가 그냥 작동함)예전 필드를 쓰는 이미 만들어진 클라이언트

비추천된 필드도 체인지로그 항목을 받아야 하는가

그렇다, 그리고 그것은 지시어 혼자보다 더 많은 일을 한다. 체인지로그는 지시어가 닿을 수 없는 사람들에게 닿기 때문이다. 스키마를 둘러보지 않고 그래프를 소비하는 파트너 팀, 몇 달 전 캐시된 스키마 사본에 맞춰 만들어진 클라이언트, 산문을 읽어야만 알아차릴 누구에게든. API 체인지로그는 항목이 호출자에게 일반적으로 무엇을 빚지는지를 다룬다. GraphQL 항목은 REST가 거의 명시적으로 밝힐 필요가 없는 한 가지를 빚지고 있다. REST 호출자는 그것을 버전 번호에서 추론하기 때문이다: 예전 필드가 오늘 여전히 작동하는지, 경고와 함께 여전히 작동하는지, 아니면 실제로 데이터를 반환하지 않게 되었는지. 지시어만으로는 스키마를 한 번도 열어본 적 없는 독자에게 이 중 어느 것도 답하지 못한다.

스키마에서 필드를 제거하는 것이 실제로 안전한 때는 언제인가

쿼리 로그가 더 이상 아무도 그것을 요청하지 않는다는 것을 보여줄 때뿐이며, 이는 사용 여부의 질문이지 달력의 질문이 아니다. 필드는 1년 동안 @deprecated를 달고도 한 번도 재구축되지 않은 하나의 클라이언트에게 여전히 필수적일 수 있다. REST의 Sunset 헤더가 종종 하듯이 고정된 일정에 따라 그것을 제거하면, 그 클라이언트를 아무 대응 가능한 경고 없이 망가뜨린다. GraphQL은 한 번도 읽은 적 없는 지시어 외에는 대응할 아무것도 주지 않기 때문이다. 제거 날짜를 정하기 전에 필드 수준의 사용량을 기록하고, 0이 아닌 쿼리 카운트는 카운트다운이 아니라 보류로 취급하라.

필드를 추가하는 것이 REST API에서와 같은 위험을 지니는가

새 필드라면 구조적으로 더 적다, GraphQL 클라이언트는 명시적으로 요청한 필드만 받기 때문이다. price 옆에 priceV2를 추가하는 것은 REST JSON 응답에 필드를 추가하는 것이 엄격한 역직렬화기를 망가뜨릴 수 있는 방식으로 기존 쿼리를 망가뜨릴 수 없다. 클라이언트가 새 필드를 요청하도록 강제하는 것이 아무것도 없기 때문이다. 같은 호흡에서 이름을 붙일 가치가 있는 예외는 기존 enum에 값을 추가하는 것이다. 강타입 언어가 권장하는, 모든 enum 값을 빠짐없이 분기하는 클라이언트는 어떤 쿼리가 그것을 요청했는지와 무관하게 새 값이 도착하는 순간 망가진다. 이 안전성은 클라이언트가 스스로 선택해 받는 필드와 유니언 멤버에만 적용되며, 클라이언트 코드가 손으로 열거하는 닫힌 집합에는 적용되지 않는다.

GraphQL 체인지로그 항목이 REST 항목에는 필요 없는 무엇을 필요로 하는가

필드 이름만이 아니라 쿼리의 형태다. “price 필드는 비추천됨”은 호출자가 실제로 필요로 하는 부분, 즉 어떤 타입과 어떤 쿼리가 그것을 건드리는지를 빠뜨리기 때문이다. 유용한 항목은 타입, 필드, 대체 필드를 명시하고, 생성할 수 있다면 여전히 예전 형태를 요청하는 프로덕션의 실제 쿼리도 명시한다. 그 마지막 부분, 비추천 처리 공지를 실제 사용량과 연결하는 것은, REST 호출자는 URL에 대한 서버 로그에서 공짜로 얻지만 GraphQL 호출자는 얻지 못하는 것이다. 무엇을 요청하든 모든 쿼리가 같은 엔드포인트를 치기 때문이다.

필드가 아닌 다른 것도 @deprecated 지시어를 달 수 있는가

enum 값도 가능하다, 필드가 아니라 그 값 자체의 정의에 같은 지시어를 쓴다:

enum ShippingMethod {
  STANDARD
  EXPRESS
  OVERNIGHT @deprecated(reason: "Use EXPRESS with priority: true instead.")
}

스펙은 안정 릴리스 기준으로 @deprecated를 정확히 두 위치, 필드 정의와 enum 값에만 정의하며 그 외에는 없다. 인자와 입력 필드 수준의 비추천 처리는 아직 초안 단계 언어로만 존재하고, 오늘날 대부분의 서버가 구현하는 것에는 없다. 이렇게 표시된 enum 값은 여전히 서버가 반환하거나 받아들일 수 있는 합법적인 값으로 남는다. 비추천된 필드가 그렇듯 파괴적이지 않다는 같은 약속이며, 그래서 실제로 값을 제거하기 전에 미리 배포해도 안전하다.

FAQ

GraphQL은 엔드포인트 전체를 위한 Sunset 헤더 같은 것을 지원하는가? 아니다, 보통 엔드포인트가 하나뿐이기 때문이다. 비추천 처리 타이밍은 필드 수준에 있다, @deprecated 지시어의 이유 텍스트 안에, 그리고 팀이 그 옆에 발행하는 어떤 체인지로그나 마이그레이션 가이드 안에 있다. 클라이언트가 프로그램적으로 읽을 수 있는 응답 헤더 안에는 없다.

비추천된 필드를 제거했다가 나중에 다른 타입으로 다시 추가할 수 있는가? 새로운 필드 이름으로만 가능하다. 타입을 바꿔서 같은 필드 이름을 다시 도입하는 것은 정확히 비추천 처리 주기가 피하려고 존재하는 그 파괴적 변경이다. priceV2가 하듯이 대체물에 고유한 이름을 주고, 이름이 재사용 가능해지기 전에 예전 것이 완전히 소멸하게 두라.

@deprecated 이유 텍스트가 체인지로그 항목으로 링크해야 하는가? 그렇다, 스키마 도구가 그것을 지원할 때는. 이유 필드는 평범한 문자열을 받아들이며, 그 문자열 안의 URL은 인트로스펙션 출력을 응시하는 개발자로부터 체인지로그 항목이 줄 수 있는 더 완전한 설명으로 가는 가장 짧은 경로다.

GraphQL 스키마 변경이 REST가 그렇지 않은 방식으로 뒤로 호환 가능한 적이 있는가? 추가적 필드 변경은 그렇다, 위의 이유로: 클라이언트는 요청한 것만 받는다. 새 enum 값은 예외다, 닫힌 집합을 열거하는 클라이언트가 예상하지 못한 값에서 망가질 수 있기 때문이다. 제거와 타입 변경은 그것의 REST 대응물만큼 정확히 파괴적이다.


이 글의 기술적 내용은 독립적으로 검토되지 않았습니다. 잘못된 부분이 있으면 알려 주세요. 바로잡겠습니다.

changeloop 관련 페이지: 개발자 문서, changelog 도구 비교

changeloop
루프를 닫는 changelog를 만드는 팀입니다. 사용자가 무언가를 요청하면 팀이 전달하고, 요청한 사람은 알게 됩니다.