알뜰폰 요금제 모요(MOYO), 모두의 요금제

OpenAPI 개발환경 개선기(feat. 자동화, LLM)

윤민상2026.04.13
OpenAPI 개발환경 개선기(feat. 자동화, LLM)

들어가며

안녕하세요. 모요 프론트엔드 플랫폼 개발자 윤민상이에요.

모요의 프론트엔드는 모노레포 안에 7개의 프로젝트가 공존하고 있어요. 메인 서비스를 비롯해 어드민 등 각기 다른 역할을 가진 앱들이 함께 운영되고 있죠.

문제는, 이 프로젝트들이 각자의 방식으로 API를 관리하고 있었다는 거예요. 백엔드에서 API 스펙이 바뀌면 각 프로젝트가 개별적으로 감지하고 대응해야 했고, 그 과정에서 크고 작은 문제들이 반복됐어요. 이 글에서는 이 문제를 어떻게 해결해 나갔는지, 그 여정을 공유하려고 해요.

왜 개선이 필요했나?

당시 각 프로젝트는 동일한 API에 대해 각자의 타입 정의를 따로 관리하고 있었어요. Swagger Codegen으로 생성한 코드는 그나마 나았지만, 수동으로 타입을 정의한 레거시 코드도 남아 있었고, 공유되는 타입이 없다 보니 시간이 지나면서 프로젝트마다 타입이 미묘하게 달라지고 있었죠. 백엔드에서 필드명이 변경됐는데 프론트엔드에는 반영되지 않은 채 방치된 경우도 있었어요.

이런 파편화된 관리 방식은 안정성생산성 모두에 영향을 줬어요. API 스펙이 변경되면 수동으로 업데이트해야 했고, 동기화가 지연되면 타입 불일치로 인한 버그가 컴파일 타임이 아닌 런타임에서야 발견되곤 했죠. 스펙이 변경될 때마다 각 프로젝트에서 반복적으로 코드를 수정해야 했고, 서비스가 커질수록 이 비용은 기하급수적으로 늘어났어요.

API 관리 파편화(Before 상태)API 관리 파편화(Before 상태)

이 상황을 근본적으로 해결하기 위해, 세 단계에 걸친 개선 작업을 진행했어요.

1단계: API 통합 관리 인프라 구축

모노레포 기반 OpenAPI 패키지

가장 먼저 한 일은 SSOT(Single Source of Truth) 를 만드는 것이었어요. 모노레포 내에 @repo/openapi라는 단일 패키지를 구축하여, 모든 프론트엔드 프로젝트가 하나의 소스에서 API 타입을 가져오도록 했어요.

1packages/L1/openapi/
2├── src/
3│ ├── openapi/
4│ │ ├── auth/ # 인증 서비스
5│ │ ├── core/ # 코어 서비스
6│ │ ├── order/ # 주문 서비스
7│ │ └── ... # 서비스 도메인별 디렉토리
8│ └── utils/ # 공통 유틸리티
1packages/L1/openapi/
2├── src/
3│ ├── openapi/
4│ │ ├── auth/ # 인증 서비스
5│ │ ├── core/ # 코어 서비스
6│ │ ├── order/ # 주문 서비스
7│ │ └── ... # 서비스 도메인별 디렉토리
8│ └── utils/ # 공통 유틸리티

각 서비스 도메인은 Swagger 스펙에서 자동 생성된 Controller(API 호출 메서드)와 Schema(요청/응답 타입)로 구성돼요. 예를 들어, 주문 서비스의 컨트롤러는 이렇게 생겼어요.

1class OrderController extends AbstractOasApiController {
2 /**
3 * @path {get} - /api/v1/orders/{orderId}
4 * @summary - 주문 상세 정보 조회
5 * @description - [MoyoUser] Authorization Header is **REQUIRED**
6 * @param paths - path에 치환되어 들어가는 값
7 * @param paths.orderId [string] - 주문 고유 ID
8 */
9 getOrderDetail(data: {
10 paths: {
11 orderId: string;
12 };
13 }) {
14 return this.api<ApiResponseOrderDetailResponse>(
15 `/api/v1/orders/${encodeURIComponent(data.paths.orderId)}`,
16 'get',
17 );
18 }
19}
1class OrderController extends AbstractOasApiController {
2 /**
3 * @path {get} - /api/v1/orders/{orderId}
4 * @summary - 주문 상세 정보 조회
5 * @description - [MoyoUser] Authorization Header is **REQUIRED**
6 * @param paths - path에 치환되어 들어가는 값
7 * @param paths.orderId [string] - 주문 고유 ID
8 */
9 getOrderDetail(data: {
10 paths: {
11 orderId: string;
12 };
13 }) {
14 return this.api<ApiResponseOrderDetailResponse>(
15 `/api/v1/orders/${encodeURIComponent(data.paths.orderId)}`,
16 'get',
17 );
18 }
19}

