리뉴얼 프로젝트에 들어가기 전까지는
DTO를 그저 DB 결과를 담는 그릇 정도로 생각했다.
그런데 레거시 시스템을 유지한 채 구조를 바꾸는 대규모 리뉴얼을 겪으면서,
DTO가 생각보다 훨씬 많은 역할을 하고 있다는 걸 체감하게 됐다.
대규모 리뉴얼 프로젝트를 겪으면서 DTO 설계 기준을 다시 세우게 된 이유를 정리한 기록이다.
기존 구조에서는 resultMap이 거의 모든 걸 책임지고 있었다. (진짜 보기 힘든 개쓰레기 코드들의 향연...)
문제1. 컬럼명 ↔ 필드명 매핑이 각기 다름
문제2. 타입이 뭔지 알아 볼 수가 없음
문제3. 중첩 객체 구성 (맵안에서 맵을 쓰는 기괴한...)
처음엔 유연해 보였지만, 시간이 지나면서 문제가 드러났다.
특히 아래 3가지 문제가 심각했다.
심각한 문제1. resultMap 하나 수정하면 여러 쿼리에 영향
심각한 문제2. DTO 구조를 한눈에 파악하기 어려움
심각한 문제3. 실제로 어떤 데이터가 내려오는지 추적이 힘듦
특히 3번 때문에 봤던 코드를 몇번이나 다시 봤는지 모른다.
이렇게 지독한 코드 다시보기를 체험하니 마음이 단호해졌다.
매핑 로직이 많아질수록 DTO는 ‘보이지 않는 객체’가 된다.
그래서 resultMap 의존도를 줄이고, DTO를 중심으로 쿼리와 매핑을 맞추는 방향으로 기준을 바꾸기로 다짐했다. (제발)
리뉴얼 초기에 가장 많이 봤던 패턴이 이거였다.
조회 결과를 담는 DTO
저장/수정 요청을 받는 DTO
계산 결과까지 담기 시작한 DTO
이게 한 클래스에 섞이기 시작하면 DTO는 금방 비대해진다.
왜냐하면 a테이블, b테이블에거 갖고온 모든 데이터들이 짬뽕처럼 섞인 김치짬뽕우웩탕이 탄생되기 때문...
사실 제일 큰 문제는 크기보다 역할이 모호해진다는 점이었다.
그래서 기준을 단순하게 잡았다.
조회 DTO: 화면이나 API에 내려줄 데이터만
요청 DTO: 입력값 검증 중심
내부 계산용 데이터는 별도 객체로 분리
DTO는 상태를 담는 객체이지, 비즈니스 판단을 하는 객체가 아니니까! 절대로 db를 그대로 때려박지 않으려고 노력했다.
레거시에서는 이런 타입들이 섞여 있었다.
날짜: String, Date, Timestamp 혼용
금액: int, long, double 혼재
리뉴얼 과정에서 이걸 그대로 가져가면,
나중에 버그가 나는 지점을 예측하기가 어려웠다.
특히 가격 계산이 중요한 프로젝트인데 숫자들이 아주 지멋대로라 굉장히 곤란...
그래서 DTO 기준을 아예 고정했다.
날짜/시간: LocalDateTime
금액/비율(정수, 소수): Integer, BigDecimal
변환 시간이 조금 들더라도 의미가 명확한 타입을 쓰는 게 유지보수에서는 훨씬 싸다는 결론이었다. 그게 조금 더 확실한 계산이 가능하니까!
리뉴얼을 하면서 가장 크게 느끼고 걱정했던 점이 이거였다.
DTO는 단순하지만 서비스 구조를 나타내는 설계의 일부분이다.
DTO 필드가 하나 추가될 때마다 로직이 늘어나고 검증이 생기고 테스트를 해야한다.
그래서 DTO를 추가할 때마다 이 필드가 정말 있어야 하는지 고민을 많이많이많이 했다.
내 선택에 후회가 없기를. (물론 이렇게 하고 다음날에 후회해서 몇백줄 바꾸고 했음;)
리뉴얼 프로젝트를 하면서 깨달은 건 하나다.
DTO는 빨리 만들수록 좋지만,
아무 기준 없이 만들면 가장 오래 발목을 잡는다.
이후로는 DTO를 만들 때 항상 이 질문을 먼저 던진다.
이 DTO의 역할은 무엇인가
이 데이터는 언제까지 유효한가
서비스 로직과 얼마나 강하게 결합되는가
DTO 설계 기준을 정리한 것만으로도
리뉴얼 이후 코드의 흔들림은 확실히 줄어들었다.
특히 도메인을 바꾼다던가 이상한 짓은 절대 하지 않기로 다짐했다.
이상... 프로젝트 8개를 한번에 리뉴얼한 개발자의 일기... 끝. (문제는 아직도 테스트 중이다.)