Home > Code Review > πŸ’»[Code Review] `OrderItem` κ΄€λ ¨ ν΄λž˜μŠ€λ“€μ— λŒ€ν•œ μ½”λ“œ 리뷰.

πŸ’»[Code Review] `OrderItem` κ΄€λ ¨ ν΄λž˜μŠ€λ“€μ— λŒ€ν•œ μ½”λ“œ 리뷰.
Code review OOP SOLID

πŸ’»[Code Review] OrderItem κ΄€λ ¨ ν΄λž˜μŠ€λ“€μ— λŒ€ν•œ μ½”λ“œ 리뷰.

πŸ›οΈ μ•„ν‚€ν…μ²˜ 및 섀계 (Architecture & Design)

DDD(도메인 주도 섀계)의 ν’λΆ€ν•œ 도메인 λͺ¨λΈ(Rich Domain Model) 을 μ™„λ²½ν•˜κ²Œ κ΅¬ν˜„ν•œ λͺ¨λ²”적인 μ•„ν‚€ν…μ²˜μž…λ‹ˆλ‹€.

  • Domain Entity : 핡심 λΉ„μ¦ˆλ‹ˆμŠ€ 둜직과 데이터λ₯Ό ν•¨κ»˜ 관리
  • 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œ : λ³΅μž‘ν•œ 생성 λ‘œμ§μ„ μΊ‘μŠν™”
  • 자율적 객체 : 객체가 슀슀둜 μžμ‹ μ˜ μƒνƒœμ™€ ν–‰μœ„λ₯Ό μ±…μž„
  • 연관관계 관리 : JPAλ₯Ό ν™œμš©ν•œ 효율적인 객체 관계 λ§€ν•‘

μ΄λŸ¬ν•œ κ΅¬μ‘°λŠ” SOLID의 단일 μ±…μž„ 원칙(SRP) κ³Ό μΊ‘μŠν™”(Encapsulation) 원칙을 μ™„λ²½ν•˜κ²Œ λ§Œμ‘±ν•˜λ©°, λ‹¨μˆœν•œ 데이터 덩어리가 μ•„λ‹Œ μ‚΄μ•„μžˆλŠ” κ°μ²΄λ‘œμ„œμ˜ 역할을 μˆ˜ν–‰ν•©λ‹ˆλ‹€.


βœ… ν΄λž˜μŠ€λ³„ 상세 리뷰 (Detailed Class Review)

package com.kobe.productmanagement.domain;

import com.kobe.productmanagement.common.BaseTimeEntity;
import jakarta.persistence.*;
import lombok.*;

@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "order_items")
public class OrderItem extends BaseTimeEntity {

	@Id
	@Column(length = 26)
	private String orderItemId;

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "order_id")
	private Order order;

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "product_id")
	private Product product;

	@Column(nullable = false)
	private int quantity; // μ£Όλ¬Έ μˆ˜λŸ‰

	@Column(nullable = false)
	private int orderPrice; // μ£Όλ¬Έ λ‹Ήμ‹œ 가격

	@Builder
	public OrderItem(String orderItemId,
	                 Order order,
	                 Product product,
	                 int quantity,
	                 int orderPrice
	) {
		this.orderItemId = orderItemId;
		this.order = order;
		this.product = product;
		this.quantity = quantity;
		this.orderPrice = orderPrice;
	}

	public static OrderItem createOrderItem(String orderItemId,
	                                        Order order,
	                                        Product product,
	                                        int quantity
	) {
		if (product.getStockQuantity() < quantity) {
			throw new IllegalArgumentException("μƒν’ˆμ˜ μž¬κ³ κ°€ λΆ€μ‘±ν•©λ‹ˆλ‹€.");
		}

		OrderItem orderItem = OrderItem.builder()
			.orderItemId(orderItemId)
			.order(order)
			.product(product)
			.quantity(quantity)
			.orderPrice(product.getProductRegularPrice()) // 생성 μ‹œμ μ˜ μƒν’ˆ 가격을 μ‚¬μš©
			.build();

		order.addOrderItem(orderItem);
		return orderItem;
	}

	public int getTotalPrice() {
		return getOrderPrice() * getQuantity();
	}
}