이제 어떤 프로젝트에서든 @repo/openapi/order를 import하면 동일한 타입을 사용할 수 있게 됐어요.

패키지는 서비스 도메인별로 entry point를 분리하고 sideEffects: false를 설정해서, 사용하지 않는 서비스의 코드는 번들에 포함되지 않도록 최적화했어요. 이런 구조이다 보니 서비스를 추가하거나 제거할 때 설정해야 할 것들이 꽤 있는데, Claude Code 스킬로 만들어서 명령 한 번으로 디렉토리 생성부터 export 구성까지 자동으로 처리할 수 있도록 했어요.

사내 OpenAPI Codegen CLI 고도화

기존에도 Swagger 스펙에서 코드를 생성하는 CLI가 있었지만, 기능이 제한적이었어요. 이를 대폭 개선했어요.

1# 전체 서비스 API 일괄 업데이트
2pnpm api-gen --target=all
3
4# 특정 서비스만 선택적으로 업데이트
5pnpm api-gen --target=auth
6
7# 임시 환경(ephemeral env)의 Swagger 스펙으로 생성
8pnpm api-gen --e=test
1# 전체 서비스 API 일괄 업데이트
2pnpm api-gen --target=all
3
4# 특정 서비스만 선택적으로 업데이트
5pnpm api-gen --target=auth
6
7# 임시 환경(ephemeral env)의 Swagger 스펙으로 생성
8pnpm api-gen --e=test
  • 전체 API 업데이트: 전체 서비스의 API를 한 번에 최신화
  • 서비스별 선택 업데이트: 변경된 서비스만 최신화
  • 검색 기능: 필요한 서비스, API를 빠르게 찾을 수 있도록(@inquirer/search 활용)
  • 임시 환경 Swagger 대응: 임시 환경에 배포된 API 스펙으로도 타입을 생성할 수 있어요 (임시 환경에 대해서는 개발 환경 테스트의 혁신 in MSA 글을 참고해 주세요)

CLI 실행 화면CLI 실행 화면

2단계: 안정적인 마이그레이션

인프라를 구축했으니, 이제 기존 7개 프로젝트를 새로운 패키지로 옮기는 작업이 남았어요. 한 번에 모든 것을 바꾸는 건 위험했기에, 점진적 마이그레이션 전략을 택했어요.

프로젝트별 점진적 전환

사용자에게 직접 노출되는 메인 서비스부터 마이그레이션을 시작했어요. 문제가 생겼을 때 가장 치명적인 서비스인 만큼 타입 정합성을 가장 먼저 확보해야 한다고 생각했거든요.

Codegen으로 생성된 코드가 대부분인 프로젝트는 비교적 수월했어요. Babel로 AST를 분석해 import 경로를 자동 변환하는 마이그레이션 스크립트를 작성했거든요. 하지만 개발자가 자동 생성된 코드를 직접 수정했거나, DTO가 실제 응답과 다르게 정의된 경우는 스크립트로 일괄 처리할 수 없었어요. 이런 부분은 AI와 팀원의 도움을 받아 하나씩 확인하며 전환했어요.

이 마이그레이션이 성공할 수 있었던 건 팀원들 덕분이에요. 2개 프로젝트는 다른 팀원들이 각각 맡아 마이그레이션을 진행해 줬고, DTO 오류가 발견될 때마다 함께 수정해 나갔어요. Swagger 스펙 자체에 문제가 있는 경우에는 백엔드 개발자분들과 함께 스펙을 맞춰 나갔고요. 영향 범위가 넓은 프로젝트는 팀 전체에 QA를 요청했는데, 빠르게 협력해 준 덕분에 안정적으로 마무리할 수 있었어요.

마이그레이션 과정에서 DTO 오류를 확인하고 팀원들과 소통하는 모습마이그레이션 과정에서 DTO 오류를 확인하고 팀원들과 소통하는 모습

백엔드 개발자에게 Swagger 스펙 수정을 요청하고 대응하는 모습백엔드 개발자에게 Swagger 스펙 수정을 요청하고 대응하는 모습

팀원이 QA 결과를 공유하는 모습팀원이 QA 결과를 공유하는 모습

불필요한 코드 제거

마이그레이션과 함께, 더 이상 사용하지 않는 API와 Schema를 정리하는 작업도 진행했어요. knip을 도입해 미사용 코드를 체계적으로 탐지하고 제거할 수 있게 했어요.

