AI가 잘 이해할 수 있는 구조를 먼저 만들었다
운영 중인 레거시 서비스를 AI와 함께 점진적으로 리팩토링 하기
- 01운영 중인 서비스를 왜 지금 리팩토링하기로 했나
- 02왜 Spring Boot, Gradle, React Router v7로 방향을 정했나
- 03리팩토링 전에 Playwright 테스트부터 만든 이유
- 04AI가 잘 이해할 수 있는 구조를 먼저 만들었다← 현재
- 05Gradle 전환과 미사용 코드 정리부터 시작한 이유
- 06domain과 infra를 나누며 백엔드 리팩토링을 시작했다
AI가 잘 이해할 수 있는 구조를 먼저 만들었다
이번 리팩토링에서 비교적 초반부터 분명히 했던 전제가 하나 있다. AI를 적극적으로 활용하되, AI가 잘 작동할 수 있는 구조를 먼저 만들어야 한다는 점이었다.
이 말은 조금 이상하게 들릴 수도 있다. 보통은 "AI를 써서 코드를 빨리 바꾼다"는 식으로 생각하기 쉽기 때문이다. 하지만 실제로는 그 반대에 가까웠다. AI를 붙일수록 오히려 구조가 더 중요해졌다. 구조가 애매한 프로젝트에서는 사람도 자주 헷갈리고, AI도 마찬가지이기 때문이다.
이번 글은 왜 내가 /backend, /frontend를 먼저 나누고, 백엔드를 멀티모듈로 가져가기로 했는지, 그리고 그 판단이 단순한 취향이 아니라 AI와 협업하기 위한 실무적인 선택이었다는 점을 정리한 글이다.
프론트는 혼자 자신 있게 밀 수 있는 영역이 아니었다
먼저 솔직한 전제를 인정하는 것부터 시작해야 했다. 프론트엔드는 내가 완전히 자신 있는 영역이 아니었다. React Router v7 SSR을 기준으로 구조를 처음부터 잡고, 이후 운영과 유지보수를 염두에 두고 밀어붙이는 일은 혼자 다 판단하기 어려웠다.
이건 리팩토링 전략에 직접적인 영향을 줬다. 내가 모든 걸 혼자 결정하는 구조로는 가면 안 된다고 봤다. 대신 사내 프론트엔드 개발자의 도움을 받아 큰 틀을 잡고, 반복 작업과 구조화, 테스트 작성, 리팩토링 보조 같은 영역에서 AI의 도움을 적극적으로 활용하는 쪽이 현실적인 선택이었다.
결과적으로 이번 프로젝트는 "사람의 판단 + AI의 생산성"을 같이 쓰는 방향으로 가게 됐다.
그런데 여기서 곧바로 부딪힌 문제가 있었다.
AI에게 어떤 작업을 맡기려 해도, 프로젝트 경계가 명확하지 않으면 계속 설명을 덧붙여야 한다는 점이었다.
예를 들어 백엔드와 프론트가 한 루트 안에서 어중간하게 섞여 있으면, "이건 서버 코드고 이건 나중에 분리할 프론트 자산이고 이건 지금은 테스트만 있는 폴더다" 같은 설명을 계속 붙여야 한다. 한두 번은 괜찮아도, 반복되는 협업에서는 이게 꽤 큰 비용이 된다.
구조가 애매하면 AI도 애매하게 일한다
AI를 실제로 써보면서 가장 빨리 체감한 건, AI는 모르는 걸 대충 메우려고 하는 경향이 있다는 점이었다. 프로젝트 구조가 명확하면 문맥을 빠르게 따라오는데, 경계가 흐리면 그 빈칸을 추측으로 채우는 일이 많아진다.
예를 들어 이런 상황들이 반복된다.
- 실제 백엔드 진입점이 어디인지 애매하다
- 프론트가 아직 없는데 테스트 워크스페이스가 들어와 있다
- 레거시 소스와 새 구조가 한 레벨에 섞여 있다
- 멀티모듈처럼 보이는데 실제 코드는 하나의 모듈 안에 몰려 있다
이런 프로젝트에서는 사람도 쉽게 실수한다. AI도 똑같다. 잘못된 파일을 수정하거나, 아직 정리되지 않은 레이어 경계를 진짜 구조로 오해하거나, 이미 죽은 코드를 살아 있는 코드처럼 다루는 일이 생긴다.
그래서 이번 리팩토링에서는 "AI를 많이 쓸 예정이니까 코드를 빨리 바꾸자"가 아니라, AI가 오해하지 않도록 구조를 먼저 만들자는 쪽으로 생각이 바뀌었다.
그래서 최상단부터 /backend, /frontend로 나눴다
가장 먼저 한 구조적 결정은 최상단 디렉터리를 /backend와 /frontend로 나누는 것이었다.
이건 보기 좋은 구조를 만들기 위한 선택이 아니었다. 오히려 "지금 당장 필요한 경계"를 만드는 작업에 가까웠다.
백엔드는 지금 바로 실행 가능한 애플리케이션을 담아야 했다. 프론트는 당장은 새 React 앱이 아니라 Playwright 테스트 워크스페이스부터 시작해야 했다. 나중에는 React Router v7 SSR 앱이 들어오겠지만, 지금은 아직 아니었다. 이 둘을 루트에서 분리해두지 않으면 곧바로 헷갈리기 시작한다.
그래서 기준을 명확히 했다.
/backend는 운영 중인 서버와 이후 리팩토링 대상/frontend는 현재는 Playwright 테스트 워크스페이스, 나중에는 실제 프론트 앱
이렇게만 나눠도 많은 것이 달라졌다. IntelliJ에서 어디를 열어야 하는지 명확해지고, 빌드 기준이 분명해지고, AI에게도 "이 폴더는 백엔드, 저 폴더는 프론트"라고 설명할 수 있게 됐다.
이 단순한 분리가 의외로 큰 효과를 냈다.
백엔드는 왜 멀티모듈로 가기로 했나
최상단 경계를 나눈 뒤에는 백엔드 내부 구조를 다시 봐야 했다. 여기서도 처음부터 모든 코드를 domain / infra / application으로 완벽하게 나누겠다는 생각은 하지 않았다. 그건 현실적이지 않았다.
대신 먼저 멀티모듈 구조를 "뼈대"로 만들고, 실제 코드는 점진적으로 옮기는 방식을 택했다.
이걸 선택한 이유는 세 가지였다.
첫째, 규칙을 구조로 강제하고 싶었다. 도메인 코드는 도메인에 있어야 하고, 인프라 코드는 인프라에 있어야 한다는 말을 문서로만 적어두면 결국 다시 흐려진다. 모듈 경계를 만들면 최소한 어디로 옮겨야 하는지는 분명해진다.
둘째, AI와 협업할 때 작업 단위를 나누기 쉽다. "이 기능은 domain에, 이건 infra에, 이건 application에"처럼 설명할 수 있어야 AI도 더 정확하게 일을 한다. 경계가 없으면 AI가 controller에 코드를 넣을지 service에 넣을지 domain에 넣을지 자꾸 애매하게 판단하게 된다.
셋째, 나중에 프론트 분리와 /api/v1 준비에도 유리하다. 지금은 Thymeleaf가 남아 있지만, 결국 목표는 API 중심 구조로 가는 것이다. 그렇다면 백엔드 내부도 presentation과 business, persistence의 경계를 미리 갖고 있어야 한다.
결국 멀티모듈은 기술 과시가 아니라, 앞으로의 변경을 감당하기 위한 최소한의 구조적 약속이었다.
하지만 처음부터 예쁘게 나누려고 하지 않았다
여기서 중요한 건 "멀티모듈로 가기로 했다"와 "처음부터 모든 코드를 분리했다"는 전혀 다른 이야기라는 점이다.
이번 리팩토링에서는 실행 가능한 상태를 잃지 않는 것이 가장 중요했다. 그래서 기존 앱은 일단 terms-backend 모듈 안에 그대로 수용했다. 즉, 멀티모듈 구조는 먼저 만들되, 실제 코드는 한 번에 다 옮기지 않았다.
이건 꽤 현실적인 타협이었다.
처음부터 모든 클래스를 domain, service, infra로 완벽하게 나누겠다고 하면, 그 순간 리팩토링 작업이 구조 정리 자체에 압도된다. 지금 필요한 것은 "완벽한 구조"가 아니라 "구조를 향해 갈 수 있는 실행 가능한 상태"였다.
그래서 순서를 이렇게 잡았다.
- 먼저 모듈 뼈대를 만든다
- 기존 앱은 실행 가능한 채로 둔다
- 그다음 도메인부터 하나씩 옮긴다
- Playwright로 현재 동작이 유지되는지 계속 확인한다
이 방식은 느려 보일 수 있지만, 운영 중인 서비스에서는 오히려 가장 빠른 길이라고 생각했다.
AI를 많이 쓸수록, 작업 단위를 더 잘게 나눠야 했다
구조를 먼저 정리하는 과정에서 AI와의 협업 방식도 조금씩 정리됐다.
AI는 추상적인 요구를 한 번에 던졌을 때보다, 작고 경계가 분명한 작업에서 훨씬 잘 작동했다. 예를 들면 이런 식이다.
backend만 보고 현재 빌드 구조를 정리하기frontend안에서 Playwright 테스트 워크스페이스 구성하기posting도메인의 저장소만 먼저 분리하기admin테스트를 read-only와 mutation으로 나누기
이런 식으로 작업 단위를 줄이면, 결과도 더 예측 가능하고 검증도 쉬워진다. 반대로 "이 레거시 전체를 다 리팩토링해줘" 같은 요구는 사람이 봐도 범위가 크고, AI도 추측이 늘어나기 쉽다.
결국 이번 작업에서 느낀 건, AI는 구조가 명확할수록 더 잘 도와주고, 구조가 명확하면 사람도 더 좋은 판단을 할 수 있다는 점이었다.
이건 AI 때문이기도 했지만, 사실 좋은 설계가 원래 그렇다. AI가 있든 없든, 경계가 분명한 프로젝트가 유지보수하기 쉽다.
/backend, /frontend는 나중에 떼기 위한 구조이기도 했다
이번 구조에서 또 하나 중요했던 점은 나중에 떼어낼 수 있어야 한다는 것이었다.
백엔드는 결국 API 중심으로 정리할 예정이고, 프론트는 React Router v7 SSR 기준으로 별도 앱처럼 운영될 가능성이 크다. 그렇다면 처음부터 /backend, /frontend를 분리해두는 것이 이후 단계에서도 훨씬 유리하다.
즉 이 구조는 현재를 위한 것이기도 하지만, 동시에 미래 전환을 위한 준비이기도 했다.
지금은 한 저장소 안에서 같이 움직이더라도, 나중에 더 강하게 분리해야 할 시점이 오면 이 구조가 그대로 도움이 될 것이라고 봤다.
마무리
이번 프로젝트에서 구조를 먼저 정리한 이유는 단순히 아키텍처 욕심 때문이 아니었다. 프론트는 내가 혼자 자신 있게 밀 수 있는 영역이 아니었고, AI를 적극적으로 활용할 예정이었고, 운영 중인 서비스라는 제약도 있었다. 이 조건들이 겹치면서 오히려 더 구조가 중요해졌다.
결국 /backend, /frontend를 먼저 나누고, 백엔드를 멀티모듈로 가져가고, 도메인과 인프라의 경계를 미리 의식한 것은 "예쁜 구조를 만들기 위해서"가 아니라, 사람과 AI가 함께 이해할 수 있는 구조를 먼저 만들기 위해서였다.
다음 글에서는 실제로 Maven에서 Gradle로 옮기고, 미사용 코드를 걷어내고, 실행 가능한 상태를 유지하면서 백엔드 리팩토링을 시작한 과정을 정리해보려고 한다.