오늘은 새로운 팀에 들어가서 리팩토링한 지난날의 과정을 회고해보려 한다!!!
나는 새로운 프로젝트에 투입되어서 동시에 여러 서비스를 만들고 유지보수 해야 했다. 기존에는 실제 고객에게 보이는 서비스 외에, AI 서버를 통해 값을 입력받아 관리하는 CMS(Admin) 서비스도 존재했다. 하지만 이 두 시스템 모두 객체지향적 설계는 거의 고려되지 않았다. 모두 JDBC 기반으로 작성되어 있었고, 마이그레이션 파일조차 없이.. 직접 쿼리를 날린 것도 아닌,, db 툴에서 GUI를 활용해서 클릭 -> 추가 이런 식으로 수작업으로 관리하셨다고 말하고 그분은 팀을 떠나셨다.. 지금까지는 늘 새로 프로젝트를 생성해서 만들다 보니 남의 코드를 보면서 고칠 일이 없었고 실제로 유지보수성과 가독성 이런 것들이 왜 중요한지 느끼게 해 준 코드였다.. 어쩌면 감사해야 할 수도..
초반에 팀을 들어갔을 때는 쓰여진 코드에 비해서 실제 사용되는 서비스의 규모가 작아서 일주일 동안은 서버 고쳐달라는 부분들을 다 고치면서 보냈던 것 같다. 하지만 점점 사용하지 않았던 기능들을 활용하고 그 코드들이 실제로 제 기능조차 못하며 돌아갔다.. 그것들을 다 찾으면서 고치고 리팩토링을 하려다 보니 정말 별 것 아닌 코드 수정에 너무 많은 시간을 할애하고 있었다.
그래서 결심했다. "새로 만드는 게 더 빠르겠다." 그때부터 본격적인 리팩토링이 시작했다.
1. 구조적 혼란에서 시작된 DB 리디자인
처음에는 유튜브 콘텐츠를 기반으로 시작한 서비스였기 때문에, 유튜버는 Celeb이라는 테이블로 정의돼 있었다. 문제는 인스타그램 기반 콘텐츠가 추가되면서 발생했다. 인스타 셀럽의 경우에는 Celeb_Insta라는 이름으로 관리되었고, 이에 따라 기존에 있던 테이블 구조들이 전부 _Insta를 붙여 분기되기 시작했다. 예를 들면 Feed가 유튜브 콘텐츠 피드라면 Feed_Insta는 인스타그램 피드였고, Cody는 유튜브 코디 정보를 담고, Cody_Insta는 인스타 코디 정보를 담았다. 도메인이 분리돼야 하는데 이름만 유사해서 헷갈리는 상황이 지속됐다.
같은 개념을 다루면서도 실제로는 다른 도메인을 의미하는 이런 네이밍 혼란은 개발자 사이에서도 커뮤니케이션에 혼선을 줬고, 더불어 필드명도 혼란을 주는 것들이 많았다. 필드명은 응답 변수명에도 영향을 줬고, API 명세를 참고하는 프론트엔드 개발자들도 매번 변수명이 달라 혼란을 겪었다. 필드명의 예시를 들어보자면 어떤 테이블은 boolean 값을 Y/N 문자열로 저장하고, 어떤 테이블은 id, idx, no를 구분 없이 혼용해서 사용했다.
그래서 리팩토링의 첫 단계로 도메인 개념 정리와 명확한 네이밍 기준 수립, 그리고 이를 기반으로 한 기획 문서화 작업을 진행했다. 이는 단순히 개발자들 간의 명세 정리를 넘어서, 기획자, 디자이너, 프론트 개발자 모두가 동일한 용어로 커뮤니케이션할 수 있는 기반을 만드는 작업이었다. 추가로 문서화를 진행한 이유는, 향후 규칙에 기반한 명확한 테이블 설계를 가능하게 하기 위함이었다. 예를 들자면, "유튜브와 인스타를 같은 사람이 모두 등록할 수 있는가?", "공통되는 정보는 무엇인가?" 같은 질문에 대한 기준을 명확히 해서 테이블 설계를 좀 더 잘하고자 한 마음이었다.이런 기획을 고려해서 구조적으로는 Celeb이라는 공통 테이블을 중심으로, 플랫폼에 따라 YoutubeProfile과 InstaProfile을 각각 분리해 설계했다. 이를 통해 SNS 플랫폼이 늘어날 경우에도 Celeb을 기준으로 확장할 수 있게 했으며, 한 명의 셀럽이 여러 개의 본인 프로필을 관리하기 쉽게 만들 수 있었다.
이 구조는 추후 셀럽이 직접 회원가입을 하고, 본인의 유튜브/인스타 계정을 연동하는 기능을 개발할 때도 큰 도움이 되었다. 예전 같았으면 유튜브와 인스타를 따로 연결하기 위한 매핑 로직이 필요했겠지만, 지금은 Celeb을 중심으로 필요한 프로필만 연결하면 된다.
또한 이 과정에서 모든 테이블의 필드명과 타입도 일관되게 정리했다. boolean 타입은 더 이상 Y/N 문자열로 저장하지 않고, 명확히 true/false로 저장되도록 수정했고, 식별자도 전 테이블에서 idx로 통일했다. 이런 작업을 통해 API 응답의 변수명도 일관성을 가지게 되었고, 프론트엔드 개발자의 혼란도 크게 줄일 수 있었다.
2. JDBC에서 JPA로 전환한 이유
기존 시스템은 모든 DB 처리를 JDBC로 작성하고 있었기 때문에, 단순한 데이터 조회나 저장도 Connection, PreparedStatement, ResultSet 등을 반복적으로 다뤄야 했다. 유지보수에 비용이 들고, 작은 쿼리 하나도 실수할 가능성이 컸다.
JPA를 도입한 건 단지 기술을 바꾸기 위함이 아니라, 빠르게 변화하고 확장해야 하는 개발 환경에서 생산성과 안정성을 높이기 위함이었다. 특히 빠르게 개발해야 하는findById, save, delete 등 직관적인 메서드만으로 많은 비즈니스 로직을 간결하게 처리할 수 있다는 점이 큰 장점으로 다가왔다.
물론 모든 상황에 JPA가 최선은 아니며, 동작 방식이나 성능상 한계도 분명히 존재한다. 하지만 이 프로젝트에서는 우선 빠르게 시스템을 안정화하고 확장하기 위한 수단으로써 JPA가 유효했고, 이를 통해 더는 반복하지 않아도 되는 수많은 JDBC 코드를 줄일 수 있었다.
3. 도메인/엔티티 분리와 Repository 추상화
빠르게 개발하면서도 JPA에 종속되지 않기 위해 선택한 방향은 도메인과 엔티티의 분리였다. 이를 위해 Java/Spring 실용주의 프로그래밍과 DDD 관련 서적들을 참고하며 구조를 설계했다.
엔티티는 오직 영속성 처리를 위한 구조로만 사용하고, 실제 비즈니스 로직은 도메인 객체에서만 처리하도록 구성했다. 예를 들어, 서비스 계층에서는 ProductEntity가 아닌 Product 도메인 객체만을 다루며, 이는 추후 테스트에서도 큰 장점이 되었다.
또한 Repository는 JPA 구현체에 직접 의존하지 않고 인터페이스를 기준으로 설계해 뒀다. 이렇게 하면 이후 MyBatis나 Jooq처럼 다른 ORM 툴로 교체하더라도, 서비스 로직을 수정하지 않고 구현체만 바꾸는 것으로 충분하다.
덕분에 순수 자바 객체로 테스트를 해볼 수 있어서 테스트 코드 작성이 쉬워졌고, 모듈 간 의존성을 줄여 유연한 변경이 가능했다. 더불어 ORM에 과도하게 묶이지 않고, 필요시 기술 전환이 가능하기 때문에 추후에 내가 아닌 다른 사람이 와서 무엇을 한다 해도 편하게 개발할 수 있을 것이라 생각했다.
4. 전역 예외 처리로 흐름 정리
기존에는 비즈니스 로직 곳곳에서 try-catch 문이 무분별하게 사용되고 있었고, 예외가 발생해도 사용자에게 어떤 문제가 있었는지 설명해 주는 방식이 통일돼 있지 않았다. 이에 따라 GlobalExceptionHandler를 구성해 예외를 일괄 처리하도록 개선했고, 커스텀 예외들을 도메인 단위로 분리하여 각 계층에서 의미 있는 에러 메시지를 던질 수 있도록 만들었다.
이 덕분에 코드 가독성도 좋아졌고, 프론트엔드와의 API 통신 시에도 어떤 문제로 실패했는지 명확히 알 수 있었다.
5. dev, main 서버 분리
초기에는 운영 서버 하나만 존재했고, 테스트도 바로 실서버에서 진행하고 있었다. 하지만 코드가 점차 커지고, 기능별 QA가 필요해지면서 실서버에서 테스트를 진행하는 것이 매우 위험해졌다.
그래서 배포 파이프라인을 정리하고 dev 서버를 따로 분리하여, 기능별 테스트 및 QA를 dev 서버에서 먼저 검증한 뒤 main으로 반영하는 구조로 바꿨다. 이를 통해 실서버에서 발생할 수 있는 리스크를 줄이고, 테스트 환경과 운영 환경을 명확히 구분할 수 있게 되었다.
회고
사실 이외에도 MSA 기반의 아키텍처도 많이 고려했었다. 하지만 이번 리팩토링 과정에서는 기존의 코드를 바탕으로 구조를 재정립하는 것을 목적으로 하는 것들만 적어봤다.
이 과정을 정리해 보니 단순해 보이지만, 실제로는 정말 많은 시간을 들여 고민하고 리서치했던 시간들이었다. 덕분에 여러 책도 찾아보고, 다양한 아키텍처를 비교하며 더 나은 방향을 계속해서 탐색했다. 물론 힘들지 않았다면 거짓말이다. 잘하고 있는 건지 불안한 날도 있었고, '내가 이걸 여기까지 손대야 하나' 싶을 때도 많았다. 추가로 이런 곳에 리소스를 들여 일정 지연이 되진 않을까 걱정도 됐다.
그럼에도 불구하고, 그냥 내 시간을 갈아 넣는 방식으로라도 최선을 다해 개선해 보자는 마음이었고, 그렇게 하나씩 해나가며 조금씩 더 나은 개발자로 나아가고 있다고 믿는다.
사실 서버 개발자는 나 혼자이기 때문에 이걸 알아봐 줄 사람이 없다고 생각했다,,ㅎ 사실 누가 알아봐 주길 바란 건 아니고, 스스로 개선이 필요하다 생각돼서 했기 때문에 그런 기대도 없었다. 그런데 어느 날, 함께 일하는 앱 개발자 분이 서버 구조나 API 설계 방식에 대해 관심을 갖고 질문을 해주셨다. 알고 보니 백엔드 관련 공부를 좀 하고 계셨고, 더불어서 내가 작업한 스키마와 API 구조들이 “훨씬 직관적이고 일관성 있어진 것 같다”라고 칭찬을 해주셨다.
그 말을 들었을 때 정말 정말 기분이 좋았다. 의도치 않게, 하지만 분명 누군가가 불편함을 느끼고 있었던 부분들을 개선했다는 점에서 뿌듯했고, 더불어 요청사항 빨리빨리 처리해 줘서 고맙다 해주셔서 서버 개발자로서 받을 수 있는 좋은 칭찬들을 다 받은 기분이었다. 덕분에 그동안의 노력이 조금은 보상받은 것 같아 정말 기뻤다.
앞으로도 이렇게 고민하고 개선하는 과정들을 게을리하지 않고, 발전하는 개발자가 되어야겠다.. 중간 회고 끝!!!!!!!
'Spring' 카테고리의 다른 글
[Spring] Tech Spec 작성부터 리팩토링 과정 (3) | 2025.06.29 |
---|---|
[Spring Cloud] MSA 환경에서 Gateway로 CORS 처리하기 - Troubleshooting (1) | 2025.05.29 |
[Spring] SpringBoot Cloud Gateway에서 Swagger 활용 (1) | 2025.04.25 |
[Spring/DB] 실제 운영 서버에서 ddl-auto update의 문제점과 Flyway 도입 (ddl-auto, Flyway) (0) | 2025.02.28 |
[Spring/Test] 분산락을 통한 동시성 제어 - Troubleshooting (4) | 2024.12.08 |