왜 Spring Boot, Gradle, React Router v7로 방향을 정했나
운영 중인 레거시 서비스를 AI와 함께 점진적으로 리팩토링 하기
- 01운영 중인 서비스를 왜 지금 리팩토링하기로 했나
- 02왜 Spring Boot, Gradle, React Router v7로 방향을 정했나← 현재
- 03리팩토링 전에 Playwright 테스트부터 만든 이유
- 04AI가 잘 이해할 수 있는 구조를 먼저 만들었다
- 05Gradle 전환과 미사용 코드 정리부터 시작한 이유
- 06domain과 infra를 나누며 백엔드 리팩토링을 시작했다
왜 Spring Boot, Gradle, React Router v7로 방향을 정했나
리팩토링을 시작하기로 결정한 뒤 가장 먼저 부딪힌 질문은 "무엇으로 바꿀 것인가"였다. 이미 운영 중인 서비스였기 때문에, 최신 기술을 나열해놓고 그중 하나를 고르는 방식으로는 답이 나오지 않았다. 이런 상황에서는 기술 선택보다 먼저, 앞으로 이 프로젝트를 어떤 방식으로 유지하고 확장할 것인지에 대한 기준이 필요했다.
이 글은 그 기준을 어떻게 세웠는지, 그리고 왜 백엔드는 Spring Boot와 Gradle, 프론트는 React Router v7 SSR을 기준으로 가기로 했는지를 정리한 기록이다.
기준은 "좋아 보이는 기술"이 아니라 "계속 가져갈 수 있는 기술"이었다
리팩토링을 할 때 가장 흔하게 빠지는 함정 중 하나는 "이참에 최신 기술로 다 바꾸자"는 유혹이라고 생각한다. 실제로 선택지는 많았다. 백엔드도 더 새로운 방식으로 가져갈 수 있었고, 프론트도 여러 프레임워크 후보가 있었다. 하지만 운영 중인 서비스에서는 "더 좋아 보이는 기술"보다 "팀이 계속 유지할 수 있는 기술"이 훨씬 중요하다.
내가 이번에 기술 선택에서 가장 중요하게 둔 기준은 세 가지였다.
첫째, 회사에서 실제로 사용하는 기술 스택과 맞아야 한다. 그래야 나중에 협업도 쉽고, 다른 개발자와 문제를 공유하기도 쉽고, 신규 인력이 들어와도 적응 비용이 낮다.
둘째, 지금의 레거시 구조를 점진적으로 바꾸는 데 무리가 없어야 한다. 처음부터 완전히 다른 생태계로 건너가면 기술 교체 비용이 리팩토링 비용을 압도하게 된다.
셋째, AI와 협업하기 쉬운 구조여야 한다. 이건 이번 프로젝트에서 꽤 중요한 기준이었다. 프론트는 내가 자신 있게 끌고 갈 수 있는 영역이 아니었고, 반복적인 정리와 테스트 작성, 구조 변경에서 AI의 도움을 많이 받을 계획이었기 때문이다.
이 기준으로 봤을 때, 백엔드는 Spring Boot와 Gradle, 프론트는 React Router v7이 가장 현실적인 선택이었다.
백엔드는 왜 Spring Boot로 가기로 했나
기존 프로젝트도 Spring 계열 위에서 돌아가고 있었기 때문에, 완전히 다른 프레임워크로 바꾸는 선택은 처음부터 우선순위가 아니었다. 지금 필요한 것은 프레임워크 전쟁이 아니라 구조 정리였다. 서비스가 운영 중인 상황에서, 핵심은 "이미 있는 자산을 버리지 않으면서 구조를 개선할 수 있느냐"였다.
Spring Boot를 선택한 이유는 명확했다.
첫째, 이미 팀과 코드베이스가 Spring 생태계 안에 있다. 이건 생각보다 중요하다. 기술을 바꿀 때 가장 많이 드는 비용은 새로운 기능을 구현하는 비용이 아니라, 기존 팀이 새로운 기준으로 사고하는 데 드는 비용이다.
둘째, 설정과 실행 기준을 더 명확하게 만들 수 있다. 기존 프로젝트는 오랜 시간 동안 여러 설정이 섞여 있었고, 실행 기준도 다소 애매한 부분이 있었다. Spring Boot 기준으로 다시 정리하면 애플리케이션 진입점, 환경 설정, 실행 방식이 훨씬 분명해진다.
셋째, 앞으로 API 중심 구조로 바꾸기에도 자연스럽다. 지금은 Thymeleaf가 남아 있지만, 최종적으로는 /api/v1를 중심으로 React Router v7 프론트가 붙는 구조를 생각하고 있다. Spring Boot는 이 전환 경로를 무리 없이 가져가기 좋다.
정리하면, Spring Boot는 "새로운 기술 도입"이라기보다, 지금의 레거시를 계속 실행 가능하게 유지하면서 구조를 현대화하기 위한 기준점에 가까웠다.
빌드는 왜 Gradle로 바꾸기로 했나
백엔드 스택을 다시 정리하면서 빌드 도구도 Maven에서 Gradle로 바꾸기로 했다. 이것도 단순히 취향 문제는 아니었다.
이번 리팩토링에서는 처음부터 멀티모듈 구조를 생각하고 있었다. 단일 프로젝트 안에 코드를 계속 우겨 넣는 방식으로는, 지금 겪고 있는 문제를 결국 다시 만나게 된다고 봤다. 도메인과 인프라, 애플리케이션 계층을 나누고, 앞으로는 /api/v1, domain, infra, support를 분리해서 가져가려면 빌드 구조도 그 방향을 자연스럽게 지원해야 했다.
Gradle을 선택한 이유는 크게 두 가지였다.
하나는 멀티모듈 구성을 다루기 편하다는 점이다. 물론 Maven으로도 멀티모듈을 만들 수 있지만, 이번처럼 구조를 자주 바꾸고, 모듈 간 의존을 빠르게 실험하고, 점진적으로 이동해야 하는 상황에서는 Gradle 쪽이 더 유연하다고 느꼈다.
다른 하나는 팀의 기술 기준과 맞추기 위해서다. 회사에서 다른 프로젝트들이 이미 Gradle 기반으로 가고 있었고, 특히 멀티모듈 구조나 공통 모듈 분리도 Gradle 기준으로 많이 정리돼 있었다. 이미 팀 안에 있는 경험치를 쓰는 편이 합리적이었다.
이번 전환에서 중요했던 건 "예쁘게 바꾸는 것"이 아니라 "실행 가능한 상태로 옮기는 것"이었다. 그래서 처음부터 코드를 다 분해하지 않고, 기존 앱을 일단 terms-backend 안에서 그대로 실행 가능하게 두고, 그 위에 Gradle 멀티모듈 뼈대를 만드는 방식을 택했다. 이건 나중에 다시 자세히 다루겠지만, 결과적으로 꽤 좋은 선택이었다.
프론트는 왜 React Router v7로 가기로 했나
프론트는 더 고민이 많았다. 지금은 Thymeleaf 기반 화면이 이미 있고, 장기적으로는 프론트를 분리해야 하는 상황이었다. 처음에는 "어떤 UI 라이브러리를 쓸까" 같은 질문보다, "이 서비스를 앞으로 어떤 방식으로 렌더링할 것인가"가 더 중요했다.
여기서 React Router v7 SSR을 기준으로 잡은 이유는 두 가지였다.
첫째, 역시 회사 표준 스택과 맞추기 위해서다. 프론트엔드는 혼자 오래 끌고 가는 코드가 아니라 협업하는 코드이기 때문에, 팀 안에서 공유 가능한 기준을 따르는 것이 중요했다.
둘째, 지금 서비스의 성격과도 맞는다고 판단했다. 현재 서비스는 검색, 게시글 상세, 소개 페이지처럼 SEO와 직접 연결되는 화면도 있고, 공개 페이지와 관리자 페이지가 함께 존재한다. 클라이언트 렌더링만으로 밀어붙이는 것보다 SSR 기준을 전제로 설계하는 쪽이 더 낫다고 봤다.
물론 이건 "처음부터 프론트를 다 갈아엎겠다"는 뜻은 아니었다. 오히려 정반대다. 프론트는 나중에 새로 붙이되, 지금은 백엔드가 /api/v1를 제공할 수 있는 구조를 먼저 만드는 것이 우선이라고 판단했다. 결국 이번 리팩토링은 프론트 기술 선택보다도, 프론트를 나중에 바꾸기 쉬운 백엔드 구조를 먼저 만드는 과정에 더 가깝다.
그런데 나는 프론트를 제대로 공부해본 적이 없었다
이번 기술 선택에서 솔직하게 인정해야 했던 부분도 있다. 프론트는 내가 가장 자신 있게 끌고 갈 수 있는 영역이 아니었다. 기능을 구현하고 화면을 맞추는 정도는 가능하지만, React Router v7 SSR을 기준으로 구조를 처음부터 설계하고 장기 유지보수 관점에서 밀어붙이는 건 혼자 결정하기 어려운 영역이었다.
그래서 이 부분은 처음부터 사내 프론트엔드 개발자의 도움을 받기로 했다. 큰 방향과 기본 스캐폴딩은 프론트엔드 개발자의 도움을 받아 틀을 잡고, 실제 구현과 반복 작업, 구조 정리, 테스트 작성 같은 영역에서는 AI의 도움을 적극적으로 받기로 했다.
이 선택은 지금 생각해도 매우 현실적이었다고 본다. 내가 잘 모르는 영역을 억지로 혼자 끌고 가는 것보다, 사람의 판단과 AI의 생산성을 함께 쓰는 쪽이 훨씬 안정적이었다.
그래서 /backend, /frontend를 먼저 나누기로 했다
이 프로젝트에서 개인적으로 가장 먼저 잘했다고 느끼는 결정 중 하나가, 최상단 구조를 /backend와 /frontend로 나눈 것이다.
처음에는 이 디렉터리 분리가 꼭 필요한가 싶기도 했다. 하지만 조금만 생각해 보면 이유는 분명했다.
프론트는 나중에 별도로 떼어낼 수 있어야 했다. 백엔드는 멀티모듈로 가져가고, 프론트는 React Router v7 SSR로 다시 구성할 계획이었다. 이 둘을 같은 루트에 섞어두면, 지금은 편할 수 있어도 점점 경계가 흐려진다. 실행 기준도 헷갈리고, 어느 쪽 설정인지 구분하기 어려워진다.
그리고 이 구조는 AI와 협업할 때도 훨씬 유리했다. AI에게 "이건 백엔드, 이건 프론트, 이건 나중에 분리될 앱"이라는 경계를 명확하게 보여줄 수 있기 때문이다. 경계가 흐린 프로젝트에서는 AI도 쉽게 잘못된 파일을 만지거나, 의도를 오해하기 쉽다. 결국 사람과 AI 둘 다를 위해서라도 경계를 먼저 분명히 할 필요가 있었다.
그래서 이 프로젝트는 먼저 /backend, /frontend로 나눴다. 그리고 백엔드 내부는 다시 Gradle 멀티모듈로 가기로 했다. 프론트는 당장은 React 앱이 아니라 Playwright 테스트 워크스페이스부터 시작하더라도, 나중에 회사 스캐폴딩이 들어와도 무리 없이 붙을 수 있게 구조를 열어두었다.
기술 선택보다 더 중요했던 건 "전환 순서"였다
지금 돌아보면, 이번에 내린 기술 선택들은 사실상 전환 순서를 정하는 작업이기도 했다.
백엔드는 Spring Boot + Gradle로 먼저 정리한다.
프론트는 나중에 React Router v7 SSR로 붙인다.
현재 화면은 Playwright로 고정한다.
API는 /api/v1 기준으로 병행한다.
인증은 우선 세션을 유지하고 나중에 JWT로 바꾼다.
DB는 우선 Mongo를 유지하고 나중에 MariaDB + Flyway로 옮긴다.
즉, 이번 리팩토링의 핵심은 "지금 당장 모든 걸 바꾸는 것"이 아니라, 무엇을 먼저 바꾸고 무엇을 나중으로 미룰 것인지 정하는 일이었다.
기술 선택은 그 판단의 결과였다.
마무리
이번 프로젝트에서 Spring Boot, Gradle, React Router v7를 선택한 이유는 최신 스택을 쓰고 싶어서가 아니었다. 운영 중인 레거시 서비스를 멈추지 않고 바꾸기 위해서, 그리고 앞으로 팀이 같이 유지할 수 있는 구조로 정렬하기 위해서였다.
결국 중요한 건 기술 이름보다 순서와 경계였다. 백엔드와 프론트를 먼저 나누고, 백엔드를 Gradle 멀티모듈로 정리하고, 프론트는 나중에 React Router v7 SSR로 붙이고, 그 과정에서 AI가 이해하기 쉬운 구조를 먼저 만드는 것. 이 흐름이 이번 리팩토링의 기술적 기준이 됐다.
다음 글에서는 왜 리팩토링 전에 Playwright 테스트부터 만들었는지, 그리고 fork 떠온 코드에서 실제로 사용되는 화면과 기능을 어떻게 먼저 파악했는지를 정리해보려고 한다.