๐Ÿš€ DDD ๊ธฐ๋ฐ˜ ์ฃผ๋ฌธ ์ƒ์„ฑ ํ”„๋กœ์„ธ์Šค ์„ค๊ณ„: Facade์™€ ๋„๋ฉ”์ธ ์„œ๋น„์Šค ๋ถ„๋ฆฌ ์—ฌ์ •

zionยท2025๋…„ 11์›” 13์ผ

์ฃผ๋ฌธ ์ƒ์„ฑ ํŠธ๋žœ์žญ์…˜์„ ๊น”๋”ํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ๋กœ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด, ๊ธฐ๋Šฅ๊ณผ ์ฑ…์ž„์„ ์ชผ๊ฐœ๊ณ  ํ•ฉ์น˜๋Š” ์—ฌ์ •์˜ ๊ธฐ๋ก์ž…๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ ์‘์šฉ ์„œ๋น„์Šค(Facade)์™€ ๋„๋ฉ”์ธ ์„œ๋น„์Šค์˜ ๊ฒฝ๊ณ„๋ฅผ ๋ช…ํ™•ํžˆ ํ–ˆ์Šต๋‹ˆ๋‹ค.

1. ๐Ÿ” ์ดˆ๊ธฐ ๋ถ„์„: ์ฃผ๋ฌธ ํŠธ๋žœ์žญ์…˜์˜ 4๋‹จ๊ณ„

๊ตฌํ˜„ํ•ด์•ผํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๋ฌธ์žฅ์œผ๋กœ,
"์ฃผ๋ฌธ์ž๊ฐ€ / ์žฌ๊ณ ๊ฐ€ ์žˆ๋Š” ์ƒํ’ˆ์„ / ์ถฉ๋ถ„ํ•œ ํฌ์ธํŠธ๋กœ ๊ฒฐ์žฌํ•˜์—ฌ / ์ฃผ๋ฌธ์„ ์ƒ์„ฑํ•œ๋‹ค."

  1. ๋ฌธ์žฅ์—์„œ ํ•„์š”ํ•œ ๋„๋ฉ”์ธ(๋Œ€์ƒ)์€?
    1) ์‚ฌ์šฉ์ž
    2) ์ƒํ’ˆ(์ฃผ๋ฌธ์— ํ•„์š”ํ•œ ์ž์›)
    3) ํฌ์ธํŠธ(์ฃผ๋ฌธ์— ํ•„์š”ํ•œ ์ž์›)
    4) ์ฃผ๋ฌธ

  2. ์–ด๋–ค ์ˆœ์„œ๋กœ ์ง„ํ–‰๋˜์–ด์•ผ ํ•˜๋Š”๊ฐ€?
    1) ์ฃผ๋ฌธ์— ํ•„์š”ํ•œ ๋„๋ฉ”์ธ์ด ์ง„์งœ ์žˆ๋Š”๊ฐ€?
    2) ์ฃผ๋ฌธ์— ํ•„์š”ํ•œ ์ž์›์€ ํ™•๋ณดํ•˜์˜€๋Š”๊ฐ€?
    3) ์ž์›์„ ์‚ฌ์šฉํ•œ๋‹ค.
    4) ์ฃผ๋ฌธ์„ ์ƒ์„ฑํ•œ๋‹ค.

๐Ÿ“‹ ์ฃผ๋ฌธ ์ƒ์„ฑ ํ”„๋กœ์„ธ์Šค ๋‹จ๊ณ„ (DDD ๊ด€์ )

์ˆœ์„œ๋ช…์นญํ•ต์‹ฌ ํ–‰์œ„๊ด€๋ จ ๋„๋ฉ”์ธ ์—”ํ‹ฐํ‹ฐ
1ํ™•์ธ (Active)์‚ฌ์šฉ์ž, ์ƒํ’ˆ, ํฌ์ธํŠธ๊ฐ€ ์กด์žฌํ•˜๋Š”๊ฐ€?User, Product, Point
2๊ฒ€์ฆ (Validate)์žฌ๊ณ /์ž”์•ก ์กฐ๊ฑด์ด ์ถฉ๋ถ„ํ•œ๊ฐ€?Product, Point
3์‹คํ–‰ (Execute)์žฌ๊ณ  ์ฐจ๊ฐ, ํฌ์ธํŠธ ์ฐจ๊ฐ (์ƒํƒœ ๋ณ€๊ฒฝ)Product, Point
4์ƒ์„ฑ (Create)์ตœ์ข… ์ฃผ๋ฌธ ๊ฐ์ฒด ์ƒ์„ฑ ๋ฐ ์ €์žฅOrder

