주식 앱 백엔드 가이드를 들춰보면 좀 뜬금없는 게 하나 있다. 리포지토리 필터 예시 코드에 "team.name:eq": "Justice League"가 박혀 있다. 주식 앱인데 웬 슈퍼히어로 팀 이름이 들어가 있나.
이유가 있었다. 그 얘기를 하려면, 내가 이 앱을 어떤 순서로 만들기 시작했는지부터 풀어야 한다.
기능보다 뼈대가 먼저였다
동료들이랑 주식 앱을 시작할 때, 나는 기능을 먼저 만들지 않았다. 공통 뼈대부터 잡았다. 동료들이 각자 도메인 개발에 들어가기 전에, 공통으로 깔릴 부분은 내가 미리 다 써놨다. 시작하면 바로 이 틀 위에서 짜라고.
당시 작업 방식은 방향은 내가 잡고 구현은 구글 무료 CLI(Gemini)가 하는 티키타카였다. 그런데 틀 없이 “포트폴리오 리포지토리 만들어”라고 던지면, AI는 매번 조금씩 다른 패턴을 내놨다. 도메인이 여섯 개면 여섯 개가 제각각 다른 방식으로 갈라진다. 그래서 도메인을 펼치기 전에 공통 기반부터 박아야 했다.
뼈대 1 — 공통 기반 클래스 세 개
app/domain/common/base 밑에 기반 클래스 세 개를 잡았다.
BaseSqlModel: 모든 테이블이 공유하는 필드(id,inserted_at/by,updated_at/by)랑 생성·수정 메서드. 새 도메인을 추가해도 이걸 상속하면 공통 필드가 그냥 딸려온다.BaseRepository:get,find,find_all,query,create,delete같은 공통 CRUD를 제네릭으로 묶은 것. 내부적으로QueryBuilder가 필터·정렬·페이지네이션·관계 로딩을 처리한다.BaseSchema계열 네 종: 요청·응답 스키마의 공통 설정.
도메인 모듈(member, account, portfolio 등) 여섯 개가 전부 이 기반 클래스를 상속한다. 이 중에서 내가 제일 마음에 들었던 건 BaseRepository였다. 기본 쿼리는 이걸로 다 처리되고, 도메인마다 특별히 필요한 쿼리만 상속받아서 얹으면 됐다. find_all에 "종목명:like" 같은 필터를 딕셔너리로 넘기면 알아서 SQL이 나가는 식이라, 도메인 리포지토리에 똑같은 조회 코드를 또 쓸 일이 없었다.
여기서 저스티스 리그가 등장한다. BaseRepository가 진짜로 도메인에 안 묶이는지 확인하려고, 필터 예시를 주식이 아니라 영웅 팀으로 써봤다. team.name이 "Justice League"인 걸 거르는 필터가 자연스럽게 읽히면, 주식이든 영웅이든 같은 API로 굴러간다는 뜻이었다. 도메인을 바꿔치기해도 문법이 안 어색하면 추상화가 제대로 된 거다. 그 흔적이 지금도 가이드에 남아 있다.
뼈대 2 — 15챕터짜리 백엔드 가이드
기반 클래스만으로는 부족했다. 그래서 가이드를 썼다. 소개, 프로젝트 구조, 공통 모듈, 모델, 스키마, 리포지토리, 라우터, 서비스, 태스크, 테스트, 마이그레이션, Docker, 예외 처리, 코딩 스타일, 그리고 Gemini 활용법까지. 챕터로 열다섯 개가 나왔다.
왜 이걸 다 썼나. AI한테 “이 틀 안에서 만들어”라고 말할 근거가 필요했기 때문이다. 가이드가 없으면 AI는 매번 다른 패턴을 제안하고, 그걸 리뷰하고 고치고 다시 정렬하는 데 시간이 들어간다. 가이드가 있으면 “05_feature_router.md 참고해서 account 도메인 라우터 만들어”가 된다. Gemini는 @로 파일을 직접 짚어주면 그 안의 규칙을 따라 일관되게 짰다.
이건 사람을 위한 문서이기도 했다. 같은 기준을 보고 사람과 AI가 같이 짜야, 누가 짠 코드인지 티가 안 나니까.
그래서 뭐가 남았나
뼈대를 먼저 깔아두니 새 도메인을 붙이는 게 가벼워졌다. 알림 같은 걸 하나 추가해도 BaseSqlModel 상속 몇 줄이면 공통 필드가 자동으로 붙고, 조회는 BaseRepository가 이미 다 해준다.
무엇보다, 방향은 내가 잡고 구현은 AI가 하는 구도에서 AI 결과물이 매번 같은 모양으로 나왔다. 그래서 리뷰가 가벼웠다. 해보고 나서 정리된 생각은 이거였다. AI는 정해진 패턴을 따르는 데는 강한데, 패턴 자체를 만드는 데는 약하다. 그 패턴을 사람이 미리 깔아주는 게 뼈대 작업이었다.
기능 열 개를 먼저 만들기 전에 공통 기반 클래스 세 개를 잡는 것. 돌아보면 그게 더 빨랐다.