Igloo-Club / Igloo-Club-BE

1 stars 1 forks source link

[fix] 인연 프로필 추천 로직 수정 #43

Closed clap-0 closed 5 months ago

clap-0 commented 5 months ago

🔥 Related Issues

💜 작업 내용

✅ PR Point

1️⃣ Acquaintance 객체 생애 주기 변경

1. '인연 프로필 뽑기' 성공 시에 `Acquaintance(본인, 상대방, RECOMMENDED)` 저장
2. '매칭 성사' 시에 `Acquaintance(본인, 상대방, MATCHED)`와 `Acquaintance(상대방, 본인, MATCHED)` 저장
3. 매일 11시에 '모든 Acquaintance(RECOMMENDED) 삭제'

또한 매 추천마다 동일한 구성(member, recommendedMember)의 Acquaintance가 쌓였기 때문에 생성 로직을 일부 수정했습니다.

private Acquaintance getAcquaintance(Member member, Member acquaintanceMember) {
    return acquaintanceRepository.findByMemberAndAcquaintanceMember(member, acquaintanceMember)
            .orElse(Acquaintance.create(member, acquaintanceMember, AcquaintanceStatus.RECOMMENDED));
}

이제 Acquaintance 데이터베이스에 동일한 (member, recommendedMember) 구성의 행은 무조건 하나만 존재해야 합니다.

2️⃣ 프로필 추천 로직 수정

주어진 Member에게 쌓인 RECOMMENDED 눈길 갯수를 조회하여, 하루 제한 횟수를 초과하였는지 확인하는 메서드를 구현했습니다.

private boolean checkLimitExcess(Member member) {
    Long count = nungilRepository.countByMemberAndStatus(member, NungilStatus.RECOMMENDED);
    return RECOMMENDATION_LIMIT <= count;
}

더불어 기존의 '인연 프로필 뽑기' 메서드를 리팩토링 했습니다.

@Transactional
public NungilResponse recommendMember(Member member, ProfileRecommendRequest request){

    // 1. 하루 제한 횟수를 초과한 경우, 예외를 발생시킨다.
    if (checkLimitExcess(member)) {
        throw new GeneralException(NungilErrorResult.LIMIT_EXCEEDED);
    }

    // 2. 회원 한 명을 추천받는다.
    Member recommendedMember = getRecommendedMember(member, request);
    if (recommendedMember == null) return null;

    // 3. 추천 받은 회원에 대한 지인 관계를 생성하고 저장한다.
    Acquaintance newAcquaintance = getAcquaintance(member, recommendedMember);
    acquaintanceRepository.save(newAcquaintance);

    // 4. 추천 받은 회원에 대한 눈길을 생성하고 저장한다.
    Nungil newNungil = Nungil.create(member, recommendedMember, NungilStatus.RECOMMENDED);
    nungilRepository.save(newNungil);

    // 5. 추천 받은 회원 정보를 반환한다.
    return convertToNungilResponse(recommendedMember);
}

3️⃣ 눈길 목록 조회 데이터에 닉네임 추가

프론트의 요청에 따라 눈길 목록 조회 데이터에 "닉네임"을 추가했습니다.

public class NungilSliceResponse {
    ...
    private String nickname;
    ...
}

😡 Trouble Shooting

1️⃣ 인스턴스 생성 메서드 추가 및 다른 방식으로의 생성 제한

앞선 "Acquaintance 클래스에 status 필드 추가"와 "NungilSliceResponse 클래스에 nickname 필드 추가" 중 어려움이 발생했습니다.

필드가 추가됨에 따라, 각 클래스에 대한 인스턴스를 생성하는 코드에서 추가된 필드도 포함시켜야 했습니다.

하지만 기존 코드는 @Builder를 사용하여 각각의 클래스에 대한 인스턴스를 생성했기 때문에, 어느 위치에서 해당 클래스를 생성하는지 추적하기 어려웠습니다.

List<NungilSliceResponse> nungilResponses = nungilSlice.getContent().stream()
        .map(nungil -> NungilSliceResponse.builder()
                .nungilId(nungil.getId())
                .animalFace(nungil.getReceiver().getAnimalFace().getTitle())
                .companyName(nungil.getReceiver().getCompany().getCompanyName())
                .job(nungil.getReceiver().getJob())
                .description(nungil.getReceiver().getDescription())
                .createdAt(nungil.getCreatedAt())
                .expiredAt(nungil.getExpiredAt())
                .build())
        .collect(Collectors.toList());

때문에 각 클래스에 대한 @Builder@AllArgsConstructor를 지우고, "정적 팩토리 메서드"를 생성하도록 했습니다.

public static NungilSliceResponse create(Nungil nungil, Member member) {
    NungilSliceResponse response = new NungilSliceResponse();
    AnimalFace animalFace = member.getAnimalFace();
    Company company = member.getCompany();

    response.nungilId = nungil.getId();
    response.createdAt = nungil.getCreatedAt();
    response.expiredAt = nungil.getExpiredAt();

    response.animalFace = (animalFace != null) ? animalFace.getTitle() : null;
    response.companyName = (company != null) ? company.getCompanyName() : null;
    response.job = member.getJob();
    response.description = member.getDescription();
    response.nickname = member.getNickname();

    return response;
}

만약 필드가 더 추가된다면, 해당 메서드를 사용하는 곳으로 바로 추적할 수 있을 것입니다.

2️⃣ 데이터베이스 쿼리 최적화

앞서 말했듯이, 매 추천마다 동일한 구성을 가진 Acquaintance 데이터가 계속 쌓이는 것을 수정했습니다.

추가적으로 매 시각마다 데이터를 삭제하는 메서드에서 과도한 쿼리가 발생한다는 것을 알게 되었습니다. 기존 코드는 SELECT 쿼리 1번 + 데이터 갯수만큼 DELETE 쿼리 N번 발생하였습니다.

@Transactional
public void deleteExpiredNungils() {
    LocalDateTime now = LocalDateTime.now();
    List<Nungil> expiredNungils = nungilRepository.findByExpiredAtBefore(now);
    for (Nungil nungil : expiredNungils) {
        nungilRepository.delete(nungil);
    }
}

JPQL을 사용하여 이를 DELETE 쿼리 1번 발생하도록 수정하였습니다.

@Transactional
public void deleteExpiredNungils() {
    LocalDateTime now = LocalDateTime.now();
    nungilRepository.deleteAllByExpiredAtBefore(now);
}
@Modifying
@Query("delete from Nungil n where n.expiredAt <= :dateTime")
void deleteAllByExpiredAtBefore(@Param("dateTime") LocalDateTime dateTime);