Service에서 현재 유저 Entity를 가져오는 경우가 굉장히 빈번하게 일어난다.
그럴 때마다 유저를 가져오는 메서드를 작성해서 쓰는데 서비스마다 하나씩 작성해서 중복되는 경향을 엄청 받았었다.
// 현재 로그인 유저 찾기
private Member getCurrentMember() {
Long currentUserId = jwtService.getCurrentUserId();
return memberRepository.findById(currentUserId).orElseThrow(
() -> new MemberException(MemberExceptionInfo.NOT_FOUND_MEMBER, currentUserId + "번 유저를 찾지 못했습니다."));
}
처음에는 이 메서드를 어떻게 묶어서 처리해야할지 난감했었다. DB 접근하는 로직이다 보니 분명 서비스에서 실행하는 것이 맞다고 생각해서 더 처리하는 방법에 애를 먹었는데 이번에 ArgumentResolver를 사용해서 해당 로직을 공통을 처리해 보았다.
ArgumentResolver
말 그대로 인자를 해결해 준다(?) 결정해 준다(?) 이런 뜻을 가졌다.
스프링 MVC 기준으로
1. 디스패쳐 서블릿 호출.
2. 핸들러 매핑으로 적절한 핸들러 탐색.
3. 핸들러 어댑터를 이용해 핸들러 호출하기 전에 ArgumentResolver를 사용해서 컨트롤러의 파라미터를 해석하고 주입.
즉 컨트롤러의 파라미터를 결정하고 제공하는 역할을 담당하게 된다.
내 로직의 경우 User를 컨트롤러 파라미터로 전달한다면 더 이상 Service에서 불필요한 공통 로직이 필요하지 않게 된다.
어노테이션을 통해 공통으로 처리하도록 먼저 CurrentUser 어노테이션을 만들어준다.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {
}
이 어노테이션을 잡을 리졸버를 설정해줘야 한다.
@Component
@RequiredArgsConstructor
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
private final JwtService jwtService;
private final MemberRepository memberRepository;
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(CurrentUser.class) != null;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Long currentUserId = jwtService.getCurrentUserId();
return memberRepository.findById(currentUserId).
orElseThrow(() -> new MemberException(MemberExceptionInfo.NOT_FOUND_MEMBER, currentUserId + "번 유저를 찾지 못했습니다."));
}
}
우선 HandlerMethodArgumentResolver를 구현해서 진행한다.
supportsParameter
해당 파라미터가 리졸버에서 지원되는지를 파악한다.
지원되면 true, 지원이 되지 않으면 false를 리턴한다.
내 경우에는 파라미터의 어노테이션을 꺼내서 CurrentUser 어노테이션이 있다면 true, 없다면 false를 리턴한다.
resolveArgument
간단하게 정리하면 해당 메서드에서 실제 필요한 로직을 구현하면 된다.
나의 경우에는 유저를 찾아오는 부분이 필요하기 때문에 여기에 전부 작성해 주었다.
그리고 리졸버를 하나 만들었으니 이 커스텀리졸버를 등록시켜줘야 한다.
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final CurrentUserArgumentResolver currentUserArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(currentUserArgumentResolver);
}
}
WebConfig를 하나 만들어서 리졸버를 추가시켜 준다.
그리고 필요한 컨트롤러에서 어노테이션과 함께 사용하면 리졸버가 정상적으로 동작을 수행한다.
// 메인화면 유저 정보 제공
@GetMapping("/main-info")
public ResponseEntity<ApiResponse<MemberInfoResponseDTO>> getMemberInfo(@CurrentUser Member member) {
MemberInfoResponseDTO userInfo = memberService.getUserInfo(member);
return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.createSuccess(userInfo, "현재 로그인 유저의 정보"));
}
여태 UserEntity를 조회하기 위해 매번 서비스에 공통 메서드를 만들어 사용했었는데 리졸버를 사용해서 더 공통으로 묶어서 처리할 수 있게 되었다.
근데 한 가지 걸리는 점이 컨트롤러 외부에서 DB 조회를 실행했다는 점이 걸린다. 보통 데이터베이스 접근은 서비스 레이어에서 처리하는 것이 좋다고 알고 있다.
일단은 공통으로 어노테이션을 사용해서 묶어두긴 했지만 문서화가 제대로 되어있지 않은 경우 어노테이션 사용을 지양하는 것이 좋다는 의견도 있었다. 추후에 다시 서비스 레이어로 돌려 사용하기도 할 것 같다.
'프로젝트 > RESTAPI 추천 서비스' 카테고리의 다른 글
certbot SSL 인증서 갱신하기 (1) | 2024.06.11 |
---|---|
서비스에서 남겨지는 log를 slack으로 보내보자. (0) | 2024.06.09 |
프로젝트 코드리뷰 (2) (0) | 2024.06.07 |
OFFSET 페이지네이션 쿼리 시간 단축과정 (1) | 2024.06.06 |