1.1. ํ™•์ธ: ๋„๋ฉ”์ธ ์ •๋ณด ์กฐํšŒ (Read-Only)

์ฃผ๋ฌธ ์ƒ์„ฑ์ด ๊ฐ€๋Šฅํ•œ ํ™œ์„ฑ ์ƒํƒœ์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

  • userService.getActiveUser(Long userId)
  • productService.getExistProducts(List<Long> productIds)
  • pointService.getAvailablePoints(Long userId) (ํฌ์ธํŠธ๋Š” ๊ฒฝ์šฐ์— ๋”ฐ๋ผ ํ•„์ˆ˜X)

1.2. ๊ฒ€์ฆ & ์‹คํ–‰: ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ์ •์˜

๊ฐ€์žฅ ๋ณต์žกํ•œ ์žฌ๊ณ /ํฌ์ธํŠธ์˜ ๊ฒ€์ฆ ๋ฐ ์ฐจ๊ฐ์€ ๋‹ค์Œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•„์š”๋กœ ํ•ฉ๋‹ˆ๋‹ค.

์ž์›ํ˜„์žฌ ์ƒํƒœ (๋„๋ฉ”์ธ)ํ•„์š” ์ˆ˜๋Ÿ‰/๊ธˆ์•ก (์š”์ฒญ)
์žฌ๊ณ List<Product> (ํ˜„์žฌ ์žฌ๊ณ )Map<Long, Long> (์ƒํ’ˆ ID โ†’ ์ˆ˜๋Ÿ‰)
ํฌ์ธํŠธPoint ๋˜๋Š” UserSUM(์ƒํ’ˆ ๊ฐ€๊ฒฉ x ์ˆ˜๋Ÿ‰)

1.3. ์‹คํ–‰

์ „ ๋‹จ๊ณ„์˜ ์ด๋ฆ„๊ณผ ํŠน์ง•์„ ์ ์–ด๋ณด์ž

1) ํ™•์ธ

  • ์œ ์ € ์ •๋ณด (DB ์กฐํšŒ)
  • ์ƒํ’ˆ ์ •๋ณด (DB ์กฐํšŒ)
  • ํฌ์ธํŠธ ์ •๋ณด (DB ์กฐํšŒ)
userService.getActiveUser(Long `์‚ฌ์šฉ์žID`)
productService.getExistProducts(List <Long\> `์ƒํ’ˆIDs`)
pointService.getAvailablePoints(Long `์‚ฌ์šฉ์žID`)

2) ์žฌ๊ณ ์ฐจ๊ฐ

  • ํ•„์š”ํ•œ ์žฌ๊ณ  ๊ฒ€์ฆ
  • ์žฌ๊ณ ์ฐจ๊ฐ(ํŠธ๋žœ์žญ์…˜)
verifyProductStock(List <Product\> `ํ˜„์žฌ์žฌ๊ณ `, List<Long ์ƒํ’ˆID ,Long ์ˆ˜๋Ÿ‰\> `ํ•„์š”ํ•œ ์žฌ๊ณ `)
deductProductStock(List<Product\> `ํ˜„์žฌ์žฌ๊ณ `, List<Long ์ƒํ’ˆID,Long ์ˆ˜๋Ÿ‰\> `ํ•„์š”ํ•œ ์žฌ๊ณ `)

3) ํฌ์ธํŠธ์ฐจ๊ฐ

  • ์ถฉ๋ถ„ํ•œ ํฌ์ธํŠธ ๊ฒ€์ฆ
  • ํฌ์ธํŠธ ์ฐจ๊ฐ(ํŠธ๋žœ์žญ์…˜)
verifyPointBalance(Point `ํ˜„์žฌ ํฌ์ธํŠธ`, `? ํ•„์š”ํ•œ ํฌ์ธํŠธ`)
useUserPoint(Point `ํ˜„์žฌ ํฌ์ธํŠธ`, `? ํ•„์š”ํ•œ ํฌ์ธํŠธ`)

4) ์ฃผ๋ฌธ์„œ ์ƒ์„ฑ(ํŠธ๋žœ์žญ์…˜)

createOrder(Order)

2. ๐Ÿงฑ ์„ค๊ณ„ ์‹œ๋„์™€ ํ•ต์‹ฌ ๊ฒฐ์ •

๊ฒฐ์ • 1: DB ์กฐํšŒ์™€ ํŠธ๋žœ์žญ์…˜์ด ์ผ์–ด๋‚˜๋Š” ๊ธฐ๋Šฅ์€ Application service, ๊ทธ ์™ธ๋Š” Domain service

