"문서화되지 않은 API에는 소비자가 의지할 명확한 계약이 없어. OpenAPI와 JSON Schema를 사용하면 문서, 클라이언트 생성기, 모의 서버, 검증 도구가 같은 인터페이스 정의를 읽을 수 있어."
OpenAPI란 무엇인가
OpenAPI는 HTTP API의 엔드포인트, 메서드, 매개변수, 요청 본문, 응답 본문, 오류 형태, 인증 방식 등을 YAML이나 JSON으로 기술하는 명세야. OpenAPI 3.1 계열의 Schema Object는 JSON Schema 2020-12와 정렬되어 있지만, 실제 지원 범위는 도구마다 확인해야 해. 하나의 OpenAPI 문서를 다음과 같은 여러 작업의 입력으로 활용할 수 있어:
- 사람을 위한 문서 — Swagger UI나 ReDoc 같은 도구로 탐색 가능한 문서를 만들 수 있어.
- 클라이언트 SDK — OpenAPI Generator 같은 도구로 여러 언어의 클라이언트 코드를 생성할 수 있어. 생성 결과는 검토와 테스트가 필요해.
- 모의 서버 — Prism 같은 도구가 명세와 예시를 바탕으로 응답을 만들어 줄 수 있어.
- 서버 뼈대 — 일부 생성기가 라우트와 모델의 초기 구조를 만들 수 있지만, 비즈니스 동작까지 구현해 주지는 않아.
- 요청과 응답 검증 — 구현의 실제 입출력이 명세와 맞는지 런타임이나 CI에서 검사할 수 있어.
- 계약 검사 — 제공자와 소비자가 합의한 인터페이스의 호환성을 배포 전에 확인할 수 있어.
OpenAPI 문서의 구조
openapi: 3.1.0
info:
title: Users API
version: 1.2.0
servers:
- url: https://api.example.com
paths:
/users/{uid}:
get:
summary: User 읽기
parameters:
- name: uid
in: path
required: true
schema: { type: string }
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
$ref: '#/components/responses/NotFound'
components:
schemas:
User:
type: object
required: [id, name]
properties:
id: { type: string, pattern: '^u_' }
name: { type: string, minLength: 1 }
email: { type: string, format: email }
responses:
NotFound:
description: Resource 못 찾음
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
여기서 User 객체의 필수 필드는 required에 들어 있는 id와 name뿐이야. email은 정의되어 있지만 필수는 아니므로 누락할 수 있어. 스키마를 재사용하면 문서, SDK, 모의 응답이 같은 형태를 참고할 수 있지. 다만 위 동결 예제의 NotFound 응답은 정의되지 않은 Error 스키마를 참조하므로 그대로는 참조 해석에 실패해. 실제 명세에서는 components.schemas에 Error를 정의해야 해.
JSON Schema — 데이터 형태와 제약을 표현하는 언어
JSON Schema는 JSON 인스턴스의 구조와 제약을 기술하는 독립적인 명세야. OpenAPI에서는 요청, 응답, 매개변수에 들어갈 데이터의 형태를 표현하는 데 활용해:
- 타입 — 문자열, 숫자, 불리언, 배열, 객체, null 같은 값의 종류를 표현해.
- 제약 — minLength, maxLength, 정규식 pattern, minimum, maximum, email·uri·date-time 같은 format을 기술해. format을 실제 오류로 처리하는지는 검증기 설정과 구현에 따라 달라질 수 있어.
- 합성 — oneOf, anyOf, allOf로 스키마를 조합하고 $ref로 정의를 재사용해.
TypeScript 타입은 주로 정적 검사 단계에서 개발 도구를 돕지만, 네트워크에서 들어온 JSON은 런타임에 별도로 검증해야 해. JSON Schema 자체는 선언문이고, 실제 거부 여부와 오류 메시지는 선택한 검증기가 결정해. 또한 properties에 필드를 적는 것만으로 필수가 되지는 않아. 객체에서 반드시 있어야 할 필드는 required에 명시해야 해.
FastAPI의 자동 생성
FastAPI는 Pydantic 모델과 라우트 시그니처를 바탕으로 OpenAPI 문서를 생성해. 기본 설정에서는 /openapi.json에서 명세를, /docs에서 Swagger UI를, /redoc에서 ReDoc을 제공해. 경로나 문서 노출 여부는 애플리케이션 설정으로 바꿀 수 있고, 운영 환경에서는 인증·노출 정책도 검토해야 해.
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI(title='Users API', version='1.2.0')
class User(BaseModel):
id: str
name: str
email: EmailStr | None = None
@app.get('/users/{uid}', response_model=User)
async def read_user(uid: str) -> User:
return User(id=uid, name='Pippa')
Pydantic 모델은 JSON Schema 기반 정의로 투영되고, 라우트는 OpenAPI path operation으로, response_model은 응답 문서와 런타임 직렬화·검증 규칙으로 이어져. 위 예제에서 id와 name은 필수고, 기본값이 None인 email은 선택 필드야. 아래 동결 코드의 responses 항목은 404 응답을 문서화할 뿐 자동으로 그 모양을 강제하지 않아. 실제 HTTPException은 detail 래퍼를 포함하므로, 문서의 Error 모델과 실제 응답 형태가 일치하도록 별도의 예외 처리나 응답 모델을 구성해야 해.
명세 우선과 코드 우선
- 코드 우선 — FastAPI처럼 모델과 라우트 구현에서 명세를 생성하는 방식이야. 구현과 문서를 가깝게 유지하기 쉽지만, 자동 생성 결과에 빠진 응답이나 오류 형태가 없는지 검토해야 해.
- 명세 우선 — 구현 전에 OpenAPI 문서로 인터페이스를 합의하고, 필요하면 서버 뼈대와 클라이언트 SDK를 생성하는 방식이야. 여러 팀과 외부 소비자가 병렬로 작업할 때 유용하지만, 생성 코드와 실제 구현의 일치 여부를 계속 검사해야 해.
어느 방식이 더 엄격한지는 작업 절차와 자동 검사에 달려 있어. 팀 규모, 소비자 수, 변경 승인 과정, 기존 코드의 상태를 기준으로 선택하고, 최종적으로는 배포된 API와 배포된 명세가 일치하도록 관리해야 해.
cwkPippa의 OpenAPI 활용
/openapi.json에서 OpenAPI 문서를, /docs에서 Swagger UI를 제공할 수 있어. 엔드포인트가 받는 매개변수와 본문 형태를 확인하고 직접 요청을 시험하는 데 유용하지. 하지만 자동 생성 문서는 실제 오류 응답, 인증 요구 사항, 동적 동작을 빠짐없이 표현하는지 별도로 검토해야 해. 내부용 API라도 계약을 소비하는 프런트엔드와 도구가 있다면 명세 변경을 추적하고 실제 응답과 비교할 가치가 있어.