diff --git a/build.gradle b/build.gradle index 87e5e293..bd5fedb7 100644 --- a/build.gradle +++ b/build.gradle @@ -64,7 +64,7 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo' testImplementation 'org.mockito:mockito-core:4.8.0' - implementation 'io.github.lotteon-maven:blooming-blooms-utils:202312260132' + implementation 'io.github.lotteon-maven:blooming-blooms-utils:202312270111' testImplementation 'com.github.tomakehurst:wiremock:2.27.2' } diff --git a/src/main/java/kr/bb/product/config/AWSConfiguration.java b/src/main/java/kr/bb/product/config/AWSConfiguration.java index 5a51065b..2e8517ef 100644 --- a/src/main/java/kr/bb/product/config/AWSConfiguration.java +++ b/src/main/java/kr/bb/product/config/AWSConfiguration.java @@ -3,6 +3,7 @@ import autovalue.shaded.org.jetbrains.annotations.NotNull; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.regions.Regions; import com.amazonaws.services.sqs.AmazonSQSAsync; import com.amazonaws.services.sqs.AmazonSQSAsyncClientBuilder; import org.springframework.beans.factory.annotation.Value; @@ -25,7 +26,7 @@ public class AWSConfiguration { @Value("${cloud.aws.credentials.SECRET_ACCESS_KEY}") private String secretAccessKey; - // java1 + @NotNull private BasicAWSCredentials getBasicAWSCredentials() { return new BasicAWSCredentials(accessKeyId, secretAccessKey); @@ -42,7 +43,7 @@ public AwsCredentialsProvider getAwsCredentials() { @Bean public AmazonSQSAsync amazonSQSAsync() { return AmazonSQSAsyncClientBuilder.standard() - .withRegion(region) + .withRegion(Regions.AP_NORTHEAST_1) .withCredentials(new AWSStaticCredentialsProvider(getBasicAWSCredentials())) .build(); } diff --git a/src/main/java/kr/bb/product/domain/product/adapter/out/mongo/ProductCommandRepository.java b/src/main/java/kr/bb/product/domain/product/adapter/out/mongo/ProductCommandRepository.java index 8a812d8b..6751dd59 100644 --- a/src/main/java/kr/bb/product/domain/product/adapter/out/mongo/ProductCommandRepository.java +++ b/src/main/java/kr/bb/product/domain/product/adapter/out/mongo/ProductCommandRepository.java @@ -5,6 +5,7 @@ import kr.bb.product.common.dto.ReviewRegisterEvent; import kr.bb.product.domain.product.application.port.out.ProductCommandOutPort; import kr.bb.product.domain.product.entity.Product; +import kr.bb.product.domain.product.entity.ProductSaleStatus; import kr.bb.product.domain.product.mapper.ProductCommand.UpdateSubscriptionProduct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -64,4 +65,21 @@ public void updateProductSaleCount(List newOrderEvent) { } bulkOperations.execute(); } + + @Override + public void updateProductSaleStatus(Product product) { + mongoTemplate.updateFirst( + Query.query(Criteria.where("_id").is(product.getProductId())), + Update.update("is_deleted", true) + .set("product_sale_status", ProductSaleStatus.DISCONTINUED), + Product.class); + } + + @Override + public void updateProductSaleStatus(Product product, ProductSaleStatus productSaleStatus) { + mongoTemplate.updateFirst( + Query.query(Criteria.where("_id").is(product.getProductId())), + Update.update("product_sale_status", productSaleStatus), + Product.class); + } } diff --git a/src/main/java/kr/bb/product/domain/product/adapter/out/mongo/ProductQueryRepository.java b/src/main/java/kr/bb/product/domain/product/adapter/out/mongo/ProductQueryRepository.java index fe0c9d3e..1f8292da 100644 --- a/src/main/java/kr/bb/product/domain/product/adapter/out/mongo/ProductQueryRepository.java +++ b/src/main/java/kr/bb/product/domain/product/adapter/out/mongo/ProductQueryRepository.java @@ -11,12 +11,21 @@ import kr.bb.product.domain.product.mapper.ProductCommand; import kr.bb.product.domain.product.mapper.ProductCommand.SelectOption; import kr.bb.product.exception.errors.ProductNotFoundException; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.aggregation.TypedAggregation; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.support.PageableExecutionUtils; @@ -202,4 +211,36 @@ public Map> findProductsByProductIdsForCartItem(List return products.stream() .collect(Collectors.groupingBy(Product::getStoreId, Collectors.toList())); } + + @Override + public Map findStoreAverageRating() { + AggregationOperation groupByStoreId = + Aggregation.group("storeId").avg("averageRating").as("averageRating"); + TypedAggregation aggregation = + Aggregation.newAggregation(Product.class, groupByStoreId); + + // Execute the aggregation + AggregationResults aggregate = + mongoTemplate.aggregate(aggregation, AverageResult.class); + List averageResults = aggregate.getMappedResults(); + + // Logging for troubleshooting + averageResults.forEach( + result -> { + System.out.println( + "storeId: " + result.get_id() + ", averageRating: " + result.getAverageRating()); + }); + + return averageResults.stream() + .collect(Collectors.toMap(AverageResult::get_id, AverageResult::getAverageRating)); + } + + @Getter + @AllArgsConstructor + @Builder + @NoArgsConstructor(access = AccessLevel.PROTECTED) + private static class AverageResult { + private Long _id; + private Double averageRating; + } } diff --git a/src/main/java/kr/bb/product/domain/product/adapter/out/mongo/ProductRepository.java b/src/main/java/kr/bb/product/domain/product/adapter/out/mongo/ProductRepository.java deleted file mode 100644 index fa75fbf3..00000000 --- a/src/main/java/kr/bb/product/domain/product/adapter/out/mongo/ProductRepository.java +++ /dev/null @@ -1,57 +0,0 @@ -package kr.bb.product.domain.product.adapter.out.mongo; - -import kr.bb.product.domain.product.application.port.out.ProductOutPort; -import kr.bb.product.domain.product.entity.Product; -import kr.bb.product.domain.product.entity.ProductSaleStatus; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class ProductRepository implements ProductOutPort { - private final MongoTemplate mongoTemplate; - private final ProductMongoRepository productMongoRepository; - - @Override - public Page findByCategory(Long categoryId, Pageable pageable) { - // TODO: sort - pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()); - return productMongoRepository.findByCategoryId(categoryId, pageable); - } - - @Override - public Page findProductsByTagId(Long tagId, Pageable pageable) { - // TODO: sort - pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()); - return productMongoRepository.findProductsByTagId(tagId, pageable); - } - - @Override - public void updateProductSaleStatus(Product product, ProductSaleStatus productSaleStatus) { - mongoTemplate.updateFirst( - Query.query(Criteria.where("_id").is(product.getProductId())), - Update.update("product_sale_status", productSaleStatus), - Product.class); - } - - @Override - public void updateProductSaleStatus(Product product) { - mongoTemplate.updateFirst( - Query.query(Criteria.where("_id").is(product.getProductId())), - Update.update("is_deleted", true) - .set("product_sale_status", ProductSaleStatus.DISCONTINUED), - Product.class); - } - - @Override - public void createProduct(Product productRequestToEntity) { - productMongoRepository.save(productRequestToEntity); - } -} diff --git a/src/main/java/kr/bb/product/domain/product/application/handler/ProductQueryHandler.java b/src/main/java/kr/bb/product/domain/product/application/handler/ProductQueryHandler.java new file mode 100644 index 00000000..558d9a56 --- /dev/null +++ b/src/main/java/kr/bb/product/domain/product/application/handler/ProductQueryHandler.java @@ -0,0 +1,20 @@ +package kr.bb.product.domain.product.application.handler; + +import java.util.Map; +import kr.bb.product.domain.product.application.usecase.ProductQueryUseCase; +import kr.bb.product.domain.product.infrastructure.event.ProductKafkaProcessor; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ProductQueryHandler { + private static final String STORE_AVERAGE_RATING_UPDATE_TOPIC = "store-average-rating-update"; + private final ProductQueryUseCase productQueryUseCase; + private final ProductKafkaProcessor> productKafkaProcessor; + + public void getStoreAverageRating() { + Map storeAverageRating = productQueryUseCase.getStoreAverageRating(); + productKafkaProcessor.send(STORE_AVERAGE_RATING_UPDATE_TOPIC, storeAverageRating); + } +} diff --git a/src/main/java/kr/bb/product/domain/product/application/port/in/ProductCommandInputPort.java b/src/main/java/kr/bb/product/domain/product/application/port/in/ProductCommandInputPort.java index acb333d0..29e92f54 100644 --- a/src/main/java/kr/bb/product/domain/product/application/port/in/ProductCommandInputPort.java +++ b/src/main/java/kr/bb/product/domain/product/application/port/in/ProductCommandInputPort.java @@ -8,7 +8,6 @@ import kr.bb.product.domain.flower.mapper.FlowerCommand.ProductFlowers; import kr.bb.product.domain.flower.mapper.FlowerCommand.ProductFlowersRequestData; import kr.bb.product.domain.product.application.port.out.ProductCommandOutPort; -import kr.bb.product.domain.product.application.port.out.ProductOutPort; import kr.bb.product.domain.product.application.port.out.ProductQueryOutPort; import kr.bb.product.domain.product.application.usecase.ProductCommandUseCase; import kr.bb.product.domain.product.entity.Product; @@ -30,7 +29,6 @@ @RequiredArgsConstructor @Transactional(readOnly = true) public class ProductCommandInputPort implements ProductCommandUseCase { - private final ProductOutPort productOutPort; private final ProductMapper productMapper; private final TagRepository tagRepository; private final CategoryRepository categoryRepository; @@ -78,14 +76,14 @@ public void updateProductSaleStatus( String productId, ProductCommand.ProductUpdate productRequestData) { Product product = productQueryOutPort.findByProductId(productId); if (productRequestData.getProductSaleStatus().equals(ProductSaleStatus.DELETED)) { - productOutPort.updateProductSaleStatus(product); + productCommandOutPort.updateProductSaleStatus(product); } else if (productRequestData.getProductSaleStatus().equals(ProductSaleStatus.SALE)) { // sqs 재입고 알림 조회 요청 publishMessageToSQS.publishProductResaleNotificationCheckQueue( productId, product.getProductName()); - productOutPort.updateProductSaleStatus(product, productRequestData.getProductSaleStatus()); + productCommandOutPort.updateProductSaleStatus(product, productRequestData.getProductSaleStatus()); } else { - productOutPort.updateProductSaleStatus(product, productRequestData.getProductSaleStatus()); + productCommandOutPort.updateProductSaleStatus(product, productRequestData.getProductSaleStatus()); } } @@ -102,7 +100,7 @@ public void createProduct(ProductCommand.ProductRegister productRequestData) { ProductFlowersRequestData representativeFlower = productRequestData.getRepresentativeFlower(); List flowers = getFlowers(productRequestData, representativeFlower); - productOutPort.createProduct( + productCommandOutPort.createProduct( productMapper.createProductRequestToEntity(productRequestData, category, tags, flowers)); } diff --git a/src/main/java/kr/bb/product/domain/product/application/port/in/ProductQueryInputPort.java b/src/main/java/kr/bb/product/domain/product/application/port/in/ProductQueryInputPort.java index 44003974..2b41c6ef 100644 --- a/src/main/java/kr/bb/product/domain/product/application/port/in/ProductQueryInputPort.java +++ b/src/main/java/kr/bb/product/domain/product/application/port/in/ProductQueryInputPort.java @@ -8,7 +8,6 @@ import bloomingblooms.domain.wishlist.cart.GetUserCartItemsResponse; import bloomingblooms.domain.wishlist.likes.LikedProductInfoResponse; import bloomingblooms.errors.EntityNotFoundException; -import com.fasterxml.jackson.databind.ObjectMapper; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -21,6 +20,9 @@ import kr.bb.product.domain.product.application.port.out.ProductQueryOutPort; import kr.bb.product.domain.product.application.usecase.ProductQueryUseCase; import kr.bb.product.domain.product.entity.Product; +import kr.bb.product.domain.product.entity.ProductSaleStatus; +import kr.bb.product.domain.product.infrastructure.client.StoreServiceClient; +import kr.bb.product.domain.product.infrastructure.client.WishlistServiceClient; import kr.bb.product.domain.product.mapper.ProductCommand; import kr.bb.product.domain.product.mapper.ProductCommand.BestSellerTopTen; import kr.bb.product.domain.product.mapper.ProductCommand.LanguageOfFlower; @@ -37,9 +39,6 @@ import kr.bb.product.domain.product.mapper.ProductCommand.StoreProductDetail; import kr.bb.product.domain.product.mapper.ProductCommand.StoreProductList; import kr.bb.product.domain.product.mapper.ProductCommand.SubscriptionProductForCustomer; -import kr.bb.product.domain.product.entity.ProductSaleStatus; -import kr.bb.product.domain.product.infrastructure.client.StoreServiceClient; -import kr.bb.product.domain.product.infrastructure.client.WishlistServiceClient; import kr.bb.product.domain.review.application.port.out.ReviewQueryOutPort; import kr.bb.product.exception.errors.ProductPriceValidationException; import lombok.RequiredArgsConstructor; @@ -66,7 +65,6 @@ public class ProductQueryInputPort implements ProductQueryUseCase { private final ProductQueryOutPort productQueryOutPort; private final FlowerQueryOutPort flowerQueryOutPort; private final ReviewQueryOutPort reviewQueryOutPort; - private final ObjectMapper objectMapper; @NotNull private static Pageable getPageable(Pageable pageable, ProductCommand.SortOption sortOption) { @@ -310,6 +308,11 @@ public List getProductInformationForLikes(List productQueryOutPort.findProductByProductIds(productIds)); } + @Override + public Map getStoreAverageRating() { + return productQueryOutPort.findStoreAverageRating(); + } + @Override public GetUserCartItemsResponse getCartItemProductInformations(Map productIds) { Map> productsByProductIdsForCartItem = diff --git a/src/main/java/kr/bb/product/domain/product/application/port/out/ProductCommandOutPort.java b/src/main/java/kr/bb/product/domain/product/application/port/out/ProductCommandOutPort.java index 267eacb4..1e34459a 100644 --- a/src/main/java/kr/bb/product/domain/product/application/port/out/ProductCommandOutPort.java +++ b/src/main/java/kr/bb/product/domain/product/application/port/out/ProductCommandOutPort.java @@ -4,6 +4,7 @@ import kr.bb.product.common.dto.NewOrderEvent.ProductCount; import kr.bb.product.common.dto.ReviewRegisterEvent; import kr.bb.product.domain.product.entity.Product; +import kr.bb.product.domain.product.entity.ProductSaleStatus; import kr.bb.product.domain.product.mapper.ProductCommand; public interface ProductCommandOutPort { @@ -14,4 +15,8 @@ public interface ProductCommandOutPort { void updateProductReviewData(ReviewRegisterEvent reviewRegisterEvent); void updateProductSaleCount(List newOrderEvent); + + void updateProductSaleStatus(Product product); + + void updateProductSaleStatus(Product product, ProductSaleStatus productSaleStatus); } diff --git a/src/main/java/kr/bb/product/domain/product/application/port/out/ProductOutPort.java b/src/main/java/kr/bb/product/domain/product/application/port/out/ProductOutPort.java deleted file mode 100644 index a413faf7..00000000 --- a/src/main/java/kr/bb/product/domain/product/application/port/out/ProductOutPort.java +++ /dev/null @@ -1,18 +0,0 @@ -package kr.bb.product.domain.product.application.port.out; - -import kr.bb.product.domain.product.entity.Product; -import kr.bb.product.domain.product.entity.ProductSaleStatus; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -public interface ProductOutPort { - void updateProductSaleStatus(Product product, ProductSaleStatus productSaleStatus); - - void updateProductSaleStatus(Product product); - - void createProduct(Product productRequestToEntity); - - Page findByCategory(Long categoryId, Pageable pageable); - - Page findProductsByTagId(Long tagId, Pageable pageable); -} diff --git a/src/main/java/kr/bb/product/domain/product/application/port/out/ProductQueryOutPort.java b/src/main/java/kr/bb/product/domain/product/application/port/out/ProductQueryOutPort.java index 43066abd..0d64642a 100644 --- a/src/main/java/kr/bb/product/domain/product/application/port/out/ProductQueryOutPort.java +++ b/src/main/java/kr/bb/product/domain/product/application/port/out/ProductQueryOutPort.java @@ -4,8 +4,8 @@ import java.util.List; import java.util.Map; import kr.bb.product.domain.product.entity.Product; -import kr.bb.product.domain.product.mapper.ProductCommand; import kr.bb.product.domain.product.entity.ProductSaleStatus; +import kr.bb.product.domain.product.mapper.ProductCommand; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -38,4 +38,6 @@ Page findStoreProducts( Map findProductNameByProductIdsForReviewByUserId(List productIds); Map> findProductsByProductIdsForCartItem(List productId); + + Map findStoreAverageRating(); } diff --git a/src/main/java/kr/bb/product/domain/product/application/usecase/ProductQueryUseCase.java b/src/main/java/kr/bb/product/domain/product/application/usecase/ProductQueryUseCase.java index b853b8bf..0e3d3281 100644 --- a/src/main/java/kr/bb/product/domain/product/application/usecase/ProductQueryUseCase.java +++ b/src/main/java/kr/bb/product/domain/product/application/usecase/ProductQueryUseCase.java @@ -76,4 +76,6 @@ ProductCommand.SubscriptionProductForCustomer getSubscriptionProductDetail( GetUserCartItemsResponse getCartItemProductInformations(Map productIds); List getProductInformationForLikes(List productIds); + + Map getStoreAverageRating(); } diff --git a/src/main/java/kr/bb/product/domain/product/infrastructure/event/ProductKafkaProcessor.java b/src/main/java/kr/bb/product/domain/product/infrastructure/event/ProductKafkaProcessor.java new file mode 100644 index 00000000..8241d7fe --- /dev/null +++ b/src/main/java/kr/bb/product/domain/product/infrastructure/event/ProductKafkaProcessor.java @@ -0,0 +1,15 @@ +package kr.bb.product.domain.product.infrastructure.event; + +import lombok.RequiredArgsConstructor; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ProductKafkaProcessor { + private final KafkaTemplate kafkaTemplate; + + public void send(String topicName, T data) { + kafkaTemplate.send(topicName, data); + } +} diff --git a/src/main/java/kr/bb/product/domain/product/adapter/in/api/ProductRestController.java b/src/main/java/kr/bb/product/domain/product/infrastructure/http/api/ProductRestController.java similarity index 99% rename from src/main/java/kr/bb/product/domain/product/adapter/in/api/ProductRestController.java rename to src/main/java/kr/bb/product/domain/product/infrastructure/http/api/ProductRestController.java index af180e41..22c48163 100644 --- a/src/main/java/kr/bb/product/domain/product/adapter/in/api/ProductRestController.java +++ b/src/main/java/kr/bb/product/domain/product/infrastructure/http/api/ProductRestController.java @@ -1,4 +1,4 @@ -package kr.bb.product.domain.product.adapter.in.api; +package kr.bb.product.domain.product.infrastructure.http.api; import bloomingblooms.response.CommonResponse; import java.util.Optional; diff --git a/src/main/java/kr/bb/product/domain/product/adapter/in/client/ProductFeignClientController.java b/src/main/java/kr/bb/product/domain/product/infrastructure/http/client/ProductFeignClientController.java similarity index 96% rename from src/main/java/kr/bb/product/domain/product/adapter/in/client/ProductFeignClientController.java rename to src/main/java/kr/bb/product/domain/product/infrastructure/http/client/ProductFeignClientController.java index a99eff60..b5a8e015 100644 --- a/src/main/java/kr/bb/product/domain/product/adapter/in/client/ProductFeignClientController.java +++ b/src/main/java/kr/bb/product/domain/product/infrastructure/http/client/ProductFeignClientController.java @@ -1,4 +1,4 @@ -package kr.bb.product.domain.product.adapter.in.client; +package kr.bb.product.domain.product.infrastructure.http.client; import bloomingblooms.domain.product.IsProductPriceValid; import bloomingblooms.domain.product.ProductInfoDto; @@ -59,7 +59,7 @@ public CommonResponse getSubscriptionProductInformation( return CommonResponse.success(productQueryUseCase.getSubscriptionProductInformation(productId)); } - @PostMapping("product/likes") + @PostMapping("products/likes") public CommonResponse> getProductInformationForLikes( @RequestBody List productId) { return CommonResponse.success(productQueryUseCase.getProductInformationForLikes(productId)); diff --git a/src/main/java/kr/bb/product/domain/product/infrastructure/message/ProductSQSListener.java b/src/main/java/kr/bb/product/domain/product/infrastructure/message/ProductSQSListener.java index 9294d052..d8000c54 100644 --- a/src/main/java/kr/bb/product/domain/product/infrastructure/message/ProductSQSListener.java +++ b/src/main/java/kr/bb/product/domain/product/infrastructure/message/ProductSQSListener.java @@ -7,6 +7,7 @@ import kr.bb.product.common.dto.NewOrderEvent; import kr.bb.product.common.dto.ReviewRegisterEvent; import kr.bb.product.domain.product.application.handler.ProductCommandHandler; +import kr.bb.product.domain.product.application.handler.ProductQueryHandler; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.aws.messaging.listener.Acknowledgment; @@ -21,7 +22,8 @@ @RequiredArgsConstructor public class ProductSQSListener { private final ObjectMapper objectMapper; - private final ProductCommandHandler productHandler; + private final ProductCommandHandler productCommandHandler; + private final ProductQueryHandler productQueryHandler; /** * 상품 리뷰 작성 시 상품 리뷰 정보 업데이트 @@ -42,7 +44,7 @@ public void consumeProductReviewDataUpdateQueue( ReviewRegisterEvent reviewRegisterEvent = objectMapper.readValue(messageFromSNS, ReviewRegisterEvent.class); - productHandler.updateReviewData(reviewRegisterEvent); + productCommandHandler.updateReviewData(reviewRegisterEvent); ack.acknowledge(); } @@ -64,7 +66,7 @@ public void consumeSaleCountUpdateQueue( NewOrderEvent newOrderEvent = objectMapper.readValue(messageFromSNS, NewOrderEvent.class); - productHandler.saleCountUpdate(newOrderEvent); + productCommandHandler.saleCountUpdate(newOrderEvent); ack.acknowledge(); } @@ -72,4 +74,15 @@ private String getMessageFromSNS(String message) throws JsonProcessingException JsonNode jsonNode = objectMapper.readTree(message); return jsonNode.get("Message").asText(); } + + @SqsListener( + value = "${cloud.aws.sqs.store-average-rating-update-queue.name}", + deletionPolicy = SqsMessageDeletionPolicy.NEVER) + public void consumeStoreAverageRatingUpdateQueue( + @Payload String message, @Headers Map headers, Acknowledgment ack) + throws JsonProcessingException { + + productQueryHandler.getStoreAverageRating(); + ack.acknowledge(); + } } diff --git a/src/main/java/kr/bb/product/domain/review/adapter/in/api/ReviewRestController.java b/src/main/java/kr/bb/product/domain/review/infrastructure/http/api/ReviewRestController.java similarity index 98% rename from src/main/java/kr/bb/product/domain/review/adapter/in/api/ReviewRestController.java rename to src/main/java/kr/bb/product/domain/review/infrastructure/http/api/ReviewRestController.java index 04ff9fbc..a7ab80dc 100644 --- a/src/main/java/kr/bb/product/domain/review/adapter/in/api/ReviewRestController.java +++ b/src/main/java/kr/bb/product/domain/review/infrastructure/http/api/ReviewRestController.java @@ -1,4 +1,4 @@ -package kr.bb.product.domain.review.adapter.in.api; +package kr.bb.product.domain.review.infrastructure.http.api; import bloomingblooms.response.CommonResponse; import kr.bb.product.domain.review.application.handler.ReviewCommandHandler; diff --git a/src/test/java/kr/bb/product/domain/product/adapter/out/mongo/ProductQueryRepositoryTest.java b/src/test/java/kr/bb/product/domain/product/adapter/out/mongo/ProductQueryRepositoryTest.java index be7af440..7f93ec54 100644 --- a/src/test/java/kr/bb/product/domain/product/adapter/out/mongo/ProductQueryRepositoryTest.java +++ b/src/test/java/kr/bb/product/domain/product/adapter/out/mongo/ProductQueryRepositoryTest.java @@ -12,9 +12,9 @@ import kr.bb.product.domain.flower.mapper.FlowerCommand.ProductFlowers; import kr.bb.product.domain.product.application.port.out.ProductQueryOutPort; import kr.bb.product.domain.product.entity.Product; +import kr.bb.product.domain.product.entity.ProductSaleStatus; import kr.bb.product.domain.product.mapper.ProductCommand.RepresentativeFlowerId; import kr.bb.product.domain.product.mapper.ProductCommand.SelectOption; -import kr.bb.product.domain.product.entity.ProductSaleStatus; import kr.bb.product.domain.tag.entity.Tag; import kr.bb.product.exception.errors.ProductNotFoundException; import org.junit.jupiter.api.DisplayName; @@ -31,7 +31,6 @@ @Transactional class ProductQueryRepositoryTest { @Autowired ProductCommandRepository productCommandRepository; - @Autowired ProductRepository productRepository; @Autowired EntityManager em; @MockBean SimpleMessageListenerContainer simpleMessageListenerContainer; @Autowired private ProductMongoRepository productMongoRepository; @@ -280,4 +279,22 @@ void findProductsByProductIds() { assertThat(productsByProductIds.size()).isEqualTo(1); assertThat(productsByProductIds.get(1L).size()).isEqualTo(2); } + + @Test + @DisplayName("가게별 평균 평점 업데이트") + void findStoreAverageRating() { + productMongoRepository.deleteAll(); + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + Product product = + Product.builder().productId("i" + i + j).averageRating(1.0 + i).storeId(1L + i).build(); + productMongoRepository.save(product); + } + } + + Map storeAverageRating = productQueryOutPort.findStoreAverageRating(); + + assertThat(storeAverageRating.keySet().size()).isEqualTo(3); + } } diff --git a/src/test/java/kr/bb/product/domain/product/adapter/out/mongo/ProductRepositoryTest.java b/src/test/java/kr/bb/product/domain/product/adapter/out/mongo/ProductRepositoryTest.java deleted file mode 100644 index 3393f4ed..00000000 --- a/src/test/java/kr/bb/product/domain/product/adapter/out/mongo/ProductRepositoryTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package kr.bb.product.domain.product.adapter.out.mongo; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import java.util.ArrayList; -import java.util.List; -import kr.bb.product.domain.flower.mapper.FlowerCommand.ProductFlowersRequestData; -import kr.bb.product.domain.product.application.port.in.ProductCommandInputPort; -import kr.bb.product.domain.product.application.port.out.ProductOutPort; -import kr.bb.product.domain.product.entity.Product; -import kr.bb.product.domain.product.mapper.ProductCommand; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Slice; -import org.springframework.transaction.annotation.Transactional; - -@SpringBootTest -@Transactional -class ProductRepositoryTest { - @MockBean SimpleMessageListenerContainer simpleMessageListenerContainer; - @Autowired private ProductOutPort productOutPort; - @Autowired private ProductCommandInputPort productStoreInputPort; - - @Test - @DisplayName("상품 리스트 조회: 카테고리별") - void productByCategory() { - for (int i = 0; i < 10; i++) { - List tagList = new ArrayList<>(); - tagList.add(1L); - tagList.add(2L); - - List list = new ArrayList<>(); - list.add(ProductFlowersRequestData.builder().flowerId(1L).flowerCount(2L).build()); - list.add(ProductFlowersRequestData.builder().flowerId(3L).flowerCount(3L).build()); - list.add(ProductFlowersRequestData.builder().flowerId(2L).flowerCount(2L).build()); - - ProductCommand.ProductRegister product = - ProductCommand.ProductRegister.builder() - .categoryId(1L) - .productTag(tagList) - .representativeFlower( - ProductFlowersRequestData.builder().flowerCount(3L).flowerId(1L).build()) - .flowers(list) - .productName("Example Product") - .productSummary("Product Summary") - .productDescriptionImage("image") - .productThumbnail("thumbnail") - .productPrice(100L) - .productDescriptionImage("image_url") - .build(); - productStoreInputPort.createProduct(product); - } - - PageRequest pageRequest = PageRequest.of(0, 2); - Slice byCategory = productOutPort.findByCategory(1L, pageRequest); - List content = byCategory.getContent(); - assertThat(content.size()).isEqualTo(2); - } - - @Test - @DisplayName("태그별 상품 리스트 조회") - void findProductByTagId() { - List tagList = new ArrayList<>(); - tagList.add(1L); - tagList.add(2L); - - List list = new ArrayList<>(); - list.add(ProductFlowersRequestData.builder().flowerId(1L).flowerCount(2L).build()); - list.add(ProductFlowersRequestData.builder().flowerId(3L).flowerCount(3L).build()); - list.add(ProductFlowersRequestData.builder().flowerId(2L).flowerCount(2L).build()); - - ProductCommand.ProductRegister product = - ProductCommand.ProductRegister.builder() - .categoryId(1L) - .productTag(tagList) - .representativeFlower( - ProductFlowersRequestData.builder().flowerCount(3L).flowerId(1L).build()) - .flowers(list) - .productName("Example Product") - .productSummary("Product Summary") - .productDescriptionImage("image") - .productThumbnail("thumbnail") - .productPrice(100L) - .productDescriptionImage("image_url") - .build(); - productStoreInputPort.createProduct(product); - PageRequest pageRequest = PageRequest.of(0, 2); - Page productsByTagId = productOutPort.findProductsByTagId(2L, pageRequest); - List content = productsByTagId.getContent(); - assertThat(content.get(0).getProductSummary()).isEqualTo(product.getProductSummary()); - } -} diff --git a/src/test/java/kr/bb/product/domain/product/application/port/in/ProductFindInputPortTest.java b/src/test/java/kr/bb/product/domain/product/application/port/in/ProductFindInputPortTest.java index c7d76a1a..3fdd8f50 100644 --- a/src/test/java/kr/bb/product/domain/product/application/port/in/ProductFindInputPortTest.java +++ b/src/test/java/kr/bb/product/domain/product/application/port/in/ProductFindInputPortTest.java @@ -1,26 +1,14 @@ package kr.bb.product.domain.product.application.port.in; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - import java.util.ArrayList; import java.util.List; import kr.bb.product.domain.flower.mapper.FlowerCommand.ProductFlowersRequestData; -import kr.bb.product.domain.product.adapter.out.mongo.ProductMongoRepository; -import kr.bb.product.domain.product.application.port.out.ProductOutPort; -import kr.bb.product.domain.product.entity.Product; import kr.bb.product.domain.product.infrastructure.client.WishlistServiceClient; import kr.bb.product.domain.product.mapper.ProductCommand; -import kr.bb.product.domain.product.mapper.ProductCommand.ProductList; -import kr.bb.product.domain.product.mapper.ProductCommand.ProductListItem; -import kr.bb.product.domain.product.mapper.ProductCommand.SortOption; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.transaction.annotation.Transactional; @SpringBootTest @@ -28,31 +16,7 @@ class ProductFindInputPortTest { @MockBean SimpleMessageListenerContainer simpleMessageListenerContainer; @Autowired WishlistServiceClient wishlistServiceClient; - @Autowired private ProductOutPort productOutPort; @Autowired private ProductCommandInputPort productStoreInputPort; - @Autowired private ProductQueryInputPort productFindInputPort; - @Autowired private ProductMongoRepository productMongoRepository; - - @Test - @DisplayName("상품 카테고리 조회 페이징") - void getProductsByCategory() { - // given - extracted(); - - // when - PageRequest pageRequest = PageRequest.of(0, 2); - Page byCategory = productOutPort.findByCategory(1L, pageRequest); - - // 반환 객체로 변환 - List productByCategories = ProductList.fromEntity(byCategory.getContent()); - ProductList data = ProductList.getData(productByCategories, byCategory.getTotalPages()); - - assertThat(data.getTotalCnt()).isEqualTo(5); - // 찜 생략 - // List data1 = - // wishlistServiceClient.getProductsMemberLikes(1L, productByCategories).getData(); - // System.out.println(data1.toString()); - } private void extracted() { for (int i = 0; i < 10; i++) { @@ -82,16 +46,4 @@ private void extracted() { productStoreInputPort.createProduct(product); } } - - @Test - @DisplayName("태그별 상품 리스트 조회") - void getProductListByTagId() { - productMongoRepository.deleteAll(); - extracted(); - PageRequest pageRequest = PageRequest.of(0, 3); - Page productsByTagId = productOutPort.findProductsByTagId(1L, pageRequest); - ProductList productsByTag = - productFindInputPort.getProductsByTag(1L, 1L, SortOption.LOW, pageRequest); - assertThat(productsByTag.getProducts().size()).isEqualTo(3); - } } diff --git a/src/test/java/kr/bb/product/domain/product/application/port/in/ProductStoreInputPortTest.java b/src/test/java/kr/bb/product/domain/product/application/port/in/ProductStoreInputPortTest.java index acb6c8cf..b194511e 100644 --- a/src/test/java/kr/bb/product/domain/product/application/port/in/ProductStoreInputPortTest.java +++ b/src/test/java/kr/bb/product/domain/product/application/port/in/ProductStoreInputPortTest.java @@ -6,7 +6,6 @@ import java.util.List; import kr.bb.product.domain.flower.mapper.FlowerCommand.ProductFlowersRequestData; import kr.bb.product.domain.product.adapter.out.mongo.ProductMongoRepository; -import kr.bb.product.domain.product.application.port.out.ProductOutPort; import kr.bb.product.domain.product.application.port.out.ProductQueryOutPort; import kr.bb.product.domain.product.entity.Product; import kr.bb.product.domain.product.entity.ProductSaleStatus; @@ -24,7 +23,6 @@ class ProductStoreInputPortTest { @MockBean SimpleMessageListenerContainer simpleMessageListenerContainer; @Autowired private ProductCommandInputPort productStoreInputPort; - @Autowired private ProductOutPort productOutPort; @Autowired private ProductMongoRepository productMongoRepository; @Autowired private ProductQueryOutPort productQueryOutPort; diff --git a/src/test/java/kr/bb/product/domain/product/repository/mongo/ProductMongoRepositoryTest.java b/src/test/java/kr/bb/product/domain/product/repository/mongo/ProductMongoRepositoryTest.java deleted file mode 100644 index 45544c50..00000000 --- a/src/test/java/kr/bb/product/domain/product/repository/mongo/ProductMongoRepositoryTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package kr.bb.product.domain.product.repository.mongo; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import kr.bb.product.domain.category.entity.Category; -import kr.bb.product.domain.flower.mapper.FlowerCommand.ProductFlowers; -import kr.bb.product.domain.product.adapter.out.mongo.ProductMongoRepository; -import kr.bb.product.domain.product.application.port.out.ProductOutPort; -import kr.bb.product.domain.product.application.port.out.ProductQueryOutPort; -import kr.bb.product.domain.product.entity.Product; -import kr.bb.product.domain.product.entity.ProductSaleStatus; -import kr.bb.product.domain.tag.entity.Tag; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.transaction.annotation.Transactional; - -@SpringBootTest -@Transactional -class ProductMongoRepositoryTest { - @MockBean SimpleMessageListenerContainer simpleMessageListenerContainer; - @Autowired MongoTemplate mongoTemplate; - @Autowired ProductOutPort productOutPort; - @Autowired ProductMongoRepository productMongoRepository; - - @Autowired - ProductQueryOutPort productQueryOutPort; - - @Test - @DisplayName("상품 판매 상태를 DISCONTINUED로 변경") - void updateSaleStatus() { - List tagList = new ArrayList<>(); - tagList.add(Tag.builder().tagId(1L).tagName("tagname").build()); - - List list = new ArrayList<>(); - list.add(ProductFlowers.builder().isRepresentative(true).flowerId(2L).flowerCount(2L).build()); - list.add(ProductFlowers.builder().flowerId(3L).flowerCount(2L).build()); - list.add(ProductFlowers.builder().flowerId(4L).flowerCount(2L).build()); - Product product = - Product.builder() - .category(Category.builder().categoryName("category").categoryId(1L).build()) - .productName("Example Product") - .productSummary("Product Summary") - .productPrice(100L) - .productSaleStatus(ProductSaleStatus.SALE) - .tag(tagList) - .productFlowers(list) - .productDescriptionImage("image_url") - .reviewCount(5L) - .productSaleAmount(50L) - .averageRating(4.5) - .storeId(1L) - .createdAt(LocalDateTime.now()) - .updatedAt(LocalDateTime.now()) - .build(); - - Product save = productMongoRepository.save(product); - - productOutPort.updateProductSaleStatus(save, ProductSaleStatus.DISCONTINUED); - - Product product1 = productQueryOutPort.findByProductId(save.getProductId()); - - System.out.println("FIND " + product1); - - Product updatedProduct = productQueryOutPort.findByProductId(save.getProductId()); - - assertThat(updatedProduct.getProductSaleStatus()).isEqualTo(ProductSaleStatus.DISCONTINUED); - } -} diff --git a/src/test/java/kr/bb/product/repository/ProductMongoRepositoryTest.java b/src/test/java/kr/bb/product/repository/ProductMongoRepositoryTest.java index 4ef7ae2f..4a11f2f7 100644 --- a/src/test/java/kr/bb/product/repository/ProductMongoRepositoryTest.java +++ b/src/test/java/kr/bb/product/repository/ProductMongoRepositoryTest.java @@ -17,7 +17,6 @@ import kr.bb.product.domain.category.entity.Category; import kr.bb.product.domain.flower.mapper.FlowerCommand.ProductFlowers; import kr.bb.product.domain.product.adapter.out.mongo.ProductMongoRepository; -import kr.bb.product.domain.product.application.port.out.ProductOutPort; import kr.bb.product.domain.product.entity.Product; import kr.bb.product.domain.product.entity.ProductSaleStatus; import kr.bb.product.domain.tag.entity.Tag; @@ -35,13 +34,10 @@ @Transactional class ProductMongoRepositoryTest { private static final String CONNECTION_STRING = "mongodb://%s:%d"; - @MockBean - SimpleMessageListenerContainer simpleMessageListenerContainer; - @Autowired - ProductMongoRepository productMongoRepository; + @MockBean SimpleMessageListenerContainer simpleMessageListenerContainer; + @Autowired ProductMongoRepository productMongoRepository; + @Autowired MongoTemplate mongoTemplate; private MongodExecutable mongodExecutable; - private MongoTemplate mongoTemplate; - @Autowired private ProductOutPort productOutPort; @AfterEach void clean() { @@ -63,8 +59,7 @@ void setup() throws Exception { mongodExecutable = starter.prepare(mongodConfig); mongodExecutable.start(); mongoTemplate = - new MongoTemplate( - MongoClients.create(String.format(CONNECTION_STRING, ip, port)), "local"); + new MongoTemplate(MongoClients.create(String.format(CONNECTION_STRING, ip, port)), "local"); } @Test diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index dd0a5d63..140ffcd6 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -60,5 +60,8 @@ cloud: name: "" url: "" sale-count-update-queue: + name: "" + url: "" + store-average-rating-update-queue: name: "" url: "" \ No newline at end of file