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
51 changes: 48 additions & 3 deletions orders.http
Original file line number Diff line number Diff line change
@@ -1,10 +1,55 @@
### 주문 생성
###############################################
# 도메인 간 소통 비교 — Facade vs API 호출
# (자세한 설명: DOMAIN_COMMUNICATION.md)
#
# 세 요청의 입력/결과는 동일하다. 다른 것은 "주문이 어떻게 소통했는가" 뿐.
###############################################

### 0) 비교 전 상품 재고 확인
GET http://localhost:8080/products/1

### 베이스라인 — OrderService: 다른 도메인 Service 빈을 직접 호출
POST http://localhost:8080/orders
Content-Type: application/json

{
"userId": 1
"userId": 1,
"requests": [ { "productId": 1, "quantity": 1 } ]
}

### 전체 주문 목록 조회
### 방식 1) Facade — in-process 직접 호출 + 단일 트랜잭션(강한 일관성)
POST http://localhost:8080/orders/facade
Content-Type: application/json

{
"userId": 1,
"requests": [ { "productId": 1, "quantity": 1 } ]
}

### 방식 2) API 호출 — RestClient 로 상품/결제를 HTTP 호출(경계·트랜잭션 분리)
POST http://localhost:8080/orders/api
Content-Type: application/json

{
"userId": 1,
"requests": [ { "productId": 1, "quantity": 1 } ]
}

### 비교 후 재고 확인 (세 번 주문했으니 3 감소)
GET http://localhost:8080/products/1

### 주문 목록
GET http://localhost:8080/orders

###############################################
# 방식 2 가 내부에서 호출하는 "공개된 도메인 API" (직접 찔러보기)
###############################################

### 상품 재고 차감 API (주문 → 상품)
POST http://localhost:8080/products/1/decrease-stock?quantity=1

### 상품 재고 복원 API (보상 트랜잭션 예시)
POST http://localhost:8080/products/1/restore-stock?quantity=1

### 결제 승인 API (주문 → 결제)
POST http://localhost:8080/payments?amount=179000
Original file line number Diff line number Diff line change
@@ -1,50 +1,60 @@
package com.growmighty.lectures.firstday.tangledmonolith;

import com.growmighty.lectures.firstday.tangledmonolith.cart.Cart;
import com.growmighty.lectures.firstday.tangledmonolith.cart.CartItem;
import com.growmighty.lectures.firstday.tangledmonolith.cart.CartRepository;
import com.growmighty.lectures.firstday.tangledmonolith.product.Product;
import com.growmighty.lectures.firstday.tangledmonolith.product.ProductRepository;
import com.growmighty.lectures.firstday.tangledmonolith.seller.Seller;
import com.growmighty.lectures.firstday.tangledmonolith.seller.SellerRepository;
import com.growmighty.lectures.firstday.tangledmonolith.user.User;
import com.growmighty.lectures.firstday.tangledmonolith.user.UserRepository;
import com.growmighty.lectures.firstday.tangledmonolith.cart.application.CartService;
import com.growmighty.lectures.firstday.tangledmonolith.cart.application.dto.AddCartItemCommand;
import com.growmighty.lectures.firstday.tangledmonolith.cart.domain.Cart;
import com.growmighty.lectures.firstday.tangledmonolith.cart.domain.CartItem;
import com.growmighty.lectures.firstday.tangledmonolith.cart.domain.CartRepository;
import com.growmighty.lectures.firstday.tangledmonolith.product.application.ProductService;
import com.growmighty.lectures.firstday.tangledmonolith.product.application.dto.ProductInfo;
import com.growmighty.lectures.firstday.tangledmonolith.product.application.dto.RegisterProductCommand;
import com.growmighty.lectures.firstday.tangledmonolith.product.domain.Product;
import com.growmighty.lectures.firstday.tangledmonolith.product.domain.ProductRepository;
import com.growmighty.lectures.firstday.tangledmonolith.seller.application.SellerService;
import com.growmighty.lectures.firstday.tangledmonolith.seller.application.dto.ApplySellerCommand;
import com.growmighty.lectures.firstday.tangledmonolith.seller.application.dto.SellerInfo;
import com.growmighty.lectures.firstday.tangledmonolith.seller.domain.Seller;
import com.growmighty.lectures.firstday.tangledmonolith.seller.domain.SellerRepository;
import com.growmighty.lectures.firstday.tangledmonolith.user.application.UserService;
import com.growmighty.lectures.firstday.tangledmonolith.user.application.dto.RegisterUserCommand;
import com.growmighty.lectures.firstday.tangledmonolith.user.application.dto.UserInfo;
import com.growmighty.lectures.firstday.tangledmonolith.user.domain.User;
import com.growmighty.lectures.firstday.tangledmonolith.user.domain.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;

