-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
로드맵 서비스 로직 가독성 개선 #1613
로드맵 서비스 로직 가독성 개선 #1613
Changes from all commits
dc4fae9
11e87dc
bc41ccb
5faa0fb
746ee8c
4eac034
c730607
d94a493
e9cc339
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,122 +1,47 @@ | ||
package wooteco.prolog.roadmap.application; | ||
|
||
import java.util.Comparator; | ||
import java.util.HashSet; | ||
import java.util.stream.Collectors; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
import wooteco.prolog.common.exception.BadRequestException; | ||
import wooteco.prolog.roadmap.application.dto.KeywordResponse; | ||
import wooteco.prolog.roadmap.application.dto.KeywordsResponse; | ||
import wooteco.prolog.roadmap.application.dto.RecommendedPostResponse; | ||
import wooteco.prolog.roadmap.domain.Curriculum; | ||
import wooteco.prolog.roadmap.domain.EssayAnswer; | ||
import wooteco.prolog.roadmap.domain.Keyword; | ||
import wooteco.prolog.roadmap.domain.Quiz; | ||
import wooteco.prolog.roadmap.domain.repository.CurriculumRepository; | ||
import wooteco.prolog.roadmap.domain.repository.EssayAnswerRepository; | ||
import wooteco.prolog.roadmap.domain.repository.KeywordRepository; | ||
import wooteco.prolog.roadmap.domain.repository.QuizRepository; | ||
import wooteco.prolog.session.domain.Session; | ||
import wooteco.prolog.session.domain.repository.SessionRepository; | ||
import wooteco.prolog.roadmap.domain.repository.dto.KeywordIdAndAnsweredQuizCount; | ||
import wooteco.prolog.roadmap.domain.repository.dto.KeywordIdAndTotalQuizCount; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
import static java.util.stream.Collectors.groupingBy; | ||
import static java.util.stream.Collectors.toList; | ||
import static java.util.stream.Collectors.toSet; | ||
import static wooteco.prolog.common.exception.BadRequestCode.CURRICULUM_NOT_FOUND_EXCEPTION; | ||
import static java.util.stream.Collectors.toMap; | ||
|
||
@RequiredArgsConstructor | ||
@Transactional | ||
@Transactional(readOnly = true) | ||
@Service | ||
public class RoadMapService { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 조회만 담당하는 서비스라면 이름에 Query를 붙여도 좋을 것 같네요... 개인적인 의견입니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 로드맵 서비스는 하나밖에 없고, 분리의 형태가 아니어도 커맨드, 쿼리로 나누는 것이 좋나요??
다른 서비스들 중에 커맨드, 쿼리로 나뉘어진 서비스가 없어서 6기가 봤을 때 더 혼란만 가중할 수도 있을 것 같구요! 추가적으로 어드민 api 관련으로 정리하고 나면 키워드 서비스로 이동할 것 같아요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 말씀을 듣고 보니 지금 로드맵 서비스 수준에서 고민할 문제는 아닌 것 같네요...! |
||
|
||
private final CurriculumRepository curriculumRepository; | ||
private final SessionRepository sessionRepository; | ||
private final KeywordRepository keywordRepository; | ||
private final QuizRepository quizRepository; | ||
private final EssayAnswerRepository essayAnswerRepository; | ||
|
||
@Transactional(readOnly = true) | ||
public KeywordsResponse findAllKeywordsWithProgress(final Long curriculumId, final Long memberId) { | ||
final Curriculum curriculum = curriculumRepository.findById(curriculumId) | ||
.orElseThrow(() -> new BadRequestException(CURRICULUM_NOT_FOUND_EXCEPTION)); | ||
final List<Keyword> keywords = keywordRepository.findAllByCurriculumId(curriculumId); | ||
final Map<Long, Integer> totalQuizCounts = getTotalQuizCounts(); | ||
final Map<Long, Integer> answeredQuizCounts = getAnsweredQuizCounts(memberId); | ||
|
||
final List<Keyword> keywordsInCurriculum = getKeywordsInCurriculum(curriculum); | ||
|
||
final Map<Keyword, Set<Quiz>> quizzesInKeywords = quizRepository.findAll().stream() | ||
.collect(groupingBy(Quiz::getKeyword, toSet())); | ||
|
||
return createResponsesWithProgress(keywordsInCurriculum, quizzesInKeywords, getDoneQuizzes(memberId)); | ||
} | ||
|
||
private Set<Quiz> getDoneQuizzes(final Long memberId) { | ||
return essayAnswerRepository.findAllByMemberId(memberId).stream() | ||
.map(EssayAnswer::getQuiz) | ||
.collect(toSet()); | ||
} | ||
|
||
private List<Keyword> getKeywordsInCurriculum(final Curriculum curriculum) { | ||
final Set<Long> sessionIds = sessionRepository.findAllByCurriculumId(curriculum.getId()) | ||
.stream() | ||
.map(Session::getId) | ||
.collect(toSet()); | ||
|
||
return keywordRepository.findBySessionIdIn(sessionIds); | ||
} | ||
|
||
private KeywordsResponse createResponsesWithProgress(final List<Keyword> keywords, | ||
final Map<Keyword, Set<Quiz>> quizzesPerKeyword, | ||
final Set<Quiz> doneQuizzes) { | ||
final List<KeywordResponse> keywordResponses = keywords.stream() | ||
.filter(Keyword::isRoot) | ||
.map(keyword -> createResponseWithProgress(keyword, quizzesPerKeyword, doneQuizzes)) | ||
.sorted(Comparator.comparing(KeywordResponse::getKeywordId)) | ||
.collect(toList()); | ||
|
||
return new KeywordsResponse(keywordResponses); | ||
} | ||
|
||
private KeywordResponse createResponseWithProgress(final Keyword keyword, | ||
final Map<Keyword, Set<Quiz>> quizzesPerKeyword, | ||
final Set<Quiz> doneQuizzes) { | ||
final int totalQuizCount = quizzesPerKeyword.getOrDefault(keyword, new HashSet<>()).size(); | ||
final int doneQuizCount = getDoneQuizCount( | ||
quizzesPerKeyword.getOrDefault(keyword, new HashSet<>()), doneQuizzes); | ||
|
||
final List<RecommendedPostResponse> recommendedPostResponses = keyword.getRecommendedPosts().stream() | ||
.map(RecommendedPostResponse::from) | ||
.collect(toList()); | ||
|
||
return new KeywordResponse( | ||
keyword.getId(), | ||
keyword.getName(), | ||
keyword.getDescription(), | ||
keyword.getSeq(), | ||
keyword.getImportance(), | ||
totalQuizCount, | ||
doneQuizCount, | ||
keyword.getParentIdOrNull(), | ||
recommendedPostResponses, | ||
createChildrenWithProgress(keyword.getChildren(), quizzesPerKeyword, doneQuizzes) | ||
); | ||
return KeywordsResponse.of(keywords, totalQuizCounts, answeredQuizCounts); | ||
} | ||
|
||
private int getDoneQuizCount(final Set<Quiz> quizzes, final Set<Quiz> doneQuizzes) { | ||
quizzes.retainAll(doneQuizzes); | ||
return quizzes.size(); | ||
private Map<Long, Integer> getTotalQuizCounts() { | ||
return keywordRepository.findTotalQuizCount().stream() | ||
.collect( | ||
toMap( | ||
KeywordIdAndTotalQuizCount::getKeywordId, | ||
KeywordIdAndTotalQuizCount::getTotalQuizCount)); | ||
} | ||
|
||
private List<KeywordResponse> createChildrenWithProgress(final Set<Keyword> children, | ||
final Map<Keyword, Set<Quiz>> quizzesPerKeyword, | ||
final Set<Quiz> userAnswers) { | ||
return children.stream() | ||
.map(child -> createResponseWithProgress(child, quizzesPerKeyword, userAnswers)) | ||
.sorted(Comparator.comparing(KeywordResponse::getKeywordId)) | ||
.collect(Collectors.toList()); | ||
private Map<Long, Integer> getAnsweredQuizCounts(final Long memberId) { | ||
return keywordRepository.findAnsweredQuizCountByMemberId(memberId).stream() | ||
.collect( | ||
toMap( | ||
KeywordIdAndAnsweredQuizCount::getKeywordId, | ||
KeywordIdAndAnsweredQuizCount::getAnsweredQuizCount)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
readOnly를 Class 레벨에 붙히면 CUD 가 실행되지 않을 수 있으니
Class 에는 Transactional 을 붙히고
method level 에 readOnly를 다는 것이 어떤가요??
기능의 효율성 보다는 작동이 더 중요하다 생각합니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CUD를 실행시키지 않으려고 해주는거 아닌가여?? 요기 클래스에 있는 것들은
읽기 클래스다
라고 명시해주는 것 같아서요There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
세 가지 방법 중 어떤 것을 선택하더라도 각각 어떻게 동작할 지만 염두에 두면 문제는 없을 것 같다고 생각해요.
푸우 말대로 했을 때는 메서드 레벨에 readOnly를 빼먹으면 조회 작업이 rw 디비로 가게 됩니다.
이는 발견하기 어려운 오동작이라고 생각되거든요!
저는 클래스 레벨에 readOnly를 명시하고, 읽기용 디비에는 select만 가능하도록 해두면 개발자가 빠뜨렸을 때 요란하게 예외가 발생하니 좋다고 생각됩니다.