API 변경

웹훅 체인지로그, 아무도 요청하지 않은 파괴적 변경

4분 분량

REST API 체인지로그가 존재하는 이유는 호출자가 이해하지 못하는 응답을 거부할 수 있거나, 적어도 누군가 알아챌 만큼 시끄럽게 오류를 로그로 남길 수 있기 때문이다. 웹훅 수신자는 그 둘 중 어느 것도 거의 하지 못한다. POST를 받고, 기대하는 필드를 읽으며, 어떤 필드가 이동했거나 타입이 바뀌었거나 사라졌다면, 엔드포인트는 아무도 지켜보지 않는 백그라운드 작업 안에서 조용히 죽거나, 더 나쁘게는 한 번도 검증한 적 없는 잘못된 값으로 계속 돌아간다. 파괴적 변경이란 무엇인가는 일반적인 정의를 다룬다. 웹훅 페이로드는 자기만의 답이 필요하다. 누군가 의도적으로 호출하는 엔드포인트와는 실패하는 방식이 다르기 때문이다.

웹훅 페이로드 변경은 왜 API 응답 변경과 다르게 깨지는가

요청의 방향이 뒤집혀 있기 때문이다. REST 호출자는 호출을 시작하며 버전 헤더를 추가하거나, 4xx에서 재시도하거나, 응답 속 사용 중단 알림을 읽을 수 있다. 웹훅 수신자는 그 중 어느 것도 시작하지 않았다. 여러분의 서버가 보내기로 결정했고, 언제 보낼지 결정했고, 본문이 어떤 형태를 가질지 결정했다. 수신자의 유일한 지렛대는 통합을 구축할 때 작성한 검증이며, 대부분의 통합은 한 번 구축되고 작동한 뒤 깨질 때까지 아무도 다시 들여다보지 않는다. 이 비대칭이 웹훅 페이로드 변경이 호출자가 능동적으로 요청한 응답 본문의 동일한 변경보다 더 많은 주의를 받을 가치가 있는 모든 이유다.

웹훅 페이로드에서 실제로 파괴적 변경으로 간주되는 것은 무엇인가

변경대부분의 수신자에게 파괴적인가
새 필드 추가수신자가 알 수 없는 필드를 무시한다면 아니오 (이 가정을 검증하라, 당연시하지 말라)
필드 제거무언가 그것을 읽는다면 예
필드 이름 변경기존 것을 제거하는 것과 기능적으로 동일하므로 예
필드 타입 변경 (문자열에서 객체로)거의 항상 예
JSON 본문의 필드 순서 변경키로 파싱하는 모든 수신자에게 아니오 (모두가 그래야 한다)
이벤트 이름이나 타입 변경수신자가 그것으로 필터링하거나 라우팅한다면 예

“필드를 추가하는 것은 안전하다”라는 줄은 팀들이 가장 많이 의존하는 줄이자 당연시하는 대신 검증할 가치가 가장 큰 줄이다. 관대한 JSON 파서는 기본적으로 알 수 없는 필드를 무시하지만, 엄격한 스키마로 역직렬화하는 수신자, 몇몇 타입 언어는 추가 설정 없이 이렇게 한다, 는 예상치 못한 필드가 나타나는 순간 전체 페이로드를 거부할 수 있다. 필드 추가가 여러분의 웹훅에 안전한 것은 오직 수신자들이 어떻게 파싱하는지 알고 있을 때뿐이며, JSON 자체가 관대해서가 아니다.

웹훅 페이로드는 어떻게 버전을 매겨야 하는가

API 응답의 경우와 거의 같지만 한 가지 차이가 있다. 수신자는 요청을 보내지 않으므로 버전을 요구할 수 없고, 송신자가 그것을 명시해야 한다. 그것은 본문에 넣을 수도 있고 전달 자체의 요청 헤더에 넣을 수도 있다. GitHub의 전달은 X-GitHub-Event와 X-GitHub-Hook-ID를 담고, Standard Webhooks 명세는 메타데이터를 webhook-* 헤더에 넣는다. 페이로드 안의 버전 필드("payload_version": 2)는 가장 저렴한 선택지이며 수신자들이 그것에 따라 분기할 의향이 있을 때 작동한다. 버전이 매겨진 이벤트 타입(invoice.updated가 수신자가 자발적으로 구독하는 별개의 이벤트로서 invoice.updated.v2가 되는 것)은 구축하는 데 더 많은 작업이 필요하지만, 한 번도 이전하지 않은 이들에게 기존 형태가 계속 흘러간다는 것을 의미하며, 이는 REST 엔드포인트보다 여기서 더 중요하다. 모든 수신자에게 전화해 업데이트하라고 요청할 수는 없기 때문이다. 웹훅 엔드포인트를 등록할 때 선택하는 구독별 설정은 매 전달마다 분기하는 대신 결정을 미리 내려두며, 이미 붙일 구독 기록을 가지고 있을 때 올바른 선택이다.

