PR 한 줄에서 시작되어, API 명세서까지 자동으로 따라오게 하기 - 1부
2부작 중 1부입니다. 1부에서는 PR 코드리뷰부터 API 명세서 생성까지의 자동화를 다룹니다. 2부에서는 이 명세를 연료로 매 머지마다 갱신되는 1,852개 EP의 서비스 토폴로지 이야기를 풀어갑니다.
코드는 매일 바뀌는데, 문서는 어디쯤에서 멈춰 있을까요
저희 부서가 운영하는 백엔드 서비스는 45개, 그 안에서 살아 움직이는 API 엔드포인트는 1,852개입니다. 매주 수십 건의 PR이 머지됩니다.
문제는 코드가 아니라 그 옆에 있어야 할 것들입니다. API가 추가될 때마다 명세서가 따라와야 하고, 서비스 간 호출 관계가 바뀔 때마다 아키텍처 문서를 손봐야 합니다. 할 수 있는 일이지만, 지속할 수 있는 일은 아닙니다. 규모가 커질수록 사람이 감당해야 할 문서 관리 비용은 선형적으로가 아니라 지수적으로 늘어나기 때문입니다.
2020년 입사 첫 해부터 같은 질문이 머릿속에 있었습니다. "백엔드 서비스들의 API 전체 지도를 한 장으로 볼 수는 없을까."
그 후로 시도는 적지 않았습니다. New Relic으로 호출 관계를 들여다보고, 구글 시트에 API 목록을 정리하고, 컨플루언스에 명세서를 작성하고, 배포 티켓이 올라올 때마다 담당자가 직접 문서를 갱신하자고 약속했습니다. 작동은 했지만, 사람이 멈추면 함께 멈췄습니다. 담당자가 바뀌면 문서는 그 자리에 남았고, 바쁜 주가 두 번 겹치면 명세서는 코드보다 뒤처져 있었습니다.
10년이 넘는 레거시 옆에서 7년 가까이 같은 문제를 안고 가면서, 수동으로 따라잡을 수 있는 속도가 아니라는 것을 인정해야 했습니다.
시장에 명세 자동 생성, 코드 리뷰 자동화, 서비스 토폴로지 시각화를 위한 도구가 없었던 건 아닙니다. 영역마다 잘 만들어진 도구들이 있고, 저희도 부분적으로는 써 왔습니다. 다만 10년 묵은 레거시 위에서 수십 개 서비스를 가로질러 "코드가 바뀌면 명세도, 그래프도 같이 바뀐다"는 한 흐름으로 묶는 일은 늘 다음 분기로 밀려 있었습니다. 도구 하나를 도입하는 것과는 결이 달랐고, 솔직히 그동안 쉽게 엄두를 못 냈던 영역에 가깝습니다. 그런데 이제 AI가 코드의 의미까지 읽어낼 수 있게 되면서, 비로소 직접 풀어볼 만하다는 생각이 들었습니다.
그래서 질문을 다시 던졌습니다. PR이 머지되는 순간, 명세서와 서비스 의존 그래프가 알아서 따라오게 만들 수는 없을까?
이 글은 그 질문에서 출발한 시스템에 대한 이야기입니다.
A.I.T는 무엇을 하는 시스템인가요
제가 만든 A.I.T는 API Intelligence Topology의 약자입니다. PR 코드리뷰부터 API 명세서 생성, E2E 콜체인 갱신, 서비스 임팩트 분석, 그리고 내부 문서 사이트 배포까지를 완전 자동화한 시스템입니다.
산출물은 두 가지입니다.
- API 명세서 — 사람이 읽는 문서 (마크다운 + MkDocs)
- 서비스 토폴로지 — 기계가 읽는 의존 그래프 (
ep-impact-map.json)
명세서는 부산물이고, 핵심은 토폴로지입니다. 매 PR 머지마다 갱신되는, 1,852개 엔드포인트의 호출 관계 + 28개 인프라 컴포넌트 의존성을 추적합니다.
개발자 ──▶ PR 머지 ──▶ 5분 후
│
├─ 코드리뷰 품질 추적
├─ API 명세서 갱신
└─ 서비스 토폴로지 갱신 (callers · callees · infra)
1부에서는 위 흐름 중 앞쪽 두 줄 — 코드리뷰와 API 명세서 자동화 — 를 다룹니다. 토폴로지는 2부의 주제입니다.
PR에서 API 명세서까지, 자동화의 첫 세 단계
PR이 오픈되는 순간부터 명세서가 따라붙기까지의 전 과정입니다. 사람의 개입은 코드를 쓰고 머지하는 것이 전부입니다.
개발자가 PR 오픈
│
▼ (60초 이내)
[1] 코드리뷰 봇
Claude 코드리뷰 → GitHub PR Review + Slack
│
│ API 변경 감지 시
▼
[2] 명세 생성 봇
로컬 소스 분석 → 문서 레포 PR 생성 (마크다운 명세)
│
▼
[3] 명세 검수 봇
명세 vs 소스 비교 → 불일치 자동 수정 커밋
│
▼ (이후 4~6단계는 2부에서)
토폴로지 갱신 → 머지 감시 → 배포
1단계 — PR이 열리는 순간 리뷰가 시작됩니다
PR 오픈 60초 이내, Claude가 리뷰를 시작합니다. 대상은 약 70개 레포, PD부서가 운영하는 백엔드 전체입니다. 출력은 GitHub PR Review와 Slack 알림으로 동시에 흐릅니다.
리뷰는 단순 코멘트가 아니라 심각도 레이블로 분류됩니다.
| 레이블 | 의미 | 머지 블로킹 |
|---|---|---|
[MUST] |
버그·보안·데이터 손실 위험 | 블로킹 |
[SHOULD] |
성능·유지보수 개선 권장 | 비블로킹 |
[CONSIDER] |
참고용 제안 | 비블로킹 |
diff만 보는 리뷰는 사이드이펙트를 잡지 못합니다
이 시스템이 단순한 GPT-스타일 PR 리뷰와 결정적으로 다른 지점이 여기입니다. 리뷰 봇은 PR diff만 보지 않습니다. 해당 브랜치를 로컬에 pull해 둔 상태에서 동작합니다. 그래서 소스코드 전체를 통째로 읽고, 변경 지점과 연결된 모든 호출처를 함께 검토합니다.
- 메서드 시그니처가 바뀌면 → 해당 메서드를 호출하는 모든 곳을 찾아 영향을 확인합니다.
- 인터페이스 구현체가 바뀌면 → DI 주입처를 거꾸로 짚어봅니다.
- 클래스나 메서드를 삭제했다면 → caller/reference가 남아 있지 않은지 확인합니다. (가장 중요)
PR diff만으로는 절대 잡을 수 없는 사이드이펙트를, 이 방식 덕분에 잡아냅니다.
MUST는 "지적하고 끝"이 아닙니다
MUST 항목은 PR별로 상태가 추적됩니다. 후속 커밋에서 해결되면 자동으로 RESOLVED 처리되고, 3영업일이 지나도 해결되지 않으면 부서 내 주간 평가의 기술 기여 점수에 감점 요소로 연계됩니다. 그래서 단순한 알림이 아니라 운영 거버넌스의 일부로 작동하게 했습니다.
그래서 무엇이 바뀌었나요 — MUST 추이 3주치
도입 직후부터 주차별 데이터를 추적했습니다.
| 주차 | 기간 | 리뷰 건수 | MUST 건수 | 건당 MUST | 발생률 |
|---|---|---|---|---|---|
| 1주차 | 4/23 ~ 4/26 | 41건 | 25개 | 0.61 | 44% |
| 2주차 | 4/27 ~ 4/30 | 72건 | 29개 | 0.40 | 33% |
| 3주차 | 5/6 ~ 5/8 | 72건 | 24개 | 0.33 | 24% |
| 합계 | 4/23 ~ 5/8 | 185건 | 78개 | 0.42 | — |
3주 만에 건당 MUST가 0.61에서 0.33으로 줄었습니다(▼ 46%). MUST가 포함된 커밋 비율도 44%에서 24%로 절반 수준이 됐습니다.
도입 첫 주에 가장 많이 지적되던 패턴들 — NPE 위험, N+1 쿼리, 트랜잭션 범위 초과, 예외 삼키기 — 이 빠르게 줄었습니다. AI가 관대해진 것이 아니라 코드 자체가 좋아졌다고 생각합니다. MUST가 줄었다는 건 개발자가 리뷰 피드백을 학습하고 있다는 뜻이라고 생각합니다. 같은 실수가 반복되지 않은 것입니다.
실사례 — 단순한 리팩터링 뒤에 숨어 있던 동작 변경
주문 취소 로직을 리팩터링하는 PR에서 Claude가 짚어낸 부분입니다.
[MUST] canCancel() 반환 조건 반전 — 체결 주문 취소 가능 상태가 됨
// 기존 코드
fun canCancel(): Boolean = status == OrderStatus.PENDING
// 수정된 코드 (리뷰 대상)
fun canCancel(): Boolean = status != OrderStatus.PENDING
─────────────────────────────────────────────────
PENDING일 때만 취소 가능하던 로직이 반전되어,
FILLED·PARTIALLY_FILLED 등 모든 상태에서 취소가 허용됩니다.
// 호출처 — PR diff에 포함되지 않은 코드
fun requestCancellation(orderId: Long) {
val order = orderRepository.findById(orderId)
if (order.canCancel()) cancelOrder(order) // 체결 완료 주문도 통과
}
등호 하나가 바뀌었을 뿐이지만, 이미 체결된 주문도 취소할 수 있는 상태가 됩니다. canCancel()을 호출하는 쪽은 PR diff에 포함되지 않았습니다. Claude는 로컬 클론에서 canCancel()의 모든 호출처를 역추적해, 이 변경이 취소 처리 흐름 전체에 어떤 영향을 미치는지 짚어냈습니다.
2단계 — API가 바뀌면 명세가 따라옵니다
코드리뷰가 끝나면 Claude는 diff를 한 번 더 분석해 API 변경 여부를 판단합니다. 변경이 있으면 PR 본문에 API_SPEC_SYNC 블록을 자동으로 삽입합니다.
<!-- API_SPEC_SYNC
{"repo": "{서비스명}", "endpoints": ["POST /v3/.../check"]}
API_SPEC_SYNC -->
이 블록을 감지하면 명세 생성 봇이 움직입니다. 로컬 소스코드에서 Controller와 DTO 클래스를 파싱해 Request/Response 스키마를 추출하고, 마크다운 형식의 명세 문서를 만든 뒤, 문서 레포에 PR을 오픈합니다.
주석이 거의 없는 코드에서도 정확한 명세가 나옵니다. 소스코드 자체가 Single Source of Truth이기 때문입니다.
자동화의 규모
| 항목 | 개수 |
|---|---|
| platform/ 명세 파일 | 2,225개 |
| console/ 명세 파일 | 333개 |
| 총합 | 2,558개 |
핵심 메시지는 이것입니다. 사람이 수작업으로 2,558개를 쓰는 일은 일어나지 않습니다. 초기에 일괄 부트스트랩을 한 뒤, 지금은 PR이 머지될 때마다 단건씩 자동으로 추가·갱신됩니다. 신규 엔드포인트가 추가되면 5분 안에 명세가 따라옵니다.
명세 파일이 늘어날수록 다음 단계인 임팩트 분석의 입력이 풍부해집니다. 명세 자동화 자체가 토폴로지 갱신을 위한 연료입니다. (2부에서 이 연료가 어떻게 양방향 의존 그래프로 변환되는지 다룹니다.)
3단계 — 명세는 다시 한 번 소스와 맞춰집니다
생성된 명세와 실제 소스코드를 1:1로 비교합니다. 불일치가 발견되면 Claude가 직접 파일을 수정하고 브랜치에 push합니다. Slack DM으로 검수 결과를 전송합니다.
명세는 한 번 만들어지고 끝나는 게 아니라, 검수를 통해 한 번 더 소스와 맞춰진 상태로 PR에 올라갑니다.
1부의 결산
코드리뷰가 만든 변화 (3주)
| 지표 | 1주차 | 3주차 | 변화 |
|---|---|---|---|
| 건당 MUST 항목 | 0.61 | 0.33 | ▼ 46% |
| MUST 포함 커밋 비율 | 44% | 24% | ▼ 절반 수준 |
| 누적 AI 리뷰 건수 | — | 185건 | — |
| 누적 MUST 지적 건수 | — | 78개 | — |
API 명세서 규모
| 지표 | 수치 |
|---|---|
| 자동 생성된 API 명세서 | 2,558개 (platform 2,225 + console 333) |
| 코드리뷰 대상 레포 | 70개 (PD부서 전체) |
| 명세 수동 작성 비율 (자동화 이후) | 0% |
1부를 마치며
여기까지가 1부입니다. PR이 열리면 60초 안에 Claude가 리뷰를 시작하고, API가 바뀌면 명세가 자동으로 따라옵니다. 코드 품질은 3주 만에 MUST 기준 46% 줄었고, 2,558개의 명세 페이지가 사람의 수작업 없이 유지되고 있습니다.
여기까지만 해도 충분한 자동화처럼 보입니다. 하지만 저희는 한 단계 더 들어갔습니다.
명세서는 사람이 읽기 좋은 부산물입니다. 진짜 자산은 그 옆에 만들어진 한 개의 JSON 파일, 시스템 전체의 의존 그래프입니다. 매 PR 머지마다 갱신되는 1,852개 EP의 양방향 토폴로지가 어떻게 만들어지고, 어떻게 장애 분석·배포 리스크·EP 폐기·인프라 점검을 바꾸는지 — 2부: PR 한 줄에서 시작되는 서비스 토폴로지에서 다룹니다.
본문 예시에 대한 안내
본문에 등장하는 코드 예시, 파라미터 이름, 응답 구조 등은 모두 설명을 돕기 위한 가상의 예시입니다. 시스템 구조와 동작 방식은 실제와 동일하지만, 운영 데이터와 식별 가능한 정보는 노출되지 않도록 익명화했습니다.