๊ฒฐ์ • 2: ๊ฒ€์ฆ๊ณผ ์ฐจ๊ฐ์€ ํ•œ๋ฒˆ์—

verify(๊ฒ€์ฆ) ํ›„ deduct(์ฐจ๊ฐ) ์„ ๋ถ„๋ฆฌํ•˜๋Š” ๋Œ€์‹ , ํ•˜๋‚˜์˜ ๋„๋ฉ”์ธ ์„œ๋น„์Šค ๋‚ด์—์„œ ๊ฒ€์ฆ๊ณผ ์ƒํƒœ ๋ณ€๊ฒฝ(์ฐจ๊ฐ)์„ ํ•˜๋‚˜์˜ ํ–‰์œ„๋กœ ํ†ตํ•ฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž…๋ ฅ๊ฐ’์ด ๋™์ผํ•˜๋ฉฐ, ๋ฆฌ์ŠคํŠธ ํ™•์ธ ๋ฐ ๋ฐ์ดํ„ฐ ์ˆ˜์ •์œผ๋กœ ์ค‘๋ณต ์กฐํšŒ๊ฐ€ ์ผ์–ด๋‚˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ž…๋‹ˆ๋‹ค.

๊ฒฐ์ • 3: Map<Long, Long>์œผ๋กœ ๋ฐ์ดํ„ฐ ํ‘œ์ค€ํ™”

public class OrderCreateV1Dto {
  public record OrderItemRequest(long productId, long quantity) {
  }
  public record OrderRequest(List<OrderItemRequest> items) {
  }
}

์žฌ๊ณ ๋ฐ์ดํ„ฐ List<Product> ์™€ ์š”์ฒญ๋ฐ›์€ List<OrderItemRequest> ์ฃผ๋ฌธ๋‚ด์—ญ์„ ๋น„๊ตํ•˜๊ธฐ ์œ„ํ•ด์„œ, List<OrderItemRequest> ๋ฅผ ์ค‘๋ณต์„ ์ œ๊ฑฐํ•œ Map<Long,Long> ํ˜•์‹์œผ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ productId๋ฅผ ํ‚ค๊ฐ’์œผ๋กœ ์ˆ˜๋Ÿ‰๊ฒ€์ƒ‰์ด ์šฉ์ดํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

public record CreateOrderCommand(Long userId, Map<Long, Long> orderItemInfo) {
  public record ItemCommand(Long productId, Map<Long, Long> quantity) {
  }
}

์ง€๊ธˆ๊นŒ์ง€์˜ ๊ฒฐ์ •์„ ๋ฐ˜ํ™˜ ํƒ€์ž…๊นŒ์ง€ ํฌํ•จํ•ด์„œ ์ ์–ด๋ณด๋ฉด,

UserService.getActiveUser(Long userId)->User
ProductService.getExistProducts(List<Long>)->List<Product>
PointService.getAvailablePoints(Long userId)->Point
 
verifyProductStock(List<Product> ํ˜„์žฌ์žฌ๊ณ , Map<Long ์ƒํ’ˆID ,Long ์ˆ˜๋Ÿ‰> ํ•„์š”ํ•œ์žฌ๊ณ )-> `์žฌ๊ณ ๋ฅผ ์ฐจ๊ฐํ•œ List<Product>`
deductProductStock(List<Product> ํ˜„์žฌ์žฌ๊ณ , Map<Long ์ƒํ’ˆID,Long ์ˆ˜๋Ÿ‰> ํ•„์š”ํ•œ ์žฌ๊ณ )

verifyPointBalance(Point ํ˜„์žฌ ํฌ์ธํŠธ, List<Product> ๊ฐ€๊ฒฉ,Map<Long,Long> ์ˆ˜๋Ÿ‰) -> `์‚ฌ์šฉ ํฌ์ธํŠธ๋ฅผ ์ฐจ๊ฐํ•œ Point`
saveUserPoint(Point ํ˜„์žฌ ํฌ์ธํŠธ, `ํ•„์š”ํ•œ ํฌ์ธํŠธ`)

createOrder(Order)-> Order

๊ฒฐ์ • 4: verifyPointBalance์—์„œ ์ฐจ๊ฐ๋œ ํฌ์ธํŠธ๊ฐ€ ์•„๋‹Œ ์ด ๊ฐ€๊ฒฉ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ž.