POST /receiver-endpoint
{
  "event": "invoice.updated",
  "payload_version": 2,
  "data": { "invoice_id": "inv_123", "status": "paid" }
}

애초에 누가 듣고 있는지 어떻게 아는가

API 체인지로그에서 이 문제의 동등한 버전보다 나쁘다. 웹훅은 호출자를 이름 짓는, 여러분 쪽의 수신 요청 로그를 가지고 있지 않기 때문이다. 여러분에게는 엔드포인트가 200을 받았다는 것만 말해주는 자신의 송신 전달 로그만 있을 뿐, 본문으로 무엇을 했는지는 알 수 없다. 최소한 두 가지를 추적하라. 내부 API 체인지로그가 내부 소비자에게 권장하는 것과 같은 규율로, 소유자가 있는 모든 등록된 엔드포인트와, 페이로드 변경 후 엔드포인트별 전달 실패율이다. 변경 직후 한 엔드포인트에서 오는 4xx나 5xx 응답의 급증은 여러분이 얻을 수 있는 스택 트레이스에 가장 가까운 것이며, 종종 수신자가 깨졌다는 유일한 신호다. 그것을 운영하는 팀이 며칠 동안 알아채지 못할 수 있기 때문이다.

웹훅 체인지로그는 API 체인지로그와 분리되어야 하는가

같은 페이지의 별도 섹션이지, 별도의 발행물이 아니다. API 체인지로그는 이미 누가 그것을 읽고 어떻게 구독하는지를 정립한다. 웹훅 페이로드 변경은 같은 피드에 속하며, “이것이 내 통합에 영향을 미치는가”를 훑어보는 수신자 측 개발자가 필터링할 수 있을 만큼 충분히 명확하게 라벨이 붙어야 한다. 웹훅 소비자는 일반적인 API 체인지로그를 확인할 다른 이유가 거의 없으며, 누군가 그곳으로 직접 안내해야만 찾게 되기 때문이다.

웹훅 페이로드에 대한 합리적인 사용 중단 기간은 어떤 모습이어야 하는가

동등한 REST 사용 중단보다 길어야 한다. 수신자 측 이전은 보통 여러분이 직접적인 연락 수단이 없을지도 모르는 두 번째 팀이 그것을 알아채고, 계획하고, 자체 긴급성 없이 배포해야 함을 의미하기 때문이다. 수신자가 여전히 관대한 라이브러리로 파싱하고 있을 가능성이 높은 필드에는 한 달이 합리적인 최소치다. 엄격한 스키마라면 완전히 거부할 필드 제거에는 세 달 이상이 더 안전하다. 가능하다면 기간 동안 기존 형태와 새 형태를 함께 보내라(기존 status 필드와 그 버전 2 대체 필드가 같은 페이로드에 함께 들어가도록). 기존 필드를 읽는 수신자는 코드를 건드리지 않고도 계속 작동하고, 이미 이전한 수신자는 더 이상 필요 없는 필드를 그냥 무시하기 때문이다.

FAQ

웹훅 소비자는 페이로드 변경이 공개되기 전에 확인해야 하는가? 기본적으로 확인 메커니즘은 존재하지 않으며, 바로 그 때문에 사용 중단 기간이 REST API보다 여기서 더 중요하다. 아무도 준비되었다고 확인하지 않으므로, 기존 형태가 사라지기 전에 대부분의 수신자가 자기 속도로 이전할 수 있을 만큼 기간이 길어야 한다.

알 수 없는 필드를 통보 없이 추가하는 것이 안전한 경우가 있는가? 수신자들이 관대하게 파싱한다는 것을 당연시하지 않고 검증한 후에만 그렇다. 체인지로그 항목 하나는 비용이 거의 들지 않고 추측을 없앤다. “JSON 파서는 여분을 무시한다”는 가정으로 조용히 필드를 추가하면 엄격한 역직렬화를 사용하는 모든 수신자가 깨진다.

페이로드 변경 후 깨진 웹훅 수신자를 감지하는 가장 빠른 방법은 무엇인가? 변경 직후 몇 시간 동안 관찰되는 엔드포인트별 전달 실패율이다. 무엇이 깨졌는지는 말해주지 않고 무언가 깨졌다는 것만 말해주지만, 여러분이 얻을 수 있는 가장 이른, 그리고 종종 유일한 신호다.

재시도 로직이 수신자가 페이로드 변경을 견디는 데 도움이 되는가? 아니다. 재시도는 같은 새 페이로드를 다시 보낼 뿐, 수신자가 파싱할 수 있는 형태로 돌아가지 않는다. 페이로드 변경은 첫 전달에서도, 이후 모든 재시도에서도 수신자를 동일하게 깨뜨린다.


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

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

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