Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ docker-compose -f ./docker/monitoring-compose.yml up
Root
├── apps ( spring-applications )
│ ├── 📦 commerce-api
│ ├── 📦 commerce-batch
│ └── 📦 commerce-streamer
├── modules ( reusable-configurations )
│ ├── 📦 jpa
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,22 @@ public class RankingFacade {
@Transactional(readOnly = true)
public RankingResult getDailyRanking(LocalDate date, int page, int size, Long userId) {
List<RankingEntry> entries = rankingService.getTopN(date, page, size);
return buildResult(entries, date, page, size, userId);
}

@Transactional(readOnly = true)
public RankingResult getWeeklyRanking(LocalDate date, int page, int size, Long userId) {
List<RankingEntry> entries = rankingService.getWeeklyTopN(date, page, size);
return buildResult(entries, date, page, size, userId);
}

@Transactional(readOnly = true)
public RankingResult getMonthlyRanking(LocalDate date, int page, int size, Long userId) {
List<RankingEntry> entries = rankingService.getMonthlyTopN(date, page, size);
return buildResult(entries, date, page, size, userId);
}

private RankingResult buildResult(List<RankingEntry> entries, LocalDate date, int page, int size, Long userId) {
if (entries.isEmpty()) {
return RankingResult.empty(date, page, size);
}
Expand Down Expand Up @@ -72,5 +87,4 @@ public RankingResult getDailyRanking(LocalDate date, int page, int size, Long us

return new RankingResult(items, page, size, date);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.loopers.domain.ranking;

public interface ProductRankView {

Long getRefProductId();

Double getScore();
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,59 @@
package com.loopers.domain.ranking;

import java.time.LocalDate;
import java.util.List;
import com.loopers.domain.ranking.mv.MonthlyProductRank;
import com.loopers.domain.ranking.mv.MonthlyProductRankRepository;
import com.loopers.domain.ranking.mv.WeeklyProductRank;
import com.loopers.domain.ranking.mv.WeeklyProductRankRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

@Slf4j
@Service
@RequiredArgsConstructor
public class RankingService {

private final RankingRepository rankingRepository;
private final RankingKeyPolicy rankingKeyPolicy;
private final WeeklyProductRankRepository weeklyProductRankRepository;
private final MonthlyProductRankRepository monthlyProductRankRepository;

public List<RankingEntry> getTopN(LocalDate date, int page, int size) {
String key = rankingKeyPolicy.buildKey(date);
return rankingRepository.getTopN(key, page, size);
}

public List<RankingEntry> getWeeklyTopN(LocalDate date, int page, int size) {
String yearWeek = toYearWeek(date);
List<WeeklyProductRank> ranks = weeklyProductRankRepository.findByYearWeekOrderByScoreDesc(yearWeek, page, size);
return toRankingEntries(ranks, page, size);
}

public List<RankingEntry> getMonthlyTopN(LocalDate date, int page, int size) {
String yearMonth = date.format(DateTimeFormatter.ofPattern("yyyy-MM"));
List<MonthlyProductRank> ranks = monthlyProductRankRepository.findByYearMonthOrderByScoreDesc(yearMonth, page, size);
return toRankingEntries(ranks, page, size);
}

private List<RankingEntry> toRankingEntries(List<? extends ProductRankView> ranks, int page, int size) {
int baseRank = page * size;
List<RankingEntry> result = new ArrayList<>();
for (int i = 0; i < ranks.size(); i++) {
ProductRankView rank = ranks.get(i);
result.add(new RankingEntry(rank.getRefProductId(), rank.getScore(), baseRank + i + 1));
}
return result;
}

public Integer getRankOrNull(LocalDate date, Long productId) {
try {
String key = rankingKeyPolicy.buildKey(date);
Expand All @@ -30,4 +63,11 @@ public Integer getRankOrNull(LocalDate date, Long productId) {
return null;
}
}

private String toYearWeek(LocalDate date) {
WeekFields weekFields = WeekFields.of(Locale.getDefault());
int weekBasedYear = date.get(weekFields.weekBasedYear());
int weekOfYear = date.get(weekFields.weekOfWeekBasedYear());
return String.format("%d-W%02d", weekBasedYear, weekOfYear);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.loopers.domain.ranking.mv;

import com.loopers.domain.ranking.ProductRankView;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.Table;
import java.time.LocalDate;
import java.time.LocalDateTime;

@Entity
@Table(name = "mv_product_rank_monthly")
@IdClass(MonthlyProductRankId.class)
public class MonthlyProductRank implements ProductRankView {

@Id
@Column(name = "ref_product_id", nullable = false)
private Long refProductId;

@Id
@Column(name = "ranking_year_month", nullable = false)
private String yearMonth;

@Column(name = "score", nullable = false)
private Double score;

@Column(name = "period_start", nullable = false)
private LocalDate periodStart;

@Column(name = "period_end", nullable = false)
private LocalDate periodEnd;

@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;

protected MonthlyProductRank() {}

public Long getRefProductId() {
return refProductId;
}

public String getYearMonth() {
return yearMonth;
}

public Double getScore() {
return score;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.loopers.domain.ranking.mv;

import java.io.Serializable;
import java.util.Objects;

public class MonthlyProductRankId implements Serializable {

private Long refProductId;
private String yearMonth;

public MonthlyProductRankId() {}

public MonthlyProductRankId(Long refProductId, String yearMonth) {
this.refProductId = refProductId;
this.yearMonth = yearMonth;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MonthlyProductRankId that = (MonthlyProductRankId) o;
return Objects.equals(refProductId, that.refProductId) && Objects.equals(yearMonth, that.yearMonth);
}

@Override
public int hashCode() {
return Objects.hash(refProductId, yearMonth);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.loopers.domain.ranking.mv;

import java.util.List;

public interface MonthlyProductRankRepository {

List<MonthlyProductRank> findByYearMonthOrderByScoreDesc(String yearMonth, int page, int size);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.loopers.domain.ranking.mv;

import com.loopers.domain.ranking.ProductRankView;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.Table;
import java.time.LocalDate;
import java.time.LocalDateTime;

@Entity
@Table(name = "mv_product_rank_weekly")
@IdClass(WeeklyProductRankId.class)
public class WeeklyProductRank implements ProductRankView {

@Id
@Column(name = "ref_product_id", nullable = false)
private Long refProductId;

@Id
@Column(name = "ranking_year_week", nullable = false)
private String yearWeek;

@Column(name = "score", nullable = false)
private Double score;

@Column(name = "period_start", nullable = false)
private LocalDate periodStart;

@Column(name = "period_end", nullable = false)
private LocalDate periodEnd;

@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;

protected WeeklyProductRank() {}

public Long getRefProductId() {
return refProductId;
}

public String getYearWeek() {
return yearWeek;
}

public Double getScore() {
return score;
}

public LocalDate getPeriodStart() {
return periodStart;
}

public LocalDate getPeriodEnd() {
return periodEnd;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.loopers.domain.ranking.mv;

import java.io.Serializable;
import java.util.Objects;

public class WeeklyProductRankId implements Serializable {

private Long refProductId;
private String yearWeek;

public WeeklyProductRankId() {}

public WeeklyProductRankId(Long refProductId, String yearWeek) {
this.refProductId = refProductId;
this.yearWeek = yearWeek;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
WeeklyProductRankId that = (WeeklyProductRankId) o;
return Objects.equals(refProductId, that.refProductId) && Objects.equals(yearWeek, that.yearWeek);
}

@Override
public int hashCode() {
return Objects.hash(refProductId, yearWeek);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.loopers.domain.ranking.mv;

import java.util.List;

public interface WeeklyProductRankRepository {

List<WeeklyProductRank> findByYearWeekOrderByScoreDesc(String yearWeek, int page, int size);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.loopers.infrastructure.ranking.mv;

import com.loopers.domain.ranking.mv.MonthlyProductRank;
import com.loopers.domain.ranking.mv.MonthlyProductRankId;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MonthlyProductRankJpaRepository extends JpaRepository<MonthlyProductRank, MonthlyProductRankId> {

List<MonthlyProductRank> findByYearMonthOrderByScoreDesc(String yearMonth, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.loopers.infrastructure.ranking.mv;

import com.loopers.domain.ranking.mv.MonthlyProductRank;
import com.loopers.domain.ranking.mv.MonthlyProductRankRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class MonthlyProductRankRepositoryImpl implements MonthlyProductRankRepository {

private final MonthlyProductRankJpaRepository jpaRepository;

@Override
public List<MonthlyProductRank> findByYearMonthOrderByScoreDesc(String yearMonth, int page, int size) {
if (page < 0 || size < 1) {
throw new IllegalArgumentException("page는 0 이상, size는 1 이상이어야 합니다");
}
return jpaRepository.findByYearMonthOrderByScoreDesc(yearMonth, PageRequest.of(page, size));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.loopers.infrastructure.ranking.mv;

import com.loopers.domain.ranking.mv.WeeklyProductRank;
import com.loopers.domain.ranking.mv.WeeklyProductRankId;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface WeeklyProductRankJpaRepository extends JpaRepository<WeeklyProductRank, WeeklyProductRankId> {

List<WeeklyProductRank> findByYearWeekOrderByScoreDesc(String yearWeek, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.loopers.infrastructure.ranking.mv;

import com.loopers.domain.ranking.mv.WeeklyProductRank;
import com.loopers.domain.ranking.mv.WeeklyProductRankRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class WeeklyProductRankRepositoryImpl implements WeeklyProductRankRepository {

private final WeeklyProductRankJpaRepository jpaRepository;

@Override
public List<WeeklyProductRank> findByYearWeekOrderByScoreDesc(String yearWeek, int page, int size) {
if (page < 0 || size < 1) {
throw new IllegalArgumentException("page는 0 이상, size는 1 이상이어야 합니다");
}
return jpaRepository.findByYearWeekOrderByScoreDesc(yearWeek, PageRequest.of(page, size));
}
}
Loading