티스토리 뷰
이전 글에서 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로 체크할 수 없으니 참고하시길.
마치며
요청 객체 검증은 귀찮지만, 사방에서 날아오는 문의를 줄이기위해서는 붙여주는게 좋다.
'개발 > SPRING' 카테고리의 다른 글
SpringBoot에서 profiles group으로 서버 profile 정리하기 (0) | 2023.08.23 |
---|---|
POST 요청 처리 후 HttpServletRequest에서 RequestBody 가져오기 - RequestWrapper 추가하기 (0) | 2023.08.22 |
스프링부트 AOP를 이용해 로깅 처리하기 (0) | 2023.07.10 |
스프링 부트에서 초간단 멀티쓰레드 구현하기 : @Async (0) | 2023.06.04 |
스프링부트 서비스에 LOG 남기기 (with. Logback) (0) | 2023.05.28 |
- Total
- Today
- Yesterday
- elasticsearch
- java
- terraform
- AWS EC2
- Elastic cloud
- lambda
- GIT
- OpenAI
- ChatGPT
- 람다
- chat GPT
- JWT
- Kotlin
- S3
- Spring
- openAI API
- MySQL
- docker
- 인프런
- springboot
- OpenFeign
- awskrug
- AWS
- 스프링부트
- Log
- CloudFront
- AOP
- cache
- serverless
- EKS
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |