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

상호 평가 반영 스케줄링 #602

Merged
merged 12 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.ddang.ddang.configuration;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling
Copy link
Collaborator

Choose a reason for hiding this comment

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

칭찬

칭찬해요!!!!!!!!!!!!!!!!!!!!!!!!!!
테스트 시 설정 파일을 exclude하는 것만으로도 깔끔하게 분리할 수 있겠네요

public class SchedulingConfiguration {
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ public Long create(final CreateReviewDto reviewDto) {
validateWriterCanReview(findAuction, writer);

final Review review = reviewDto.toEntity(findAuction, writer, target);
final Review persistReview = saveReviewAndUpdateReliability(review, target);

return persistReview.getId();
return reviewRepository.save(review)
.getId();
}

private void validateWriterCanReview(final Auction auction, final User writer) {
Expand All @@ -63,18 +63,9 @@ private void validateAlreadyReviewed(final Auction auction, final User writer) {
}
}

private Review saveReviewAndUpdateReliability(final Review review, final User target) {
final Review persistReview = reviewRepository.save(review);

final List<Review> targetReviews = reviewRepository.findAllByTargetId(target.getId());
target.updateReliability(targetReviews);

return persistReview;
}

public ReadReviewDetailDto readByReviewId(final Long reviewId) {
final Review findReview = reviewRepository.findById(reviewId)
.orElseThrow(() -> new ReviewNotFoundException("해당 평가를 찾을 수 없습니다."));
.orElseThrow(() -> new ReviewNotFoundException("해당 평가를 찾을 수 없습니다."));

return ReadReviewDetailDto.from(findReview);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.ddang.ddang.review.domain;

import com.ddang.ddang.user.domain.User;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

import java.util.List;
import java.util.Set;

import static java.util.stream.Collectors.toSet;

@RequiredArgsConstructor
@Getter
@ToString
public class Reviews {

private final List<Review> reviews;

public double addAllReviewScore() {
return reviews.stream()
.mapToDouble(review -> review.getScore().getValue())
.sum();
}

public int size() {
return reviews.size();
}

public boolean isEmpty() {
return reviews.isEmpty();
}

public Set<User> findReviewTargets() {
return reviews.stream()
.map(Review::getTarget)
.collect(toSet());
}

public List<Review> findReviewsByTarget(final User targetUser) {
return reviews.stream()
.filter(review -> review.getTarget().equals(targetUser))
.toList();
}

public Long findLastReviewId() {
final int lastIndex = reviews.size() - 1;

return reviews.get(lastIndex)
.getId();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ public interface JpaReviewRepository extends JpaRepository<Review, Long> {
boolean existsByAuctionIdAndWriterId(final Long auctionId, final Long writerId);

@Query("""
SELECT r FROM Review r JOIN FETCH r.writer w JOIN FETCH r.target t
SELECT r FROM Review r
JOIN FETCH r.writer w
JOIN FETCH r.target t
WHERE t.id = :targetId
ORDER BY r.id DESC
""")
List<Review> findAllByTargetId(final Long targetId);
Copy link
Collaborator

Choose a reason for hiding this comment

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

선택? 필수?

JOIN FETCH 부분은 개행을 일부러 안해주신 건가요?.?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

아무 생각 없었어요 ㅠ 개행 추가했습니다.


Optional<Review> findByAuctionIdAndWriterId(final Long auctionId, final Long writerId);

List<Review> findAllByIdGreaterThan(final Long id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.ddang.ddang.user.application.schedule;

import com.ddang.ddang.review.domain.Review;
import com.ddang.ddang.review.domain.Reviews;
import com.ddang.ddang.review.infrastructure.persistence.JpaReviewRepository;
import com.ddang.ddang.user.domain.ReliabilityUpdateHistory;
import com.ddang.ddang.user.domain.User;
import com.ddang.ddang.user.domain.UserReliability;
import com.ddang.ddang.user.infrastructure.persistence.JpaReliabilityUpdateHistoryRepository;
import com.ddang.ddang.user.infrastructure.persistence.JpaUserReliabilityRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Set;

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

private final JpaReliabilityUpdateHistoryRepository updateHistoryRepository;
private final JpaReviewRepository reviewRepository;
private final JpaUserReliabilityRepository userReliabilityRepository;

@Transactional
@Scheduled(cron = "0 0 4 ? * MON")
public void updateAllUserReliability() {
final ReliabilityUpdateHistory updateHistory = updateHistoryRepository.findFirstByOrderByIdDesc()
.orElse(new ReliabilityUpdateHistory());
final Long lastAppliedReviewId = updateHistory.getLastAppliedReviewId();
final List<Review> findNewReviews = reviewRepository.findAllByIdGreaterThan(lastAppliedReviewId);
final Reviews newReviews = new Reviews(findNewReviews);

if (newReviews.isEmpty()) {
return;
}

update(newReviews);
}

private void update(final Reviews newReviews) {
final Set<User> targetUsers = newReviews.findReviewTargets();
for (final User targetUser : targetUsers) {
final UserReliability userReliability = userReliabilityRepository.findByUserId(targetUser.getId())
.orElseGet(() -> createUserReliability(targetUser));

final List<Review> targetReviews = newReviews.findReviewsByTarget(targetUser);

userReliability.updateReliability(new Reviews(targetReviews));
}

final ReliabilityUpdateHistory newHistory = new ReliabilityUpdateHistory(newReviews.findLastReviewId());

updateHistoryRepository.save(newHistory);
Copy link
Collaborator

Choose a reason for hiding this comment

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

선택

개행..?

}

private UserReliability createUserReliability(final User user) {
final UserReliability userReliability = new UserReliability(user);

return userReliabilityRepository.save(userReliability);
}
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,29 @@
package com.ddang.ddang.user.domain;

import com.ddang.ddang.review.domain.Review;
import jakarta.persistence.Embeddable;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.List;

@Embeddable
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EqualsAndHashCode
@ToString
public class Reliability {

public static final Reliability INITIAL_RELIABILITY = new Reliability(null);
private static final double INITIAL_RELIABILITY_VALUE = Double.MIN_VALUE;
public static final Reliability INITIAL_RELIABILITY = new Reliability(INITIAL_RELIABILITY_VALUE);

private Double value;
private double value;
Copy link
Collaborator

Choose a reason for hiding this comment

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

칭찬

원시타입으로 바꿔주셔서 마음이 굉장히 편안해졌어요 감사합니다@@


public Reliability(final Double value) {
public Reliability(final double value) {
this.value = value;
}

public void updateReliability(final List<Review> reviews) {
if (reviews.isEmpty()) {
this.value = null;

return;
}

this.value = reviews.stream()
.mapToDouble(review -> review.getScore().getValue())
.average()
.orElseGet(null);
public double calculateReviewScoreSum(final int appliedReviewCount) {
return value * appliedReviewCount;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.ddang.ddang.user.domain;

import com.ddang.ddang.common.entity.BaseCreateTimeEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@NoArgsConstructor
@Getter
@EqualsAndHashCode(of = "id", callSuper = false)
@ToString(of = {"id", "lastAppliedReviewId"})
public class ReliabilityUpdateHistory extends BaseCreateTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private Long lastAppliedReviewId = 0L;

public ReliabilityUpdateHistory(final Long lastAppliedReviewId) {
this.lastAppliedReviewId = lastAppliedReviewId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.ddang.ddang.common.entity.BaseTimeEntity;
import com.ddang.ddang.image.domain.ProfileImage;
import com.ddang.ddang.review.domain.Review;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
Expand All @@ -23,8 +22,6 @@
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.List;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
Expand Down Expand Up @@ -89,7 +86,7 @@ public void withdrawal() {
this.deleted = DELETED_STATUS;
}

public void updateReliability(final List<Review> reviews) {
reliability.updateReliability(reviews);
public void updateReliability(final Reliability reliability) {
this.reliability = reliability;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.ddang.ddang.user.domain;

import com.ddang.ddang.review.domain.Reviews;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EqualsAndHashCode(of = "id", callSuper = false)
@ToString(of = {"id", "reliability", "appliedReviewCount"})
public class UserReliability {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
@JoinColumn(name = "user_id", foreignKey = @ForeignKey(name = "fk_user_reliability_user"), nullable = false)
private User user;

@Embedded
@AttributeOverride(name = "value", column = @Column(name = "reliability"))
private Reliability reliability;

private int appliedReviewCount = 0;

public UserReliability(final User user) {
this.user = user;
this.reliability = user.getReliability();
}

public void updateReliability(final Reviews newReviews) {
if (newReviews.isEmpty()) {
return;
}
final Reliability newReliability = calculateNewReliability(newReviews);

this.reliability = newReliability;
addAppliedReviewCount(newReviews.size());
user.updateReliability(newReliability);
}

private Reliability calculateNewReliability(final Reviews newReviews) {
final double currentReviewScoreSum = reliability.calculateReviewScoreSum(appliedReviewCount);
final double newReviewScoreSum = newReviews.addAllReviewScore();
final int allReviewCount = appliedReviewCount + newReviews.size();

final double newReliabilityValue = (currentReviewScoreSum + newReviewScoreSum) / allReviewCount;

return new Reliability(newReliabilityValue);
}

private void addAppliedReviewCount(final int newReviewCount) {
this.appliedReviewCount += newReviewCount;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.ddang.ddang.user.infrastructure.persistence;

import com.ddang.ddang.user.domain.ReliabilityUpdateHistory;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface JpaReliabilityUpdateHistoryRepository extends JpaRepository<ReliabilityUpdateHistory, Long> {

Optional<ReliabilityUpdateHistory> findFirstByOrderByIdDesc();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.ddang.ddang.user.infrastructure.persistence;

import com.ddang.ddang.user.domain.UserReliability;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface JpaUserReliabilityRepository extends JpaRepository<UserReliability, Long> {

Optional<UserReliability> findByUserId(final Long userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
create table reliability_update_history
(
id bigint not null auto_increment,
created_time datetime(6) not null,
last_applied_review_id bigint,
primary key (id)
);

create table user_reliability
(
id bigint not null auto_increment,
applied_review_count integer not null,
reliability float(53),
user_id bigint not null,
primary key (id)
);

alter table user_reliability add constraint fk_user_reliability_user foreign key (user_id) references users (id);
Loading
Loading