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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ out/

### Kotlin ###
.kotlin
CLAUDE.md
6 changes: 6 additions & 0 deletions apps/commerce-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ dependencies {
implementation(project(":supports:logging"))
implementation(project(":supports:monitoring"))

// security (for password encoding)
implementation("org.springframework.security:spring-security-crypto")

// web
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator")
Expand All @@ -19,4 +22,7 @@ dependencies {
// test-fixtures
testImplementation(testFixtures(project(":modules:jpa")))
testImplementation(testFixtures(project(":modules:redis")))

// arch test
testImplementation("com.tngtech.archunit:archunit-junit5:1.3.0")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.loopers.application.brand;

import com.loopers.domain.brand.Brand;
import com.loopers.domain.brand.BrandService;
import com.loopers.domain.product.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;

@RequiredArgsConstructor
@Component
public class BrandFacade {

private final BrandService brandService;
private final ProductService productService;

public BrandInfo register(String name, String description, String imageUrl) {
Brand brand = brandService.register(name, description, imageUrl);
return BrandInfo.from(brand);
}

public BrandInfo getBrand(Long id) {
Brand brand = brandService.getBrand(id);
return BrandInfo.from(brand);
}

public List<BrandInfo> getAllBrands() {
return brandService.getAllBrands().stream()
.map(BrandInfo::from)
.toList();
}

public BrandInfo update(Long id, String name, String description, String imageUrl) {
Brand brand = brandService.update(id, name, description, imageUrl);
return BrandInfo.from(brand);
}

public void delete(Long id) {
brandService.delete(id);
productService.deleteAllByBrandId(id);
Comment on lines +39 to +41
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

브랜드 삭제와 상품 삭제가 원자적으로 처리되지 않는다

  • 운영 관점: 브랜드 삭제 후 상품 삭제가 실패하면 브랜드 없는 상품이 남아 조회/정산 오류와 재처리 비용이 발생한다.
  • 수정안: delete 메서드에 @Transactional을 부여하거나 BrandService.delete 내부에서 productService.deleteAllByBrandId를 호출해 단일 트랜잭션으로 묶는다.
  • 추가 테스트: productService.deleteAllByBrandId가 예외를 던질 때 브랜드 삭제가 롤백되는지 통합 테스트를 추가한다.
🔧 수정 예시
+import org.springframework.transaction.annotation.Transactional;
@@
-    public void delete(Long id) {
+    `@Transactional`
+    public void delete(Long id) {
         brandService.delete(id);
         productService.deleteAllByBrandId(id);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void delete(Long id) {
brandService.delete(id);
productService.deleteAllByBrandId(id);
`@Transactional`
public void delete(Long id) {
brandService.delete(id);
productService.deleteAllByBrandId(id);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/commerce-api/src/main/java/com/loopers/application/brand/BrandFacade.java`
around lines 39 - 41, Brand deletion currently calls brandService.delete(id)
then productService.deleteAllByBrandId(id) separately, so if product deletion
fails the brand remains deleted; make the operation atomic by wrapping the
deletion in a single transaction—either annotate BrandFacade.delete with
`@Transactional` or move the productService.deleteAllByBrandId call inside
BrandService.delete so both delete operations execute within one transactional
method (reference BrandFacade.delete, BrandService.delete,
productService.deleteAllByBrandId). Also add an integration test that simulates
productService.deleteAllByBrandId throwing an exception and asserts that the
brand deletion is rolled back.

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.loopers.application.brand;

import com.loopers.domain.brand.Brand;

public record BrandInfo(
Long brandId,
String name,
String description,
String imageUrl
) {
public static BrandInfo from(Brand brand) {
return new BrandInfo(
brand.getId(),
brand.getName(),
brand.getDescription(),
brand.getImageUrl()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.loopers.application.like;

import com.loopers.application.product.ProductInfo;
import com.loopers.domain.brand.Brand;
import com.loopers.domain.brand.BrandService;
import com.loopers.domain.like.ProductLike;
import com.loopers.domain.like.ProductLikeService;
import com.loopers.domain.product.Product;
import com.loopers.domain.product.ProductService;
import com.loopers.support.error.CoreException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Objects;

@RequiredArgsConstructor
@Component
public class ProductLikeFacade {

private final ProductService productService;
private final ProductLikeService productLikeService;
private final BrandService brandService;

public void like(Long userId, Long productId) {
productService.getProduct(productId);
productLikeService.like(userId, productId);
}

public void unlike(Long userId, Long productId) {
productLikeService.unlike(userId, productId);
}
Comment on lines +30 to +32
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

unlike()에 상품 존재 여부 검증이 누락되어 있다

like()productService.getProduct(productId)로 상품 존재를 검증하지만 unlike()는 검증 없이 바로 삭제를 위임한다. 존재하지 않는 productId가 전달되어도 무음 처리되어, 클라이언트가 잘못된 ID를 전달했을 때 오류 피드백이 없다.

수정안: like()와 동일하게 productService.getProduct(productId) 호출을 추가한다.

추가 테스트: 존재하지 않는 productIdunlike 호출 시 NOT_FOUND가 반환됨을 검증하는 테스트를 추가한다.

🔧 수정 예시
 public void unlike(Long userId, Long productId) {
+    productService.getProduct(productId);
     productLikeService.unlike(userId, productId);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void unlike(Long userId, Long productId) {
productLikeService.unlike(userId, productId);
}
public void unlike(Long userId, Long productId) {
productService.getProduct(productId);
productLikeService.unlike(userId, productId);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/commerce-api/src/main/java/com/loopers/application/like/ProductLikeFacade.java`
around lines 30 - 32, unlike() currently delegates straight to
productLikeService.unlike(userId, productId) without verifying the product
exists, unlike like() which calls productService.getProduct(productId); update
ProductLikeFacade.unlike(Long userId, Long productId) to first call
productService.getProduct(productId) (same check as in like()) before calling
productLikeService.unlike(...), and add a test that calls unlike with a
non-existent productId and asserts a NOT_FOUND response.


public ProductLikeInfo getLikeInfo(Long userId, Long productId) {
long likeCount = productLikeService.getLikeCount(productId);
boolean liked = productLikeService.isLiked(userId, productId);
return ProductLikeInfo.of(productId, likeCount, liked);
Comment on lines +34 to +37
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

존재하지 않는 상품에 대해 좋아요 정보가 정상 응답으로 반환될 수 있다

  • 운영 관점: 잘못된 상품 ID에 대해 0 likes 응답이 캐시에 저장되면 이후 정상 상품 등록/복구 시에도 잘못된 상태가 유지될 수 있다.
  • 수정안: getLikeInfo에서 productService.getProduct(productId)로 존재 확인 후 like 정보를 조회한다.
  • 추가 테스트: 존재하지 않는 상품 ID 요청 시 NOT_FOUND 반환을 검증하는 테스트를 추가한다.
🔧 수정 예시
     public ProductLikeInfo getLikeInfo(Long userId, Long productId) {
+        productService.getProduct(productId);
         long likeCount = productLikeService.getLikeCount(productId);
         boolean liked = productLikeService.isLiked(userId, productId);
         return ProductLikeInfo.of(productId, likeCount, liked);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/commerce-api/src/main/java/com/loopers/application/like/ProductLikeFacade.java`
around lines 24 - 27, ProductLikeFacade.getLikeInfo currently returns like info
for any productId; first call productService.getProduct(productId) to verify
existence and if not found throw the application's standard not-found exception
(or translate to an HTTP 404) before calling productLikeService.getLikeCount and
productLikeService.isLiked, then return ProductLikeInfo.of(productId, likeCount,
liked); also add a unit/integration test asserting that requesting a
non-existent productId via ProductLikeFacade (or the controller that uses it)
results in NOT_FOUND.

}

public List<ProductInfo> getLikedProducts(Long userId) {
List<ProductLike> likes = productLikeService.getLikedProducts(userId);
return likes.stream()
.map(like -> {
try {
Product product = productService.getProduct(like.getProductId());
Brand brand = brandService.getBrand(product.getBrandId());
long likeCount = productLikeService.getLikeCount(product.getId());
return ProductInfo.of(product, brand, likeCount, true);
} catch (CoreException e) {
return null;
}
Comment on lines +44 to +51
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

CoreException 무음 삭제로 인한 운영 가시성 손실

productService.getProduct() 또는 brandService.getBrand()CoreException을 던질 경우 해당 ProductLike 항목이 반환 목록에서 조용히 제거된다. 운영 환경에서 고아 ProductLike 레코드(상품이 삭제된 좋아요 항목)가 누적되어도 감지가 불가능하다.

수정안: 예외 발생 시 warn 수준으로 로그를 남긴 후 필터링한다.

🔧 수정 예시
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;

 public class ProductLikeFacade {
+    private static final Logger log = LoggerFactory.getLogger(ProductLikeFacade.class);
     ...
                 } catch (CoreException e) {
+                    log.warn("좋아요 상품 조회 실패 - productId={}", like.getProductId(), e);
                     return null;
                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
Product product = productService.getProduct(like.getProductId());
Brand brand = brandService.getBrand(product.getBrandId());
long likeCount = productLikeService.getLikeCount(product.getId());
return ProductInfo.of(product, brand, likeCount, true);
} catch (CoreException e) {
return null;
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProductLikeFacade {
private static final Logger log = LoggerFactory.getLogger(ProductLikeFacade.class);
// ... existing fields and methods ...
try {
Product product = productService.getProduct(like.getProductId());
Brand brand = brandService.getBrand(product.getBrandId());
long likeCount = productLikeService.getLikeCount(product.getId());
return ProductInfo.of(product, brand, likeCount, true);
} catch (CoreException e) {
log.warn("좋아요 상품 조회 실패 - productId={}", like.getProductId(), e);
return null;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/commerce-api/src/main/java/com/loopers/application/like/ProductLikeFacade.java`
around lines 44 - 51, The catch block in ProductLikeFacade currently swallows
CoreException causing missing operational visibility; update the catch in the
block that calls productService.getProduct, brandService.getBrand and
productLikeService.getLikeCount to log a warning (e.g., logger.warn) including
the exception and context (productId and like id or product.getId()) before
returning null so orphaned ProductLike records are visible in logs while still
filtering the item from the result.

})
.filter(Objects::nonNull)
.toList();
}
Comment on lines +40 to +55
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

getLikedProducts()에서 N+1 쿼리 발생

좋아요 항목 수(N)만큼 getProduct(), getBrand(), getLikeCount()가 개별 호출되어 총 3N+1 쿼리가 발생한다. 좋아요가 많은 사용자의 요청에서 응답 지연 및 DB 부하 급증이 예상된다.

수정안:

  • 상품 ID 목록을 모아 배치 조회(productService.getProductsByIds())하고, 브랜드 ID 목록을 추출하여 배치로 브랜드를 조회하도록 서비스 레이어를 확장한다.
  • getLikeCount()도 한 번의 집계 쿼리(countByProductIdIn(ids))로 대체한다.

추가 테스트: 좋아요 수가 많을 때 쿼리 수가 일정(O(1))한지 검증하는 통합 테스트를 추가한다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/commerce-api/src/main/java/com/loopers/application/like/ProductLikeFacade.java`
around lines 40 - 55, getLikedProducts currently issues N+1 queries by calling
productService.getProduct, brandService.getBrand and
productLikeService.getLikeCount per like; fix it by batching: collect productIds
from productLikeService.getLikedProducts(userId), call a new/extended
productService.getProductsByIds(productIds) and
brandService.getBrandsByIds(brandIds) (extract brandIds from returned products),
and replace per-product getLikeCount calls with a single
productLikeService.countByProductIdIn(productIds) that returns a map
productId->count; then rebuild the List<ProductInfo> by joining products, brands
and counts and preserve the liked=true flag. Ensure you add/adjust service
methods signatures (getProductsByIds, getBrandsByIds, countByProductIdIn) and
update getLikedProducts to use them.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.loopers.application.like;

public record ProductLikeInfo(
Long productId,
long likeCount,
boolean liked
) {
public static ProductLikeInfo of(Long productId, long likeCount, boolean liked) {
return new ProductLikeInfo(productId, likeCount, liked);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.loopers.application.order;

public record OrderCreateItem(Long productId, int quantity) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.loopers.application.order;

import com.loopers.domain.brand.Brand;
import com.loopers.domain.brand.BrandService;
import com.loopers.domain.order.Order;
import com.loopers.domain.order.OrderItem;
import com.loopers.domain.order.OrderService;
import com.loopers.domain.product.Product;
import com.loopers.domain.product.ProductService;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@RequiredArgsConstructor
@Component
public class OrderFacade {

private final ProductService productService;
private final BrandService brandService;
private final OrderService orderService;

public OrderInfo createOrder(Long userId, List<OrderCreateItem> items) {
List<OrderItem> orderItems = new ArrayList<>();

for (OrderCreateItem createItem : items) {
Product product = productService.getProduct(createItem.productId());

if (!product.hasEnoughStock(createItem.quantity())) {
throw new CoreException(ErrorType.BAD_REQUEST, "재고가 부족합니다.");
}

productService.decreaseStock(createItem.productId(), createItem.quantity());

Brand brand = brandService.getBrand(product.getBrandId());

OrderItem orderItem = OrderItem.createSnapshot(
product.getId(),
brand.getName(),
product.getName(),
product.getPrice(),
createItem.quantity()
);
orderItems.add(orderItem);
}

Order order = orderService.createOrder(userId, orderItems);
List<OrderItem> savedItems = orderService.getOrderItems(order.getId());
List<OrderItemInfo> itemInfos = savedItems.stream()
.map(OrderItemInfo::from)
.toList();

return OrderInfo.of(order, itemInfos);
}
Comment on lines +26 to +57
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

주문 생성 전체를 단일 트랜잭션으로 묶어야 한다.
운영 관점에서 재고 감소와 주문 저장이 서로 다른 트랜잭션이면 주문 저장 실패 시 재고만 감소해 데이터 불일치와 CS 이슈가 발생한다.
수정안으로 createOrder에 트랜잭션을 적용하거나 동일 유스케이스를 서비스로 이동해 재고 감소와 주문 생성이 한 트랜잭션에서 처리되도록 해야 한다.
추가 테스트로 주문 생성 도중 예외를 유도했을 때 재고 감소가 롤백되는지 검증해야 한다.

🛡️ 제안 수정안(예시)
+import org.springframework.transaction.annotation.Transactional;
 
 public class OrderFacade {
 
+    `@Transactional`
     public OrderInfo createOrder(Long userId, List<OrderCreateItem> items) {
         List<OrderItem> orderItems = new ArrayList<>();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/commerce-api/src/main/java/com/loopers/application/order/OrderFacade.java`
around lines 26 - 57, The createOrder method currently performs
productService.decreaseStock and orderService.createOrder in separate
transactions; wrap the entire flow (the loop that checks stock, calls
productService.decreaseStock, and the subsequent orderService.createOrder and
orderService.getOrderItems calls) in a single transactional boundary so both
stock decrement and order persistence commit or rollback together—either
annotate OrderFacade.createOrder with a transactional annotation (e.g.,
`@Transactional`) or move the logic into a dedicated transactional service method;
ensure you call productService.decreaseStock and orderService.createOrder within
that transaction and add an integration test that throws an exception mid-way to
assert that productService.decreaseStock operations are rolled back when order
creation fails.


public OrderInfo getOrder(Long orderId) {
Order order = orderService.getOrder(orderId);
List<OrderItem> items = orderService.getOrderItems(orderId);
List<OrderItemInfo> itemInfos = items.stream()
.map(OrderItemInfo::from)
.toList();
return OrderInfo.of(order, itemInfos);
}

public List<OrderInfo> getOrdersByUserId(Long userId) {
List<Order> orders = orderService.getOrdersByUserId(userId);
return orders.stream()
.map(order -> {
List<OrderItem> items = orderService.getOrderItems(order.getId());
List<OrderItemInfo> itemInfos = items.stream()
.map(OrderItemInfo::from)
.toList();
return OrderInfo.of(order, itemInfos);
})
.toList();
}

public List<OrderInfo> getAllOrders() {
List<Order> orders = orderService.getAllOrders();
return orders.stream()
.map(order -> {
List<OrderItem> items = orderService.getOrderItems(order.getId());
List<OrderItemInfo> itemInfos = items.stream()
.map(OrderItemInfo::from)
.toList();
return OrderInfo.of(order, itemInfos);
})
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.loopers.application.order;

import com.loopers.domain.order.Order;

import java.time.ZonedDateTime;
import java.util.List;

public record OrderInfo(
Long orderId,
Long userId,
int totalAmount,
List<OrderItemInfo> items,
ZonedDateTime createdAt
) {
public static OrderInfo of(Order order, List<OrderItemInfo> items) {
return new OrderInfo(
order.getId(),
order.getUserId(),
order.getTotalAmount(),
items,
order.getCreatedAt()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.loopers.application.order;

import com.loopers.domain.order.OrderItem;

public record OrderItemInfo(
Long orderItemId,
Long productId,
String brandName,
String productName,
int price,
int quantity
) {
public static OrderItemInfo from(OrderItem item) {
return new OrderItemInfo(
item.getId(),
item.getProductId(),
item.getBrandName(),
item.getProductName(),
item.getPrice(),
item.getQuantity()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.loopers.application.product;

import com.loopers.domain.brand.Brand;
import com.loopers.domain.brand.BrandService;
import com.loopers.domain.like.ProductLikeService;
import com.loopers.domain.product.Product;
import com.loopers.domain.product.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;

@RequiredArgsConstructor
@Component
public class ProductFacade {

private final ProductService productService;
private final BrandService brandService;
private final ProductLikeService productLikeService;

public ProductInfo getProductDetail(Long productId, Long userId) {
Product product = productService.getProduct(productId);
Brand brand = brandService.getBrand(product.getBrandId());
long likeCount = productLikeService.getLikeCount(productId);
boolean liked = userId != null && productLikeService.isLiked(userId, productId);
return ProductInfo.of(product, brand, likeCount, liked);
}

public List<ProductInfo> getProducts(Long brandId) {
List<Product> products;
if (brandId != null) {
products = productService.getProductsByBrandId(brandId);
} else {
products = productService.getAllProducts();
}
return products.stream()
.map(product -> {
Brand brand = brandService.getBrand(product.getBrandId());
long likeCount = productLikeService.getLikeCount(product.getId());
return ProductInfo.of(product, brand, likeCount, false);
})
.toList();
Comment on lines +36 to +42
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

getProducts()에서 N+1 쿼리 발생

상품 수(N)만큼 brandService.getBrand()productLikeService.getLikeCount()가 개별 호출되어 총 2N+1 쿼리가 발생한다. 상품 목록이 커질수록 응답 지연과 DB 커넥션 포화 위험이 증가한다.

수정안: 상품의 brandId 집합을 추출하여 브랜드를 일괄 조회(brandService.getBrandsByIds())하고, 상품 ID 목록으로 좋아요 수를 집계 쿼리 한 번으로 처리하도록 서비스 레이어를 확장한다.

추가 테스트: 상품이 다수일 때 실행 쿼리 수가 N에 비례하지 않음을 검증하는 통합 테스트를 추가한다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java`
around lines 36 - 42, getProducts() currently causes N+1 queries by calling
brandService.getBrand(product.getBrandId()) and
productLikeService.getLikeCount(product.getId()) per product; fix by collecting
unique brandIds and productIds from the products list, call a new/extended batch
method brandService.getBrandsByIds(Set<Long>) to load all Brand objects into a
Map<Id,Brand> and add a batch method
productLikeService.getLikeCountsByProductIds(List<Long>) to return
Map<productId,likeCount>, then replace the per-item calls in the stream mapping
to lookup brand and likeCount from those maps before calling
ProductInfo.of(product, brand, likeCount, false); also add an integration test
ensuring query count does not scale linearly with product list size.

}

public ProductInfo registerProduct(Long brandId, String name, String description, int price, int stock, String imageUrl) {
brandService.getBrand(brandId);
Product product = productService.register(brandId, name, description, price, stock, imageUrl);
Brand brand = brandService.getBrand(product.getBrandId());
return ProductInfo.of(product, brand, 0L, false);
}

public ProductInfo updateProduct(Long productId, String name, String description, int price, int stock, String imageUrl) {
Product product = productService.update(productId, name, description, price, stock, imageUrl);
Brand brand = brandService.getBrand(product.getBrandId());
long likeCount = productLikeService.getLikeCount(productId);
return ProductInfo.of(product, brand, likeCount, false);
}

public void deleteProduct(Long productId) {
productService.delete(productId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.loopers.application.product;

import com.loopers.domain.brand.Brand;
import com.loopers.domain.product.Product;

public record ProductInfo(
Long productId,
String productName,
String description,
int price,
int stock,
String imageUrl,
Long brandId,
String brandName,
long likeCount,
boolean liked
) {
public static ProductInfo of(Product product, Brand brand, long likeCount, boolean liked) {
return new ProductInfo(
product.getId(),
product.getName(),
product.getDescription(),
product.getPrice(),
product.getStock(),
product.getImageUrl(),
brand.getId(),
brand.getName(),
likeCount,
liked
);
}
}
Loading