Skip to content
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: 리뷰어/리뷰이 매칭 기능 구현 (#38) #70

Merged
merged 9 commits into from
Jul 19, 2024
10 changes: 5 additions & 5 deletions backend/src/main/java/corea/exception/ExceptionType.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

public enum ExceptionType {

SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR,"서버에 문제가 발생했습니다."),
SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버에 문제가 발생했습니다."),
MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "해당 멤버를 찾을 수 없습니다."),
ROOM_NOT_FOUND(HttpStatus.BAD_REQUEST, "방을 찾을 수 없습니다."),
NOT_FOUND_ERROR(HttpStatus.NOT_FOUND,"해당하는 값이 없습니다."),
AUTHORIZATION_ERROR(HttpStatus.UNAUTHORIZED,"인증에 실패했습니다."),
ALREADY_APPLY(HttpStatus.BAD_REQUEST,"해당 방에 이미 참여했습니다"),
;
NOT_FOUND_ERROR(HttpStatus.NOT_FOUND, "해당하는 값이 없습니다."),
AUTHORIZATION_ERROR(HttpStatus.UNAUTHORIZED, "인증에 실패했습니다."),
ALREADY_APPLY(HttpStatus.BAD_REQUEST, "해당 방에 이미 참여했습니다"),
PARTICIPANT_SIZE_LACK(HttpStatus.BAD_REQUEST, "참여 인원 수가 부족합니다.");

