배경
저희 비즈니스 로직을 살펴보면, 교수님에 대한 평점을 CRUD 할 수 있는 요구사항이 있습니다.
(Delete 기능은 없지만,,,일단.. 예...)
계정 당 최초 1회에 한해 교수님들에 대한 평점을 1점~5점 이내로 등록할 수 있는데요.
쉽게 바꿔 말하자면 각 교수님들에 대한 평점 등록을 한 번 밖에 못한다는 뜻입니다.
또한 평점을 확인하러 특정 교수님의 페이지에 접속한다면, (당연히) 교수님의 평균 평점을 확인할 수 있습니다.
이런 요구사항을 종합해본다면,
- 한 계정 당 특정 교수님의 평점은 한번만 등록할 수 있다.
- 특정 교수 페이지에서 각 사용자들이 평가한 평균 평점을 확인할 수 있다.
- 사용자는 기존에 등록한 평점을 수정할 수 있다.
정도입니다.
사실 이런 요구사항을 선정하는데까지 여러 논의가 있었는데요.
논의 1. 글을 쓸 때마다 평점을 함께 등록하도록 할지?
서비스 취지에 맞게 각 피드들은 모두 교수님들에 대한 리뷰와 개인적인 생각을 나누는 피드가 대부분일 것입니다.
이를 한번에 대변할 수 있는 평점을 각 피드를 작성할 때마다 등록하는 것이 어떻겠냐라는 의견이 먼저 나왔었습니다.
물론 이렇게 한다면, 평점 도메인의 활성화 자체는 성공적일 것 같고, 로직도 많이 단순화 될 것 같았습니다.
하지만 결론적으론 이 방법을 선택하지 않았는데요.
"평점 자체의 신뢰성이 떨어질 것 같다" 가 그 이유였습니다.
사실 케이스를 생각해보면, 이번 피드에서는 교수님 평점은 1점 주고, 다음 피드에서는 교수님 평점 5점 주는 케이스가 비일비재할 것 같았습니다. 이러면 자연스럽게 평점 자체의 신뢰도는 떨어지겠죠. 조작도 쉬울 것 같구요..!
또한 매번 피드를 쓸 때마다 평점을 입력해야하는 것도 번거로울 것 같다고 판단했습니다.
이런 맥락을 고려해봤을 때, 평점은 최초 1회만 가능하도록 요구사항을 결정했습니다.
단, 생각은 언제든 바뀔 수 있으니 평점 수정이라는 기능은 추가하도록 했습니다.
논의 2. 교수 평점 항목을 세분화 할지?
정말 많이 고민했던 내용입니다.
교수 리뷰를 메인으로 다루는 서비스니깐, 리뷰에 대한 항목이 세부적이어야하는게 자연스러운 흐름이기 때문입니다.
실제로 초창기 서비스 기획을 할때는, 교수 리뷰 지표를 5가지 정도로 나누어 시각화하는 것도 생각했습니다. 게임 캐릭터 능력치처럼요..!
다만 회의가 진행되면서 교수님에 대한 학생들의 평가가 직관적이어야한다는 의견에 무게가 실렸습니다.
여러 지표를 사용하는 것도 좋지만, 딱 교수님 페이지에 갔을 때 학생들의 평가가 어떤지 보이는 것이 해당 교수님을 모르는 사용자가 볼 때 작관적이라 판단했기 때문입니다.
물론 평가지표를 세분화하여 보여주는 방법 또한 서비스 발전에 있어 필요한 항목이니,이는 향후 서비스 고도화 시 추가하고자 합니다.
평균 평점 구현
무튼..위와 같은 히스토리를 거쳐, 저는 각 교수의 평균 평점을 계산해야만 했습니다.
근데 Grade 테이블에서 사용자들이 해당 교수에게 평가한 평점을 필터링하고 이걸 서비스 레이어에서 하나하나 평균 낼 생각을 하니 벌써 부터 머리가 아팠습니다. 그리고 그다지 효율적인 방법도 아니라고 생각했구요.
일단 grade 테이블부터 간단히 살펴보겠습니다.
칼럼은 각각 id(PK), 생성날짜, 수정날짜, gradePoint(평점),member_id(FK=평점 등록한 사용자Id),prof_id(FK=평점 등록된 교수id) 로 구성되어 있습니다.
테이블 구조 자체는 그리 어렵지 않습니다. 각 교수의 평균 평점을 구하려면 그냥 평점 등록된 교수 id를 파라미터로 받아서 JPA로 해당 교수에 대해 평가된 평점 리스트만 받아오면 됩니다. (그리고 이걸 하나하나 값을 가져와서 평균 내면 됨;;;)
이걸 서비스 레이어에서 하나하나 값을 받아와서 평균 내자니 평점 값이 점점 많아질수록 로직 실행 시간이 길어질 것이고, JPA에서 해결하자니 마땅한 명령어가 없었습니다.
그렇기에 해결책을 찾은 곳이 바로 queryDSL을 이용하자는 것이었습니다.
@RequiredArgsConstructor
public class GradeRepositoryCustomImpl implements GradeRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public List<GradeGetDto> professorAverageGradepoint(Long profId) {
return queryFactory
.select(new QGradeGetDto(grade.gradepoint.avg()))
.from(grade)
.where(grade.professor.profId.eq(profId))
.fetch();
}
}
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class GradeGetDto {
private Double averageGradepoint;
@QueryProjection
public GradeGetDto(Double averageGradepoint) {
this.averageGradepoint = averageGradepoint;
}
@Override
public String toString() {
return String.valueOf(averageGradepoint);
}
}
저는 queryDSL에서 생성자 주입을 활용했습니다.
이 로직을 위한 DTO를 별도로 만들어서 @QueryProjection을 사용하는 방법인데요. 저는 원래도 항상 DTO의 책임을 분리하기 위해 각 로직별로 다 다른 DTO를 만들어 사용하는 편입니다. 그렇기에 이번 방법 또한 DTO를 따로 만들어서 사용했습니다.
쿼리DSL 코드 자체 또한 복잡하지 않습니다.
기존 서비스 레이어에서 하나하나 해야할 일을 레포지토리 레이어에서 수행하도록 했을 뿐입니다.
.select(new QGradeGetDto(grade.gradepoint.avg()))
그럼에도 제가 쿼리DSL을 선택한 이유는 위 코드 때문입니다.
테이블에서 필터링된 값의 평균을 계산할 수 있는 메서드가 있기 때문에 보다 편리하게 개발할 수 있기 때문입니다.
이를 통해 매번 교수 평균 평점을 계산하지 않고 위 메서드만 부르면 되는 편리성을 확보했습니다.
뭔가 블로그에 한 번 정리하기 좋은 주제인 것 같아서 적어봤는데, 생각보다 쉽게 끝난 기분이네요..
짜잔~쉽죠? 이런 느낌이라... 점차 수정해가거나 지금 생각나지 않은 무언가가 떠오른다면 추가하러 오겠습니다ㅎㅎ
'Project > GASIP_대학 커뮤니티' 카테고리의 다른 글
[Gasip] Redis & Sync Schedule 을 적용해 조회수 동시성 처리 (2) | 2024.04.12 |
---|---|
[Gasip] 게시글 조회수 중복 방지 처리 로직 구현 (2) | 2024.04.01 |
[Gasip Project] JPA / could not initialize proxy - no Session. (2) | 2023.11.07 |
[Gasip Project] 왜 Column Name이 중복이라고 뜨죠? _ DuplicateMappingException (2) | 2023.11.05 |