OpenAPI 스펙 비교 도구 사용기: 두 빌드 간 200개 이상의 변경 사항 감지 (배열 순서가 문제였음)
요약
OpenAPI 스펙 비교 도구 개발 경험을 공유하며, 단순한 직렬화된 배열 비교의 한계를 극복하는 방법을 제시합니다. 경로와 작업 등 모든 요소를 식별 키(identity keys) 기반으로 재구성하여 정확하고 의미론적인 변경 목록을 생성할 수 있었습니다.
핵심 포인트
- OpenAPI 스펙은 시각적으로 순서가 있어도 의미론적으로는 맵/세트처럼 다루어야 합니다.
- 단순 배열 비교 대신, 경로 문자열이나 HTTP 메서드 등 식별 키를 사용해야 정확한 차이점 분석이 가능합니다.
- 변경 사항을 '깨지는(breaking)' 것으로 분류할 때 요청과 응답의 방향성을 고려하는 것이 중요합니다.
저는 작은 공개 API를 유지 관리하고 있었고, 이전에는 배포 후 한 시간 뒤에 기억나는 대로 릴리스 노트를 작성하곤 했습니다. 그래서 저는 결정론적인 OpenAPI 비교 도구(differ)를 만들었습니다. 이 도구는 기본 스펙과 새 스펙을 입력받아 구조화된 변경 목록을 반환하며, 꾸미기 위해 LLM을 사용하지 않습니다.
처음으로 실제로 실행했을 때, 3일 간격의 두 빌드를 비교했고 200개가 넘는 수정 사항이 보고되었습니다. 제 통합 테스트는 모두 통과했습니다. 따라서 테스트가 쓸모없었거나, 아니면 이 비교 도구가 거짓말을 하고 있는 것이었습니다.
문제는 비교 도구였습니다. 저는 두 스펙을 JSON으로 파싱한 다음 재귀적으로 순회하며 인덱스별로 배열을 비교하고 있었습니다. OpenAPI의 paths 객체는 명목상 맵(map)이지만, 저는 사실 직렬화된 키 순서를 차이점 분석(diffing)하고 있었고—개발자가 중간에 새로운 엔드포인트를 하나 삽입할 때마다, 그 뒤의 모든 경로는 잘못된 이웃과 정렬되었습니다. 각 경로 아래의 작업(operation) 및 매개변수 목록에서도 같은 실패 모드가 발생했습니다. 아주 작은 실제 변경 사항이 거짓 양성(false positives)의 벽으로 이어졌습니다.
해결책은 모든 비교를 식별 키(identity keys)로 전환하는 것이었습니다: 경로는 경로 문자열을 키로, 작업은 HTTP 메서드를 키로, 매개변수는 (in, name)을 키로, 스키마 속성은 이름으로 했습니다. 형식이 순서가 있는 것처럼 보이더라도 배열이 아닌 맵과 세트(set)를 사용했습니다. 그 후에는 동일한 두 빌드가 정확히 세 개의 실제 라인만 포함하는 차이를 생성했습니다: 새로운 엔드포인트 하나, 선택적 요청 필드 하나, 그리고 설명 수정 하나였습니다.
두 번째 교훈은 변경 사항을 '깨지는(breaking)' 것으로 분류하기 시작했을 때 얻었습니다. 이는 대칭적이지 않습니다. 요청 속성을 제거하거나 필수 속성으로 만드는 것은 기존 호출자들에게 문제를 일으키지만; 응답 측면에서의 같은 수정들은 대부분 아무도 피해를 입지 않습니다. 하지만 응답 필드를 제거하면 그것을 파싱하는 모든 클라이언트가 문제가 생깁니다. Enum 범위를 좁히는 것은 요청 송신자들을 방해합니다. 저는 단순히 각 변경 사항에 심각도 플래그(severity flag)를 붙이는 것이 아니라, 규칙 안에 방향성—요청 대 응답—을 인코딩해야 했습니다.
결국 저는 이를 OpenAPI 변경 로그 API 형태로 패키징했습니다. 기본 스펙 URL과 새로운 스펙을 입력하면, 기계가 읽을 수 있는 변경 목록과 사람이 읽는 릴리스 노트가 반환되며, 이는 결정론적인(deterministic) 종단 간(end-to-end) 프로세스를 제공합니다. 이것을 배포하면서 저는 OpenAPI 객체 모델에 대해 사양서를 읽을 때보다 더 많이 배웠습니다. 그 문서의 거의 모든 것이 시각적으로 순서가 있는 것처럼 보일지라도, 의미론적으로는 순서가 정해져 있지 않습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기