Skip to content

Commit 3e2e0f4

Browse files
committed
round3: Product, Brand, Like, Order 도메인 구현
1 parent d06a0d0 commit 3e2e0f4

71 files changed

Lines changed: 5780 additions & 52 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.loopers.application.like.product;
2+
3+
import com.loopers.domain.brand.Brand;
4+
import com.loopers.domain.brand.BrandService;
5+
import com.loopers.domain.like.product.LikeProduct;
6+
import com.loopers.domain.like.product.LikeProductService;
7+
import com.loopers.domain.metrics.product.ProductMetrics;
8+
import com.loopers.domain.metrics.product.ProductMetricsService;
9+
import com.loopers.domain.product.Product;
10+
import com.loopers.domain.product.ProductService;
11+
import com.loopers.domain.supply.Supply;
12+
import com.loopers.domain.supply.SupplyService;
13+
import com.loopers.domain.user.User;
14+
import com.loopers.domain.user.UserService;
15+
import com.loopers.support.error.CoreException;
16+
import com.loopers.support.error.ErrorType;
17+
import lombok.RequiredArgsConstructor;
18+
import org.springframework.data.domain.Page;
19+
import org.springframework.data.domain.Pageable;
20+
import org.springframework.stereotype.Component;
21+
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.Set;
25+
import java.util.stream.Collectors;
26+
27+
@RequiredArgsConstructor
28+
@Component
29+
public class LikeProductFacade {
30+
private final LikeProductService likeProductService;
31+
private final UserService userService;
32+
private final ProductService productService;
33+
private final ProductMetricsService productMetricsService;
34+
private final BrandService brandService;
35+
private final SupplyService supplyService;
36+
37+
public void likeProduct(String userId, Long productId) {
38+
User user = userService.findByUserId(userId).orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "사용자를 찾을 수 없습니다."));
39+
if (!productService.existsById(productId)) {
40+
throw new CoreException(ErrorType.NOT_FOUND, "상품을 찾을 수 없습니다.");
41+
}
42+
likeProductService.likeProduct(user.getId(), productId);
43+
}
44+
45+
public void unlikeProduct(String userId, Long productId) {
46+
User user = userService.findByUserId(userId).orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "사용자를 찾을 수 없습니다."));
47+
if (!productService.existsById(productId)) {
48+
throw new CoreException(ErrorType.NOT_FOUND, "상품을 찾을 수 없습니다.");
49+
}
50+
likeProductService.unlikeProduct(user.getId(), productId);
51+
}
52+
53+
public Page<LikeProductInfo> getLikedProducts(String userId, Pageable pageable) {
54+
User user = userService.findByUserId(userId).orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "사용자를 찾을 수 없습니다."));
55+
Page<LikeProduct> likedProducts = likeProductService.getLikedProducts(user.getId(), pageable);
56+
57+
List<Long> productIds = likedProducts.map(LikeProduct::getProductId).toList();
58+
Map<Long, Product> productMap = productService.getProductMapByIds(productIds);
59+
60+
Set<Long> brandIds = productMap.values().stream().map(Product::getBrandId).collect(Collectors.toSet());
61+
62+
Map<Long, ProductMetrics> metricsMap = productMetricsService.getMetricsMapByProductIds(productIds);
63+
Map<Long, Supply> supplyMap = supplyService.getSupplyMapByProductIds(productIds);
64+
Map<Long, Brand> brandMap = brandService.getBrandMapByBrandIds(brandIds);
65+
66+
return likedProducts.map(likeProduct -> {
67+
Product product = productMap.get(likeProduct.getProductId());
68+
ProductMetrics metrics = metricsMap.get(product.getId());
69+
Brand brand = brandMap.get(product.getBrandId());
70+
Supply supply = supplyMap.get(product.getId());
71+
72+
return new LikeProductInfo(
73+
product.getId(),
74+
product.getName(),
75+
brand.getName(),
76+
product.getPrice().amount(),
77+
metrics.getLikeCount(),
78+
supply.getStock().quantity()
79+
);
80+
});
81+
82+
}
83+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.loopers.application.like.product;
2+
3+
public record LikeProductInfo(
4+
Long id,
5+
String name,
6+
String brand,
7+
int price,
8+
int likes,
9+
int stock
10+
) {
11+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.loopers.application.order;
2+
3+
import com.loopers.domain.order.Order;
4+
import com.loopers.domain.order.OrderItem;
5+
import com.loopers.domain.order.OrderService;
6+
import com.loopers.domain.point.PointService;
7+
import com.loopers.domain.product.Product;
8+
import com.loopers.domain.product.ProductService;
9+
import com.loopers.domain.supply.SupplyService;
10+
import com.loopers.domain.user.User;
11+
import com.loopers.domain.user.UserService;
12+
import com.loopers.interfaces.api.order.OrderV1Dto;
13+
import com.loopers.support.error.CoreException;
14+
import com.loopers.support.error.ErrorType;
15+
import lombok.RequiredArgsConstructor;
16+
import org.springframework.data.domain.Page;
17+
import org.springframework.data.domain.Pageable;
18+
import org.springframework.stereotype.Component;
19+
import org.springframework.transaction.annotation.Transactional;
20+
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.stream.Collectors;
24+
25+
@RequiredArgsConstructor
26+
@Component
27+
public class OrderFacade {
28+
private final UserService userService;
29+
private final OrderService orderService;
30+
private final ProductService productService;
31+
private final PointService pointService;
32+
private final SupplyService supplyService;
33+
34+
public OrderInfo getOrderInfo(String userId, Long orderId) {
35+
User user = userService.findByUserId(userId).orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "사용자를 찾을 수 없습니다."));
36+
Order order = orderService.getOrderByIdAndUserId(orderId, user.getId());
37+
38+
return OrderInfo.from(order);
39+
}
40+
41+
@Transactional(readOnly = true)
42+
public Page<OrderInfo> getOrderList(String userId, Pageable pageable) {
43+
User user = userService.findByUserId(userId).orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "사용자를 찾을 수 없습니다."));
44+
Page<Order> orders = orderService.getOrdersByUserId(user.getId(), pageable);
45+
return orders.map(OrderInfo::from);
46+
}
47+
48+
@Transactional
49+
public OrderInfo createOrder(String userId, OrderV1Dto.OrderRequest request) {
50+
User user = userService.findByUserId(userId).orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "사용자를 찾을 수 없습니다."));
51+
52+
// request에서 productId - quantity 맵 생성
53+
Map<Long, Integer> productQuantityMap = request.items().stream()
54+
.collect(Collectors.toMap(
55+
OrderV1Dto.OrderRequest.OrderItemRequest::productId,
56+
OrderV1Dto.OrderRequest.OrderItemRequest::quantity
57+
));
58+
59+
Map<Long, Product> productMap = productService.getProductMapByIds(productQuantityMap.keySet());
60+
61+
request.items().forEach(item -> {
62+
supplyService.checkAndDecreaseStock(item.productId(), item.quantity());
63+
});
64+
65+
Integer totalAmount = productService.calculateTotalAmount(productQuantityMap);
66+
67+
pointService.checkAndDeductPoint(user.getId(), totalAmount);
68+
69+
List<OrderItem> orderItems = request.items()
70+
.stream()
71+
.map(item -> OrderItem.create(
72+
item.productId(),
73+
productMap.get(item.productId()).getName(),
74+
item.quantity(),
75+
productMap.get(item.productId()).getPrice()
76+
))
77+
.toList();
78+
Order order = Order.create(user.getId(), orderItems);
79+
80+
orderService.save(order);
81+
82+
return OrderInfo.from(order);
83+
}
84+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.loopers.application.order;
2+
3+
import com.loopers.domain.order.Order;
4+
5+
import java.util.List;
6+
7+
public record OrderInfo(
8+
Long orderId,
9+
Long userId,
10+
Integer totalPrice,
11+
List<OrderItemInfo> items
12+
) {
13+
public static OrderInfo from(Order order) {
14+
return new OrderInfo(
15+
order.getId(),
16+
order.getUserId(),
17+
order.getTotalPrice().amount(),
18+
OrderItemInfo.fromList(order.getOrderItems())
19+
);
20+
}
21+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.loopers.application.order;
2+
3+
import com.loopers.domain.order.OrderItem;
4+
5+
import java.util.List;
6+
7+
public record OrderItemInfo(
8+
Long productId,
9+
String productName,
10+
Integer quantity,
11+
Integer totalPrice
12+
) {
13+
public static OrderItemInfo from(OrderItem orderItem) {
14+
return new OrderItemInfo(
15+
orderItem.getProductId(),
16+
orderItem.getProductName(),
17+
orderItem.getQuantity(),
18+
orderItem.getTotalPrice()
19+
);
20+
}
21+
22+
public static List<OrderItemInfo> fromList(List<OrderItem> items) {
23+
return items.stream()
24+
.map(OrderItemInfo::from)
25+
.toList();
26+
}
27+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.loopers.application.product;
2+
3+
import com.loopers.domain.brand.Brand;
4+
import com.loopers.domain.brand.BrandService;
5+
import com.loopers.domain.metrics.product.ProductMetrics;
6+
import com.loopers.domain.metrics.product.ProductMetricsService;
7+
import com.loopers.domain.product.Product;
8+
import com.loopers.domain.product.ProductService;
9+
import com.loopers.domain.supply.Supply;
10+
import com.loopers.domain.supply.SupplyService;
11+
import lombok.RequiredArgsConstructor;
12+
import org.springframework.data.domain.Page;
13+
import org.springframework.data.domain.Pageable;
14+
import org.springframework.stereotype.Component;
15+
import org.springframework.transaction.annotation.Transactional;
16+
17+
import java.util.List;
18+
import java.util.Map;
19+
import java.util.Set;
20+
21+
@RequiredArgsConstructor
22+
@Component
23+
public class ProductFacade {
24+
private final ProductService productService;
25+
private final ProductMetricsService productMetricsService;
26+
private final BrandService brandService;
27+
private final SupplyService supplyService;
28+
29+
@Transactional(readOnly = true)
30+
public Page<ProductInfo> getProductList(Pageable pageable) {
31+
Page<Product> products = productService.getProducts(pageable);
32+
33+
List<Long> productIds = products.map(Product::getId).toList();
34+
Set<Long> brandIds = products.map(Product::getBrandId).toSet();
35+
36+
Map<Long, ProductMetrics> metricsMap = productMetricsService.getMetricsMapByProductIds(productIds);
37+
Map<Long, Supply> supplyMap = supplyService.getSupplyMapByProductIds(productIds);
38+
Map<Long, Brand> brandMap = brandService.getBrandMapByBrandIds(brandIds);
39+
40+
return products.map(product -> {
41+
ProductMetrics metrics = metricsMap.get(product.getId());
42+
Brand brand = brandMap.get(product.getBrandId());
43+
Supply supply = supplyMap.get(product.getId());
44+
45+
return new ProductInfo(
46+
product.getId(),
47+
product.getName(),
48+
brand.getName(),
49+
product.getPrice().amount(),
50+
metrics.getLikeCount(),
51+
supply.getStock().quantity()
52+
);
53+
});
54+
}
55+
56+
@Transactional(readOnly = true)
57+
public ProductInfo getProductDetail(Long productId) {
58+
Product product = productService.getProductById(productId);
59+
ProductMetrics metrics = productMetricsService.getMetricsByProductId(productId);
60+
Brand brand = brandService.getBrandById(product.getBrandId());
61+
Supply supply = supplyService.getSupplyByProductId(productId);
62+
63+
return new ProductInfo(
64+
productId,
65+
product.getName(),
66+
brand.getName(),
67+
product.getPrice().amount(),
68+
metrics.getLikeCount(),
69+
supply.getStock().quantity()
70+
);
71+
}
72+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.loopers.application.product;
2+
3+
public record ProductInfo(
4+
Long id,
5+
String name,
6+
String brand,
7+
int price,
8+
int likes,
9+
int stock
10+
) {
11+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.loopers.domain.brand;
2+
3+
import com.loopers.domain.BaseEntity;
4+
import com.loopers.support.error.CoreException;
5+
import com.loopers.support.error.ErrorType;
6+
import jakarta.persistence.Entity;
7+
import jakarta.persistence.Table;
8+
import lombok.Getter;
9+
import org.apache.commons.lang3.StringUtils;
10+
11+
@Entity
12+
@Table(name = "tb_brand")
13+
@Getter
14+
public class Brand extends BaseEntity {
15+
private String name;
16+
17+
protected Brand() {
18+
}
19+
20+
private Brand(String name) {
21+
this.name = name;
22+
}
23+
24+
public static Brand create(String name) {
25+
if (StringUtils.isBlank(name)) {
26+
throw new CoreException(ErrorType.BAD_REQUEST, "브랜드 이름은 필수이며 1자 이상이어야 합니다.");
27+
}
28+
return new Brand(name);
29+
}
30+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.loopers.domain.brand;
2+
3+
import java.util.Collection;
4+
import java.util.Optional;
5+
6+
public interface BrandRepository {
7+
Optional<Brand> findById(Long id);
8+
9+
Collection<Brand> findAllByIdIn(Collection<Long> ids);
10+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.loopers.domain.brand;
2+
3+
import com.loopers.support.error.CoreException;
4+
import com.loopers.support.error.ErrorType;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.stereotype.Component;
7+
8+
import java.util.Collection;
9+
import java.util.Map;
10+
11+
@RequiredArgsConstructor
12+
@Component
13+
public class BrandService {
14+
private final BrandRepository brandRepository;
15+
16+
public Brand getBrandById(Long brandId) {
17+
return brandRepository.findById(brandId)
18+
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "브랜드를 찾을 수 없습니다."));
19+
}
20+
21+
public Map<Long, Brand> getBrandMapByBrandIds(Collection<Long> brandIds) {
22+
return brandRepository.findAllByIdIn(brandIds)
23+
.stream()
24+
.collect(java.util.stream.Collectors.toMap(Brand::getId, brand -> brand));
25+
}
26+
}

0 commit comments

Comments
 (0)