
내일배움캠프 26일차 TIL : Spring - @Valid
프로젝트를 진행하면서 Entity에 유효성 검사 어노테이션을 달았었다.
그런데 유효성 검사 어노테이션을 달아둔 의미가 무색하게
데이터베이스에 저장할 때 어떤 값이든 저장되었기에 다시 한 번 개념을 잡으려고 오늘 포스팅을 공부했다.
물론 공부했지만 일단 여기 작성한 내용은 내가 진행한 프로젝트에서 사용했던 방법이기 때문에 좋지 않은 방법일 가능성이 매우 높다는 점을 유의해야 한다.
일단 내가 먼저 했던 실수를 얘기하자면 다음과 같다.
Entity선언시 어노테이션을 붙여 두면 유효성 검사가 이뤄지는줄 알았다는 점
근데 그게 아니었더라...
그리고 여기서 RequestDto는 본인의 작업환경이며 보통 DTO라고 생각하면 된다.
역시나 프로젝트의 build.gradle에 의존성을 추가해줘야 한다.
implementation 'org.springframework.boot:spring-boot-starter-validation'
RequestDto = (@RequestBody를 사용하여 요청 본문을 받는 클래스)
일단 첫 번째 유효성 검사는 RequestDto에서 이루어진다.

RequestDto에서 유효성 검사를 시행하면 Controller 레이어에서 검사가 이루어 지는데,
클라이언트로부터 받은 데이터가 Service 레이어로 넘어가기 전에 유효한지 확인하는 것이다.
이 과정에서 이루어지는 유효성 검사의 장단점을 알아보자
조기 유효성 검증: 데이터가 서비스 레이어에 도달하기 전에 잘못된 데이터를 걸러낼 수 있다.
명확한 책임 분리: 유효성 검사는 DTO에서 처리하고, 비즈니스 로직은 서비스 레이어에서 처리한다.
자동 응답 처리: Spring Boot는 유효성 검사 실패 시 자동으로 적절한 에러 메시지와 함께 400 Bad Request 응답을 반환한다.
중복 유효성 검사: DTO와 서비스 레이어에서 중복된 유효성 검사를 할 수 있다.
제한된 컨텍스트: DTO 레벨의 검사는 단순한 필드 검증에 적합하지만, 복잡한 비즈니스 규칙 검증에는 적합하지 않다.
public class UserRegisterReqeustDto {
private String nickname;
@Size(min = 4, max = 10)
@Pattern(regexp = "[a-z0-9]+")
private String username;
@Size(min = 8, max = 16)
@Pattern(regexp = "[a-z0-9]+")
private String password;
@Basic
@Temporal(TemporalType.TIMESTAMP)
@CreationTimestamp
private Timestamp createdOn;
private String role;
}
위의 코드와 같이 Entity가 아닌 DTO에 어노테이션을 붙여준다.
첫 번째가 DTO에서 유효성 검사를 했다면 두 번째는 Service 레이어에서 진행한다.
서비스 레이어에서 유효성 검사를 수행하면, 컨트롤러를 통해 전달된 데이터를 서비스 메서드 내부에서 검증한다.
이는 비즈니스 로직과 유효성 검사가 좀 더 밀접하게 연결될 수 있는 방법이다.
비즈니스 로직과의 통합: 복잡한 비즈니스 규칙 검증을 수행할 수 있다.
단일 책임 원칙: 서비스 레이어에서 모든 비즈니스 로직을 처리하므로 유지보수가 용이하다.
더 큰 컨텍스트: 컨트롤러와 서비스 간의 유효성 검증을 분리하여 더 복잡한 유효성 검증이 가능하다.
지연된 유효성 검증: 잘못된 데이터가 서비스 레이어까지 전달된 후에야 검증되므로, 초기 단계에서의 잘못된 데이터를 걸러내기 어렵다.
명확한 책임 분리가 부족할 수 있음: 모든 유효성 검사를 서비스 레이어에 포함시키면 컨트롤러의 책임이 모호해질 수 있다.
public UserRegisterResponseDto registerUser(UserRegisterReqeustDto requestDto) {
// 중복된 username 확인
if (userRepository.existsByUsername(requestDto.getUsername())) {
throw new IllegalArgumentException("Username already exists.");
}
if (requestDto.getPassword().length() < 8 || requestDto.getPassword().length() > 16) {
throw new IllegalArgumentException("Password length must be between 8 and 16 characters.");
}
User user = new User(requestDto);
user.setCreatedOn(new Timestamp(System.currentTimeMillis()));
user.setPassword(passwordEncoder.encode(requestDto.getPassword()));
userRepository.save(user);
return new UserRegisterResponseDto(user);
}
- RequestDto에서 유효성 검사는 주로 컨트롤러 레벨에서 이루어지며, 클라이언트로부터 받은 데이터의 초기 유효성 검사를 담당한다.
이는 유효성 검사가 빠르고 자동 응답 처리가 가능하다는 장점이 있습니다.
- Service 레이어에서 유효성 검사는 비즈니스 로직과 긴밀히 통합되어 복잡한 유효성 검증을 수행할 수 있다.
이는 더 복잡한 검증이 가능하지만, 잘못된 데이터가 서비스 레이어까지 도달할 수 있다는 단점이 있다.