Skip to content

Commit

Permalink
Merge pull request #1613 from woowacourse/refactor/1609-roadmap
Browse files Browse the repository at this point in the history
로드맵 서비스 로직 가독성 개선
  • Loading branch information
nuyh99 authored Nov 22, 2023
2 parents 0a401b0 + e9cc339 commit 1f8d48a
Show file tree
Hide file tree
Showing 12 changed files with 396 additions and 278 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package wooteco.prolog.roadmap.application;

import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_KEYWORD_NOT_FOUND_EXCEPTION;
import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_SESSION_NOT_FOUND_EXCEPTION;

import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import wooteco.prolog.common.exception.BadRequestException;
Expand All @@ -15,6 +11,12 @@
import wooteco.prolog.roadmap.domain.repository.KeywordRepository;
import wooteco.prolog.session.domain.repository.SessionRepository;

import java.util.List;

import static java.util.Collections.emptyMap;
import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_KEYWORD_NOT_FOUND_EXCEPTION;
import static wooteco.prolog.common.exception.BadRequestCode.ROADMAP_SESSION_NOT_FOUND_EXCEPTION;

@Transactional
@Service
public class KeywordService {
Expand All @@ -23,7 +25,7 @@ public class KeywordService {
private final KeywordRepository keywordRepository;

public KeywordService(final SessionRepository sessionRepository,
final KeywordRepository keywordRepository) {
final KeywordRepository keywordRepository) {
this.sessionRepository = sessionRepository;
this.keywordRepository = keywordRepository;
}
Expand Down Expand Up @@ -65,7 +67,7 @@ public KeywordResponse findKeywordWithAllChild(final Long sessionId, final Long

Keyword keyword = keywordRepository.findFetchByIdOrderBySeq(keywordId);

return KeywordResponse.createWithAllChildResponse(keyword);
return KeywordResponse.createWithAllChildResponse(keyword, emptyMap(), emptyMap());
}

@Transactional(readOnly = true)
Expand All @@ -74,7 +76,7 @@ public KeywordResponse newFindKeywordWithAllChild(final Long keywordId) {

Keyword keyword = keywordRepository.findFetchByIdOrderBySeq(keywordId);

return KeywordResponse.createWithAllChildResponse(keyword);
return KeywordResponse.createWithAllChildResponse(keyword, emptyMap(), emptyMap());
}

@Transactional(readOnly = true)
Expand All @@ -83,14 +85,14 @@ public KeywordsResponse findSessionIncludeRootKeywords(final Long sessionId) {

List<Keyword> keywords = keywordRepository.findBySessionIdAndParentIsNull(sessionId);

return KeywordsResponse.createResponse(keywords);
return KeywordsResponse.of(keywords, emptyMap(), emptyMap());
}

@Transactional(readOnly = true)
public KeywordsResponse newFindSessionIncludeRootKeywords() {
List<Keyword> keywords = keywordRepository.newFindByParentIsNull();

return KeywordsResponse.createResponse(keywords);
return KeywordsResponse.of(keywords, emptyMap(), emptyMap());
}

public void updateKeyword(
Expand Down
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 {

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));
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package wooteco.prolog.roadmap.application.dto;

import java.util.ArrayList;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import wooteco.prolog.roadmap.domain.Keyword;

import java.util.HashSet;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

Expand All @@ -21,14 +21,14 @@ public class KeywordResponse {
private int order;
private int importance;
private int totalQuizCount;
private int doneQuizCount;
private int answeredQuizCount;
private Long parentKeywordId;
private List<RecommendedPostResponse> recommendedPosts;
private List<KeywordResponse> childrenKeywords;

public KeywordResponse(final Long keywordId, final String name, final String description,
final int order, final int importance, final int totalQuizCount,
final int doneQuizCount, final Long parentKeywordId,
final int answeredQuizCount, final Long parentKeywordId,
final List<RecommendedPostResponse> recommendedPosts,
final List<KeywordResponse> childrenKeywords) {
this.keywordId = keywordId;
Expand All @@ -37,7 +37,7 @@ public KeywordResponse(final Long keywordId, final String name, final String des
this.order = order;
this.importance = importance;
this.totalQuizCount = totalQuizCount;
this.doneQuizCount = doneQuizCount;
this.answeredQuizCount = answeredQuizCount;
this.parentKeywordId = parentKeywordId;
this.recommendedPosts = recommendedPosts;
this.childrenKeywords = childrenKeywords;
Expand All @@ -53,34 +53,36 @@ public static KeywordResponse createResponse(final Keyword keyword) {
0, 0,
keyword.getParentIdOrNull(),
createRecommendedPostResponses(keyword),
null);
Collections.emptyList());
}

private static List<RecommendedPostResponse> createRecommendedPostResponses(final Keyword keyword) {
return keyword.getRecommendedPosts().stream()
.map(RecommendedPostResponse::from)
.collect(Collectors.toList());
}

public static KeywordResponse createWithAllChildResponse(final Keyword keyword) {
public static KeywordResponse createWithAllChildResponse(final Keyword keyword,
final Map<Long, Integer> totalQuizCounts,
final Map<Long, Integer> answeredQuizCounts) {
return new KeywordResponse(
keyword.getId(),
keyword.getName(),
keyword.getDescription(),
keyword.getSeq(),
keyword.getImportance(),
0,
0,
totalQuizCounts.getOrDefault(keyword.getId(), 0),
answeredQuizCounts.getOrDefault(keyword.getId(), 0),
keyword.getParentIdOrNull(),
createRecommendedPostResponses(keyword),
createChildren(keyword.getChildren()));
createChildren(keyword.getChildren(), totalQuizCounts, answeredQuizCounts));
}

private static List<KeywordResponse> createChildren(final Set<Keyword> children) {
List<KeywordResponse> keywords = new ArrayList<>();
for (Keyword keyword : children) {
keywords.add(createWithAllChildResponse(keyword));
}
return keywords;
private static List<RecommendedPostResponse> createRecommendedPostResponses(final Keyword keyword) {
return keyword.getRecommendedPosts().stream()
.map(RecommendedPostResponse::from)
.collect(Collectors.toList());
}

private static List<KeywordResponse> createChildren(final Set<Keyword> children,
final Map<Long, Integer> totalQuizCounts,
final Map<Long, Integer> answeredQuizCounts) {
return children.stream()
.map(keyword -> createWithAllChildResponse(keyword, totalQuizCounts, answeredQuizCounts))
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package wooteco.prolog.roadmap.application.dto;

import java.util.List;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import wooteco.prolog.roadmap.domain.Keyword;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class KeywordsResponse {
Expand All @@ -17,18 +19,14 @@ public KeywordsResponse(final List<KeywordResponse> data) {
this.data = data;
}

public static KeywordsResponse createResponse(final List<Keyword> keywords) {
List<KeywordResponse> keywordsResponse = keywords.stream()
.map(KeywordResponse::createResponse)
.collect(Collectors.toList());
return new KeywordsResponse(keywordsResponse);
}

public static KeywordsResponse createResponseWithChildren(final List<Keyword> keywords) {
List<KeywordResponse> keywordsResponse = keywords.stream()
public static KeywordsResponse of(final List<Keyword> keywords,
final Map<Long, Integer> totalQuizCounts,
final Map<Long, Integer> answeredQuizCounts) {
final List<KeywordResponse> keywordsResponse = keywords.stream()
.filter(Keyword::isRoot)
.map(KeywordResponse::createWithAllChildResponse)
.map(rootKeyword -> KeywordResponse.createWithAllChildResponse(rootKeyword, totalQuizCounts, answeredQuizCounts))
.collect(Collectors.toList());

return new KeywordsResponse(keywordsResponse);
}
}
17 changes: 13 additions & 4 deletions backend/src/main/java/wooteco/prolog/roadmap/domain/Keyword.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@
import org.hibernate.annotations.BatchSize;
import wooteco.prolog.common.exception.BadRequestException;

import javax.persistence.*;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
Expand Down Expand Up @@ -38,6 +47,7 @@ public class Keyword {
@Column(name = "session_id", nullable = false)
private Long sessionId;

@BatchSize(size = 1000)
@OneToMany(mappedBy = "keyword", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<RecommendedPost> recommendedPosts = new HashSet<>();

Expand All @@ -50,8 +60,7 @@ public class Keyword {
private Set<Keyword> children = new HashSet<>();

public Keyword(final Long id, final String name, final String description, final int seq,
final int importance,
final Long sessionId, final Keyword parent, final Set<Keyword> children) {
final int importance, final Long sessionId, final Keyword parent, final Set<Keyword> children) {
validateSeq(seq);
this.id = id;
this.name = name;
Expand All @@ -69,7 +78,7 @@ public static Keyword createKeyword(final String name,
final int importance,
final Long sessionId,
final Keyword parent) {
return new Keyword(null, name, description, seq, importance, sessionId, parent, null);
return new Keyword(null, name, description, seq, importance, sessionId, parent, new HashSet<>());
}

public void update(final String name, final String description, final int seq,
Expand Down
Loading

0 comments on commit 1f8d48a

Please sign in to comment.