티스토리 뷰
처음 써보는 웹플럭스에 처음 써보는 리액티브 몽고+레디스를 사용해 마이그레이션을 시도할 때
가장 먼저 당황한 부분이 바로 이 부분이다.
MVC패턴에선 당연히, 아주 손쉽게 이루어졌던 <애너테이션을 이용한 유효성 검증>이
함수형 엔드포인트를 사용하는 웹플럭스에서는 작동하지 않았던 것.
물론 내 경험의 산물이라 100% 확신할 수는 없지만,
여태까지 내가 알기론 함수형 엔드포인트에서 기존의 방식대로 간편하게 유효성 검증을 하는 방법은 없다, 혹은
다른 라이브러리를 불러서 사용해야 한다.
아이 그럼 어쩌라고?
에 대해 헤매면서 얻은 답을 정리한다.
Validator
결론부터 말하자면, 웹플럭스에서는 검증이 필요한 클래스(DTO와 같은)마다 개별 검증 클래스를 구현해야 한다.
따라서 CRUD에 따라 검증 로직이 다르고 복잡하다면, 패키지 안에 유효성 검증 클래스만 몇 개씩 만들어야 한다.
내가 개발 중인 패키지를 예로 들면 아래와 같다.
사용자 패키지 안에 회원가입, 최초 로그인, 업데이트 등에 사용하는 검증 클래스가 따로 존재한다.
이 클래스는 전부 Validator라는 인터페이스를 구현해서 만드는데, 그 과정에서 validate() 메서드를 구현해야 하고
이를 핸들러 클래스에서 불러다 사용하는 것이다.
해서 위 클래스 중 가장 복잡한 아이를 import문을 포함해 적어보면 아래와 같이 생겼다.
package backend.domain.user.validator;
import static backend.domain.constant.Constant.*;
import java.util.regex.Pattern;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import backend.domain.user.dto.UserRequestDto;
import lombok.RequiredArgsConstructor;
@Component
@RequiredArgsConstructor
public class UserUpdateInfoValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return UserRequestDto.UpdateInfoRequest.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "nickname", "NICKNAME_IS_EMPTY", "Nickname Is Empty");
ValidationUtils.rejectIfEmpty(errors, "genderType", "GENDER_TYPE_IS_EMPTY", "Gender Type Is Empty");
ValidationUtils.rejectIfEmpty(errors, "ageType", "AGE_TYPE_IS_EMPTY", "Age Type Is Empty");
ValidationUtils.rejectIfEmpty(errors, "favoriteBeerCategoryList", "FAVORITE_BEER_CATEGORY_IS_EMPTY", "Favorite Beer Category Is Empty");
ValidationUtils.rejectIfEmpty(errors, "favoriteBeerTagList", "FAVORITE_BEER_TAG_IS_EMPTY", "Favorite Beer Tag Is Empty");
UserRequestDto.UpdateInfoRequest targetUserDto = (UserRequestDto.UpdateInfoRequest)target;
String nicknamePattern = "^[가-힣ㄱ-ㅎa-zA-Z0-9._-]{2,8}$";
if (!Pattern.matches(nicknamePattern, targetUserDto.getNickname())) {
errors.rejectValue("nickname", "NICKNAME_NOT_VALID", "닉네임은 2-8 자리의 숫자, 영어, 한국어와 언더스코어만 허용됩니다.");
}
if (!GENDER_TYPE_SET.contains(targetUserDto.getGenderType())) {
errors.rejectValue("genderType", "GENDER_TYPE_NOT_VALID", "Gender Type Is Not Valid");
}
if (!AGE_TYPE_SET.contains(targetUserDto.getAgeType())) {
errors.rejectValue("ageType", "AGE_TYPE_NOT_VALID", "Age Type Is Not Valid");
}
if (!BEER_CATEGORY_SET.containsAll(targetUserDto.getFavoriteBeerCategoryList())) {
errors.rejectValue("favoriteBeerCategoryList", "FAVORITE_BEER_CATEGORY_NOT_VALID", "Favorite Beer Category Is Not Valid");
}
if (targetUserDto.getFavoriteBeerCategoryList().size() > 2) {
errors.rejectValue("favoriteBeerCategoryList", "TOO_MANY_FAVORITE_BEER_CATEGORY", "선호 맥주 카테고리는 최대 두 개까지 선택할 수 있습니다.");
}
if (!BEER_TAG_SET.containsAll(targetUserDto.getFavoriteBeerTagList())) {
errors.rejectValue("favoriteBeerTagList", "FAVORITE_BEER_TAG_NOT_VALID", "Favorite Beer Tag Is Not Valid");
}
if (targetUserDto.getFavoriteBeerTagList().size() > 4) {
errors.rejectValue("favoriteBeerTagList", "TOO_MANY_FAVORITE_BEER_TAG", "선호 맥주 태그는 최대 네 개까지 선택할 수 있습니다.");
}
}
}
유효성 검증을 손으로 하나씩(...) 적어준 것을 확인할 수 있겠다.
글을 올리면서도 분명 이거보다 단순하면서도 우아한 방법이 어디엔가 있으리라 믿음이 생긴다.
하여간에 DTO 클래스에 애너테이션을 추가하는 것은 먹히지 않고, 위 클래스처럼 구현하면
일단은 유효성 검증이 가능해진다.
위에도 적었지만 이렇게 구현한 코드를 핸들러메서드에서 아래와 같이 불러온다.
private void validate(UserRequestDto.UpdateInfoRequest updateInfoRequest) {
Errors errors = new BeanPropertyBindingResult(updateInfoRequest, UserRequestDto.UpdateInfoRequest.class.getName());
userUpdateInfoValidator.validate(updateInfoRequest, errors);
if (errors.hasErrors()) {
throw new ServerWebInputException(errors.toString());
}
}
이렇게 적은 뒤에 핸들러메서드를 구현하면서 유효성 검증을 진행하면 된다.
그 글은 핸들러메서드를 구현하는 글에서 다시 보기로 하고,
이런 방법을 이용해서라도, 나처럼 헤매는 사람이 줄어들면 좋겠다...
Addition: Sinks.Many<User> usersSink = Sinks.many().replay().latest();
레퍼런스 코드에서만 보고 뭔 뜻인지도 모르고 사용하다가 이 글을 적는 김에 공부했다.
한 마디로 말하자면 구독자에게 최신 데이터만 전달하기 위한 메서드 체인이라고 한다.
구체적으로는
- Sinks - 다수의 객체를 다룰 수 있는 인터페이스
- many() - 다수의 이벤트를 위한 Hot Source(Sinkk.Many) 생성
- replay() - 새로운 구독자가 구독을 시작하면 Sink.Many가 이후의 이벤트를 전달할 수 있도록 이벤트 재생성
- latest() - 구독자에게 최신 값만을 전달, 이전 값은 무시
가 되며, 구독자는 구독 시점 이후에 발생하는 User 객체만을 수신하게 된다.
'Java+Spring > WebFlux' 카테고리의 다른 글
[WebFlux]자주 사용하는 연산자 정리(4) + (4) | 2023.06.04 |
---|---|
[WebFlux]자주 사용하는 연산자 정리(3), 그리고 (0) | 2023.05.21 |
[WebFlux]자주 사용하는 연산자 정리(2) - buffer (0) | 2023.05.21 |
[WebFlux]라우터, 함수형 엔드포인트 (2) | 2023.04.07 |
[WebFlux]현재 요청을 보낸, 로그인 사용자 정보 불러오기 (4) | 2023.04.06 |
[WebFlux]Reactive MongoDB - 쿼리, 정렬, 페이지네이션 (2) | 2023.04.05 |
- Total
- Today
- Yesterday
- 지지
- 여행
- 야경
- 자바
- Python
- 면접 준비
- Backjoon
- 파이썬
- 중남미
- spring
- 세계여행
- java
- a6000
- 유럽
- 기술면접
- 백준
- 유럽여행
- Algorithm
- 리스트
- 맛집
- RX100M5
- 세모
- 동적계획법
- 알고리즘
- 세계일주
- 스트림
- 남미
- 스프링
- BOJ
- 칼이사
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |