MYMONDAY.ai
서비스스튜디오도구데이터·포맷73CSS 생성기19색상·이미지10인코딩·보안10웹·SEO11개발·데브옵스10파일·내보내기18계산기6가이드

9분 읽기 · 마지막 업데이트

OpenAPI 문서 리뷰하는 법: operationId, $ref, 파라미터, 응답 점검

생성기와 문서 사이트는 OpenAPI 파일을 문자 그대로 읽습니다. 식별자·참조·응답을 몇 분만 점검해 두면 나중에 바꿀 수 없는 SDK 메서드 이름을 피할 수 있습니다.

생성하기 전에 명세부터 리뷰하는 이유

OpenAPI 문서는 계약이면서 하위 도구에게는 입력 데이터입니다. SDK 생성기는 오퍼레이션을 메서드 이름으로, 문서 사이트는 탐색 메뉴로, 계약 테스트는 테스트 케이스로 바꿉니다. 파일 속 부실한 식별자는 여러 언어의 공개 메서드 이름이 되고, 나중에 이름을 바꾸면 모든 사용자에게 깨지는 변경이 됩니다.

그래서 생성 전에 리뷰할 가치가 있습니다. 이 글은 OpenAPI 3.0·3.1·3.2 문서에서 무엇을 볼지 operationId부터 짚습니다. 뷰어는 3.2를 3.1처럼 읽으므로 3.2 전용 query와 additionalOperations는 검사하지 않습니다. 예시는 일부러 흠을 넣은 3.0 샘플에 분석 코드를 직접 실행한 결과입니다.

명세가 operationId에 대해 말하는 것

operationId는 경로 아래 get·post 같은 메서드 밑의 오퍼레이션 객체에 붙는 선택 문자열입니다. OpenAPI 명세는 이 값이 API에 기술된 모든 오퍼레이션에서 고유해야 하고 대소문자를 구분한다고 정합니다. 또 도구와 라이브러리가 오퍼레이션을 식별하는 데 쓸 수 있으므로 일반적인 프로그래밍 명명 관례를 따를 것을 권장합니다. 명세는 특정 스타일을 강제하지 않지만, 이 식별자를 코드가 소비한다는 점은 분명히 합니다.

고유성은 경로나 태그 단위가 아니라 문서 전체 기준입니다. /orders/{orderId}의 getOrder와 /invoices/{invoiceId}의 getOrder는 경로가 달라도 충돌합니다. OpenAPI 3.1이 최상위에 허용하는 웹훅도 마찬가지이고, OpenAPI 뷰어는 웹훅을 다른 오퍼레이션과 함께 나열하며 중복 검사에도 포함합니다.

필드가 선택이라서 operationId가 하나도 없는 문서도 유효합니다. 리뷰가 필요한 이유가 바로 이것입니다. 형식이 필드를 강제하지 않으니 생성기가 이름을 지어내야 할 때, 보통 경로와 메서드에서 조합한 이름이 나올 때에야 빈틈이 드러나고, 아무도 고르지 않은 이름이 공개 API가 됩니다.

좋은 이름과 나쁜 이름

API 전체에 하나의 규칙을 정하고 기계적으로 적용하세요. 보통은 동사에 명사를 붙인 camelCase를 씁니다. 대부분의 생성기가 이 문자열을 메서드나 함수 이름으로 바꾸고, camelCase가 주요 대상 언어에 자연스럽게 대응하기 때문입니다. 아래 기준은 식별자가 여러 언어에서 식별자로 유효하도록 지키는 것이며, 실제 제약은 그 부분입니다.

  • 동사 집합을 통일합니다. list, get, create, update, replace, delete, 단순 CRUD가 아닌 동작에는 cancel, refund 같은 도메인 동사를 씁니다.
  • 식별자는 영문자와 숫자만, 영문자로 시작하고 공백이나 기호를 넣지 않습니다.
  • 대소문자만 다른 id를 재사용하지 않습니다. getOrder와 GetOrder는 명세에서는 다른 값이지만 대소문자를 정규화하는 도구에게는 같은 이름입니다.
  • id는 공개 이름으로 취급합니다. 릴리스 후에 바꾸면 생성된 메서드 이름이 바뀌므로 한 번에 정하세요.
