클라이언트가 보내는 요청 데이터에 대해 필수 여부, 길이 제한, 형식 검사 등을 수행해야 할 때, 그 책임을 어디에 둘 것인지에 따라 코드의 가독성, 재사용성, 테스트성이 크게 달라지게 된다.
이번 글에서는 Spring 기반의 프로젝트에서 유효성 검사를 어디에 두는 것이 적절한지, 그리고 각 위치별 장단점과 사용 시점에 대해 정리해보려 한다.
✅ 유효성 검사란?
유효성 검사(Validation)는 클라이언트가 보낸 데이터가 시스템이 요구하는 조건을 만족하는지 확인하는 과정이다.
예시
- 빈 값 검사
- 숫자 범위 검사
- 날짜 형식 검사
- enum 값 제한
- JSON 구조의 필수 속성 체크
📍 유효성 검사를 할 수 있는 위치들
Spring에서는 보통 다음과 같은 위치에서 유효성 검사를 수행한다.
위치 | 설명 |
1. Controller 계층 | @Valid, @Validated를 이용한 검증 |
2. Service 계층 | 비즈니스 로직 수행 전 검증 |
3. Validator 클래스 | 책임 분리된 전용 클래스에서 검증 |
4. DTO 내부 | 필드에 어노테이션으로 제약 명시 |
5. Interceptor/Filter | 요청 전체에 대한 사전 필터링 수행 |
1. Controller 계층에서의 유효성 검사
@PostMapping("/messages/basic")
public ResponseEntity<?> sendMessagesBasic(@Valid @RequestBody MessageRequestDto dto, BindingResult result) {
if (result.hasErrors()) {
return ResponseEntity.badRequest().body(result.getAllErrors());
}
messageService.save(dto);
return ResponseEntity.ok().build();
}
✅ 장점
- 간단한 입력 검증에 적합
- @Valid를 이용해 선언적으로 처리 가능
❌ 단점
- 컨트롤러가 검증 로직으로 인해 비대해질 수 있음
- 복잡한 검증 조건에는 부적합
💡단순한 null 체크, 길이 제한 등 입력값 검증에 적합
2. Service 계층에서의 유효성 검사
public void save(UserRequestDto dto) {
if (!dto.getEmail().contains("@")) {
throw new ValidationException("이메일 형식이 올바르지 않습니다.");
}
// 저장 로직
}
✅ 장점
- 도메인 규칙과 함께 검증 가능
- DB 조회 후 상태 기반 검증 가능 (ex. 중복 체크)
❌ 단점
- 검증과 비즈니스 로직이 섞여 관심사 분리에 위배
- 테스트와 재사용이 어려움
💡 "탈퇴한 유저는 저장 불가" 같은 비즈니스 조건 검증에 적합
3. Validator 클래스를 분리하는 방식
@Component
public class UserValidator {
public void validate(UserRequestDto dto) {
if (!dto.getEmail().contains("@")) {
throw new ValidationException("이메일 형식 오류");
}
}
}
userValidator.validate(dto);
✅ 장점
- 관심사 분리로 구조가 명확
- 테스트, 재사용 용이
- 복잡한 검증 로직을 메서드 단위로 세분화 가능
❌ 단점
- Validator 클래스 수가 많아질 경우 관리 복잡도 증가
- 명확한 설계 기준이 필요
💡도메인별 검증 로직이 많거나 복잡한 경우, 다양한 계층(Service, Handler, Event 등)에서 공통 검증이 필요한 경우 사용
4. DTO에 어노테이션 사용
public class UserRequestDto {
@NotBlank
private String name;
@Min(1)
@Max(99)
private int age;
}
✅ 장점
- 선언적으로 조건을 명시할 수 있어 가독성이 높음
- 공통 제약을 간결하게 표현 가능
❌ 단점
- 복잡한 조건은 표현 불가
- 조건이 자주 바뀌면 DTO를 재사용하기 어려움
💡 null, 길이, 범위 같은기본 제약 조건에 적합
5. Interceptor/Filter에서의 유효성 검사
public class ApiKeyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String apiKey = request.getHeader("X-API-KEY");
if (!"SECRET".equals(apiKey)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
return true;
}
}
✅ 장점
- 전역적인 요청 검증 가능
- 인증, 보안 등 비즈니스 외적 검증에 적합
❌ 단점
- JSON 바디는 읽기 어려워 Payload 검증에는 부적절
- 비즈니스 검증은 절대 포함하면 안 됨
💡 API 키, 토큰, 헤더 등 보안 관련 전처리에 적합
마무리
유효성 검사는 단순한 조건 체크를 넘어서 시스템의 신뢰성과 유지보수성, 확장성까지 좌우하는 핵심 설계 요소라는 것을 다시금 깨달았다.
앞으로는 각 계층의 역할에 맞게 검증 책임을 명확히 나누고, 검증 로직 또한 깔끔하고 유연하게 설계할 수 있도록 구조적인 접근을 습관화해야겠다.
'Backend > Spring' 카테고리의 다른 글
Spring 서비스 컴포넌트 Bean 주입 문제와 해결 방법 (0) | 2025.07.16 |
---|---|
Spring REST API 프로젝트에서 계층을 나누는 이유 (0) | 2025.07.13 |
DTO에서 Map 대신 HashMap을 쓰지 말아야 하는 이유 (5) | 2025.07.10 |
Spring에서 Jackson 사용 시 snake_case 매핑 문제 해결하기 (0) | 2025.07.07 |
Spring API에서 JSON 요청은 Map, DTO, JsonNode 중 무엇으로 받아야 할까? (1) | 2025.07.06 |