List 조회하는 API를 만들 때(볼 때) 되게 마음에 안들었던 부분이 있다. 정렬 조건, 검색 조건 을 String 리터럴로 사용하는 것이다!
@GetMapping("/test")
public List<FooDto> foo(@RequestParam String searchType, // 검색 조건
@RequestParam Sring sortCondition, // 정렬 조건
// ...){
// ...
}
저렇게 관리하니 좀 불편하더라.
일반적으로 List(Paging) 조회를 할 때, 정렬 조건은
와 같고
검색 조건은 (경우에 따라 다르지만) 내가 지금 진행중인 사이드 프로젝트에서는 다음과 같은 검색 조건을 가지고 있다.
일단, 첫번째 문제(검색/정렬 조건으로 어떤 리터럴이 들어 올 지 예측하기 힘들다) 부터 해결해보자.
이런 경우 Java에서 무엇을 사용해야 할 지는 바로 생각나는 것이 있었다. Enum!
@Getter
public enum SortCondition {
LIKES,
VIEWS,
RECENT
}
@GetMapping("/posts")
public List<PostListResponseDto> getAllPosts(
@RequestParam PostSearchType postSearchType,
@RequestParam(required = false) String searchString,
@RequestParam SortCondition postSortCondition
) {
return postReadService.getPostList(postSearchType, searchString, postSortCondition);
}
private final JPAQueryFactory jpaQueryFactory;
public OrderSpecifier<?>[] sortByVies(QPost post) {
return new OrderSpecifier<?>[] { post.viewCount.desc(), post.createdAt.desc() };
}
public OrderSpecifier<?>[] sortByLikes(QPost post) {
return new OrderSpecifier<?>[] { post.postLikeList.size().desc() , post.createdAt.desc()};
}
public OrderSpecifier<?>[] sortByRecent(QPost post) {
return new OrderSpecifier<?>[] { post.createdAt.desc() };
}
public OrderSpecifier<?>[] getBySortType(PostSortCondition postSortCondition, QPost post) {
if (postSortCondition == postSortCondition.RECENT){
return sortByRecent(post);
}
// if (...)
}
public List<Post> findPostListBySearchTypeAndSortCondition(PostSearchType postSearchType, String searchString, PostSortCondition postSortCondition){
return jpaQueryFactory.selectFrom(post)
.leftJoin(post.author)
// ...
.orderBy(getBySortType(postSortCondition, post))
.fetch();
}
두 번째 문제(검색 조건이 실제 사용되는 곳과 너무 멀다) 도 해결해보자.
public enum SortCondition {
LIKES() {
@Override
public OrderSpecifier<?>[] getSpecifier(QPost post) {
return new OrderSpecifier<?>[] { post.postLikeList.size().desc() , post.createdAt.desc()};
}
},
VIEWS() {
@Override
public OrderSpecifier<?>[] getSpecifier(QPost post) {
return new OrderSpecifier<?>[] { post.viewCount.desc(), post.createdAt.desc() };
}
},
RECENT() {
@Override
public OrderSpecifier<?>[] getSpecifier(QPost post) {
return new OrderSpecifier<?>[] { post.createdAt.desc() };
}
};
public abstract OrderSpecifier<?>[] getSpecifier(QPost post);
}
- Enum 클래스 안에 있는 추상매서드가 좀 신경쓰였다. Post가 아닌 다른 녀석들에게는 저 팩토리 매서드를 사용하게 할 수 없는걸까?
- 당연히 있다. 우리는 이미 알고 있는, Function 타입의 무언가! 바로 FunctionalInterface 를 직접 정의 후 사용해보자.
@FunctionalInterface
public interface SortCondition<T> {
OrderSpecifier<?>[] getSpecifier(T targetQEntity);
}
public enum PostSortCondition implements SortCondition<QPost> {
LIKES() {
@Override
public OrderSpecifier<?>[] getSpecifier(QPost qPost) {
return new OrderSpecifier<?>[] { qPost.postLikeList.size().desc() , qPost.createdAt.desc()};
}
},
VIEWS() {
@Override
public OrderSpecifier<?>[] getSpecifier(QPost qPost) {
return new OrderSpecifier<?>[] { qPost.viewCount.desc(), qPost.createdAt.desc() };
}
},
RECENT() {
@Override
public OrderSpecifier<?>[] getSpecifier(QPost qPost) {
return new OrderSpecifier<?>[] { qPost.createdAt.desc() };
}
};
}
@FunctionalInterface
public interface SortCondition<T extends EntityPathBase<?>> {
OrderSpecifier<?>[] getSpecifier(T targetQEntity);
}
public enum PostSortCondition implements SortCondition<QPost> {
LIKES() {
@Override
public OrderSpecifier<?>[] getSpecifier(QPost qPost) {
return new OrderSpecifier<?>[] { qPost.postLikeList.size().desc() , qPost.createdAt.desc()};
}
},
VIEWS() {
@Override
public OrderSpecifier<?>[] getSpecifier(QPost qPost) {
return new OrderSpecifier<?>[] { qPost.viewCount.desc(), qPost.createdAt.desc() };
}
},
RECENT() {
@Override
public OrderSpecifier<?>[] getSpecifier(QPost qPost) {
return new OrderSpecifier<?>[] { qPost.createdAt.desc() };
}
};
...
패키지 어따두었는고?
좀 맘에 안들긴 한다...특히 base 라는 이름. (이건 정말로 고치고싶다. 근디 내가 한 게 아니라...)
TMI : 사실 1와 3차는 없었다. 2차에서 4차로의 리팩토링 전후에 저런 과정이 있으면 더 읽기 쉬울 것 같아서 넣어봄