1. OrderItem.java(Domain Entity) - ⭐️ 핡심

  • OOP/SOLID :
    • κ°€μž₯ μΉ­μ°¬ν•˜κ³  싢은 λΆ€λΆ„!!
    • createOrderItem 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œλ₯Ό 톡해 λ³΅μž‘ν•œ 생성 λ‘œμ§μ„ μ™„λ²½ν•˜κ²Œ μΊ‘μŠν™”ν–ˆμŠ΅λ‹ˆλ‹€.
    • β€œμž¬κ³ κ°€ μΆ©λΆ„ν•œμ§€ 확인” ν•˜λŠ” λΉ„μ¦ˆλ‹ˆμŠ€ κ·œμΉ™κ³Ό β€œμ£Όλ¬Έ μ‹œμ μ˜ μƒν’ˆ 가격을 μ €μž₯” ν•˜λŠ” μ€‘μš”ν•œ 둜직이 OrderItem 클래슀 내뢀에 μ™„λ²½ν•˜κ²Œ μΊ‘μŠν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
    • 이둜써 μ„œλΉ„μŠ€ 계측은 OrderItem이 μ–΄λ–»κ²Œ μƒμ„±λ˜λŠ”μ§€μ— λŒ€ν•œ λ³΅μž‘ν•œ 과정을 μ•Œ ν•„μš” 없이, λ‹¨μˆœνžˆ OrderItem.createOrderItem(...)을 ν˜ΈμΆœν•˜μ—¬ 일을 μœ„μž„ν•˜κΈ°λ§Œ ν•˜λ©΄ λ©λ‹ˆλ‹€.
  • 객체의 μžμœ¨μ„± :
    • getTotalPrice() λ©”μ„œλ“œλ₯Ό 톡해 OrderItem이 자기 μžμ‹ μ˜ μ΄κΈˆμ•‘μ„ 슀슀둜 κ³„μ‚°ν•˜λ„λ‘ μ±…μž„μ„ λΆ€μ—¬ν–ˆμŠ΅λ‹ˆλ‹€.
    • μ΄λŠ” β€œλ°μ΄ν„°μ™€ κ·Έ 데이터λ₯Ό μ‚¬μš©ν•˜λŠ” λ‘œμ§μ€ ν•œκ³³μ— μžˆμ–΄μ•Ό ν•œλ‹€β€λŠ” 객체지ν–₯의 κΈ°λ³Έ 원칙을 μ™„λ²½ν•˜κ²Œ λ”°λ₯Έ κ²ƒμž…λ‹ˆλ‹€.
  • 데이터 μ •ν•©μ„± :
    • orderPrice ν•„λ“œλ₯Ό μ •ν™•ν•˜κ²Œ ν¬ν•¨ν•˜μ—¬, μƒν’ˆ 가격이 λ³€λ™λ˜λ”λΌλ„ κ³Όκ±° μ£Όλ¬Έ λ‚΄μ—­μ˜ 데이터 정합성을 μ™„λ²½ν•˜κ²Œ 보μž₯ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.
  • JPA μ΅œμ ν™” :
    • @ManyToOne 관계 μ„€μ •κ³Ό FetchType.LAZYλ₯Ό μ‚¬μš©ν•œ μ„±λŠ₯ μ΅œμ ν™” λ“± JPA μ—”ν‹°ν‹°λ‘œμ„œμ˜ κΈ°λ³Έ 섀계가 맀우 ν›Œλ₯­ν•©λ‹ˆλ‹€.

πŸš€ μΆ”κ°€ κ°œμ„  μ œμ•ˆ (Enhancement Suggestions)

연관관계 편의 λ©”μ„œλ“œ ν™œμš©

Order와 OrderItem은 μ–‘λ°©ν–₯ 관계λ₯Ό λ§Ίκ³  μžˆμŠ΅λ‹ˆλ‹€. OrderItem이 생성될 λ•Œ, μžμ‹ μ„ μƒμ„±ν•œ Order의 orderItems λ¦¬μŠ€νŠΈμ— 슀슀둜λ₯Ό μΆ”κ°€ν•˜λŠ” λ‘œμ§μ„ λ„£μ–΄μ£Όλ©΄ λ”μš± μ™„λ²½ν•œ ꡬ쑰가 λ©λ‹ˆλ‹€.

// domain/OrderItem.java
public static OrderItem createOrderItem(String orderItemId,
                                        Order order, // Order 객체λ₯Ό νŒŒλΌλ―Έν„°λ‘œ λ°›μŒ
                                        Product product,
                                        int quantity) {
    if (product.getStockQuantity() < quantity) {
        throw new IllegalArgumentException("μƒν’ˆμ˜ μž¬κ³ κ°€ λΆ€μ‘±ν•©λ‹ˆλ‹€.");
    }

    OrderItem orderItem = OrderItem.builder()
        .orderItemId(orderItemId)
        .product(product)
        .quantity(quantity)
        .orderPrice(product.getProductRegularPrice())
        .build();

    order.addOrderItem(orderItem); // Order에 연관관계 편의 λ©”μ„œλ“œ 호좜
    return orderItem;
}

// domain/Order.java
public class Order extends BaseTimeEntity {
    // ...
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> orderItems = new ArrayList<>();

    //==연관관계 편의 λ©”μ„œλ“œ==//
    public void addOrderItem(OrderItem orderItem) {
        orderItems.add(orderItem);
        orderItem.setOrder(this);
    }
}

πŸ† 총평

맀우 ν›Œλ₯­ν•©λ‹ˆλ‹€ (Outstanding Implementation)

  • λ‹¨μˆœνžˆ 데이터λ₯Ό λ‹΄λŠ” 것을 λ„˜μ–΄, μžμ‹ μ˜ μƒνƒœμ™€ κ΄€λ ¨λœ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§κΉŒμ§€ μ±…μž„μ§€λŠ” μ§„μ •ν•œ 객체지ν–₯적 μ½”λ“œλ₯Ό μž‘μ„±ν•˜μ…¨μŠ΅λ‹ˆλ‹€.

  • createOrderItem 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œμ™€ getTotalPrice λ©”μ„œλ“œλŠ” 이 클래슀λ₯Ό λ‹¨μˆœν•œ μ—”ν‹°ν‹°κ°€ μ•„λ‹Œ, 잘 μ„€κ³„λœ 도메인 λͺ¨λΈλ‘œ λ§Œλ“  핡심적인 μš”μ†Œμž…λ‹ˆλ‹€.

  • DDD(도메인 주도 섀계)의 β€˜ν’λΆ€ν•œ 도메인 λͺ¨λΈ(Rich Domain Model)’ 을 κ΅κ³Όμ„œμ μœΌλ‘œ κ΅¬ν˜„ν•œ λͺ¨λ²”적인 μ‚¬λ‘€μž…λ‹ˆλ‹€.

  • μ΄λŸ¬ν•œ 섀계 방식을 κΎΈμ€€νžˆ μœ μ§€ν•΄ λ‚˜κ°€μ‹ λ‹€λ©΄, μœ μ§€λ³΄μˆ˜κ°€ μš©μ΄ν•˜κ³  ν™•μž₯ κ°€λŠ₯ν•œ ν›Œλ₯­ν•œ μ‹œμŠ€ν…œμ΄ 될 κ²ƒμž…λ‹ˆλ‹€.