private final HttpStatus httpStatus;
private final String message;
Expand Down
6 changes: 6 additions & 0 deletions backend/src/main/java/corea/matching/domain/MatchResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,18 @@ public class MatchResult {
private long roomId;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "fromMemberId", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 부분은 DB 일관성을 위해서
from_member_id 는 어떤지 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영했습니다~

private Member fromMember;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "toMemberId", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Member toMember;

private String prLink;

private boolean isReviewed;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹여나 가능하다면 ENUM으로 바꿔주셔도 좋을 것 같아요~ 😀

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


public MatchResult(long roomId, Member fromMember, Member toMember, String prLink) {
this(null, roomId, fromMember, toMember, prLink, false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package corea.matching.domain;

import java.util.List;

public interface MatchingStrategy {

List<Pair> matchPairs(List<Long> memberIds, int matchingSize);
}
12 changes: 12 additions & 0 deletions backend/src/main/java/corea/matching/domain/Pair.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package corea.matching.domain;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public class Pair {

private final Long fromMemberId;
private final Long toMemberId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package corea.matching.domain;

import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Component
public class PlainRandomMatching implements MatchingStrategy {

@Override
public List<Pair> matchPairs(List<Long> memberIds, int matchingSize) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

붙여주세용~

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List<Long> shuffledMemberIds = new ArrayList<>(memberIds);
Collections.shuffle(shuffledMemberIds);

return match(matchingSize, shuffledMemberIds);
}

private List<Pair> match(int matchingSize, List<Long> shuffledMemberIds) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위에 매개변수는 memberIds, matchingSize 순인데
아래는 다른데 통일하는건 어떤지

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영했습니다~

List<Pair> reviewerResult = new ArrayList<>();
for (int i = 0; i < shuffledMemberIds.size(); i++) {
for (int j = 1; j <= matchingSize; j++) {
reviewerResult.add(new Pair(shuffledMemberIds.get(i), shuffledMemberIds.get((i + j) % shuffledMemberIds.size())));
}
}
return reviewerResult;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package corea.matching.repository;

import corea.matching.domain.MatchResult;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MatchResultRepository extends JpaRepository<MatchResult, Long> {
}
31 changes: 0 additions & 31 deletions backend/src/main/java/corea/matching/service/Matching.java

This file was deleted.

62 changes: 41 additions & 21 deletions backend/src/main/java/corea/member/service/MatchingService.java
Original file line number Diff line number Diff line change
@@ -1,35 +1,55 @@
package corea.member.service;

import corea.exception.CoreaException;
import corea.exception.ExceptionType;
import corea.matching.domain.MatchResult;
import corea.matching.domain.Pair;
import corea.matching.domain.Participation;
import corea.matching.service.Matching;
import corea.member.entity.MatchedGroup;
import corea.matching.repository.MatchedGroupRepository;
import lombok.AllArgsConstructor;
import corea.matching.domain.PlainRandomMatching;
import corea.matching.repository.MatchResultRepository;
import corea.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
@AllArgsConstructor
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MatchingService {

private final MatchedGroupRepository matchedGroupRepository;
private final Matching matching;
private final PlainRandomMatching plainRandomMatching;
private final MatchResultRepository matchResultRepository;
private final MemberRepository memberRepository;

public void matchMaking(final List<Participation> participations, final int matchingSize) {
final ArrayList<Long> memberIds = new ArrayList<>(participations.stream()
public void matchMaking(List<Participation> participations, int matchingSize) {
validateParticipationSize(participations, matchingSize);
List<Long> memberIds = participations.stream()
.map(Participation::getMemberId)
.toList()
);

final Map<Long, List<Long>> results = matching.matchGroup(memberIds, matchingSize);
results.entrySet()
.stream()
.flatMap(entry -> entry.getValue()
.stream()
.map(memberId -> new MatchedGroup(entry.getKey(), memberId)))
.forEach(matchedGroupRepository::save);
.toList();

long roomId = participations.get(0).getRoomId();

List<Pair> results = plainRandomMatching.matchPairs(memberIds, matchingSize);

results.stream()
.map(pair -> new MatchResult(
roomId,
memberRepository.findById(pair.getFromMemberId()).orElseThrow(
() -> new CoreaException(ExceptionType.MEMBER_NOT_FOUND, String.format(String.format("%d에 해당하는 멤버가 없습니다.", pair.getFromMemberId())))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String.format(String.format()) 인데 의도한건지

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아니용 ㅎㅎㅎ 반영했습니다 감사용~~

),
memberRepository.findById(pair.getToMemberId()).orElseThrow(
() -> new CoreaException(ExceptionType.MEMBER_NOT_FOUND, String.format(String.format("%d에 해당하는 멤버가 없습니다.", pair.getToMemberId())))
),
null))
//TODO: prLink 차후 수정
.forEach(matchResultRepository::save);
}

private void validateParticipationSize(List<Participation> participations, int matchingSize) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일단은 예외 던지고, 나중에는 바꿔나가면 괜찮을듯 🙂

if (participations.size() <= matchingSize) {
throw new CoreaException(ExceptionType.PARTICIPANT_SIZE_LACK);
}
}
}
21 changes: 21 additions & 0 deletions backend/src/test/java/corea/fixture/ParticipationFixture.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package corea.fixture;

import corea.matching.domain.Participation;

import java.util.List;

public class ParticipationFixture {

public static List<Participation> PARTICIPATIONS_EIGHT() {
return List.of(
new Participation(1L, 1L),
new Participation(1L, 2L),
new Participation(1L, 3L),
new Participation(1L, 4L),
new Participation(1L, 5L),
new Participation(1L, 6L),
new Participation(1L, 7L),
new Participation(1L, 8L)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package corea.matching.domain;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

class PlainRandomMatchingTest {

private final PlainRandomMatching plainRandomMatching = new PlainRandomMatching();

@Test
@DisplayName("아이디의 리스트가 들어오면 매칭 사이즈 만큼 매칭된 결과를 반환한다.")
void matchPairs_1() {
List<Long> memberIds = List.of(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L);
int matchingSize = 3;

List<Pair> result = plainRandomMatching.matchPairs(memberIds, matchingSize);

assertThat(result).hasSize(matchingSize * memberIds.size());
}

@Test
@DisplayName("매칭을 수행할 때에, 본인을 제외한 멤버 중에서 매칭이 된다.")
void matchPairs_2() {
List<Long> memberIds = List.of(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L);
int matchingSize = 3;

List<Pair> result = plainRandomMatching.matchPairs(memberIds, matchingSize);

for (Pair pair : result) {
assertThat(pair.getFromMemberId()).isNotEqualTo(pair.getToMemberId());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package corea.member.service;

import config.ServiceTest;
import corea.exception.CoreaException;
import corea.fixture.MemberFixture;
import corea.fixture.ParticipationFixture;
import corea.matching.domain.Participation;
import corea.member.repository.MemberRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

@ServiceTest
class MatchingServiceTest {

@Autowired
private MatchingService matchingService;

@Autowired
private MemberRepository memberRepository;

@Test
@DisplayName("인원 수가 매칭 사이즈보다 큰 경우 매칭을 수행한다.")
void matchMaking() {
List<Participation> participations = new ArrayList<>();
int matchingSize = 3;

for (int i = 0; i < 4; i++) {
participations.add(new Participation(1L, memberRepository.save(MemberFixture.MEMBER_DOMAIN()).getId()));
participations.add(new Participation(1L, memberRepository.save(MemberFixture.MEMBER_MANAGER()).getId()));
}

assertThatCode(() -> matchingService.matchMaking(participations, matchingSize))
.doesNotThrowAnyException();
}

@Test
@DisplayName("매칭을 수행할 때에, 인원 수가 매칭 사이즈보다 작거나 같으면 예외를 발생한다.")
void matchMakingLackOfParticipationException() {
List<Participation> participations = ParticipationFixture.PARTICIPATIONS_EIGHT();
int matchingSize = 9;

assertThatThrownBy(() -> matchingService.matchMaking(participations, matchingSize))
.isInstanceOf(CoreaException.class)
.hasMessage("참여 인원 수가 부족합니다.");
}
}
1 change: 1 addition & 0 deletions backend/src/test/resources/clear.sql
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
DELETE FROM MATCH_RESULT;
DELETE FROM MEMBER;
DELETE FROM ROOM;
Loading