기존 문제점
기존 포스트에서 작성한 서비스 로직에서는 두 가지 문제점이 있습니다:
- 서비스 로직 내에서 오류 처리를 수행한다: 이로 인해 코드가 복잡해지고 가독성이 떨어집니다.
- 오류 발생 시 상태 코드 200을 반환한다: 클라이언트는 오류가 발생했음에도 불구하고 정상 응답으로 인식할 수 있습니다.
이 문제를 해결하기 위해 ResponseEntity를 사용하여 상태 코드를 적절하게 설정하고, ExceptionHandler를 사용하여 예외 처리를 분리하는 방법을 적용해 보겠습니다.
Step 1: ResponseEntity를 활용한 상태 코드 설정
먼저, ResponseEntity를 사용하여 상태 코드를 반환하도록 변경하겠습니다. 이렇게 하면 오류 발생 시 적절한 상태 코드를 반환할 수 있습니다.
변경된 StudentValidationController 코드
package org.example.controller;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.example.model.Api;
import org.example.model.Student;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.stream.Collectors;
@RestController
@Slf4j
@RequestMapping("/student/info")
public class StudentValidationController {
@PostMapping("")
// 반환 값을 ResponseEntity
public ResponseEntity<Api<? extends Object>> studentInfo(
@Valid @RequestBody Api<Student> student,
BindingResult bindingResult
) {
if (bindingResult.hasErrors()) {
var errorList = bindingResult.getFieldErrors().stream()
.map(it -> {
var format = "%s : { %s } 은 %s";
var errorFormat = String.format(format, it.getField(), it.getRejectedValue(), it.getDefaultMessage());
return errorFormat;
}).collect(Collectors.toList());
var body = Api.Error.builder()
.errorList(errorList)
.build();
var response = Api.builder()
.statusCode(String.valueOf(HttpStatus.BAD_REQUEST.value()))
.message(HttpStatus.BAD_REQUEST.getReasonPhrase())
.error(body)
.build();
// 400 상태 코드로 설정하고 body에 데이터 보내기
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(response);
}
log.info("info : {}", student);
// 200 코드로 설정 후 body로 데이터 보내기
return ResponseEntity
.status(HttpStatus.OK)
.body(student);
}
}
이렇게 하면 오류 상태에서 데이터를 전송했을 때 200 대신 적절한 상태 코드(400)를 반환할 수 있습니다.
Step 2: ExceptionHandler를 활용한 예외 처리 분리
서비스 로직에서 오류 처리를 분리하기 위해 ExceptionHandler를 사용합니다. 이를 통해 예외 처리를 전담하는 클래스를 생성합니다.
ValidationExceptionHandler 클래스 생성
package org.example.exception;
import lombok.extern.slf4j.Slf4j;
import org.example.model.Api;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.stream.Collectors;
@Slf4j
@RestControllerAdvice // 예외처리 어노테이션
public class ValidationExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Api<? extends Object>> handleValidationExceptions(MethodArgumentNotValidException exception) {
var errorList = exception.getFieldErrors().stream()
.map(it -> {
var format = "%s : { %s } 은 %s";
var errorFormat = String.format(format, it.getField(), it.getRejectedValue(), it.getDefaultMessage());
return errorFormat;
}).collect(Collectors.toList());
var body = Api.Error.builder()
.errorList(errorList)
.build();
var response = Api.builder()
.statusCode(String.valueOf(HttpStatus.BAD_REQUEST.value()))
.message(HttpStatus.BAD_REQUEST.getReasonPhrase())
.error(body)
.build();
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(response);
}
}
이 클래스를 통해 MethodArgumentNotValidException이 발생할 때마다 지정된 메서드가 실행되어 예외를 처리합니다. 이를 통해 서비스 로직에서 예외 처리 코드가 제거되어 코드가 더 깔끔해집니다.
최종 코드
StudentValidationController.java
package org.example.controller;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.example.model.Api;
import org.example.model.Student;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
@RequestMapping("/student/info")
public class StudentValidationController {
@PostMapping("")
public Api<Student> studentInfo(
@Valid
@RequestBody
Api<Student> student) {
log.info("info : {}", student);
var body = student.getData();
var response = Api.<Student>builder()
.statusCode(String.valueOf(HttpStatus.OK.value()))
.message(HttpStatus.OK.getReasonPhrase())
.error(body)
.build();
.
return student;
}
}
ValidationExceptionHandler.java
package org.example.exception;
import lombok.extern.slf4j.Slf4j;
import org.example.model.Api;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.stream.Collectors;
@Slf4j
@RestControllerAdvice
public class ValidationExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Api<?>> handleValidationExceptions(MethodArgumentNotValidException exception) {
var errorList = exception.getFieldErrors().stream()
.map(it -> {
var format = "%s : { %s } 은 %s";
var errorFormat = String.format(format, it.getField(), it.getRejectedValue(), it.getDefaultMessage());
return errorFormat;
}).collect(Collectors.toList());
var body = Api.Error.builder()
.errorList(errorList)
.build();
var response = Api.builder()
.statusCode(String.valueOf(HttpStatus.BAD_REQUEST.value()))
.message(HttpStatus.BAD_REQUEST.getReasonPhrase())
.error(body)
.build();
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(response);
}
}
요약
이 글에서는 Spring Boot에서 유효성 검사와 예외 처리를 분리하여 API를 더 깔끔하고 유지보수하기 쉽게 만드는 방법을 설명했습니다. ResponseEntity를 사용하여 상태 코드를 설정하고, ExceptionHandler를 사용하여 예외 처리를 전담하는 클래스를 생성함으로써 코드의 가독성과 확장성을 높였습니다. 이를 통해 오류 발생 시 클라이언트에게 정확한 상태 코드와 메시지를 전달할 수 있게 되었습니다.
'Spring Boot' 카테고리의 다른 글
[Spring Boot] LoggerFilter만들기 (0) | 2024.07.22 |
---|---|
[Spring Boot] IoC/DI (0) | 2024.07.10 |
[Spring Boot] Validation 유효성 검사 클라이언트 오류 보내기 (0) | 2024.07.04 |
[Spring Boot] Validation 유효성 검사 (0) | 2024.07.04 |
[Spring Boot] Web에서 응답 만드는 방법 - Response Entity (0) | 2024.07.02 |