T_era
Validation과 예외 처리: 함께 하지 않아야 하는가? 본문
일부에서 스프링 개발 시 Validation과 예외 처리를 함께 하지 말라는 주장이 제기된다. 이는 Validation 로직과 예외 처리 로직의 관심사 분리를 강조하는 것으로 이해해야 한다.
1. 관심사의 분리 원칙
- Validation: 입력 데이터의 유효성을 검증하는 고유의 역할 수행. 데이터 형식, 길이, 필수 여부 등을 확인한다.
- 예외 처리: 애플리케이션 실행 중 발생하는 예외 상황에 대한 대응 및 사용자 피드백 제공을 담당한다.
각각의 책임 영역을 분리함으로써 코드의 명확성 및 유지보수성을 향상시킬 수 있다. 또한, Validation 로직의 재사용성을 높이는 이점이 존재한다.
2. "함께 하지 말라"는 주장의 의미
Validation 실패 시 발생하는 예외를 애플리케이션 로직 내에서 직접 처리하는 방식을 지양해야 한다는 의미로 해석할 수 있다. 컨트롤러 내에서 @Valid 및 BindingResult를 활용하여 직접 에러 응답을 구성하는 방식은 바람직하지 않다.
3. 권장되는 처리 방식
- @Valid와 BindingResult 활용: 컨트롤러에서 Validation 수행 결과를 확인한다.
- MethodArgumentNotValidException 처리: Validation 실패 시 스프링이 던지는 예외를 @ExceptionHandler를 통해 전역적으로 처리한다.
- Custom 예외 활용: 특정 Validation 실패에 대한 custom 예외를 정의하고 예외 처리기에서 관리한다.
나쁜 예: 컨트롤러에서 Validation 실패를 직접 처리
@RestController
public class UserController {
@PostMapping("/users")
public ResponseEntity<?> createUser(@Valid @RequestBody UserDto userDto, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// Validation 실패 시 직접 에러 메시지 생성 및 응답
List<String> errors = bindingResult.getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.toList());
return ResponseEntity.badRequest().body(new ErrorResponse("Validation failed", errors));
}
// 사용자 생성 로직
User newUser = new User(userDto.getName(), userDto.getEmail());
// ... 저장 로직 ...
return ResponseEntity.status(HttpStatus.CREATED).body(newUser);
}
}
문제점:
- Validation 실패 처리 로직이 컨트롤러 메소드 내에 직접 포함되어 코드의 가독성을 저하시킨다.
- Validation 실패 응답 형식이 여러 컨트롤러에서 다르게 구현될 가능성이 있어 일관성이 떨어진다.
- Validation 로직 변경 시 여러 컨트롤러를 수정해야 할 수 있어 유지보수성이 낮아진다.
좋은 예: @ExceptionHandler를 이용한 전역 예외 처리
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.toList());
ErrorResponse errorResponse = new ErrorResponse("Validation failed", errors);
return ResponseEntity.badRequest().body(errorResponse);
}
}
@RestController
public class UserController {
@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody UserDto userDto) {
// Validation 실패 시 MethodArgumentNotValidException 발생
User newUser = new User(userDto.getName(), userDto.getEmail());
// ... 저장 로직 ...
return ResponseEntity.status(HttpStatus.CREATED).body(newUser);
}
}
스프링 Validation 실패 시 MethodArgumentNotValidException 처리 메커니즘
스프링 프레임워크에서 Validation 실패 시 자동으로 발생하는 MethodArgumentNotValidException과 이를 처리하기 위한 @ExceptionHandler의 역할을 명확히 이해하는 것은 중요하다.
MethodArgumentNotValidException의 발생
@Valid 어노테이션이 적용된 객체에 대한 Validation이 실패하면, 스프링은 별도의 코드 구현 없이 내장된 예외 클래스인 MethodArgumentNotValidException을 자동으로 발생시킨다. 이는 스프링 프레임워크의 핵심적인 동작 방식이다.
@ExceptionHandler를 통한 후처리
@ExceptionHandler 어노테이션은 특정 예외가 발생했을 때 이를 처리하기 위한 메소드를 지정하는 데 사용된다. @ExceptionHandler(MethodArgumentNotValidException.class)를 적용한 메소드는 스프링이 던진 MethodArgumentNotValidException을 포착하여 개발자가 정의한 로직을 수행한다. 이러한 후처리를 통해 다음과 같은 작업을 수행할 수 있다.
- 응답 형태 정의: Validation 실패에 대한 응답 구조 및 형식을 결정한다.
- 에러 메시지 가공: BindingResult에 포함된 상세 에러 정보를 활용하여 사용자에게 적절한 에러 메시지를 생성한다.
- 로깅: Validation 실패 관련 정보를 기록하여 문제 발생 시 추적 및 분석을 용이하게 한다.
- 추가적인 로직 수행: 특정 Validation 실패 조건에 따라 필요한 추가적인 비즈니스 로직을 구현할 수 있다.
결론
Validation 로직과 예외 처리 로직은 분리하여 관리하는 것이 효율적이다. Validation 실패로 인해 발생하는 예외는 적절한 예외 처리 메커니즘을 통해 처리해야 한다. "함께 하지 말라"는 주장은 Validation 로직과 비즈니스 로직의 혼합 또는 세부적인 애플리케이션 로직 내에서의 직접적인 예외 처리를 지양하라는 의미로 받아들여야 한다. MethodArgumentNotValidException은 스프링 라이브러리에 의해 자동으로 발생하는 예외이며, 개발자는 @ExceptionHandler를 활용하여 이 예외를 효과적으로 처리하고, 애플리케이션의 요구사항에 맞는 응답 및 후속 조치를 구현할 수 있다. 이는 Validation 실패 처리를 중앙 집중적으로 관리하고, 시스템의 안정성과 유지보수성을 향상시키는 데 기여한다.
'Programing > Spring' 카테고리의 다른 글
| Token 사용 이유 및 작동 방식 (0) | 2025.05.15 |
|---|---|
| Cookie와 Session (0) | 2025.05.15 |
| @Transactional에 대하여 (0) | 2025.05.13 |
| JdbcTemplate.query에서 sql의 컬럼을 바인딩할 수 없는 이유 (0) | 2025.05.13 |
| @Valid와 BindingResult의 관계 및 처리 방법 (0) | 2025.05.13 |