Skip to content
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.loopers.application.catalog;
package com.loopers.application.brand;

import com.loopers.domain.brand.Brand;
import com.loopers.domain.brand.BrandRepository;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
* ๋ธŒ๋žœ๋“œ ์กฐํšŒ ํŒŒ์‚ฌ๋“œ.
Expand All @@ -18,19 +21,45 @@
*/
@RequiredArgsConstructor
@Component
public class CatalogBrandFacade {
public class BrandService {
private final BrandRepository brandRepository;

/**
* ๋ธŒ๋žœ๋“œ ID๋กœ ๋ธŒ๋žœ๋“œ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
*
* @param brandId ๋ธŒ๋žœ๋“œ ID
* @return ์กฐํšŒ๋œ ๋ธŒ๋žœ๋“œ
* @throws CoreException ๋ธŒ๋žœ๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ
*/
@Transactional(readOnly = true)
public Brand getBrand(Long brandId) {
return brandRepository.findById(brandId)
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "๋ธŒ๋žœ๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."));
}

/**
* ๋ธŒ๋žœ๋“œ ID ๋ชฉ๋ก์œผ๋กœ ๋ธŒ๋žœ๋“œ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
* <p>
* ๋ฐฐ์น˜ ์กฐํšŒ๋ฅผ ํ†ตํ•ด N+1 ์ฟผ๋ฆฌ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.
* </p>
*
* @param brandIds ์กฐํšŒํ•  ๋ธŒ๋žœ๋“œ ID ๋ชฉ๋ก
* @return ์กฐํšŒ๋œ ๋ธŒ๋žœ๋“œ ๋ชฉ๋ก
*/
@Transactional(readOnly = true)
public List<Brand> getBrands(List<Long> brandIds) {
return brandRepository.findAllById(brandIds);
}