결과적으로 4만 줄 이상의 불필요한 코드를 제거할 수 있었어요. 앞으로는 프론트엔드에서 탐지한 미사용 API 목록을 백엔드에 주기적으로 전달해, 더 이상 사용되지 않는 API를 백엔드에서도 정리할 수 있도록 하는 프로세스를 고려하고 있어요. 기존에는 "이 API 아직 사용하나요?"라고 일일이 확인해야 했는데, 이런 불필요한 커뮤니케이션 비용을 줄일 수 있을 거예요.

3단계: 자동 업데이트 파이프라인

API 자동 업데이트 workflow

마이그레이션을 마친 후, GitHub Actions로 매주 금요일 전체 서비스의 API 타입을 자동으로 최신화하는 workflow를 구축했어요.

그런데 일주일에 한 번 실행하다 보니 변경사항이 한꺼번에 쌓여서 타입 에러가 너무 많았어요. 타입 에러의 상당수는 백엔드의 변경사항이 프론트엔드에 전달되지 않은 경우였어요. DTO 이름 변경, 메서드가 다른 컨트롤러로 이동, Swagger 태그 누락으로 인한 컨트롤러 재분류 등 서버에서는 문제가 없는 작업이라서 프론트엔드에 전달하는 게 놓쳐지기 쉬웠죠.

PR에 타입 에러 목록을 정리해 놓거나, 담당 팀원을 태그하는 등 여러 시도를 해봤지만 현실적으로 잘 돌아가지 않았어요. 그래서 백엔드 배포 시에도 자동으로 트리거되도록 확장해 변경사항을 작은 단위로 받을 수 있게 했어요. 에러 규모는 줄었지만, 배포가 잦다 보니 결국 사람이 매번 대응해야 하는 빈도가 늘어나는 문제가 생겼어요.

Swagger 태그 기반이 아닌 API path 기반으로 codegen을 전환하는 것도 고려했어요. 이전에 직접 만들어 본 경험이 있는 만큼 구현에는 자신이 있었지만, 기존 코드의 마이그레이션 비용이 너무 커서 고민이 되었어요.

Claude Code 도입

결국 Claude Code를 GitHub Actions에 도입하면서 이런 문제들이 자연스럽게 해소됐어요.

전체 파이프라인은 이렇게 동작해요.

  1. 코드 생성: Swagger 스펙에서 API 코드를 새로 생성하고 커밋
  2. 타입 검증: package.json을 분석해 @repo/openapi를 의존하는 앱과 패키지만 골라 타입 체크를 수행
  3. 1차 AI 수정: 타입 에러가 발생하면 Claude Code가 에러 목록과 git diff를 분석해 수정을 시도
  4. 미사용 코드 정리: 타입 체크를 통과하면 knip으로 미사용 API/Schema를 제거
  5. 2차 AI 수정: 정리 과정에서 삭제된 Schema를 앱 코드에서 참조하고 있는 경우, Claude Code가 대체 DTO를 찾아 교체
  6. PR 생성: 모든 변경사항을 담은 PR을 자동으로 생성

각 단계마다 실패에 대한 처리도 꼼꼼하게 넣었어요. Claude Code가 max turn 내에 해결하지 못하면 커밋되지 않은 변경사항을 자동으로 버리고, 린트까지 자동 수정한 뒤 PR에 경고 댓글을 남겨요. 타입 에러가 남아 cleanup을 실행하지 못한 경우에도 수동으로 해결할 수 있도록 안내 댓글과 명령어를 제공하고요. PR body에도 각 단계의 성공/실패 상태를 표시해서, PR만 보면 현재 상태를 바로 파악할 수 있도록 했어요.

Claude Code에게 맥락 제공하기

Claude Code가 타입 에러를 잘 고치려면 충분한 맥락이 필요해요. 단순히 "에러를 고쳐줘"가 아니라, 그동안 직접 수많은 타입 에러를 해결하면서 파악한 패턴들을 프롬프트에 구체적으로 담았어요. 몇 가지만 가볍게 소개해볼게요.

  • 변경사항 파악부터: 에러를 바로 고치게 하지 않고, 먼저 git diff로 어떤 Schema/Controller가 어떻게 바뀌었는지 파악하도록 했어요. 맥락 없이 에러만 보고 수정하면 엉뚱한 방향으로 고치는 경우가 많았거든요.
  • 에러 패턴별 대응 방법: DTO 이름 변경, 컨트롤러 이동, enum 값 추가, 중복 DTO 등 자주 발생하는 패턴마다 구체적인 대응 방법을 가이드해놨어요.
  • 수정마다 즉시 커밋: 한꺼번에 수정하고 마지막에 커밋하는 대신, 수정할 때마다 바로 커밋하게 했어요. max turn에 도달하더라도 그때까지의 진행 상황이 보존돼요.
  • 명확한 금지 규칙: as 타입 단언이나 @ts-ignore는 금지하고, 3~4번 시도해도 해결이 안 되면 TODO 주석을 남기고 넘어가도록 했어요. AI가 억지로 고치는 것보다 사람이 판단하는 게 나은 경우가 분명히 있으니까요.

