결정 (ADR)

0001. HTTP API 계약은 서버 코드가 기준, 문서는 서버마다 따로

0001. HTTP API 계약은 서버 코드가 기준, 문서는 서버마다 따로

  • 상태: 채택
  • 날짜: 2026-10-17
  • 관련: contract/openapi/, contract/index.ts, clients/packages/client, docs/contracts.md

맥락

처음에는 손으로 쓴 openapi.yaml 하나에서 서버 · 클라이언트 타입을 만들었다(contract-first). Node 는 생성 타입으로 컴파일 때 맞춰졌지만 Spring 은 손으로 맞춰야 해서 문서와 서버가 어긋날 수 있었다. 서버는 언어가 여럿이고 API 도 서버마다 따로 자란다. 서버끼리 Note · /api/note 처럼 같은 이름이 나온다.

결정

  • 서버가 자기 코드에서 openapi 문서를 만든다: notes(Node) = zod 스키마(검증과 같은 것), activity(Spring) = springdoc(테스트 의존성, 실제 서버를 랜덤 포트로 띄워 받음), tags(FastAPI) = FastAPI(검증 실패 400 으로 문서도 고침). pnpm gen 이 contract/openapi/<서버>.json 에 쓴다.
  • 클라이언트 타입은 그 문서에서 만든다(openapi-typescript → <서버>.ts). 손으로 쓰는 계약 문서는 두지 않는다.
  • 문서는 서버마다 따로 두고 하나로 합치지 않는다. TS 에서는 서버별 네임스페이스(notes.paths)로 갈린다.
  • 한 서버 안에서는 경로 · 스키마 이름 · operationId 가 유일해야 한다.
  • 생성물은 커밋한다. 직접 고치지 않는다(hook 이 막는다). CI 는 pnpm gen && git diff --exit-code contract/.

결과

  • 문서를 따로 손으로 관리하는 중복을 줄이고, 생성된 타입과 클라이언트 코드가 어긋나면 typecheck 에서 걸린다(필드 이름을 바꿔 web · mobile · desktop 이 모두 깨지는 것을 확인).
  • 실제 응답의 런타임 검증과 배포된 옛 클라이언트의 호환은 별도다. 보장 범위는 docs/contracts.md.
  • pnpm typecheck 가 gen 을 먼저 돌려 Java · uv 가 필요하고 몇 초 더 걸린다.
  • 같은 API 를 여러 서버가 구현해도 서로 같은지는 보장하지 않는다. 필요하면 네임스페이스끼리 타입 동등성 검사를 둔다.
  • Spring(springdoc)은 같은 단순 클래스명이 문서에서 조용히 덮어써진다. 이름 규칙으로 막는다(servers/activity/AGENTS.md).
  • 게이트웨이용으로 합쳐야 하면 합칠 때 서버 이름을 접두사로 붙인다.
GitHub 에서 고치기