π₯ λλ©μΈ λͺ¨λΈ ν¨ν΄μ΄λ?
λλ©μΈ λͺ¨λΈ ν¨ν΄(Domain Model Pattern)μ μ ν리μΌμ΄μ μ λΉμ¦λμ€ λ‘μ§μ λλ©μΈ κ°μ²΄(μν°ν°) λ΄λΆμ ν¬ν¨μν€λ μ€κ³ ν¨ν΄μ λλ€. μ¦, λΉμ¦λμ€ κ·μΉ, λ°μ΄ν° λ³κ²½, λ°μ΄ν° κ²μ¦ λ±μ λλ©μΈ κ°μ²΄κ° μ§μ μννλλ‘ μ€κ³ν©λλ€.
μ΄ ν¨ν΄μ DDD(Domain-Driven Design)μ ν΅μ¬ κ°λ μ€ νλμ΄λ©°, κ°μ²΄μ§ν₯μ μ₯μ μ μ΅λν νμ©ν©λλ€.
νΉμ§
- λλ©μΈ κ°μ²΄μ μ± μ λΆμ¬:κ°μ²΄κ° μμ μ μνλ₯Ό κ΄λ¦¬νκ³ , κ΄λ ¨λ λΉμ¦λμ€ λ‘μ§μ μνν©λλ€.
- μ:
Order
κ°μ²΄κ° μ£Όλ¬Έ μμ±, μ£Όλ¬Έ μ·¨μ λ±μ μν. - λΉμ¦λμ€ λ‘μ§μ μΊ‘μν:λ°μ΄ν°μ μ΄λ₯Ό μ²λ¦¬νλ λ‘μ§μ΄ ν κ°μ²΄ λ΄λΆμ μΊ‘μνλ©λλ€.
- μν°ν° μ€μ¬ μ€κ³:μν°ν° κ°μ²΄κ° λ¨μν λ°μ΄ν°λ₯Ό μ μ₯νλ μν λ§ νμ§ μκ³ , λΉμ¦λμ€ λ‘μ§μ λ΄κ³ μμ΅λλ€.
λλ©μΈ λͺ¨λΈ ν¨ν΄μ ꡬ쑰
1. μν°ν°(Entity)
μ ν리μΌμ΄μ μ λΉμ¦λμ€ λ‘μ§κ³Ό λ°μ΄ν°λ₯Ό μΊ‘μνν©λλ€.
μ: Order
, Member
, Product
λ±.
2. λ°Έλ₯(Value Object)
νΉμ κ°μ νννλ κ°μ²΄λ‘, κ° μμ²΄κ° μ€μνλ©° λΆλ³μ±μ κ°μ§λλ€.
μ: Address
, Money
λ±.
3. μ 그리κ²μ΄νΈ(Aggregate)
μν°ν°μ λ°Έλ₯ κ°μ²΄κ° νλλ‘ λ¬ΆμΈ μ§ν©μ λλ€.
μ 그리κ²μ΄νΈ 루νΈ(μ£Όλ‘ μν°ν°)κ° μΈλΆμμ μνΈμμ©μ λ΄λΉνλ©°, λ΄λΆ κ΅¬μ± μμλ₯Ό κ΄λ¦¬ν©λλ€.
λλ©μΈ λͺ¨λΈ ν¨ν΄μ μ₯μ
- κ°μ²΄μ§ν₯μ μ₯μ νμ©: λ°μ΄ν°μ νμλ₯Ό νλμ κ°μ²΄λ‘ λ¬Άμ΄ κ°μ²΄μ§ν₯μ μ€κ³λ₯Ό μ΄μ§ν©λλ€.
- λΉμ¦λμ€ λ‘μ§ μ§μ€ν: λΉμ¦λμ€ λ‘μ§μ΄ κ°μ²΄μ μ§μ€λμ΄ μ½λλ₯Ό μ΄ν΄νκΈ° μ½μ΅λλ€.
- μμ§λ ν₯μ: κ΄λ ¨λ λ‘μ§κ³Ό λ°μ΄ν°κ° ν κ³³μ λͺ¨μ¬ μ μ§λ³΄μκ° μ¬μμ§λλ€.
- μ€λ³΅ μ½λ κ°μ: κ°μ²΄κ° μνλ₯Ό μ€μ€λ‘ κ΄λ¦¬νλ―λ‘, μλΉμ€ κ³μΈ΅μ λ‘μ§ μ€λ³΅μ μ€μΌ μ μμ΅λλ€.
λλ©μΈ λͺ¨λΈ ν¨ν΄μ λ¨μ
- μ΄κΈ° μ€κ³ λΉμ©: λλ©μΈ λͺ¨λΈμ μ€κ³νκ³ κ΅¬ννλ λ° λ§μ μκ°μ΄ νμν©λλ€.
- 볡μ‘μ± μ¦κ°: κ°μ²΄ κ° κ΄κ³μ λΉμ¦λμ€ λ‘μ§μ΄ 볡μ‘ν κ²½μ°, κ΄λ¦¬κ° μ΄λ €μμ§ μ μμ΅λλ€.
- μλ ¨λ κ°λ°μ νμ: κ°μ²΄μ§ν₯ μ€κ³μ λλ©μΈ μ£Όλ μ€κ³(DDD)μ λν κΉμ μ΄ν΄κ° νμν©λλ€.
μμ: Order ν΄λμ€
@Entity
public class Order {
@Id
@GeneratedValue
private Long id;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List orderItems = new ArrayList<>();
@OneToOne(cascade = CascadeType.ALL)
private Delivery delivery;
private LocalDateTime orderDate;
@Enumerated(EnumType.STRING)
private OrderStatus status; // μ: ORDERED, CANCELED
//== λΉμ¦λμ€ λ‘μ§ ==//
// μ£Όλ¬Έ μμ±
public static Order createOrder(Member member, Delivery delivery, OrderItem... orderItems) {
Order order = new Order();
order.setMember(member);
order.setDelivery(delivery);
for (OrderItem orderItem : orderItems) {
order.addOrderItem(orderItem);
}
order.setOrderDate(LocalDateTime.now());
order.setStatus(OrderStatus.ORDERED);
return order;
}
// μ£Όλ¬Έ μ·¨μ
public void cancel() {
if (delivery.getStatus() == DeliveryStatus.COMPLETE) {
throw new IllegalStateException("μ΄λ―Έ λ°°μ‘ μλ£λ μ£Όλ¬Έμ μ·¨μν μ μμ΅λλ€.");
}
this.setStatus(OrderStatus.CANCELED);
for (OrderItem orderItem : orderItems) {
orderItem.cancel(); // μ£Όλ¬Έμνλ μ·¨μ
}
}
// μ΄ μ£Όλ¬Έ κ°κ²© κ³μ°
public int getTotalPrice() {
return orderItems.stream()
.mapToInt(OrderItem::getTotalPrice)
.sum();
}
}
λΆμ
1. createOrder
λ©μλ
μ μ λ©μλλ‘ μ£Όλ¬Έμ μμ±νλ©°, Member
, Delivery
, OrderItem
μ μ€μ ν©λλ€.
μ£Όλ¬Έ μμ± κ³Όμ μμμ 볡μ‘ν λ‘μ§μ μΊ‘μννμ¬ μλΉμ€ κ³μΈ΅μ λ¨μνν©λλ€.
2. cancel
λ©μλ
μ£Όλ¬Έ μνλ₯Ό μ·¨μλ‘ λ³κ²½νλ©°, μ°κ΄λ OrderItem
λ μ·¨μν©λλ€.
λΉμ¦λμ€ κ·μΉ(μ: λ°°μ‘ μλ£ ν μ·¨μ λΆκ°)μ μΊ‘μνν©λλ€.
3. getTotalPrice
λ©μλ
μ£Όλ¬Έμνμ μ΄ κ°κ²©μ κ³μ°ν©λλ€.
λͺ¨λ κ³μ° λ‘μ§μ΄ Order
λ΄λΆμ μμΌλ―λ‘, μΈλΆμμ κ³μ° λ‘μ§μ μ νμκ° μμ΅λλ€.
μλΉμ€ κ³μΈ΅μ λ¨μν
λλ©μΈ λͺ¨λΈ ν¨ν΄μ μ¬μ©νλ©΄ μλΉμ€ κ³μΈ΅μ μ½λκ° κ°κ²°ν΄μ§λλ€.
// μλΉμ€ κ³μΈ΅
@Transactional
public Long order(Member member, Delivery delivery, OrderItem... orderItems) {
Order order = Order.createOrder(member, delivery, orderItems);
orderRepository.save(order);
return order.getId();
}
μλΉμ€ κ³μΈ΅μμλ λΉμ¦λμ€ λ‘μ§μ μννλ λμ , λλ©μΈ κ°μ²΄λ₯Ό μ‘°ν©νκ³ μ μ₯νλ μν λ§ μνν©λλ€.
λλ©μΈ λͺ¨λΈ ν¨ν΄κ³Ό JPA
JPAλ μν°ν° κ°μ²΄λ₯Ό κ΄λ¦¬νλ©°, λλ©μΈ λͺ¨λΈ ν¨ν΄κ³Ό μ λ§μ΅λλ€.
νΉν JPAμ μμμ± μ»¨ν μ€νΈλ₯Ό νμ©νλ©΄, λλ©μΈ κ°μ²΄κ° μνλ₯Ό λ³κ²½νλλΌλ JPAκ° μ΄λ₯Ό κ°μ§νκ³ μλμΌλ‘ λ°μ΄ν°λ² μ΄μ€μ λ°μν©λλ€.
λλ©μΈ λͺ¨λΈ ν¨ν΄μ νμ©
1. μ ν©ν κ²½μ°
- λΉμ¦λμ€ λ‘μ§μ΄ 볡μ‘νκ³ , κ°μ²΄ κ° κ΄κ³κ° λ§μ λλ©μΈ.
- μ½λμ μ¬μ¬μ©μ±κ³Ό μμ§λκ° μ€μν λκ·λͺ¨ μ ν리μΌμ΄μ .
2. μ ν©νμ§ μμ κ²½μ°
- κ°λ¨ν CRUD μ ν리μΌμ΄μ .
- λΉμ¦λμ€ λ‘μ§μ΄ λ¨μν κ²½μ°, νΈλμμ μ€ν¬λ¦½νΈ ν¨ν΄μ΄ λ μ ν©ν μ μμ΅λλ€.
π₯ νΈλμμ μ€ν¬λ¦½νΈ ν¨ν΄ μμ
νΈλμμ μ€ν¬λ¦½νΈ ν¨ν΄μ λΉμ¦λμ€ λ‘μ§μ μν°ν°κ° μλ μλΉμ€ κ³μΈ΅μμ μ²λ¦¬νλ λ°©μμ λλ€.
μ΄ ν¨ν΄μ λ¨μν λΉμ¦λμ€ λ‘μ§κ³Ό CRUD μμ μ μ ν©νλ©°, λλ©μΈ λͺ¨λΈ ν¨ν΄λ³΄λ€ ꡬνμ΄ κ°λ¨ν©λλ€.
1. μν°ν° κ°μν
μν°ν° ν΄λμ€λ λ°μ΄ν°λ₯Ό μ μ₯νκ³ κ°μ Έμ€λ μν λ§ μννλ©°, λΉμ¦λμ€ λ‘μ§μ΄ μμ΅λλ€.
@Entity
public class Order {
@Id
@GeneratedValue
private Long id;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List orderItems = new ArrayList<>();
@OneToOne(cascade = CascadeType.ALL)
private Delivery delivery;
private LocalDateTime orderDate;
@Enumerated(EnumType.STRING)
private OrderStatus status; // μ: ORDERED, CANCELED
// Getter, Setterλ§ μ‘΄μ¬
public void addOrderItem(OrderItem orderItem) {
this.orderItems.add(orderItem);
orderItem.setOrder(this);
}
}
2. μλΉμ€ κ³μΈ΅μμ λͺ¨λ λΉμ¦λμ€ λ‘μ§ μ²λ¦¬
@Service
@Transactional
public class OrderService {
private final OrderRepository orderRepository;
private final MemberRepository memberRepository;
public OrderService(OrderRepository orderRepository, MemberRepository memberRepository) {
this.orderRepository = orderRepository;
this.memberRepository = memberRepository;
}
// μ£Όλ¬Έ μμ±
public Long createOrder(Long memberId, Delivery delivery, List orderItems) {
// νμ μ‘°ν
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new IllegalArgumentException("νμμ΄ μ‘΄μ¬νμ§ μμ΅λλ€."));
// μ£Όλ¬Έ μμ±
Order order = new Order();
order.setMember(member);
order.setDelivery(delivery);
for (OrderItem orderItem : orderItems) {
order.addOrderItem(orderItem);
}
order.setOrderDate(LocalDateTime.now());
order.setStatus(OrderStatus.ORDERED);
// μ£Όλ¬Έ μ μ₯
orderRepository.save(order);
return order.getId();
}
// μ£Όλ¬Έ μ·¨μ
public void cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new IllegalArgumentException("μ£Όλ¬Έμ΄ μ‘΄μ¬νμ§ μμ΅λλ€."));
if (order.getDelivery().getStatus() == DeliveryStatus.COMPLETE) {
throw new IllegalStateException("μ΄λ―Έ λ°°μ‘ μλ£λ μ£Όλ¬Έμ μ·¨μν μ μμ΅λλ€.");
}
// μν λ³κ²½
order.setStatus(OrderStatus.CANCELED);
// μ£Όλ¬Έμν μ·¨μ
for (OrderItem orderItem : order.getOrderItems()) {
orderItem.cancel(); // μ£Όλ¬Έμν μ·¨μ λ‘μ§ νΈμΆ
}
}
// μ΄ μ£Όλ¬Έ κ°κ²© κ³μ°
public int getTotalPrice(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new IllegalArgumentException("μ£Όλ¬Έμ΄ μ‘΄μ¬νμ§ μμ΅λλ€."));
return order.getOrderItems().stream()
.mapToInt(OrderItem::getTotalPrice)
.sum();
}
}
νΈλμμ μ€ν¬λ¦½νΈ ν¨ν΄μ νΉμ§
- μν°ν°λ λ¨μν λ°μ΄ν°μ κ΄κ³λ₯Ό νννλ μν λ§ μν.
- λΉμ¦λμ€ λ‘μ§μ λͺ¨λ μλΉμ€ κ³μΈ΅μμ μ²λ¦¬.
- λ°μ΄ν° νλ¦μ΄ κ°λ¨ν νλ‘μ νΈμμ ν¨κ³Όμ .
λλ©μΈ λͺ¨λΈ ν¨ν΄κ³Ό λΉκ΅
νΉμ§ | λλ©μΈ λͺ¨λΈ ν¨ν΄ | νΈλμμ μ€ν¬λ¦½νΈ ν¨ν΄ |
---|---|---|
λ‘μ§ μμΉ | μν°ν° λ΄λΆ (λλ©μΈ κ°μ²΄) | μλΉμ€ κ³μΈ΅ |
λΉμ¦λμ€ λ‘μ§ λ³΅μ‘λ | 볡μ‘ν λΉμ¦λμ€ λ‘μ§μ μ ν© | κ°λ¨ν CRUD λ° λΉμ¦λμ€ λ‘μ§μ μ ν© |
μ μ§λ³΄μ | κ°μ²΄μ§ν₯μ μ€κ³λ‘ μμ§λκ° λμ | κ΅¬μ‘°κ° λ¨μνμ§λ§ λΉμ¦λμ€ λ‘μ§μ΄ λΆμ°λ μ μμ |
μ¬μ¬μ©μ± | μν°ν° λ‘μ§μ μ¬μ¬μ©νκΈ° μ©μ΄ | μλΉμ€ κ³μΈ΅μμ κ°μ λ‘μ§μ΄ λ°λ³΅λ κ°λ₯μ± μμ |
μ€κ³ λ° κ΅¬ν | μ΄κΈ° μ€κ³ λΉμ©μ΄ νΌ | ꡬνμ΄ κ°λ¨νκ³ λΉ λ¦ |
κ²°λ‘
νΈλμμ μ€ν¬λ¦½νΈ ν¨ν΄μ λ¨μν μ ν리μΌμ΄μ μ μ ν©νλ©°, λΉμ¦λμ€ λ‘μ§μ΄ 볡μ‘νμ§ μμ λ λΉ λ₯΄κ³ ν¨μ¨μ μΈ μ νμ λλ€.
νμ§λ§ λ‘μ§μ΄ 볡μ‘ν΄μ§κ±°λ μ¬μ¬μ©μ±μ΄ μ€μν΄μ§λ€λ©΄ λλ©μΈ λͺ¨λΈ ν¨ν΄μΌλ‘ μ νμ κ³ λ €ν΄μΌ ν©λλ€.
λκΈ