์‹œ๋„ 1: verifyPointBalance ์—์„œ ํฌ์ธํŠธ๋ฅผ ์ฐจ๊ฐํ•˜๊ณ  ์ž”์•ก Point๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ž?

verifyPointBalance์œผ๋กœ( Point, List<Product>, Map<Long,Long>)์„ ๋ณด๋‚ด๊ณ , Point ์—์„œ ์ด๊ฐ€๊ฒฉ์„ ๋บ€ ๋‚จ์€ Point ๋ฅผ ๋ฆฌํ„ดํ•˜๊ณ  ๊ทธ๋Œ€๋กœ Point ๋ฅผ ์ €์žฅํ•˜์ž.

ํฌ์ธํŠธ ์ฐจ๊ฐํ›„, ์ฃผ๋ฌธ ์ƒ์„ฑ์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š”?

  • Order: ์œ ์ €ID, ์ด ๊ฐ€๊ฒฉ

  • OrderItem: ์ƒํ’ˆID, ์ˆ˜๋Ÿ‰, ๋‹จ๊ฐ€

    ์ด๊ฐ€๊ฒฉ์„ ๋น„๊ตํ•˜๋Š” verifyPointBalance์—์„œ ์ฐจ๊ฐ๋œ ์ž”์•ก ํฌ์ธํŠธ๊ฐ€ ๋ฆฌํ„ด๋œ๋‹ค. ๋”ฐ๋ผ์„œ ์–ด๋””์„œ๋„ ์ด๊ฐ€๊ฒฉ์„ ์ €์žฅํ•˜๊ณ  ์žˆ์ง€ ์•Š์•„, ๊ฒ€์ฆ ์ดํ›„ ์ด ๊ฐ€๊ฒฉ์„ ๋‹ค์‹œ ๊ตฌํ•ด์•ผ ํ•œ๋‹ค. verifyPointBalance ์—์„œ ์ฐจ๊ฐ๋œ ํฌ์ธํŠธ๊ฐ€ ์•„๋‹Œ ์ด๊ฐ€๊ฒฉ์„ ๋ฐ˜ํ™˜ํ•˜์ž.

Order Facade์—์„œ ์ •๋ฆฌํ•˜๋ฉด,

