-
Spring Boot 고급 시리즈 4편 – 커스텀 Validation과 도메인 유효성 설계 전략기술과 산업/언어 및 프레임워크 2025. 4. 21. 19:35728x90
Spring Boot에서 @Valid를 넘어서는 고급 Validation 전략을 소개합니다. 커스텀 애노테이션 생성과 도메인 중심 유효성 설계 방식을 실무 예제로 정리했습니다.
Spring Boot 고급 시리즈 4편 – 커스텀 Validation과 도메인 유효성 설계 전략
기본적인 유효성 검사는 @Valid와 @NotBlank, @Email 등의 애노테이션으로 충분합니다.
하지만 복잡한 비즈니스 조건이 추가되면, 다음과 같은 고민이 생기기 시작하죠.- "이메일이 이미 존재하는지 DB에서 체크해야 해"
- "두 필드의 값이 서로 일치해야 하는데 어떻게 검사하지?"
- "도메인 정책에 따라 유효성 로직이 달라져야 해"
이번 글에서는 이런 복잡한 검증을 아름답고 유지보수 가능하게 처리하는 방법을 설명합니다.
🧱 1. 한계에 부딪힌 기본 유효성 검증
public class RegisterUserRequest { @NotBlank @Email private String email; @NotBlank private String password; @NotBlank private String confirmPassword; }
이 구조로는 다음과 같은 검증을 할 수 없습니다:
- 이메일 중복 여부(DB 조회 필요)
- password와 confirmPassword의 일치 여부
- 도메인에 따라 유효성 조건이 달라지는 경우
✅ 2. 커스텀 검증 애노테이션 만들기
예제: 이메일 중복 여부 검사
1️⃣ 애노테이션 생성
@Documented @Constraint(validatedBy = EmailNotExistsValidator.class) @Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface EmailNotExists { String message() default "이미 사용 중인 이메일입니다."; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
2️⃣ Validator 구현
@Component public class EmailNotExistsValidator implements ConstraintValidator<EmailNotExists, String> { private final UserRepository userRepository; public EmailNotExistsValidator(UserRepository userRepository) { this.userRepository = userRepository; } @Override public boolean isValid(String value, ConstraintValidatorContext context) { return value != null && !userRepository.existsByEmail(value); } }
3️⃣ DTO 적용
public class RegisterUserRequest { @NotBlank @Email @EmailNotExists private String email; @NotBlank private String password; @NotBlank private String confirmPassword; }
- @EmailNotExists는 DB 검증을 자동화하며, 코드 분리와 가독성을 크게 향상시킵니다.
🔁 3. 필드 간 검증 – Cross-field Validation
예제: 비밀번호와 확인 비밀번호 일치 여부
1️⃣ 애노테이션 정의
@Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = PasswordMatchesValidator.class) public @interface PasswordMatches { String message() default "비밀번호와 확인 비밀번호가 일치하지 않습니다."; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
2️⃣ Validator 구현
public class PasswordMatchesValidator implements ConstraintValidator<PasswordMatches, RegisterUserRequest> { @Override public boolean isValid(RegisterUserRequest dto, ConstraintValidatorContext context) { return dto.getPassword().equals(dto.getConfirmPassword()); } }
3️⃣ DTO 적용
@PasswordMatches public class RegisterUserRequest { private String password; private String confirmPassword; // getter/setter 생략 }
🧠 4. 도메인 계층으로 유효성 이관하기
복잡한 정책 기반 검증은 DTO에서 해결할 수 없습니다. 이럴 땐 도메인 객체 내부에서 검증하는 것이 정답입니다.
@Entity public class User { public void changePassword(String currentPassword, String newPassword) { if (!this.password.equals(currentPassword)) { throw new BusinessException(ErrorCode.INVALID_PASSWORD); } this.password = newPassword; } }
- 도메인은 스스로의 유효성을 스스로 판단해야 합니다.
- 검증 로직이 도메인 외부에 흩어지지 않도록 주의하세요.
🧪 5. 테스트 전략
검증 대상 테스트 위치
커스텀 Validator Validator 단위 테스트 (@WebMvcTest 가능) 도메인 유효성 Domain 단위 테스트 (JPA 없이도 가능) DTO @Valid 적용 Controller 통합 테스트로 검증
✅ 마무리 요약
항목 전략 요약
기본 한계 @Valid만으로 복잡한 검증 처리 어려움 커스텀 Validator @EmailNotExists, @PasswordMatches 등 재사용 가능 구조 도메인 검증 엔티티 내부에서 책임 있게 정책 적용 테스트 Validator/Domain 계층 별도 테스트 가능 유지보수성 조건 변경 시 DTO 수정 없이 Validator 교체만으로 대응
📌 다음 편 예고
Spring Boot 고급 시리즈 5편: REST API 버전 관리 전략 – URI vs Header 방식 실무 적용법
728x90'기술과 산업 > 언어 및 프레임워크' 카테고리의 다른 글
Spring Boot 고급 시리즈 6편 – 비동기 처리와 이벤트 기반 아키텍처 전략 (0) 2025.04.22 Spring Boot 고급 시리즈 5편 – REST API 버전 관리 전략: 실무에 강한 설계와 적용법 (0) 2025.04.21 Spring Boot 고급 시리즈 3편 – 커스텀 예외와 오류 코드 설계 전략 (0) 2025.04.18 Spring Boot 고급 시리즈 2편 – 유지보수 가능한 서비스 아키텍처 설계 전략 (0) 2025.04.18 Spring Boot 고급 시리즈 1편 – @Transactional 완전 정복: 실무 트랜잭션 설계 전략 (0) 2025.04.17