/**
* ๋ธŒ๋žœ๋“œ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
*
* @param brandId ๋ธŒ๋žœ๋“œ ID
* @return ๋ธŒ๋žœ๋“œ ์ •๋ณด
* @throws CoreException ๋ธŒ๋žœ๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ
*/
public BrandInfo getBrand(Long brandId) {
Brand brand = brandRepository.findById(brandId)
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "๋ธŒ๋žœ๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."));
public BrandInfo getBrandInfo(Long brandId) {
Brand brand = getBrand(brandId);
return BrandInfo.from(brand);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.loopers.application.catalog;

import com.loopers.application.brand.BrandService;
import com.loopers.application.product.ProductCacheService;
import com.loopers.application.product.ProductService;
import com.loopers.domain.brand.Brand;
import com.loopers.domain.brand.BrandRepository;
import com.loopers.domain.product.Product;
import com.loopers.domain.product.ProductDetail;
import com.loopers.domain.product.ProductRepository;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
import lombok.RequiredArgsConstructor;
Expand All @@ -25,9 +26,9 @@
*/
@RequiredArgsConstructor
@Component
public class CatalogProductFacade {
private final ProductRepository productRepository;
private final BrandRepository brandRepository;
public class CatalogFacade {
private final BrandService brandService;
private final ProductService productService;
private final ProductCacheService productCacheService;

/**
Expand All @@ -54,8 +55,8 @@ public ProductInfoList getProducts(Long brandId, String sort, int page, int size
}

// ์บ์‹œ์— ์—†์œผ๋ฉด DB์—์„œ ์กฐํšŒ
long totalCount = productRepository.countAll(brandId);
List<Product> products = productRepository.findAll(brandId, normalizedSort, page, size);
long totalCount = productService.countAll(brandId);
List<Product> products = productService.findAll(brandId, normalizedSort, page, size);

if (products.isEmpty()) {
ProductInfoList emptyResult = new ProductInfoList(List.of(), totalCount, page, size);
Expand All @@ -72,7 +73,7 @@ public ProductInfoList getProducts(Long brandId, String sort, int page, int size
.toList();

// ๋ธŒ๋žœ๋“œ ๋ฐฐ์น˜ ์กฐํšŒ ๋ฐ Map์œผ๋กœ ๋ณ€ํ™˜ (O(1) ์กฐํšŒ๋ฅผ ์œ„ํ•ด)
Map<Long, Brand> brandMap = brandRepository.findAllById(brandIds).stream()
Map<Long, Brand> brandMap = brandService.getBrands(brandIds).stream()
.collect(Collectors.toMap(Brand::getId, brand -> brand));

// ์ƒํ’ˆ ์ •๋ณด ๋ณ€ํ™˜ (์ด๋ฏธ ์กฐํšŒํ•œ Product ์žฌ์‚ฌ์šฉ)
Expand Down Expand Up @@ -116,12 +117,10 @@ public ProductInfo getProduct(Long productId) {
}

// ์บ์‹œ์— ์—†์œผ๋ฉด DB์—์„œ ์กฐํšŒ
Product product = productRepository.findById(productId)
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "์ƒํ’ˆ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."));
Product product = productService.getProduct(productId);

// ๋ธŒ๋žœ๋“œ ์กฐํšŒ
Brand brand = brandRepository.findById(product.getBrandId())
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "๋ธŒ๋žœ๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."));
Brand brand = brandService.getBrand(product.getBrandId());

// โœ… Product.likeCount ํ•„๋“œ ์‚ฌ์šฉ (๋น„๋™๊ธฐ ์ง‘๊ณ„๋œ ๊ฐ’)
Long likesCount = product.getLikeCount();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.loopers.domain.coupon;
package com.loopers.application.coupon;

import com.loopers.domain.coupon.Coupon;
import com.loopers.domain.coupon.CouponRepository;
import com.loopers.domain.coupon.UserCoupon;
import com.loopers.domain.coupon.UserCouponRepository;
import com.loopers.domain.coupon.discount.CouponDiscountStrategyFactory;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
Expand All @@ -9,10 +13,10 @@
import org.springframework.transaction.annotation.Transactional;

/**
* ์ฟ ํฐ ๋„๋ฉ”์ธ ์„œ๋น„์Šค.
* ์ฟ ํฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋น„์Šค.
* <p>
* ์ฟ ํฐ ์กฐํšŒ, ์‚ฌ์šฉ ๋“ฑ์˜ ๋„๋ฉ”์ธ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
* Repository์— ์˜์กดํ•˜๋ฉฐ ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™์„ ์บก์Аํ™”ํ•ฉ๋‹ˆ๋‹ค.
* ์ฟ ํฐ ์กฐํšŒ, ์‚ฌ์šฉ ๋“ฑ์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
* Repository์— ์˜์กดํ•˜๋ฉฐ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ ๋ฐ ๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
* </p>
*
* @author Loopers
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.loopers.application.like;
package com.loopers.application.heart;

import com.loopers.application.catalog.ProductCacheService;
import com.loopers.application.like.LikeService;
import com.loopers.application.product.ProductCacheService;
import com.loopers.application.product.ProductService;
import com.loopers.application.user.UserService;
import com.loopers.domain.like.Like;
import com.loopers.domain.like.LikeRepository;
import com.loopers.domain.product.Product;
import com.loopers.domain.product.ProductRepository;
import com.loopers.domain.user.User;
import com.loopers.domain.user.UserRepository;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -29,10 +29,10 @@
*/
@RequiredArgsConstructor
@Component
public class LikeFacade {
private final LikeRepository likeRepository;
private final UserRepository userRepository;
private final ProductRepository productRepository;
public class HeartFacade {
private final LikeService likeService;
private final UserService userService;
private final ProductService productService;
private final ProductCacheService productCacheService;

/**
Expand Down Expand Up @@ -69,7 +69,7 @@ public void addLike(String userId, Long productId) {
// ๋จผ์ € ์ผ๋ฐ˜ ์กฐํšŒ๋กœ ์ค‘๋ณต ์ฒดํฌ (๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๋น ๋ฅด๊ฒŒ ์ฒ˜๋ฆฌ)
// โš ๏ธ ์ฃผ์˜: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ ์ฒดํฌ๋งŒ์œผ๋กœ๋Š” race condition์„ ์™„์ „ํžˆ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์—†์Œ
// ๋™์‹œ์— ๋‘ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด ๋‘˜ ๋‹ค "์—†์Œ"์œผ๋กœ ํŒ๋‹จ โ†’ ๋‘˜ ๋‹ค ์ €์žฅ ์‹œ๋„ ๊ฐ€๋Šฅ
Optional<Like> existingLike = likeRepository.findByUserIdAndProductId(user.getId(), productId);
Optional<Like> existingLike = likeService.getLike(user.getId(), productId);
if (existingLike.isPresent()) {
return;
}
Expand All @@ -79,7 +79,7 @@ public void addLike(String userId, Long productId) {
// @Transactional์ด ์—†์–ด๋„ save() ํ˜ธ์ถœ ์‹œ ์ž๋™ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ์˜ˆ์™ธ๋ฅผ catchํ•  ์ˆ˜ ์žˆ์Œ
Like like = Like.of(user.getId(), productId);
try {
likeRepository.save(like);
likeService.save(like);
// ์ข‹์•„์š” ์ถ”๊ฐ€ ์„ฑ๊ณต ์‹œ ๋กœ์ปฌ ์บ์‹œ์˜ ๋ธํƒ€ ์ฆ๊ฐ€
productCacheService.incrementLikeCountDelta(productId);
} catch (org.springframework.dao.DataIntegrityViolationException e) {
Expand All @@ -105,13 +105,13 @@ public void removeLike(String userId, Long productId) {
User user = loadUser(userId);
loadProduct(productId);

Optional<Like> like = likeRepository.findByUserIdAndProductId(user.getId(), productId);
Optional<Like> like = likeService.getLike(user.getId(), productId);
if (like.isEmpty()) {
return;
}

try {
likeRepository.delete(like.get());
likeService.delete(like.get());
// ์ข‹์•„์š” ์ทจ์†Œ ์„ฑ๊ณต ์‹œ ๋กœ์ปฌ ์บ์‹œ์˜ ๋ธํƒ€ ๊ฐ์†Œ
productCacheService.decrementLikeCountDelta(productId);
} catch (Exception e) {
Expand Down Expand Up @@ -144,7 +144,7 @@ public List<LikedProduct> getLikedProducts(String userId) {
User user = loadUser(userId);

// ์‚ฌ์šฉ์ž์˜ ์ข‹์•„์š” ๋ชฉ๋ก ์กฐํšŒ
List<Like> likes = likeRepository.findAllByUserId(user.getId());
List<Like> likes = likeService.getLikesByUserId(user.getId());

if (likes.isEmpty()) {
return List.of();
Expand All @@ -156,7 +156,7 @@ public List<LikedProduct> getLikedProducts(String userId) {
.toList();

// โœ… ๋ฐฐ์น˜ ์กฐํšŒ๋กœ N+1 ์ฟผ๋ฆฌ ๋ฌธ์ œ ํ•ด๊ฒฐ
Map<Long, Product> productMap = productRepository.findAllById(productIds).stream()
Map<Long, Product> productMap = productService.getProducts(productIds).stream()
.collect(Collectors.toMap(Product::getId, product -> product));

// ์š”์ฒญํ•œ ์ƒํ’ˆ ID์™€ ์กฐํšŒ๋œ ์ƒํ’ˆ ์ˆ˜๊ฐ€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ
Expand All @@ -180,17 +180,11 @@ public List<LikedProduct> getLikedProducts(String userId) {
}

private User loadUser(String userId) {
User user = userRepository.findByUserId(userId);
if (user == null) {
throw new CoreException(ErrorType.NOT_FOUND, "์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
}
return user;
return userService.getUser(userId);
}

private Product loadProduct(Long productId) {
return productRepository.findById(productId)
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND,
String.format("์ƒํ’ˆ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. (์ƒํ’ˆ ID: %d)", productId)));
return productService.getProduct(productId);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.loopers.application.like;

import com.loopers.domain.like.Like;
import com.loopers.domain.like.LikeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

/**
* ์ข‹์•„์š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋น„์Šค.
* <p>
* ์ข‹์•„์š” ์กฐํšŒ, ์ €์žฅ, ์‚ญ์ œ ๋“ฑ์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
* Repository์— ์˜์กดํ•˜๋ฉฐ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
* </p>
*
* @author Loopers
* @version 1.0
*/
@RequiredArgsConstructor
@Component
public class LikeService {
private final LikeRepository likeRepository;

/**
* ์‚ฌ์šฉ์ž ID์™€ ์ƒํ’ˆ ID๋กœ ์ข‹์•„์š”๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
*
* @param userId ์‚ฌ์šฉ์ž ID
* @param productId ์ƒํ’ˆ ID
* @return ์กฐํšŒ๋œ ์ข‹์•„์š”๋ฅผ ๋‹ด์€ Optional
*/
@Transactional(readOnly = true)
public Optional<Like> getLike(Long userId, Long productId) {
return likeRepository.findByUserIdAndProductId(userId, productId);
}

/**
* ์ข‹์•„์š”๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
*
* @param like ์ €์žฅํ•  ์ข‹์•„์š”
* @return ์ €์žฅ๋œ ์ข‹์•„์š”
*/
@Transactional
public Like save(Like like) {
return likeRepository.save(like);
}

/**
* ์ข‹์•„์š”๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.
*
* @param like ์‚ญ์ œํ•  ์ข‹์•„์š”
*/
@Transactional
public void delete(Like like) {
likeRepository.delete(like);
}

/**
* ์‚ฌ์šฉ์ž ID๋กœ ์ข‹์•„์š”ํ•œ ์ƒํ’ˆ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
*
* @param userId ์‚ฌ์šฉ์ž ID
* @return ์ข‹์•„์š” ๋ชฉ๋ก
*/
@Transactional(readOnly = true)
public List<Like> getLikesByUserId(Long userId) {
return likeRepository.findAllByUserId(userId);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.loopers.domain.order;
package com.loopers.application.order;

import com.loopers.domain.order.Order;
import com.loopers.domain.order.OrderItem;
import com.loopers.domain.order.OrderRepository;
import com.loopers.domain.order.OrderStatus;
import com.loopers.domain.payment.PaymentStatus;
import com.loopers.domain.product.Product;
import com.loopers.domain.user.Point;
Expand All @@ -15,9 +19,10 @@
import java.util.Optional;

/**
* ์ฃผ๋ฌธ ๋„๋ฉ”์ธ ์„œ๋น„์Šค.
* ์ฃผ๋ฌธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋น„์Šค.
* <p>
* ์ฃผ๋ฌธ์˜ ๊ธฐ๋ณธ CRUD ๋ฐ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
* ์ฃผ๋ฌธ์˜ ๊ธฐ๋ณธ CRUD ๋ฐ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๋‹ด๋‹นํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.
* Repository์— ์˜์กดํ•˜๋ฉฐ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
* </p>
*
* @author Loopers
Expand Down Expand Up @@ -60,7 +65,7 @@ public Order getById(Long orderId) {
* @return ์กฐํšŒ๋œ ์ฃผ๋ฌธ (์—†์œผ๋ฉด Optional.empty())
*/
@Transactional(readOnly = true)
public Optional<Order> findById(Long orderId) {
public Optional<Order> getOrder(Long orderId) {
return orderRepository.findById(orderId);
}

Expand All @@ -71,7 +76,7 @@ public Optional<Order> findById(Long orderId) {
* @return ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ์ฃผ๋ฌธ ๋ชฉ๋ก
*/
@Transactional(readOnly = true)
public List<Order> findAllByUserId(Long userId) {
public List<Order> getOrdersByUserId(Long userId) {
return orderRepository.findAllByUserId(userId);
}

Expand All @@ -82,7 +87,7 @@ public List<Order> findAllByUserId(Long userId) {
* @return ํ•ด๋‹น ์ƒํƒœ์˜ ์ฃผ๋ฌธ ๋ชฉ๋ก
*/
@Transactional(readOnly = true)
public List<Order> findAllByStatus(OrderStatus status) {
public List<Order> getOrdersByStatus(OrderStatus status) {
return orderRepository.findAllByStatus(status);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.loopers.application.purchasing;
package com.loopers.application.payment;

import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
Expand Down
Loading