
public List<ResponseProjectListDto> selectProjectDsl(SelectProjectListRequestDto dto) {
return queryFactory
.selectDistinct(Projections.constructor(ResponseProjectListDto.class,
projectEntity.기본 정보들,
// completeRate
completeRate(projectEntity),
// like count
likeCount(projectEntity),
// user_count
fundingUserCount(projectEntity),
// is_like
isLike(projectEntity, dto.getUserId()),
// view_count
viewCount(projectEntity),
// total _ select count :: totalCount
totalCount(projectEntity, dto)
))
.from(projectEntity)
.leftJoin(생략)
.where(
where(projectEntity, projectTagEntity, dto.getUserId(), dto.getTagIds(), dto.getSearch(), dto.getMyPageWhere())
)
.orderBy(
orderByType(projectEntity, dto.getUserId(), ProjectOrderType.getType(dto.getOrder()), dto.getDesc()),
projectEntity.createdAt.desc()
)
.limit(pageCount)
.offset((dto.getPage() - 1) * pageCount)
.fetch();
}
조건에 따른 where 절 작성
tag(List tadIds)를 통한 tag 조건
search(String)를 통한 제목 검색
order Type에 따른 order 절 작성
COMPLETION, FUNDING_COUNT, CREATED, EXPIRED, VIEW_COUNT
project list 조회 api를 사용하는 service가 더 필요해졌음 이에 따른 기능 확장 필요
main page : tag, search 별 project list 조회
my page : 내가 펀딩한, 내가 좋아요한, 내가 최근 조회한 project list 조회
2개 이상의 service에서 조건에 맞는 Project List api를 만들어야 할 필요가 생겼다.
간단하게 구현한다면 그냥 한개의 함수에서 모든 기능에 필요한 paramter(Type)을 받고 엄청나게 많은 분기점을 잡으면 된다.
//모든 기능에서 사용되는 모든 Type값들을 Parameter로 받고
public List<ResponseProjectListDto> selectProjectDsl(
Long userId, List<Long> tagIds, String search,
MyPageType type, OrderByType orderType, Long limit, Long pageCount
);
// where절, order 절에 모든 type들의 분기점을 잡으면 된다
// (order by는 where절도 필요하니 where절의 약 2배 정도 된다)
BooleanExpression where(List<Long>tagIds, String search, MyPageType type) {
if(...)
if(...)
if(...)
...
}
결론 : 유지보수, 확장 불가능 / 다른 table(project view, like table)과도 추가 확장이 필요한 만큼 위의 방법만큼은 용납할 수 없다.
query dsl을 사용방법을 보며 한 가지의 방식을 떠올렸다
queryFactory
.select(...)
.where(...)
.fetch()
query dsl처럼 where절과 order by절을 람다로 구현 할 수 있다면?
service부분에서 필요한 project들의 where절과 oreder 절을 구현하고 ProjectSelectRepo로 넘겨줄 수 있다면?
구현 목표 (다른 Service별 사용 방법 예시)
List<ProjectListDto> list = ProjectSelectListRepos
.select()
.where((QEntity)->{}) // QProjectEntity 외 다른 QEntity까지
.oderBy((QEntity)->{}) // default project.createdAt desc
.limit()
.offset()
.commit()
여러개의 service로 확장한다 하더라도 각 service에 맞는 where, order by절만 구현하면 됨으로 중복 코드가 발생하지 않는다.
그렇다면 위의 목표대로 where, order by절을 외부에서 주입 받는게 가능한까?
일단 구현에 필요한 FunctionalInterface부터 만든다
@FunctionalInterface
public interface FunctionInterface<RT, T extends EntityPathBase> {
RT function(T entity);
}
// RT는 return type의 약자, T는 QEntity.class들 중 한개이다
// where, order by절에 필요한 QEntity(QProjectEntity, QProjectTagEntity, ...) 중 한개이다
이런 FunctionalInterface를 Service에서 구현하고 넘겨 받으면 목표를 구현할 수 있을 것이다
// 받는 부분
public void where(Class<T> entityClass, FunctionalInterface whereFunction) {
BooleanExpression where = whereFunction.function(projectTagEntity);
}
// service(주는 부분), where절 구현
.where(QProjectTagEntity.class, new FunctionalInterfacr<BooleanExpression, QProjectTagEntity.class> {
@Override
where(QProjectTagEntity tag) {return tag.id.in(tagIds);}
})
하지만 QEntity값은 첫번 째 파라미터 Class entityClass로 충분히 추정 가능하기 때문에 Service에서 FunctionInterface를 직접 선언할 필요가 없다
// 받는 부분
public void where(Class<T> entityClass, FunctionalInterface<BooleanExpression, T>whereFunction) {
BooleanExpression where = whereFunction.function(projectTagEntity);
}
// service(주는 부분), where절 구현
.where(QProjectTagEntity.class, (projectTag)->tag.id.in(tagIds));
where, order 절을 query dsl 함수에서 바로 받으면 한 번만 받을 수 있다
여러개의 where, order by절을 받기 위해 builder class를 추가한다
public class Builder {
private BooleanExpression where = Expressions.TRUE;
private List<OrderSpecifier> orderBy = new ArrayList<>();
private long limit;
private long page;
private <T extends EntityPathBase> T getEntity(Class<T> entityClass) {
if(entityClass.equals(QProjectTagEntity.class))
return (T) projectTagEntity;
if(entityClass.equals(QProjectViewEntity.class))
return (T) projectViewEntity;
if(entityClass.equals(QLikeEntity.class))
return (T) likeEntity;
else
return (T) projectEntity;
}
}
이제 Builder class에 위에 구현한 where, order by함수를 추가한다
public class Builder {
public <T extends EntityPathBase> Builder where(Class<T> entityClass, FunctionInterface<BooleanExpression, T> whereFunction) {
where = where.and(whereFunction.function(getEntity(entityClass))); return this;
}
public <T extends EntityPathBase> Builder orderBy(Class<T> entityClass, FunctionInterface<OrderSpecifier, T> whereFunction) {
orderBy.add(whereFunction.function(getEntity(entityClass))); return this;
}
}
이제 builder class를 통해서 where, order 절을 받고 각각 where, orderBy 변수에 추가하면 된다
builder class와 service부분 사용한 실제 사용 예시이다
public class Builder {
private BooleanExpression where = Expressions.TRUE;
private List<OrderSpecifier> orderBy = new ArrayList<>();
private long limit;
private long page;
public <T extends EntityPathBase> Builder where(Class<T> entityClass, FunctionInterface<BooleanExpression, T> whereFunction) {
where = where.and(whereFunction.function(getEntity(entityClass))); return this;
}
public <T extends EntityPathBase> Builder orderBy(Class<T> entityClass, FunctionInterface<OrderSpecifier, T> whereFunction) {
orderBy.add(whereFunction.function(getEntity(entityClass))); return this;
}
public Builder limit(long limit, long page) {
this.limit = limit; this.page = page; return this;
}
public ResponseProjectListDto commit(Long userId){
orderBy.add(projectEntity.createdAt.desc());
List<ResponseProjectListDetailDto> projectList = selectProjectsWithSingle(userId, where, orderBy.toArray(OrderSpecifier[]::new), limit, page * limit);
projectList = setTag(projectList);
return new ResponseProjectListDto(projectList, totalCount(where), limit, page);
}
private <T extends EntityPathBase> T getEntity(Class<T> entityClass) {
if(entityClass.equals(QProjectTagEntity.class))
return (T) projectTagEntity;
if(entityClass.equals(QProjectViewEntity.class))
return (T) projectViewEntity;
if(entityClass.equals(QLikeEntity.class))
return (T) likeEntity;
else
return (T) projectEntity;
}
}
실 사용 예시
public List<ResponseProjectDto> selectProjectDsl(RequestMainPageProjectListDto dto) {
List<ResponseProjectDto> projectList = selectProjectRepository
.builder()
.where(QProjectEntity.class, (project)->whereSearch(project, dto.getSearch()))
.where(QProjectEntity.class, (project)->whereTag(project, dto.getTagIds()))
.orderBy(QProjectEntity.class, (project)->orderByType(project, ProjectOrderType.getType(dto.getOrder()), dto.getDesc()))
.limit(dto.getLimit(), dto.getPage())
.commit(dto.getUserId());
return projectList;
}
이제 다른 service에서 확장을 해도, 이전 기능들에 다른 where조건을 추가할 때도 언제나 Service부분에서 충분히 처리가 가능해졌다. (목표 달성!)
각각의 service에서 충분히 사용가능할 정도까지 refactoring이 진행되었다. 하지만 그만큼 project dsl repository의 중요성이 올라가게되었다. main query가 최적화 되지 않는다면 모든 service에서의 query또한 느려진다.
언제나 다른 사람들의 의견을 기다리고 있습니다!