[위니아에이드] 팀 프로젝트 - AOP를 활용한 Validation 체크
회원가입을 할 때 올바르지 않은 정보들로 회원가입을 진행하면 안 됩니다.
아이디의 정해진 길이가 있을 것이고
비밀번호도 정해진 규칙과 길이가 있을 것입니다.
이런 데이터들을 전부 검증을 해주고 모든 검증에 통과가 되었다면 회원가입을 진행시켜도 되고
검증에 통과를 못 했다면 예외를 던져주어 사용자에게 알려주면 됩니다.
이러한 로직은 회원가입 이라는 기능에서 주요 로직이라고 볼 수 없기에 AOP를 통해 분리시켜주면 좋을 것입니다.
AOP(Aspect Oriented Programming)는 관점 지향 프로그래밍입니다.
AOP를 사용하는 이유는 비즈니스 로직과 공통 기능으로 구분을 하고,
공통 기능은 필요한 시점에 불러와서 적용하는 프로그래밍 방법입니다.
@ValidationCheck
@PostMapping("signup")
public ResponseEntity<?> signupUser(@RequestBody @Valid SignupRequestDto signupRequestDto, BindingResult bindingResult) {
boolean status = false;
try {
status = authService.signup(signupRequestDto);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.ok(new CustomResponseDto<>(1, "Member registration failed", status));
}
return ResponseEntity.ok(new CustomResponseDto<>(1, "Member registration successful", status));
}
@Data
public class SignupRequestDto {
@NotBlank
private String userName;
@NotBlank
@Pattern(regexp = "^[\\w\\d]{4,10}$", message = "4자에서 10자 이하로 입력해주세요.")
private String userId;
@NotBlank
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^+=])[A-Za-z0-9!@#$%^+=]{8,10}$", message = "※영문+숫자+특수문자의 8~10자 이내 ※ 패스워드에 사용 가능한 특수문자 : ! @ # $ % ^ + =")
private String userPassword;
@NotBlank
@Pattern(regexp = "^[A-Za-z0-9]+@[A-Za-z0-9]+.com$", message = "이메일 형식을 지켜주세요.")
private String userEmail;
private int userGender;
@NotBlank
private String userBirth;
private int birthType;
@NotBlank
private String postalCode;
@NotBlank
private String mainAddress;
@NotBlank
private String detailAddress;
@NotBlank
private String mainPhoneNumber;
private String subPhoneNumber;
private boolean emailReceiveFlag;
private boolean mailReceiveFlag;
private boolean smsReceiveFlag;
private String staffCompany;
private String employeeNumber;
@AssertTrue(message = "아이디 중복확인이 되지 않았습니다.")
private boolean checkUserIdFlag;
}
}
컨트롤러 입니다. @Valid를 붙여서 Validation 검사를 합니다.
BindingResult는 검증 오류가 발생할 경우 오류 내용을 보관하는 Spring Framework에서 제공하는 Error와 관련된 interface입니다.
@Retention(RUNTIME)
@Target({ TYPE, METHOD })
public @interface ValidationCheck {}
@Target({TYPE, METHOD}): 해당 어노테이션을 타입과 메소드에 선언 가능합니다.
@Retention(RUNTIME): 해당 어노테이션이 유지되는 시간으로써 컴파일 이후에도 JVM 에 의해서 계속 참조가 가능합니다.
@Aspect
@Component
@Slf4j
public class ValidationAop {
@Pointcut("@annotation(com.project.winiaaid.handler.aop.annotation.ValidationCheck)")
private void enableValidationCheck(){}
// PointCut으로 지정해준 어노테이션이 붙은 로직은 밑의 로직을 거치게 됩니다.
@Before("enableValidationCheck()")
public void validationCheck(JoinPoint joinPoint) {
log.info(">>>>>> 유효성 검사중...");
Object[] args = joinPoint.getArgs(); // 메소드의 매개변수를 Object배열로 가지고 옵니다.
for(Object arg : args) {
if(arg.getClass() == BeanPropertyBindingResult.class) {
BindingResult bindingResult = (BindingResult) arg;
if(bindingResult.hasErrors()) { // Validation 검사 중 오류가 있다면
Map<String, Object> errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach(error -> {
errorMap.put(error.getField(), error.getDefaultMessage());
});
throw new CustomValidationApiException("유효성 검사 실패", errorMap);
}
}
}
}
// 정상적으로 로직이 수행되었을 때
@AfterReturning(value = "enableValidationCheck()", returning = "returnObject")
public void validationCheckAfter(JoinPoint joinPoint, Object returnObject) {
log.info(">>>>>> 유효성 검사 완료: {}", returnObject);
}
}
signupUser() 메소드가 실행되기 전에 Validation 체크를 먼저 하고 오류가 있다면 예외를 던져서 처리를 해주고
오류가 없다면 정상적으로 메소드가 진행시켜줍니다.
컨트롤러에서 if문을 통해 Validation 체크를 해주어도 되지만 그렇게 한다면 코드가 복잡해지고 가독성이 낮아집니다.
그래서 Spring에서 제공해주는 AOP를 사용해 주요 로직이 아닌 것은 따로 분리를 시켜 주요 로직에 집중할 수 있게 해 줄 수 있습니다.
AOP를 통해 주요 로직이 아닌 Validation 체크와 같은 로직들은 분리시켜서 가독성을 높이면 좋을 것 같습니다.