피할 것권장문제
userslistUsers명사만으로는 동작을 알 수 없고 생성(create) 호출과 충돌하기 쉬움
get-ordergetOrder하이픈은 대부분의 언어에서 식별자로 쓸 수 없어 생성기가 이름을 고쳐야 함
GET_/orders/{id}getOrder이미 다른 곳에 있고 따로 바뀌는 경로·메서드를 이름에 중복 인코딩함
orders.listlistOrders점과 네임스페이스는 구분자로 쪼개는 도구를 혼란스럽게 함
getOrderV2FinalgetOrder (버전은 경로나 서버에)버전은 API 버전에 속하고 모든 식별자에 붙일 일이 아님
ordercreateOrder / getOrder / cancelOrder한 단어를 네 가지 동작에 쓰면 고유성을 지킬 수 없음

흠 있는 샘플과 뷰어의 보고

아래 샘플에는 깨끗한 오퍼레이션 하나와 실제 파일에 쌓이는 문제들이 들어 있습니다. summary 누락, 중복 id, id 누락, 대소문자만 다른 id, responses 없는 오퍼레이션, 쓸 수 없는 경로 항목 두 개입니다.

메서드와 경로operationId상태보고된 문제
GET /orderslistOrdersok없음
POST /orderscreateOrderoknoSummary
GET /orders/{orderId}getOrderreviewduplicateOperationId, caseOnlyOperationId (GetOrder)
PUT /orders/{orderId}getOrderreviewpathParamMissing (orderId), duplicateOperationId, caseOnlyOperationId (GetOrder)
DELETE /orders/{orderId}(없음)reviewpathParamMissing (orderId), noOperationId
POST /orders/{orderId}/refundGetOrderreviewpathParamMissing (orderId), noResponses, caseOnlyOperationId (getOrder)
openapi: 3.0.3
info: { title: Orders API, version: 1.2.0 }
paths:
  /orders:
    get:
      operationId: listOrders
      summary: List orders
      responses: { "200": { description: OK } }
    post:
      operationId: createOrder
      responses: { "201": { description: Created } }
  /orders/{orderId}:
    get:
      operationId: getOrder
      summary: Get one order
      parameters:
        - { name: orderId, in: path, required: true, schema: { type: string } }
      responses:
        "200": { description: OK }
        default: { description: Error }
    put:
      operationId: getOrder
      summary: Replace an order
      responses: { "200": { description: OK } }
    delete:
      summary: Delete an order
      responses: { "204": { description: Deleted } }
  /orders/{orderId}/refund:
    post:
      operationId: GetOrder
      summary: Refund an order
  /health:
    $ref: "#/components/pathItems/Missing"
  /ext:
    $ref: "./other.yaml#/Ext"

보고서를 읽는 법: 잡힌 것과 못 잡은 것

뷰어는 중복된 getOrder, DELETE의 id 누락, POST /orders의 summary 누락, refund 오퍼레이션의 responses 누락, 읽을 수 없는 $ref 두 개를 잡았습니다. 이 절에서 이어 설명하는 선언되지 않은 {orderId}와 getOrder·GetOrder의 대소문자 충돌도 함께 잡습니다.

GetOrder와 getOrder는 관련된 세 오퍼레이션 모두에서 대소문자 충돌로 보고되고, 메시지마다 다른 쪽 철자가 함께 표시됩니다. id가 대소문자를 구분하므로 명세 문면으로는 위반이 아니어서 경고로만 다루지만, 실무에서는 함정입니다. 이름을 snake_case나 camelCase로 변환하는 생성기나, 오퍼레이션마다 파일을 하나씩 쓰면서 대소문자를 구분하지 않는 파일 시스템에서는 두 오퍼레이션이 같은 이름을 갖게 될 수 있습니다. 두 개의 getOrder는 완전히 같은 id의 중복이라 따로 보고되며 행이 review가 됩니다.

PUT /orders/{orderId}, DELETE /orders/{orderId}, refund 오퍼레이션이 {orderId}를 쓰면서 선언하지 않은 점도 보고됩니다. 명세는 경로 템플릿의 모든 표현식이 경로 항목이나 각 오퍼레이션에 선언된 in: path 파라미터와 대응해야 한다고 요구하며, 같은 이름의 오퍼레이션 수준 선언은 경로 항목 수준 선언을 덮어씁니다. 이 샘플에서 선언한 곳은 GET /orders/{orderId}뿐입니다. 뷰어는 오퍼레이션과 파라미터 이름마다 하나씩 보고하므로 어느 메서드를 고쳐야 하는지 바로 알 수 있고, 문서에 #/components/parameters/...가 있으면 그 $ref도 따라갑니다. required: true가 아닌 경로 파라미터와, 선언했지만 경로 템플릿에 없는 파라미터도 보고합니다. 파라미터 스키마나 다른 위치(query, header 등)는 검사하지 않습니다.

진단이 아니라 목록을 넘겨야 할 때는 같은 분석이 마크다운 표도 만듭니다. 이 샘플의 내보내기에는 | POST | /orders | createOrder | — | ok |, | DELETE | /orders/{orderId} | — | Delete an order | review | 같은 줄이 들어가며, 풀 리퀘스트에 붙이기 좋은 결과물입니다.

id가 없거나 중복일 때 생성기가 하는 일

동작은 도구마다 다르니 가정하지 말고 쓰는 도구에서 확인하세요. 일반적으로 operationId가 없으면 생성기가 이름을 합성해야 하고, 보통 HTTP 메서드와 경로에서 만들어서 길고 어색하며 경로를 재배치하면 바뀝니다. 중복을 만나면 실패하거나, 경고하거나, 접미사를 붙여 한쪽 이름을 조용히 바꿀 수 있습니다. 어느 경우든 결과는 다른 사람이 가져다 쓸 라이브러리에 내가 고르지 않은 이름이 들어가는 것입니다.

실용적인 원칙이 둘 나옵니다. 첫째, 쓰는 생성기가 눈감아 주더라도 자체 파이프라인에서는 두 경우를 오류로 취급하세요. 나중에 생성기를 바꿀 수 있습니다. 둘째, 모든 오퍼레이션에 의도한 id가 있지 않다면 명세를 고쳐도 생성된 이름이 유지된다고 믿지 마세요. 합성된 이름은 경로가 바뀌면 같이 바뀝니다.

태그와 summary

태그는 오퍼레이션에 붙는 문자열이고 대부분의 도구가 문서 섹션, API 클라이언트 클래스, 모듈 같은 묶음 단위로 씁니다. 최상위 tags 배열은 선택이지만 태그 설명을 적고 묶음의 표시 순서를 정하는 자리입니다. 태그가 없는 오퍼레이션은 많은 도구에서 기본 그룹에 들어가기 때문에 이름 없는 묶음으로 흩어집니다. 뷰어는 최상위 태그와 실제로 쓰인 태그의 합집합을 보여 주므로 order와 orders 같은 오타를 나란히 볼 수 있습니다.

summary는 한 줄 요약이고 description은 긴 설명입니다. 문서 도구는 목록에 summary를 보여 주므로 List orders처럼 동사로 시작하는 짧은 구로 쓰세요. summary 누락은 명세 오류가 아니라서 뷰어도 행을 review로 만들지 않고 보고만 하지만, 탐색 항목이 비어 보이게 됩니다. 한 태그 안에서는 summary를 겹치지 않게 하세요. 사이드바에 Get이 열 개 있으면 탐색할 수 없습니다.

참조와 components 재사용

$ref는 문서의 다른 부분을 가리키며 #/components/schemas/Order처럼 JSON Pointer로 씁니다. 재사용이 핵심입니다. 스키마·파라미터·응답을 components 아래 한 번만 정의하고 어디서든 참조하세요. 인라인 스키마가 중복되면 서로 어긋나고, 하나의 개념에 생성 타입이 여러 개 생깁니다.

OpenAPI 3.0에서는 $ref 옆의 다른 키가 무시되므로 참조 옆에 적은 description이 조용히 사라집니다. OpenAPI 3.1은 참조의 summary와 description에 한해 이를 완화합니다. 경로 항목 자체도 참조가 될 수 있고 3.1은 components.pathItems 섹션을 추가했습니다. 뷰어는 로컬 경로 항목 $ref를 해석하고, 순환을 막으며 체인을 따라가고, 외부 파일과 해석되지 않는 대상을 버리지 않고 보고합니다. 샘플에서 끊어진 /health 참조와 ./other.yaml 참조가 둘 다 보고되었고, 엔드포인트 개수를 믿기 전에 이를 해결하라는 신호입니다.