@Component
@Profile("!test")
@RequiredArgsConstructor
public class DataInitializer implements CommandLineRunner {

private final UserRepository userRepository;
private final SellerRepository sellerRepository;
private final ProductRepository productRepository;
private final CartRepository cartRepository;
private final UserService userService;
private final SellerService sellerService;
private final ProductService productService;
private final CartService cartService;

@Override
public void run(String... args) {
User buyer = userRepository.save(
User.register("buyer@growmighty.co.kr", "encoded-pw", "구매자", "010-1111-1111"));
User sellerOwner = userRepository.save(
User.register("seller@growmighty.co.kr", "encoded-pw", "판매자", "010-2222-2222"));
UserInfo buyer = userService.register(
new RegisterUserCommand("buyer@growmighty.co.kr", "rawPassword1!", "구매자", "010-1111-1111"));
UserInfo sellerOwner = userService.register(
new RegisterUserCommand("seller@growmighty.co.kr", "rawPassword2!", "판매자", "010-2222-2222"));

Seller seller = sellerRepository.save(Seller.create(sellerOwner));
SellerInfo seller = sellerService.apply(new ApplySellerCommand(sellerOwner.id(), "그로마이티 가구"));

Product chair = productRepository.save(
Product.create(seller, "Dofia 이동식 접이식 식탁 의자 4개 세트", BigDecimal.valueOf(179000), 10, "가정용 소형주택 신축식"));
Product table = productRepository.save(
Product.create(seller, "원목 4인용 식탁", BigDecimal.valueOf(259000), 5, "북유럽 스타일 원목 식탁"));
ProductInfo chair = productService.register(new RegisterProductCommand(
seller.id(), "Dofia 이동식 접이식 식탁 의자 4개 세트", BigDecimal.valueOf(179000), 10, "가정용 소형주택 신축식"));
ProductInfo table = productService.register(new RegisterProductCommand(
seller.id(), "원목 4인용 식탁", BigDecimal.valueOf(259000), 5, "북유럽 스타일 원목 식탁"));

Cart cart = Cart.create(buyer);
cart.addItem(CartItem.create(chair, 2));
cart.addItem(CartItem.create(table, 1));
cartRepository.save(cart);
cartService.addItem(new AddCartItemCommand(buyer.id(), chair.id(), 2));
cartService.addItem(new AddCartItemCommand(buyer.id(), table.id(), 1));

System.out.printf(
"[seed] 구매자 id=%d, 장바구니 id=%d 준비 완료. 예) POST /orders?userId=%d%n",
buyer.getId(), cart.getId(), buyer.getId());
"[seed] 구매자 id=%d 준비 완료. 예) POST /orders/from-cart?userId=%d 또는 POST /orders%n",
buyer.id(), buyer.id());
}
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.growmighty.lectures.firstday.tangledmonolith.cart.application;

import com.growmighty.lectures.firstday.tangledmonolith.cart.domain.Cart;
import com.growmighty.lectures.firstday.tangledmonolith.cart.domain.CartRepository;
import com.growmighty.lectures.firstday.tangledmonolith.cart.application.dto.AddCartItemCommand;
import com.growmighty.lectures.firstday.tangledmonolith.cart.application.dto.CartView;
import com.growmighty.lectures.firstday.tangledmonolith.common.exception.EntityNotFoundException;
import com.growmighty.lectures.firstday.tangledmonolith.product.application.ProductService;
import com.growmighty.lectures.firstday.tangledmonolith.product.application.dto.ProductInfo;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class CartService {
private final CartRepository cartRepository;
private final ProductService productService;

@Transactional
public CartView addItem(AddCartItemCommand command) {
ProductInfo product = productService.getProductInfo(command.productId());
if (!product.isOrderable()) {
throw new IllegalStateException("현재 구매할 수 없는 상품입니다. productId = " + command.productId());
}
Cart cart = cartRepository.findByUserId(command.userId())
.orElseGet(() -> Cart.create(command.userId()));
cart.addItem(command.productId(), command.quantity());
return CartView.from(cartRepository.save(cart));
}

@Transactional
public CartView changeQuantity(Long userId, Long productId, int quantity) {
Cart cart = getCartEntity(userId);
cart.changeQuantity(productId, quantity);
return CartView.from(cart);
}

@Transactional
public CartView removeItem(Long userId, Long productId) {
Cart cart = getCartEntity(userId);
cart.removeItem(productId);
return CartView.from(cart);
}

@Transactional
public void clear(Long userId) {
getCartEntity(userId).clear();
}

@Transactional(readOnly = true)
public CartView getCart(Long userId) {
return CartView.from(getCartEntity(userId));
}

private Cart getCartEntity(Long userId) {
return cartRepository.findByUserId(userId)
.orElseThrow(() -> new EntityNotFoundException("장바구니가 비어 있습니다. userId=" + userId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.growmighty.lectures.firstday.tangledmonolith.cart.application.dto;

public record AddCartItemCommand(Long userId, Long productId, int quantity) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.growmighty.lectures.firstday.tangledmonolith.cart.application.dto;

import com.growmighty.lectures.firstday.tangledmonolith.cart.domain.Cart;

import java.util.List;

public record CartView (Long cartId, Long userId, List<Line> items) {
public record Line(Long productId, int quantity) {
}

public static CartView from(Cart cart) {
List<Line> lines = cart.getItems().stream()
.map(item -> new Line(item.getProductId(), item.getQuantity()))
.toList();
return new CartView(cart.getId(), cart.getUserId(), lines);
}
}
Loading