방치된 사이드 프로젝트를 AI 코딩 도구로 되살린 실전 사례: OpenSubsonic API 연동기 (id.news.hada.io)
목차(4)
한줄 요약
방치된 개인 프로젝트에 AI 코딩 도구를 붙이면, 절대 안 끝낼 것 같던 구현 작업을 하룻밤 만에 동작하는 서비스로 바꿀 수 있다.
어떤 상황에서 필요한가?
개인 프로젝트 폴더에는 두 종류가 있다. 새로운 기술을 익히기 위해 시작한 "학습용 프로젝트"와, 그냥 있었으면 하는데 손이 안 가는 "있어야 할 프로젝트"다. 문제는 후자다. 아이디어는 명확하고 구현 방향도 알고 있는데, 막상 앉아서 하려면 지루하거나 시간이 없다는 이유로 계속 미뤄진다.
이번 사례가 딱 그 경우다. YouTube Music을 OpenSubsonic API 스펙에 맞게 노출하는 shim 서버를 만들려 했다. OpenSubsonic은 Subsonic 계열 클라이언트들이 공통으로 사용하는 API 계약이고, 이를 구현한 서버가 있으면 Feishin(데스크탑), Symfonium(안드로이드) 같은 클라이언트를 그대로 붙여 쓸 수 있다. 구조는 단순하다. 클라이언트 요청을 받아서 ytmusicapi로 메타데이터를 가져오고, yt-dlp로 스트리밍 URL을 추출하면 된다.
이런 종류의 작업은 AI 코딩 도구와 궁합이 좋다. 새로 발명할 것이 없고, 따라야 할 명세(OpenAPI spec)가 존재하며, 반복적인 endpoint 구현이 대부분이기 때문이다.
핵심 구현 방법
시작 전 최소 구조를 먼저 잡는다
프롬프트에 모든 것을 설명하는 대신, 코드베이스 자체가 컨텍스트가 되도록 세팅한다. uv로 프로젝트를 만들고 FastAPI, Pydantic, ytmusicapi, yt-dlp를 의존성으로 추가한다. OpenSubsonic의 openapi.json 스펙 파일을 프로젝트 폴더 안에 넣고, README에는 서버의 역할, 사용 라이브러리, 문서 URL, 스펙 파일 위치를 간략히 기록한다.
여기서 중요한 것이 CLAUDE.md다. 이 파일에 구현 컨벤션을 한 번만 써두면 매 프롬프트마다 반복 설명할 필요가 없다. 타입 어노테이션 필수, Pydantic V2 스타일, Google 스타일 docstring, pytest 최신 스타일(함수 레벨, assert 사용) 같은 규칙을 명시해두면 AI가 일관된 코드를 생성한다.
짧은 반복 사이클로 MVP를 쌓는다
플래닝 모드로 다음 작업을 던지고, 결과를 검토한 뒤 "Accept and clear context"로 컨텍스트를 초기화하고 다시 반복한다. 컨텍스트를 계속 누적하면 오히려 품질이 떨어진다.
첫 번째 패스는 OpenSubsonic 스펙의 JSON 형식 신규 endpoint만 대상으로 stub를 생성하는 것이다. XML 구버전 endpoint는 제외하고 스코프를 좁혔다. 생성 후에는 별도 검증 단계를 두어 모든 메서드가 스펙과 맞는지 다시 확인했다. 스펙이 있어도 첫 번째 구현에는 빠진 부분이 나오는데, 두 번째 검증 패스에서 대부분 잡힌다.
실제 클라이언트 연결로 검증한다
스펙을 보고 구현한 것이 실제 클라이언트에서 동작하는 건 별개 문제다. Feishin을 붙여보니 바로 실패했다. 예를 들어 endpoint URL에 .view 접미사가 붙는 것 같은 스펙에 명시되지 않은 디테일들이 있었다. 이런 경우 서버 요청 로그를 그대로 AI에게 전달해서 반복 수정하는 방식이 효과적이다. 에러가 날 때마다 해당 케이스에 대한 유닛 테스트를 추가해서 이후 회귀를 막는다.
MVP 기준으로 실제로 구현이 필요한 endpoint는 생각보다 적다. getLicense, getUser, getGenres, getMusicDirectories는 빈 컬렉션만 반환해도 되고, search3는 ytmusicapi 호출, stream은 yt-dlp에서 "bestaudio" URL 추출, getCoverArt는 커버 이미지 URL 반환으로 충분하다. 나머지 stub endpoint들도 빈 응답이라도 올바른 구조로 반환해야 클라이언트가 크래시하지 않는다.
장기 기능 확장
기본 동작 이후 추가한 것들은 실용성을 위한 것들이었다. ytmusicapi 호출에 메모리 캐시를 달아 사용량 제한을 피하고, SQLite로 음악 메타데이터를 저장해서 브라우징 카테고리 endpoint를 전부 구현했다. 스트리밍 시 파일을 디스크에 캐싱해서 재다운로드를 막고, 클라이언트가 스트림 도중 연결을 끊을 경우 미완성 파일을 정리하는 처리도 추가했다. 인증은 배포 계획이 없었기 때문에 건너뛰었다.
실전에서 주의할 점
AI 코딩 도구가 잘 맞는 프로젝트와 그렇지 않은 프로젝트를 구분하는 게 핵심이다. 이번 사례처럼 명세가 명확하고, 새로운 알고리즘을 발명할 필요가 없으며, 반복 구현이 많은 작업에는 탁월하다. 반면 기술 자체를 배우기 위한 학습용 프로젝트에 AI를 붙이면 배움이 없는 결과물만 나온다.
AI가 생성한 구현이 설득력 있어 보여도 실제 연결에서 바로 깨지는 경우가 많다. 스펙과 실제 클라이언트 구현 사이에는 문서화되지 않은 간극이 항상 존재한다. 로그 기반 반복 수정과 유닛 테스트 추가를 루틴으로 만들어야 그 간극을 메울 수 있다.
컨텍스트 관리도 의식적으로 해야 한다. 큰 작업 단위가 끝날 때마다 컨텍스트를 초기화하고 CLAUDE.md를 /init으로 갱신하는 습관이 코드 일관성을 유지하는 데 도움이 된다.
무엇보다 중요한 것은, 이런 식으로 "있어야 할 프로젝트"를 처리하면서 "학습용 프로젝트"까지 게을리하지 않는 균형이다. AI 도구에 대한 의존성으로 인한 역량 저하를 경계하는 시선은 타당하다. 도구를 쓰되, 배우는 프로젝트는 직접 손으로 짜야 한다.
자주 묻는 질문
Q.OpenSubsonic API가 뭔가? 왜 이걸 구현했나?
OpenSubsonic은 Subsonic 계열 음악 스트리밍 서비스가 공통으로 따르는 API 스펙이다. 이 스펙을 구현한 서버라면 Feishin, Symfonium 같은 다양한 클라이언트를 그대로 붙여 쓸 수 있다. 이번 사례는 YouTube Music을 백엔드로 쓰면서 OpenSubsonic 인터페이스를 앞단에 노출하는 shim 서버를 만든 것이다. 클라이언트와 서버를 API 계약으로 분리하면 백엔드를 바꿔도 클라이언트 설정을 그대로 유지할 수 있다.
Q.CLAUDE.md 파일을 만드는 게 실제로 효과가 있나?
효과가 분명하다. 매 프롬프트마다 "Pydantic V2 스타일로 작성해줘", "Google docstring 써줘" 같은 말을 반복하는 대신, CLAUDE.md에 한 번 명시해두면 세션 전체에서 일관성이 유지된다. 특히 컨텍스트를 초기화하고 새 세션을 시작할 때도 컨벤션이 유지되는 게 핵심 장점이다. 큰 변경 이후에는 `/init`으로 CLAUDE.md를 현재 상태에 맞게 갱신하는 것도 중요하다.
Q.AI로 구현한 코드가 스펙을 따르는데도 왜 실제 클라이언트에서 실패하나?
API 스펙은 클라이언트 구현의 모든 디테일을 담지 않는다. 실제 클라이언트가 기대하는 URL 형식, 파라미터 처리 방식, 응답 구조의 미묘한 차이가 스펙 외부에 존재한다. 이번 사례에서도 endpoint URL의 `.view` 접미사 처리 같은 것들이 실제 연결 전까지 드러나지 않았다. 그래서 스펙 기반 구현 이후 반드시 실제 클라이언트로 연결 테스트를 해야 하고, 에러 로그를 AI에게 피드백해서 반복 수정하는 사이클이 필요하다.