Skip to content

Commit

Permalink
[BE] feat: 리뷰어/리뷰이 매칭 기능 구현 (#38) (#70)
Browse files Browse the repository at this point in the history
* refactor: 도메인 수정

* feat: 기본 랜덤 매칭 기능 구현

* feat: 매칭 서비스 구현

* refactor: 리뷰 사항 반영

컬럼 네이밍 통일성 있게 수정, 메서드 파라미터 순서 수정, 중복된 사용 제거

* refactor: 리뷰 사항 반영

불필요한 개행 제거, Boolean 대신 ENUM 으로 수정
  • Loading branch information
hjk0761 authored Jul 19, 2024
1 parent 4040333 commit bf15ea3
Show file tree
Hide file tree
Showing 13 changed files with 228 additions and 58 deletions.
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
9 changes: 8 additions & 1 deletion backend/src/main/java/corea/matching/domain/MatchResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,19 @@ public class MatchResult {
private long roomId;

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

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

private String prLink;

private boolean isReviewed;
@Enumerated(EnumType.STRING)
private ReviewStatus reviewStatus;

public MatchResult(long roomId, Member fromMember, Member toMember, String prLink) {
this(null, roomId, fromMember, toMember, prLink, ReviewStatus.INCOMPLETE);
}
}
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,29 @@
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) {
List<Long> shuffledMemberIds = new ArrayList<>(memberIds);
Collections.shuffle(shuffledMemberIds);

return match(shuffledMemberIds, matchingSize);
}

private List<Pair> match(List<Long> shuffledMemberIds, int matchingSize) {
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;
}
}
6 changes: 6 additions & 0 deletions backend/src/main/java/corea/matching/domain/ReviewStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package corea.matching.domain;

public enum ReviewStatus {

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

private void validateParticipationSize(List<Participation> participations, int matchingSize) {
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;

0 comments on commit bf15ea3

Please sign in to comment.