프로젝트 초반에는 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가 컨트롤러 경계를 벗어나는 순간, 그 신뢰도가 조금씩 낮아진다는 점은 여러 번 겪게 되는 부분입니다.
'개발 > Typescript' 카테고리의 다른 글
| [TYPESCRIPT] 에러 타입을 나누기 시작하면 코드에서 달라지는 것들 (0) | 2026.02.08 |
|---|---|
| [TYPESCRIPT] 스키마 검증을 붙이기 시작하면 코드에서 달라지는 것들 (0) | 2026.02.07 |
| [TYPESCRIPT] 타입 가드를 쓰다 보면 결국 마주치게 되는 문제들 (0) | 2026.02.06 |
| [TYPESCRIPT] Decorator를 쓰기 전에 먼저 고민하게 되는 것들 (0) | 2026.02.05 |
| [TYPESCRIPT] Variadic Tuple Types를 실무에서 쓰는 경우 (0) | 2026.02.04 |
