[TYPESCRIPT] DTO를 믿기 시작했을 때 놓치기 쉬운 지점들

프로젝트 초반에는 DTO가 거의 등장하지 않습니다. 컨트롤러에서 값을 바로 받아서 쓰거나, 간단한 타입 정의 정도로 흐름을 유지합니다.

 

그러다 코드가 늘어나고, 요청과 응답이 복잡해지기 시작하면 DTO가 하나씩 생깁니다. ValidationPipe를 붙이고, class-validator를 적용하면서 “여기까지 오면 이제 입력은 안전하다”는 느낌이 들기 시작합니다.

 

문제는 이 시점부터입니다. DTO를 신뢰하기 시작하면서, 자연스럽게 빠져나가는 가정들이 생깁니다. 대부분은 바로 티가 나지 않고, 운영 단계에서 조금씩 드러납니다.

 

DTO가 있다는 이유로 생기는 가정

DTO를 도입하면, 코드 곳곳에서 비슷한 전제가 깔립니다.

  • 이 값은 이미 검증되었다
  • 형태는 항상 DTO 정의와 같다
  • null이나 undefined는 들어오지 않는다

 

이 가정은 컨트롤러 경계에서는 대체로 맞습니다. 하지만 이 값이 한 레이어만 이동해도, 상황은 조금씩 달라집니다.

 

컨트롤러를 벗어난 DTO

NestJS 기준으로 보면, DTO는 보통 컨트롤러에서만 직접 사용됩니다.

 


@Post('login')
async login(@Body() dto: LoginDto) {
  return this.authService.login(dto);
}

 

이 코드만 보면, authService.login 안에서는 dto를 그대로 믿고 써도 될 것처럼 보입니다. 실제로 많은 코드가 그렇게 작성됩니다.

 

문제는 이 dto가 더 아래로 내려갈 때입니다.

  • 서비스에서 다시 다른 서비스로 전달될 때
  • 큐 메시지로 직렬화될 때
  • 캐시에 저장됐다가 다시 읽힐 때

 

이 순간부터 dto는 “검증된 입력”이 아니라 “과거에 한 번 검증되었을 가능성이 있는 값”이 됩니다.

 

ValidationPipe가 해주지 않는 것들

ValidationPipe를 붙이면 많은 것이 해결된 것처럼 느껴집니다. 하지만 실제로는 다루지 않는 영역도 분명합니다.

 

  • 문자열이 비어 있는지 여부
  • 숫자 범위가 비즈니스 규칙에 맞는지
  • 다른 필드와의 관계

 

예를 들어 이런 DTO가 있다고 가정해 보겠습니다.

 


class UpdateProfileDto {
  @IsString()
  nickname: string;

  @IsNumber()
  age: number;
}

 

이 DTO는 형태만 보면 문제없습니다. 하지만 실제로는,

  • nickname이 빈 문자열인 경우
  • age가 0이거나 비정상적으로 큰 경우

 

같은 값도 그대로 통과합니다. 이런 조건은 결국 서비스 로직에서 다시 등장합니다.

 

DTO에 비즈니스 규칙이 섞이기 시작할 때

DTO를 믿기 시작하면, 검증 로직을 조금 더 넣고 싶어집니다.

 

  • 이 값은 특정 상태에서만 허용된다
  • 다른 필드와 조합해서 판단해야 한다
  • DB에 있는 값과 비교해야 한다

 

이 요구를 DTO에 계속 추가하다 보면, 검증 코드가 점점 비즈니스 로직에 가까워집니다.

 

이 시점부터 DTO는 입력 형태를 설명하는 객체라기보다, 하나의 판단 레이어가 됩니다. 이 구조를 계속 가져갈지 여부는 팀마다 고민이 필요한 지점입니다.

 

응답 DTO를 그대로 믿는 경우

요청 DTO뿐 아니라, 응답 DTO에서도 비슷한 문제가 생깁니다.

 

응답용 DTO를 만들어두면, 이 값은 항상 이 형태로 나간다고 믿기 쉽습니다. 하지만 실제로는,

  • 조건에 따라 일부 필드가 빠지거나
  • null로 채워지거나
  • 버전이 다른 클라이언트로 전달되거나

 

같은 상황이 반복됩니다. 이때 DTO 정의와 실제 응답 사이에 미묘한 어긋남이 생기기 시작합니다.

 

운영에서 드러나는 문제들

DTO를 과하게 신뢰한 코드에서 운영 중 자주 보게 되는 상황은 다음과 같습니다.

  • 예상하지 못한 값으로 인해 서비스 로직에서 예외 발생
  • 캐시나 메시지 큐를 거친 데이터가 다시 검증되지 않음
  • DTO 변경이 하위 로직까지 연쇄적으로 영향을 줌

 

이런 문제는 코드 리뷰 단계에서는 잘 드러나지 않습니다. 대부분 운영 환경에서 처음 마주치게 됩니다.

 


 

DTO는 입력과 출력을 정리하는 데 분명히 도움이 됩니다. 다만 DTO가 있다는 이유만으로 값 전체를 신뢰하기 시작하면, 그 이후의 코드가 취약해지는 경우도 많습니다.

 

어디까지 DTO가 책임질지, 어디부터는 서비스 로직에서 다시 확인할지는 명확히 나뉘어 있지 않습니다. 다만 DTO가 컨트롤러 경계를 벗어나는 순간, 그 신뢰도가 조금씩 낮아진다는 점은 여러 번 겪게 되는 부분입니다.