OrderFacade {
  public OrderInfo createOrder(CreateOrderCommand command) {
  User user = UserService.getActiveUser(Long userId);
  List<Product> products = ProductService.getExistProducts(List<Long> productIds);
  Point point = PointService.getAvailablePoints(Long userId);
  
  List<Product> deductedProducts = OrderPreparer.verifyProductStock(List<Product> products, List<Long ์ƒํ’ˆID ,Long ์ˆ˜๋Ÿ‰> command.getOrderItemCommand);
  deductProductStock(deductedProducts)
  
  BigDecimal requestedPoints = OrderPreparer.verifyPointBalance(Point ํ˜„์žฌ ํฌ์ธํŠธ, List<Product> ๊ฐ€๊ฒฉ,Map<Long,Long> ์ˆ˜๋Ÿ‰);
  saveUserPoint(Point ํ˜„์žฌ ํฌ์ธํŠธ - requestedPoints)

  Order order = createOrder(User,List<Product>,Point)
  return OrderInfo.from(savedOrder);
  }

๊ฒฐ์ • 5: ์ฃผ๋ฌธ ์ƒ์„ฑ์‹œ, ์ด๊ฐ€๊ฒฉ ๋Œ€์‹  Map<Long ์ƒํ’ˆID ,Long ์ˆ˜๋Ÿ‰>

์ฃผ๋ฌธ ์ƒ์„ธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ์„ ์œ„ํ•ด ์ƒํ’ˆ์˜ ๋‹จ๊ฐ€,์ˆ˜๋Ÿ‰์„ ์•Œ๋ ค๋ฉด Map<Long ์ƒํ’ˆID ,Long ์ˆ˜๋Ÿ‰> ๋ฅผ ๋„ฃ์–ด์•ผ ํ•˜๊ณ , List ๋งŒ๋“ค๋ฉด์„œ ์ด๊ฐ€๊ฒฉ์„ ๊ณ„์‚ฐํ• ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ด๊ฐ€๊ฒฉ ๋Œ€์‹  Map<Long ์ƒํ’ˆID ,Long ์ˆ˜๋Ÿ‰> ์„ ๋„ฃ๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ์ • 6: ํฌ์ธํŠธ ์ €์žฅ ๋Œ€์‹  ํฌ์ธํŠธ ์ฐจ๊ฐ

์ฐจ๊ฐํ•œ ํฌ์ธํŠธ๋ฅผ ์ €์žฅํ•˜๋ ค๋‹ค๊ฐ€, "ํฌ์ธํŠธ๋ฅผ ์ €์žฅํ•œ๋‹ค" ๋Œ€์‹  "ํฌ์ธํŠธ๋ฅผ ์ฐจ๊ฐํ•œ๋‹ค" ์˜ ์˜๋ฏธ๊ฐ€ ํ–‰๋™์˜ ์˜๋ฏธ๋ฅผ ์ž˜ ๋‹ด๊ณ  ์žˆ๋Š”๊ฒƒ ๊ฐ™์•„์„œ, verifyPointBalance ๋Œ€์‹ , ์ด๊ฐ€๊ฒฉ์„ ์กฐํšŒํ•˜๋Š” ํ•จ์ˆ˜๋กœ ๋ฐ”๊พธ๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

@Transactional
  public OrderInfo createOrder(CreateOrderCommand command) {
    Map<Long, Long> quantityMap = command.orderItemInfo();
    User user = userService.getActiveUser(command.userId());
    List<Product> productList = productService.getExistingProducts(quantityMap.keySet());
    Point point = pointService.getAvailablePoints(user.getId());

    List<Product> deductedProducts = OrderPreparer.verifyProductStock(productList, quantityMap);
    productService.save(deductedProducts);

    BigDecimal totalAmt = OrderPreparer.getTotalAmt(point, productList, quantityMap);
    pointService.use(user, totalAmt);
    
    Order savedOrder = createOrderService.save(user, productList, quantityMap);

    return OrderInfo.from(savedOrder);
  }

๊ฒฐ์ • 7:verify(๊ฒ€์ฆ) ํ›„ **deduct(์‹คํ–‰)์„ ํ•ฉ์น˜์ž

๊ฐ™์€ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์“ฐ๊ณ  ์žˆ๋Š” ์žฌ๊ณ ๊ฒ€์ฆ ๋ฐ ์ฐจ๊ฐ ๊ณผ ํฌ์ธํŠธ๊ฒ€์ฆ ๋ฐ ์ฐจ๊ฐ์„ productStockService,userPointService๋กœ ํ•ฉ์ณค์Šต๋‹ˆ๋‹ค.

@Transactional
  public OrderInfo createOrder(CreateOrderCommand command) {
    Map<Long, Long> quantityMap = command.orderItemInfo();
    User user = userService.getActiveUser(command.userId());
    List<Product> productList = productService.getExistingProducts(quantityMap.keySet());
    
    productStockService.deduct(productList, quantityMap);
    userPointService.use(user, productList, quantityMap);

    Order savedOrder = createOrderService.save(user, productList, quantityMap);
    return OrderInfo.from(savedOrder);
  }

๐Ÿ’ก ์ •๋ฆฌ ๋ฐ ๊ฒฐ๋ก 

์ฒ˜์Œ ๋ณต์žกํ•˜๊ฒŒ ๋А๊ปด์กŒ๋˜ ์ฃผ๋ฌธ ์ƒ์„ฑ ๋กœ์ง์ด, ์ฑ…์ž„(์žฌ๊ณ , ํฌ์ธํŠธ)์„ ๊ธฐ์ค€์œผ๋กœ ๋„๋ฉ”์ธ ์„œ๋น„์Šค(Manager)๋ฅผ ๋ถ„์„ํ•˜๊ณ , ์ตœ์ข…์ ์œผ๋กœ ๊ฐ„๊ฒฐํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด ํ˜•ํƒœ์˜ OrderFacade๋ฅผ ๋งŒ๋“ค์–ด๋‚ด์—ˆ์Šต๋‹ˆ๋‹ค. ์ถ”๊ฐ€๋กœ,์ดˆ๊ธฐ ๋ชจ๋ธ์˜ ์กด์žฌ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๋Š” ๋ถ€๋ถ„๋„ ํ•œ๋ฒˆ ํ•ฉ์ณ๋ณด๋ฉด ๋” ๋ณด๊ธฐ์ข‹์€ ์ฝ”๋“œ๊ฐ€ ๋ ๊ฒƒ ๊ฐ™์€๋ฐ ์ด๋ ‡๊ฒŒ ๊ณ„์† ํ•ฉ์ณ๋„ ๋˜๋Š”์ง€, ์˜๋ฌธ์ด ๋“ค๊ธฐ๋„ ํ–ˆ์Šต๋‹ˆ๋‹ค.

profile
be_zion

0๊ฐœ์˜ ๋Œ“๊ธ€