-
Notifications
You must be signed in to change notification settings - Fork 2
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
[BE] feat: 키워드 도메인 구체화, 정책 구현 #40
Changes from all commits
7a460c0
f6ffd87
bc59cd9
05b8de4
48e7b68
120698b
ffc52cd
5683b37
5341ffb
40f095a
6368aac
b4ad6a1
01a24a7
dc2cfc9
c5f23aa
3b6a631
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 |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.Table; | ||
import java.util.Objects; | ||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
@@ -23,7 +24,34 @@ public class Keyword { | |
@Column(name = "detail", nullable = false) | ||
private String detail; | ||
|
||
public Keyword(String detail) { | ||
Keyword(Long id, String detail) { | ||
this.id = id; | ||
this.detail = detail; | ||
} | ||
|
||
public Keyword(String detail) { | ||
this(null, detail); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) { | ||
return true; | ||
} | ||
if (!(o instanceof Keyword keyword)) { | ||
return false; | ||
} | ||
if (id == null) { | ||
return Objects.equals(detail, keyword.detail); | ||
} | ||
return Objects.equals(id, keyword.id); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
if (id == null) { | ||
return Objects.hash(detail); | ||
} | ||
return Objects.hash(id); | ||
} | ||
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. equals, hashCode에서 엔티티가 영속화 되지 않으면 id가 null 일텐데 괜찮을까요? 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.
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package reviewme.keyword.domain; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
import reviewme.keyword.domain.exception.DuplicateKeywordException; | ||
import reviewme.keyword.domain.exception.KeywordLimitExceedException; | ||
|
||
public class SelectedKeywords { | ||
|
||
private static final int MAX_KEYWORD_COUNT = 5; | ||
|
||
private final List<Keyword> keywords; | ||
|
||
public SelectedKeywords(List<Keyword> selectedKeywords) { | ||
if (selectedKeywords.size() > MAX_KEYWORD_COUNT) { | ||
throw new KeywordLimitExceedException(MAX_KEYWORD_COUNT); | ||
} | ||
if (hasDuplicateKeywords(selectedKeywords)) { | ||
throw new DuplicateKeywordException(); | ||
} | ||
Comment on lines
+15
to
+20
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. 아직은 메서드 분리를 하지 않는 것이 더 나을까요? 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. 검증은 한 줄로 이루어져야 읽기 편하다고 생각해서 분리해 두었습니다. 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. 앗 제가 얘기한 건 validateSize, validateDuplicate 등으로 메서드 분리를 하지 않은 이유가 검증 로직이 많지 않아서인지의 궁금증이었습니다! 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. 네 아직 읽기 불편하지 않았습니다 🔥 |
||
this.keywords = selectedKeywords; | ||
} | ||
|
||
private boolean hasDuplicateKeywords(List<Keyword> selectedKeywords) { | ||
long distinctKeywordCount = selectedKeywords.stream() | ||
.distinct() | ||
.count(); | ||
return selectedKeywords.size() != distinctKeywordCount; | ||
} | ||
|
||
public List<Keyword> getKeywords() { | ||
return Collections.unmodifiableList(keywords); | ||
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. 좋네요👍 |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package reviewme.keyword.domain.exception; | ||
|
||
import reviewme.global.exception.BadRequestException; | ||
|
||
public class DuplicateKeywordException extends BadRequestException { | ||
|
||
public DuplicateKeywordException() { | ||
super("키워드는 중복되지 않게 선택해 주세요."); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package reviewme.keyword.domain.exception; | ||
|
||
import reviewme.global.exception.BadRequestException; | ||
|
||
public class KeywordLimitExceedException extends BadRequestException { | ||
|
||
public KeywordLimitExceedException(int maxSize) { | ||
super("키워드는 최대 %d개 선택할 수 있습니다.".formatted(maxSize)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package reviewme.review.service; | ||
|
||
import java.util.List; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
import reviewme.keyword.domain.Keyword; | ||
import reviewme.keyword.domain.SelectedKeywords; | ||
import reviewme.review.domain.Review; | ||
import reviewme.review.domain.ReviewKeyword; | ||
import reviewme.review.repository.ReviewKeywordRepository; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class ReviewKeywordService { | ||
|
||
private final ReviewKeywordRepository reviewKeywordRepository; | ||
|
||
@Transactional | ||
public List<ReviewKeyword> attachSelectedKeywordsOnReview(Review review, List<Keyword> selectedKeywords) { | ||
reviewKeywordRepository.deleteAllByReview(review); | ||
SelectedKeywords keywords = new SelectedKeywords(selectedKeywords); | ||
List<ReviewKeyword> reviewKeywords = keywords.getKeywords() | ||
.stream() | ||
.map(keyword -> new ReviewKeyword(review, keyword)) | ||
.toList(); | ||
return reviewKeywordRepository.saveAll(reviewKeywords); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package reviewme.fixture; | ||
|
||
import reviewme.keyword.domain.Keyword; | ||
|
||
public enum KeywordFixture { | ||
|
||
추진력이_좋아요, | ||
회의를_이끌어요, | ||
의견을_잘_조율해요, | ||
꼼꼼하게_기록해요, | ||
; | ||
|
||
public Keyword create() { | ||
return new Keyword(replaceUnderscore()); | ||
} | ||
|
||
private String replaceUnderscore() { | ||
return name().replace("_", " "); | ||
} | ||
} | ||
Comment on lines
+5
to
+20
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. enum 으로 만든 이유가 따로 있나요? 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. 다른 Fixture와의 통일성이 한몫합니다. 추가적인 필드가 들어오면 대응하기에도 훨씬 수월하다고 생각했어요. 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. 익숙하진 않지만, 정적인 Fixture 와 동적인 Fixture 를 모두 커버할 수 있어 좋은 아이디어라 생각합니다! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package reviewme.fixture; | ||
|
||
import java.time.LocalDateTime; | ||
import lombok.RequiredArgsConstructor; | ||
import reviewme.member.domain.Member; | ||
import reviewme.member.domain.ReviewerGroup; | ||
|
||
@RequiredArgsConstructor | ||
public enum ReviewerGroupFixture { | ||
|
||
리뷰_그룹("리뷰 그룹", "그룹 설명", LocalDateTime.of(2024, 1, 1, 12, 0)), | ||
; | ||
|
||
private final String groupName; | ||
private final String description; | ||
private final LocalDateTime createdAt; | ||
|
||
public ReviewerGroup create(Member reviewee) { | ||
return new ReviewerGroup(reviewee, groupName, description, createdAt); | ||
} | ||
Comment on lines
+18
to
+20
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. 아하 이렇게 하면 리뷰이를 그 때 그 때 넣어줄 수 있겠군요!! 얻어갑니다😋 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package reviewme.keyword.domain; | ||
|
||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; | ||
import static reviewme.fixture.KeywordFixture.꼼꼼하게_기록해요; | ||
|
||
import java.util.List; | ||
import java.util.stream.Stream; | ||
import org.junit.jupiter.api.Test; | ||
import reviewme.keyword.domain.exception.DuplicateKeywordException; | ||
import reviewme.keyword.domain.exception.KeywordLimitExceedException; | ||
|
||
class SelectedKeywordsTest { | ||
|
||
@Test | ||
void 키워드는_최대_5개만_선택할_수_있다() { | ||
// given | ||
List<Keyword> keywords = Stream.of("1", "2", "3", "4", "5") | ||
.map(Keyword::new) | ||
.toList(); | ||
|
||
// when, then | ||
assertDoesNotThrow(() -> new SelectedKeywords(keywords)); | ||
} | ||
|
||
@Test | ||
void 키워드는_5개를_초과해서_선택할_수_없다() { | ||
// given | ||
List<Keyword> keywords = Stream.of("1", "2", "3", "4", "5", "6") | ||
.map(Keyword::new) | ||
.toList(); | ||
|
||
// when, then | ||
assertThatThrownBy(() -> new SelectedKeywords(keywords)) | ||
.isInstanceOf(KeywordLimitExceedException.class); | ||
} | ||
|
||
@Test | ||
void 키워드는_중복으로_선택할_수_없다() { | ||
// given | ||
List<Keyword> keywords = List.of( | ||
꼼꼼하게_기록해요.create(), | ||
꼼꼼하게_기록해요.create() | ||
); | ||
|
||
// when, then | ||
assertThatThrownBy(() -> new SelectedKeywords(keywords)) | ||
.isInstanceOf(DuplicateKeywordException.class); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package reviewme.review.repository; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static reviewme.fixture.KeywordFixture.꼼꼼하게_기록해요; | ||
import static reviewme.fixture.KeywordFixture.의견을_잘_조율해요; | ||
import static reviewme.fixture.KeywordFixture.회의를_이끌어요; | ||
import static reviewme.fixture.ReviewerGroupFixture.리뷰_그룹; | ||
|
||
import java.util.List; | ||
import java.util.stream.Stream; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; | ||
import reviewme.fixture.KeywordFixture; | ||
import reviewme.keyword.repository.KeywordRepository; | ||
import reviewme.member.domain.Member; | ||
import reviewme.member.domain.ReviewerGroup; | ||
import reviewme.member.repository.MemberRepository; | ||
import reviewme.member.repository.ReviewerGroupRepository; | ||
import reviewme.review.domain.Review; | ||
import reviewme.review.domain.ReviewKeyword; | ||
|
||
@DataJpaTest | ||
class ReviewKeywordRepositoryTest { | ||
|
||
@Autowired | ||
private ReviewKeywordRepository reviewKeywordRepository; | ||
|
||
@Autowired | ||
private KeywordRepository keywordRepository; | ||
|
||
@Autowired | ||
private MemberRepository memberRepository; | ||
|
||
@Autowired | ||
private ReviewerGroupRepository reviewerGroupRepository; | ||
|
||
@Autowired | ||
private ReviewRepository reviewRepository; | ||
|
||
@Test | ||
void 리뷰에_해당하는_키워드를_모두_삭제한다() { | ||
// given | ||
Member sancho = memberRepository.save(new Member("산초")); | ||
Member kirby = memberRepository.save(new Member("커비")); | ||
ReviewerGroup group = reviewerGroupRepository.save(리뷰_그룹.create(sancho)); | ||
Review review = reviewRepository.save(new Review(kirby, group)); | ||
Comment on lines
+42
to
+47
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. 함수에 '기존에 키워드가 있다면 수정하는' 기능도 있는 만큼, 그 기능도 테스트할 필요가 있어 보여요! 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.
|
||
List<ReviewKeyword> reviewKeywords = Stream.of(꼼꼼하게_기록해요, 회의를_이끌어요, 의견을_잘_조율해요) | ||
.map(KeywordFixture::create) | ||
.map(keywordRepository::save) | ||
.map(keyword -> new ReviewKeyword(review, keyword)) | ||
.toList(); | ||
reviewKeywordRepository.saveAll(reviewKeywords); | ||
|
||
// when | ||
reviewKeywordRepository.deleteAllByReview(review); | ||
|
||
// then | ||
List<ReviewKeyword> actual = reviewKeywordRepository.findByReview(review); | ||
assertThat(actual).isEmpty(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package reviewme.review.service; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static reviewme.fixture.KeywordFixture.꼼꼼하게_기록해요; | ||
import static reviewme.fixture.KeywordFixture.의견을_잘_조율해요; | ||
import static reviewme.fixture.KeywordFixture.회의를_이끌어요; | ||
import static reviewme.fixture.ReviewerGroupFixture.리뷰_그룹; | ||
|
||
import java.util.List; | ||
import java.util.stream.Stream; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import reviewme.fixture.KeywordFixture; | ||
import reviewme.keyword.domain.Keyword; | ||
import reviewme.keyword.repository.KeywordRepository; | ||
import reviewme.member.domain.Member; | ||
import reviewme.member.domain.ReviewerGroup; | ||
import reviewme.member.repository.MemberRepository; | ||
import reviewme.member.repository.ReviewerGroupRepository; | ||
import reviewme.review.domain.Review; | ||
import reviewme.review.domain.ReviewKeyword; | ||
import reviewme.review.repository.ReviewKeywordRepository; | ||
import reviewme.review.repository.ReviewRepository; | ||
import reviewme.support.ServiceTest; | ||
|
||
@ServiceTest | ||
class ReviewKeywordServiceTest { | ||
|
||
@Autowired | ||
private ReviewKeywordService reviewKeywordService; | ||
|
||
@Autowired | ||
private ReviewKeywordRepository reviewKeywordRepository; | ||
|
||
@Autowired | ||
private KeywordRepository keywordRepository; | ||
|
||
@Autowired | ||
private MemberRepository memberRepository; | ||
|
||
@Autowired | ||
private ReviewerGroupRepository reviewerGroupRepository; | ||
|
||
@Autowired | ||
private ReviewRepository reviewRepository; | ||
|
||
@Test | ||
void 리뷰에_키워드를_추가한다() { | ||
// given | ||
Member sancho = memberRepository.save(new Member("산초")); | ||
Member kirby = memberRepository.save(new Member("커비")); | ||
ReviewerGroup group = reviewerGroupRepository.save(리뷰_그룹.create(sancho)); | ||
Review review = reviewRepository.save(new Review(kirby, group)); | ||
|
||
List<Keyword> keywords = Stream.of(꼼꼼하게_기록해요, 회의를_이끌어요, 의견을_잘_조율해요) | ||
.map(KeywordFixture::create) | ||
.map(keywordRepository::save) | ||
.toList(); | ||
List<Keyword> selectedKeywords = List.of(keywords.get(0), keywords.get(1)); | ||
|
||
// when | ||
reviewKeywordService.attachSelectedKeywordsOnReview(review, selectedKeywords); | ||
|
||
// then | ||
List<ReviewKeyword> actual = reviewKeywordRepository.findByReview(review); | ||
assertThat(actual).hasSize(2); | ||
} | ||
|
||
@Test | ||
void 키워드가_이미_존재하는_경우_키워드_등록_시_모두_대체된다() { | ||
// given | ||
Member sancho = memberRepository.save(new Member("산초")); | ||
Member kirby = memberRepository.save(new Member("커비")); | ||
ReviewerGroup group = reviewerGroupRepository.save(리뷰_그룹.create(sancho)); | ||
Review review = reviewRepository.save(new Review(kirby, group)); | ||
|
||
List<Keyword> keywords = Stream.of(꼼꼼하게_기록해요, 회의를_이끌어요, 의견을_잘_조율해요) | ||
.map(KeywordFixture::create) | ||
.map(keywordRepository::save) | ||
.toList(); | ||
reviewKeywordRepository.save(new ReviewKeyword(review, keywords.get(0))); | ||
List<Keyword> selectedKeywords = List.of(keywords.get(1), keywords.get(2)); | ||
|
||
// when | ||
reviewKeywordService.attachSelectedKeywordsOnReview(review, selectedKeywords); | ||
|
||
// then | ||
List<ReviewKeyword> actual = reviewKeywordRepository.findByReview(review); | ||
assertThat(actual).extracting(ReviewKeyword::getKeyword) | ||
.containsExactlyInAnyOrderElementsOf(selectedKeywords); | ||
} | ||
} |
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.
👍