티스토리 뷰

 

 

이전 글에서 AOP로 로그를 처리하도록 구현하고 만족도가 너무 높아서 추가적으로 AOP로 간단히 처리할게 뭐가 있을까에 대해 고민했었다.

 

코드를 쭉 보다보니, 요청 객체를 검증하기 위해 사용하는 @Valid 어노테이션과 BindingResult가 눈에 들어왔다.

@GetMapping("...")
public ResultVo<?> validRequestTest(@Valid RequestObject requestObjectDto, BindingResult bindingResult) {
    if(bindingResult.hasErrors()) {
        throw new BadRequestException();
    }

    return exampleService.get(requestObjectDto);
}

요청 객체가 정상적이지 않을 때의 응답 처리를 위해서 매번 bindingReult의 에러를 체크하는 코드를 붙여줬어야 했다.

 

이 역시도 매번 작성하는게 귀찮다는 생각이 들어 AOP로 한 곳에서 처리하고 싶었다.

 

그 전에 응답 규격을 검증하는데 있어서, 왜 BindingResult를 사용할까? 에 대한 고민이 들어서 조금 알아 보았다.

 

@Valid와 bindingResult를 쓰는 이유

1. @Valid를 사용하면, 요청 객체 내부에서 어노테이션으로 검증할 수 있다.

간단히 예를 들어보면,

@Data
public class RequestObject {
    @Positive
    private int number;
    @NotBlank @NotNull
    private String string;
    @Email
    private String eMail;
}

아래와 같이 number는 양수만, String은 빈 값과 null이 아니게, email은 Email 형태인지 검증할 수 있게 된다.

 

2. 그러나 @Valid만 사용하면, 몇 가지 문제가 있다.

valid만으로도 요청 객체에 대한 검증이 가능하긴 하지만, 내가 생각하는 두 가지 큰 문제가 있다. 

2-1. 의도치 않은 500에러가 발생할 수 있다.

요청 객체가 잘 못 전달된 것에 대한 에러는 일반적으로 400에러를 사용한다.

 

그런데 internal server error인 500 에러가 발생하면 FE나 네이티브 쪽에서 잘못된 문의를 하러 올 가능성이 크다.

 

서버에 문제가 있는데요? 하고 와서 요청 로그를 까보면 그 친구들이 잘못 보낸 거였다.

 

이런 불필요한 공수를 줄여가야 한다.

 

2-2. 원하는 형식의 응답이 아닌 디폴트 형태로 응답이 나갈 수 있음

아래는, 스프링 부트에서 자체적으로 만들어 준 400에러에 대한 응답 규격이다.

{
  "timestamp": "2023-07-14T12:34:56Z",
  "code": 400,
  "error": "Bad Request",
  "path": "/api/example"
}

문제는 각 서비스마다 자체적으로 사용하는 응답 규격이 있을 것이기 때문에

 

위의 응답규격에 대한 대응 코드가 존재 하지 않는다면, 서비스 자체에 문제가 생길 가능성이 있다.

(제대로 된 응답을 받지 못했다 생각해 먹통이 되버리는 경우...가 대표적인 예이다)

 

때문에 BindingReult를 사용하고, BindingResult.hasError()를 이용해 응답을 핸들링한다.

 

하지만, 모든 요청에 대해 BindingResult의 분기를 만드는 건 너무나 비효율 적이다.

 

AOP를 사용해 처리해보자.

 

AOP로 BindingReult처리하기

생각보다 간단하다.

 

로그에서 했던 것과 마찬가지로, 스프링부트에서 AOP를 사용하기 위해 먼저 Configuration을 설정해줘야 한다.

(미리해놨다면 추가적으로 할 필요는 없음)

@Configuration
@EnableAspectJAutoProxy
public class AspectConfig {
}

간단하게 @EnableAspectJAutoProxy를 붙인 Aspect Configuration을 작성해준다.

 

여기서는 @Before를 통해 컨트롤러로 들어오기 전에, @Valid를 사용한 요청에 대해서만 핸들링하도록 AOP 코드를 구현한다.

 

@Before("execution(* com.polarishare..*Controller.*(.., @javax.validation.Valid (*), ..))")
public void validationAspect(JoinPoint joinPoint) {
    Object[] args = joinPoint.getArgs();

    for (Object arg : args) {
        if (arg instanceof BindingResult) {
            BindingResult bindingResult = (BindingResult) arg;
            if (bindingResult.hasErrors()) {
                throw new BadRequestException();
            }
        }
    }
}

BadRequestException은 서비스 응답 규격에 맞춘 400에러를 발생시키는 Custom Exception이다.

 

위와 같이 작성하고 사용한다면, 이제 아래와 같이 요청 객체마다 BindingResult.hasErrors()를 체크할 필요가 없다.

@GetMapping("...")
public ResultVo<?> validRequestTest(@Valid RequestObject requestObjectDto, BindingResult bindingResult) {
    return exampleService.get(requestObjectDto);
}

다만, AOP에서 BindingResult를 캐치하기 때문에 BindingReult는 꼭 붙어주어야한다.

 

다만 요청 객체 검증에 대해서 한 가지 추가적인 이슈가 있는데,

 

그건 파일에 대한 검증이다. 

 

MultipartFile은 @NotNull 어노테이션으로 검증이 불가능하기 때문에 isEmpty로 체크를 한번 더 해줘야한다.

 

때문에 BindingReult로 체크할 수 없으니 참고하시길.

 

마치며

요청 객체 검증은 귀찮지만, 사방에서 날아오는 문의를 줄이기위해서는 붙여주는게 좋다.

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함