diff --git a/README.md b/README.md index 102dc4b..2cc1f62 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,13 @@ docker-compose -f ./docker/monitoring-compose.yml up ``` Root ├── apps ( spring-applications ) -│ └── 📦 commerce-api +│ ├── 📦 commerce-api +│ ├── 📦 commerce-streamer +│ └── 📦 pg-simulator ├── modules ( reusable-configurations ) │ ├── 📦 jpa │ ├── 📦 redis +│ ├── 📦 kafka │ ├── 📦 feign │ └── 📦 resilience4j └── supports ( add-ons ) diff --git a/apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java index 9420c52..6a1b69c 100644 --- a/apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java +++ b/apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java @@ -5,6 +5,8 @@ import com.loopers.domain.brand.BrandService; import com.loopers.domain.product.ProductResult; import com.loopers.domain.product.ProductService; +import com.loopers.domain.ranking.RankingCommand; +import com.loopers.domain.ranking.RankingService; import com.loopers.domain.user.UserService; import com.loopers.support.error.BusinessException; import com.loopers.support.error.CommonErrorType; @@ -16,6 +18,7 @@ import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; +import java.time.LocalDate; import java.util.Optional; @Component @@ -24,6 +27,7 @@ public class ProductFacade { private final ProductService productService; private final BrandService brandService; + private final RankingService rankingService; private final UserService userService; @Qualifier(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) @@ -38,6 +42,9 @@ public ProductOutput.GetProductDetail getProductDetail(ProductInput.GetProductDe BrandResult.GetBrand brand = brandService.getBrand(detail.brandId()).orElse(null); + RankingCommand.FindRank rankCommand = new RankingCommand.FindRank(LocalDate.now(), productId); + Long rank = rankingService.findRank(rankCommand).orElse(null); + // 회원이면 상품 조회 이벤트를 발행한다. Optional.ofNullable(input.getUserName()) .filter(StringUtils::hasText) @@ -48,7 +55,7 @@ public ProductOutput.GetProductDetail getProductDetail(ProductInput.GetProductDe )) )); - return ProductOutput.GetProductDetail.from(detail, brand); + return ProductOutput.GetProductDetail.from(detail, brand, rank); } } diff --git a/apps/commerce-api/src/main/java/com/loopers/application/product/ProductOutput.java b/apps/commerce-api/src/main/java/com/loopers/application/product/ProductOutput.java index 2621bfb..68e91cd 100644 --- a/apps/commerce-api/src/main/java/com/loopers/application/product/ProductOutput.java +++ b/apps/commerce-api/src/main/java/com/loopers/application/product/ProductOutput.java @@ -25,11 +25,15 @@ public static class GetProductDetail { private final String brandName; @Nullable private final String brandDescription; + @Nullable + private final Long rank; public static GetProductDetail from( ProductResult.GetProductDetail product, @Nullable - BrandResult.GetBrand brand + BrandResult.GetBrand brand, + @Nullable + Long rank ) { return GetProductDetail.builder() .productId(product.productId()) @@ -40,6 +44,7 @@ public static GetProductDetail from( .brandId(brand == null ? null : brand.getBrandId()) .brandName(brand == null ? null : brand.getBrandName()) .brandDescription(brand == null ? null : brand.getBrandDescription()) + .rank(rank) .build(); } diff --git a/apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.java new file mode 100644 index 0000000..d41c521 --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.java @@ -0,0 +1,41 @@ +package com.loopers.application.ranking; + +import com.loopers.domain.product.ProductResult; +import com.loopers.domain.product.ProductService; +import com.loopers.domain.ranking.RankingCommand; +import com.loopers.domain.ranking.RankingResult; +import com.loopers.domain.ranking.RankingService; +import com.loopers.support.error.BusinessException; +import com.loopers.support.error.CommonErrorType; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class RankingFacade { + + private final RankingService rankingService; + private final ProductService productService; + + public RankingOutput.SearchRankings searchRankings(RankingInput.SearchRankings input) { + RankingCommand.SearchRanks rankCommand = new RankingCommand.SearchRanks(input.date(), input.page(), input.size()); + RankingResult.SearchRanks ranks = rankingService.searchRanks(rankCommand); + + if (CollectionUtils.isEmpty(ranks.items())) { + return RankingOutput.SearchRankings.empty(ranks); + } + + List details = ranks.items() + .stream() + .map(rank -> productService.getProductDetail(rank.productId()) + .orElseThrow(() -> new BusinessException(CommonErrorType.NOT_FOUND)) + ) + .toList(); + + return RankingOutput.SearchRankings.from(ranks, details); + } + +} diff --git a/apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingInput.java b/apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingInput.java new file mode 100644 index 0000000..a2be596 --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingInput.java @@ -0,0 +1,14 @@ +package com.loopers.application.ranking; + +import java.time.LocalDate; + +public record RankingInput() { + + public record SearchRankings( + LocalDate date, + Integer page, + Integer size + ) { + } + +} diff --git a/apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingOutput.java b/apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingOutput.java new file mode 100644 index 0000000..96138da --- /dev/null +++ b/apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingOutput.java @@ -0,0 +1,51 @@ +package com.loopers.application.ranking; + +import com.loopers.domain.product.ProductResult; +import com.loopers.domain.ranking.RankingResult; + +import java.util.List; + +public record RankingOutput() { + + public record SearchRankings( + Integer totalPages, + Long totalItems, + Integer page, + Integer size, + List items + ) { + public static SearchRankings empty(RankingResult.SearchRanks result) { + return new SearchRankings(result.totalPages(), result.totalItems(), result.page(), result.size(), List.of()); + } + + public static SearchRankings from(RankingResult.SearchRanks ranks, List details) { + return new SearchRankings( + ranks.totalPages(), + ranks.totalItems(), + ranks.page(), + ranks.size(), + details.stream() + .map(detail -> new Item( + detail.productId(), + detail.productName(), + detail.basePrice(), + detail.likeCount(), + detail.brandId(), + detail.brandName() + )) + .toList() + ); + } + + public record Item( + Long productId, + String productName, + Integer basePrice, + Long likeCount, + Long brandId, + String brandName + ) { + } + } + +} diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductCacheRepository.java b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductCacheRepository.java index c366804..6efae37 100644 --- a/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductCacheRepository.java +++ b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductCacheRepository.java @@ -13,11 +13,11 @@ void saveProducts( Page page ); - Optional findProductDetailById(Long productId); + Optional findDetail(Long productId); - void saveProductDetail( + void saveDetail( Long productId, - ProductQueryResult.ProductDetail productDetail + ProductQueryResult.ProductDetail detail ); } diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductQueryResult.java b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductQueryResult.java index 0098fed..d37dbf3 100644 --- a/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductQueryResult.java +++ b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductQueryResult.java @@ -1,70 +1,56 @@ package com.loopers.domain.product; -import lombok.*; - import java.util.List; -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class ProductQueryResult { +public record ProductQueryResult() { - @Getter - @Builder - @RequiredArgsConstructor(access = AccessLevel.PRIVATE) - public static class Products { - private final Long productId; - private final String productName; - private final Integer basePrice; - private final Long likeCount; - private final Long brandId; - private final String brandName; + public record Products( + Long productId, + String productName, + Integer basePrice, + Long likeCount, + Long brandId, + String brandName + ) { } // ------------------------------------------------------------------------------------------------- - @Getter - @Builder - @RequiredArgsConstructor(access = AccessLevel.PRIVATE) - public static class ProductDetail { + public record ProductDetail( + Long productId, + String productName, + Integer basePrice, + Long likeCount, + Long brandId, + String brandName, + List