Cleanup 단계에서도 별도의 프롬프트를 사용해요. Controller에서 참조하지 않는 Schema는 대부분 변경이 누락된 경우라서, 몇 가지 예외만 남기고 과감하게 제거하고 있어요. 이때 앱 코드에서 삭제된 Schema를 직접 import하고 있으면 타입 에러가 발생하는데, 삭제 전 내용 확인부터 대체 DTO 검색, import 교체까지의 과정을 단계별로 가이드해놨어요.

물론 자동화로 해결되지 않는 영역도 있어요. 예를 들어 응답 DTO의 구조 자체가 변경되어 프론트엔드 로직까지 수정이 필요한 경우나, 여러 API에 걸친 변경이 하나의 기능에 복합적으로 영향을 주는 경우는 개발자가 직접 판단하고 수정해야 해요. 이런 경우를 위해 PR에 해결되지 않은 에러 목록과 영향 범위를 함께 남기도록 했어요. 반복적인 타입 수정은 자동화하되, 판단이 필요한 부분은 사람에게 잘 넘기는 것이 이 파이프라인의 설계 방향이에요.

프로세스 세부 최적화

자동화 파이프라인을 운영하면서 여러 실전 문제들을 마주했고, 이를 하나씩 해결해 나갔어요.

  • 충돌 처리: 기존 PR에 main을 merge할 때 충돌이 발생하면, 자동으로 새 브랜치를 생성해 PR을 분리
  • 비용 최적화: 모요는 배포가 굉장히 자주 일어나다 보니 Claude Code API 비용이 문제가 됐어요. max turn과 GitHub Actions 캐시를 활용해 실행을 제한해서 해결했어요
  • 서버별 CODEOWNERS 세분화: 담당 스쿼드가 있는 서비스는 해당 스쿼드에, 그 외에는 최근 배포한 백엔드 담당자와 같은 스쿼드의 프론트엔드 개발자를 CODEOWNERS로 지정했어요. 최근 배포가 없는 서비스는 플랫폼 팀이 owner를 가져가고 있고요. 담당자가 바뀌어도 유연하게 변경할 수 있도록 구성했어요
  • PR 보호: 사람이 직접 커밋한 이력이 있거나 oas:working 라벨이 붙어 있는 PR은 덮어쓰지 않고 새로운 PR을 생성하도록 했어요. 개발자가 수정 중인 PR을 보호하면서도 최신 변경사항을 놓치지 않도록 한 거죠

git commit loggit commit log

결과: 무엇이 달라졌나?

BeforeAfter
관리프로젝트별 개별 타입 관리, 중복 정의단일 패키지 통합 관리, 4만 줄 이상 제거
안정성런타임에서야 발견되는 타입 에러컴파일 타임 검증 + 자동 수정
생산성codegen → 수동 추적/수정 (수십 분~수 시간)자동 PR 생성 → 리뷰만으로 완료

마치며

이 프로젝트는 프로덕트 개발자로 스쿼드에서 일하던 시절, 반복되는 API 관리 문제를 보면서 시작한 작업이었어요. 혼자서 인프라 구축과 마이그레이션을 진행하다가, 플랫폼 팀으로 이전한 후 자동화 파이프라인까지 마무리할 수 있었어요. 모요의 핵심가치 중 하나가 "주도적이고 한 걸음 더 노력합니다"인 만큼, 어떤 업무든 주도적으로 끝까지 해볼 수 있는 환경이에요.

파편화된 시스템을 통합하고, 반복적인 작업을 자동화하며, AI를 활용해 복잡한 문제를 해결하는 과정에서 많은 것을 배웠어요. 물론 아직 남은 과제도 있어요. 자동 생성된 PR은 아무래도 리뷰 우선순위가 밀리기 쉽고, workflow도 기능이 늘어나면서 1,000줄이 넘어가(SKILL 분리를 해도) 유지보수가 부담스러워지고 있어요. API 관리가 개발자의 걱정거리가 아닌 자연스럽게 흘러가는 프로세스가 되는 것이 궁극적인 목표예요.

모요는 빠르게 성장해왔고 지금도 성장하고 있어요. 그만큼 개선할 것도 도전할 것도 많은 환경이에요. 관심이 있으시다면 채용 페이지를 확인해 주세요.