1. 초기 방식 vs. DTO 사용 방식
이전의 형태: @Body()
로 각각의 필드를 개별적으로 가져오는 방식
@Post('register/email')
postRegisterEmail(
@Body('email') email: string,
@Body('password', new MaxLengthPipe(8), new MinLengthPipe(3)) password: string,
@Body('nickname') nickname: string,
) {
return this.authService.registerWithEmail({
nickname,
email,
password,
});
}
- 특징:
- 모든 필드를 개별적으로
@Body()
로 가져와야 함. - 각 필드마다 필요한 파이프(
MaxLengthPipe
,MinLengthPipe
)를 설정해야 함. - 코드가 중복되고 새로운 필드 추가 시 수정할 부분이 많아 유지보수가 어려움.
- 모든 필드를 개별적으로
변경된 형태: DTO 사용하여 요청 데이터를 한 번에 처리
@Post('register/email')
postRegisterEmail(
@Body() body: RegisterUserDto
) {
return this.authService.registerWithEmail(body);
}
- 장점:
- 요청 데이터를 DTO로 한 번에 받아서 처리하므로 코드가 간결해짐.
- 새로운 필드를 추가할 경우 DTO 클래스만 수정하면 됨.
- 유지보수가 용이해지고 가독성이 높아짐.
2. Validator의 사용 - 하드코딩 vs. Validator 통합
이전의 형태: 유효성 검사 메시지를 하드코딩
@Column()
@IsString({
message: 'nickname은 문자열이어야 합니다.',
})
@Length(1, 20, {
message: '닉네임은 1~20자 사이여야 합니다.',
})
nickname: string;
- 특징:
- 유효성 검사 메시지가 하드코딩되어 있음.
- 여러 곳에서 동일한 메시지가 필요할 경우 중복이 발생할 가능성이 큼.
변경된 형태: Validator 통합으로 유효성 검사를 공통 함수로 관리
import { stringValidationMessage, lengthValidationMessage } from 'common/validation-messages';
@Column()
@IsString({
message: stringValidationMessage,
})
@Length(1, 20, {
message: lengthValidationMessage,
})
nickname: string;
- 장점:
- 메시지를 함수로 분리하여 여러 곳에서 재사용할 수 있음.
- 유지보수가 용이해짐(메시지 수정 시 한 곳만 수정하면 됨).
- 코드의 가독성과 일관성이 높아짐.
3. 기존 모델을 재사용 - 직접 정의 vs. PickType 사용
이전의 형태: DTO 직접 정의
export class RegisterUserDto {
nickname: string;
email: string;
password: string;
}
- 특징:
- 엔티티에 이미 정의된 필드와 중복이 발생할 수 있음.
- 코드의 중복으로 인해 변경 사항 발생 시 여러 군데를 수정해야 할 가능성이 높음.
변경된 형태: PickType을 사용하여 기존 모델 재사용
import { PickType } from "@nestjs/mapped-types";
import { UsersModel } from "src/users/entities/users.entity";
export class RegisterUserDto extends PickType(UsersModel, ['nickname', 'email', 'password']) {}
- 장점:
- 기존 엔티티(
UsersModel
)에서 필요한 필드만 선택하여 재사용함으로써 중복을 줄임. - 유지보수가 쉬워지고 변경 사항을 DTO와 엔티티에 동시에 반영할 수 있음.
- 기존 엔티티(
4. Update 요청 처리 - 개별 필드로 관리 vs. DTO와 PartialType 사용
이전의 형태: Update 요청을 개별 필드로 처리
@Put(':id')
putPost(
@Param('id', ParseIntPipe) id: number,
@Body('title') title?: string,
@Body('content') content?: string,
) {
return this.postsService.updatePost(id, { title, content });
}
- 특징:
- 필드가 많아질수록 코드가 복잡해짐.
- 모든 필드를 선택적으로 업데이트하기 어렵고 코드 중복이 발생할 가능성이 있음.
변경된 형태: DTO와 PartialType 사용하여 관리
import { PartialType } from "@nestjs/mapped-types";
import { CreatePostDto } from "./create-post.dto";
export class UpdatePostDto extends PartialType(CreatePostDto) {}
@Put(':id')
putPost(
@Param('id', ParseIntPipe) id: number,
@Body() body: UpdatePostDto,
) {
return this.postsService.updatePost(id, body);
}
- 장점:
- 모든 필드를 선택적(optional)으로 처리하여 일부만 업데이트 가능.
- 기존 DTO(
CreatePostDto
)를 확장해 재사용하므로 코드의 중복을 줄임. - 코드가 간결해지고 유지보수가 쉬워짐.
5. Validation Message 관리 - 하드코딩 vs. 함수형 메시지 분리
이전의 형태: 하드코딩된 Validation Message
@Column()
@IsString({
message: 'title은 문자열이어야 합니다.',
})
@Length(1, 20, {
message: 'title은 1~20자 사이여야 합니다.',
})
title: string;
- 특징:
- 동일한 유효성 검사 메시지가 여러 곳에 중복되어 나타날 가능성이 있음.
- 메시지 수정 시 모든 곳을 수정해야 함.
변경된 형태: 공통 Validation Message 함수 사용
import { stringValidationMessage, lengthValidationMessage } from 'common/validation-messages';
@Column()
@IsString({
message: stringValidationMessage,
})
@Length(1, 20, {
message: lengthValidationMessage,
})
title: string;
Validation Message 함수 예시
import { ValidationArguments } from "class-validator";
export const stringValidationMessage = (args: ValidationArguments) => {
return `${args.property}에는 문자열만 입력할 수 있습니다.`;
};
export const lengthValidationMessage = (args: ValidationArguments) => {
return `${args.property}은 ${args.constraints[0]}~${args.constraints[1]} 글자 사이여야 합니다.`;
};
- 장점:
- 메시지를 공통 함수로 분리하여 코드 중복을 줄임.
- 메시지 수정 시 한 곳만 수정하면 되어 유지보수성이 크게 향상됨.
- 코드가 명확해지고 가독성이 높아짐.
6. 서비스 계층에서 DTO를 활용하여 데이터 처리
이전의 형태: 개별 데이터 직접 전달
async registerWithEmail(nickname: string, email: string, password: string) {
const hash = await bcrypt.hash(password, HASH_ROUNDS);
const newUser = await this.usersService.createUser({ nickname, email, password: hash });
return this.loginUser(newUser);
}
- 특징:
- 서비스로 전달되는 데이터가 개별적으로 관리되며, 중복된 코드가 발생할 가능성이 높음.
- 데이터의 구조적 명확성이 부족하여 유지보수가 어려울 수 있음.
변경된 형태: DTO를 활용하여 데이터 구조적 처리
async registerWithEmail(user: RegisterUserDto) {
const hash = await bcrypt.hash(user.password, HASH_ROUNDS);
const newUser = await this.usersService.createUser({ ...user, password: hash });
return this.loginUser(newUser);
}
- 장점:
- DTO를 사용하여 데이터 구조가 명확해짐.
- 서비스 코드가 간결해지고, 중복된 코드가 줄어듦.
- 유효성 검사를 컨트롤러에서 통과한 데이터만 전달받아 데이터 무결성을 보장함.
결론: DTO와 Validator 활용의 효과적인 변화
이전의 방식에서는 필드별로 직접 데이터를 다루고 유효성 검사 메시지를 하드코딩하는 등 유지보수가 어렵고 확장성이 부족한 문제점이 있었습니다. 이를 개선하기 위해 DTO와 class-validator
를 활용하여 데이터를 구조적으로 처리하고, Validator 메시지를 함수로 분리하여 재사용성을 높였습니다.
이러한 변화의 주요 장점은 다음과 같습니다:
- 코드 중복 제거: DTO와 Validator 통합, Validation Message 함수 사용으로 중복된 코드 제거.
- 유지보수 용이성: 필드 추가/수정 시 DTO나 Validator 메시지를 수정하기만 하면 되어 유지보수가 쉬워짐.
- 가독성 및 확장성 향상: 코드가 더 구조적이고 명확해져서, 프로젝트가 커질수록 유지보수에 유리해짐.
- 데이터 무결성 보장:
class-validator
를 사용하여 입력 데이터의 유효성을 보장.
이렇게 개선된 방식은 프로젝트가 커지거나 복잡해질수록 더 큰 장점을 발휘하며, 개발자들이 일관성 있는 코드 스타일을 유지하면서 효율적으로 작업할 수 있게 도와줍니다.
'NestJS' 카테고리의 다른 글
NestJS 페이징처리 (0) | 2024.11.19 |
---|---|
NestJS Class-transfomer (0) | 2024.11.18 |
NestJS 데코레이터 (0) | 2024.11.16 |
NestJS Guard (0) | 2024.11.16 |
NestJS 핫리로드 트러블슈팅 (1) | 2024.11.16 |