뷰어가 해석하는 것은 경로 항목 참조뿐입니다. 이 밖에는 경로 파라미터 세 가지 규칙을 확인하려고 경로 파라미터의 $ref를 components.parameters로 따라가는 것뿐입니다. 오퍼레이션 안의 스키마 $ref가 풀리는지는 확인하지 않으므로, 스키마 참조가 깨진 경우는 검증기가 필요합니다. 여러 파일로 나뉜 명세는 뷰어가 붙여 넣은 파일 하나만 읽으므로 하나로 번들한 뒤 리뷰하세요.

경로 파라미터, 응답, 보안

경로 파라미터에는 어기기 쉬운 규칙이 둘 있습니다. 경로 템플릿의 {name}마다 in: path인 파라미터가 있어야 하고, 경로 파라미터는 required: true여야 합니다. 값 없이는 URL을 만들 수 없기 때문입니다. 경로 항목 수준에서 선언한 파라미터는 그 아래 모든 오퍼레이션이 물려받으므로 공통 파라미터는 거기에 한 번만 선언하세요. 뷰어는 선언 없는 템플릿 이름, required: true가 없는 선언, 경로에 없는 이름의 선언을 모두 확인합니다.

응답은 "200", "404" 같은 따옴표 문자열 상태 코드나 2XX 같은 범위를 키로 씁니다. 특수 키 default는 나열하지 않은 상태를 대신합니다. OpenAPI 3.0은 responses를 요구하고 그 객체에 항목이 최소 하나 있어야 하므로, 뷰어는 누락을 review로 표시합니다. OpenAPI 3.1과 3.2에서는 선택 사항이라 참고만 표시하지만, 성공 응답과 대응 가능한 실패를 문서화하는 편이 좋습니다.

보안은 두 부분입니다. 스킴은 components.securitySchemes에 정의하고, 그것을 이름으로 가리키는 요구 사항은 모든 오퍼레이션에 적용되는 최상위 security 배열이나 이를 덮어쓰는 개별 오퍼레이션에 둡니다. 오퍼레이션 수준의 빈 배열은 인증 없음을 뜻하고, 공개 헬스 체크를 그렇게 표현합니다. 정의되지 않은 스킴을 가리키는 요구 사항은 결함입니다. 뷰어는 스킴이 몇 개인지(샘플에서는 1개)는 보고하지만 요구 사항이 그것을 가리키는지는 검증하지 않습니다.

릴리스 전 리뷰 체크리스트

명세를 릴리스하기 전에, 가능하면 CI에서 아래 항목을 확인하세요. 첫 점검은 뷰어의 엔드포인트 목록으로 빠르게 할 수 있습니다.

  • 모든 오퍼레이션에 같은 규칙으로 쓴 operationId가 있고, 중복도 대소문자만 다른 변형도 없습니다.
  • 생성 클라이언트에 깨지는 변경을 의도한 경우가 아니라면 지난 릴리스 이후 바뀐 operationId가 없습니다.
  • 모든 오퍼레이션에 분명한 summary가 하나 있고, 태그 철자가 일관되며 각 태그에 최상위 tags 항목이 있습니다.
  • 따라가지 못하는 도구에 문서를 넘길 때 깨졌거나 외부를 가리키는 $ref가 없고, 공유 스키마는 components에 있습니다.
  • 경로의 모든 {template}에 required: true인 경로 파라미터가 선언되어 있습니다.
  • 3.1·3.2에서 responses가 선택 사항이어도 모든 오퍼레이션에 성공 응답과 default 또는 구체적인 오류 응답을 문서화합니다.
  • 보안 스킴은 한 번만 정의되고 각 요구 사항이 실제 존재하는 스킴을 가리키며, 공개 오퍼레이션은 명시적으로 제외합니다.
  • 곧 사라질 오퍼레이션에는 deprecated를 지정해 생성된 문서에 표시되게 합니다.

직접 해보기