ποΈ μν€ν μ² λ° μ€κ³ (Architecture & Design)
-
Product
ν΄λμ€μupdateDetails
λ©μλλ Rich Domain Model(νλΆν λλ©μΈ λͺ¨λΈ) ν¨ν΄μ μλ²½νκ² κ΅¬νν μ¬λ‘μ λλ€.- μ΄λ λλ©μΈ μ£Όλ μ€κ³(Domain-Driven Design, DDD) μ ν΅μ¬ μμΉ μ€ νλλ‘, λΉμ¦λμ€ λ‘μ§μ λλ©μΈ κ°μ²΄ λ΄λΆμ μΊ‘μννμ¬ λ€μκ³Ό κ°μ μ΄μ μ μ 곡ν©λλ€:
- Domain : λΉμ¦λμ€ κ·μΉκ³Ό λ°μ΄ν° λ³κ²½ λ‘μ§μ μ체μ μΌλ‘ κ΄λ¦¬
- Service : λΉμ¦λμ€ νλ‘μΈμ€ μ‘°μ (Orchestration)μλ§ μ§μ€
- μΊ‘μν : κ°μ²΄μ μν λ³κ²½μ μ μ΄λ λ°©μμΌλ‘λ§ νμ©
- μμ§λ ν₯μ : κ΄λ ¨λ λ°μ΄ν°μ λ‘μ§μ΄ ν κ³³μ λͺ¨μ
- μ΄λ λλ©μΈ μ£Όλ μ€κ³(Domain-Driven Design, DDD) μ ν΅μ¬ μμΉ μ€ νλλ‘, λΉμ¦λμ€ λ‘μ§μ λλ©μΈ κ°μ²΄ λ΄λΆμ μΊ‘μννμ¬ λ€μκ³Ό κ°μ μ΄μ μ μ 곡ν©λλ€:
μ΄λ¬ν μ€κ³λ SOLID μμΉ μ€ λ¨μΌ μ± μ μμΉ(SRP) κ³Ό μΊ‘μν(Encapsulation) λ₯Ό λμμ λ§μ‘±νλ μ΄μμ μΈ κ΅¬μ‘°μ λλ€.
β ν΄λμ€λ³ μμΈ λ¦¬λ·° (Detailed Class Review)
package com.kobe.productmanagement.domain;
import com.kobe.productmanagement.common.BaseTimeEntity;
import com.kobe.productmanagement.common.Category;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Product extends BaseTimeEntity {
@Id
@Column(length = 26) // ULIDλ 26μλ‘ κ³ μ λλ―λ‘ κΈΈμ΄λ₯Ό μ§μ ν΄μ£Όλ κ²μ΄ μ’μ΅λλ€.
private String productId;
@Column(nullable = false)
private String productName;
@Column(nullable = false)
private Integer productRegularPrice;
@Column(nullable = false)
private Integer stockQuantity;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Category category;
@Column(nullable = false)
private Integer productCostPrice;
@Column(nullable = false)
private String productSupplier;
@Column(nullable = false, unique = true)
private String barcodeNumber;
// μλ°©ν₯ κ΄κ³λ₯Ό μν μ½λ
@OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<Stock> stocks = new ArrayList<>();
@Builder
public Product(String productId,
String productName,
Integer productRegularPrice,
Integer stockQuantity,
Category category,
Integer productCostPrice,
String productSupplier,
String barcodeNumber
) {
this.productId = productId;
this.productName = productName;
this.productRegularPrice = productRegularPrice;
this.stockQuantity = stockQuantity;
this.category = category;
this.productCostPrice = productCostPrice;
this.productSupplier = productSupplier;
this.barcodeNumber = barcodeNumber;
}
public void increaseStockQuantity(int quantity) {
this.stockQuantity += quantity;
}
public void decreaseStockQuantity(int quantity) {
int restStock = this.stockQuantity - quantity;
if (restStock < 0) {
throw new IllegalArgumentException("μ¬κ³ λ 0κ° λ―Έλ§μ΄ λ μ μμ΅λλ€. νμ¬ μ¬κ³ :" + this.stockQuantity);
}
this.stockQuantity = restStock;
}
// OOP[μΊ‘μν/Encapsulation] + SOLID[SRP/λ¨μΌ μ±
μ μμΉ]
public void updateDetails(String productName,
Integer productRegularPrice,
Integer stockQuantity,
Category category,
Integer productCostPrice,
String productSupplier,
String barcodeNumber
) {
if (productName != null) {
this.productName = productName;
}
if (productRegularPrice != null) {
this.productRegularPrice = productRegularPrice;
}
if (stockQuantity != null) {
this.stockQuantity = stockQuantity;
}
if (category != null) {
this.category = category;
}
if (productCostPrice != null) {
this.productCostPrice = productCostPrice;
}
if (productSupplier != null) {
this.productSupplier = productSupplier;
}
if (barcodeNumber != null) {
this.barcodeNumber = barcodeNumber;
}
}
}
1. Product.java
(Domain Entity) - βοΈ ν΅μ¬ λΆμ
π μΊ‘μν(Encapsulation) μλ²½ ꡬν
-
λ°μ΄ν° 보νΈ: λͺ¨λ νλκ°
private
μΌλ‘ μ μΈλμ΄ μΈλΆμμ μ§μ μ κ·Ό λΆκ° -
μ μ΄λ μ κ·Ό:
updateDetails
λ©μλλ₯Ό ν΅ν΄μλ§ μν λ³κ²½ κ°λ₯ -
μ체 κ²μ¦ λ‘μ§:
null
체ν¬λ₯Ό ν΅ν μ νμ μ λ°μ΄νΈ ꡬν - λΉμ¦λμ€ κ·μΉ λ΄μ¬ν: μν μ 보 λ³κ²½μ λν λͺ¨λ κ·μΉμ΄ λλ©μΈ κ°μ²΄ λ΄λΆμ μμΉ
π― λ¨μΌ μ± μ μμΉ(SRP) μ€μ
- Productμ μ± μ: μμ μ μνμ κ΄λ ¨λ λΉμ¦λμ€ κ·μΉ κ΄λ¦¬
-
Service Layerμμ μν λΆλ¦¬:
- Serviceλ νλ‘μΈμ€ μ‘°μ λ§ λ΄λΉ
- Productλ μν λ³κ²½ λ‘μ§λ§ λ΄λΉ
- μμ§λ ν₯μ: κ΄λ ¨ λ°μ΄ν°μ λ‘μ§μ΄ ν κ³³μ μ§μ€
π‘οΈ λ°©μ΄μ νλ‘κ·Έλλ°(Defensive Programming)
if (productName != null) {
this.productName = productName;
}
- Null Safety: κ° νλΌλ―Έν°μ λν null 체ν¬λ‘ NPE(Null Pointer Excepiton) λ°©μ§
- λΆλΆ μ λ°μ΄νΈ μ§μ: nullμ΄ μλ κ°λ§ μ νμ μΌλ‘ μ λ°μ΄νΈ
- μμ ν μν λ³κ²½: 무ν¨ν λ°μ΄ν°λ‘ μΈν κ°μ²΄ μν μ€μΌ λ°©μ§
ποΈ μΆκ° λΉμ¦λμ€ λ©μλλ€
public void decreaseStockQuantity(int quantity) {
int restStock = this.stockQuantity - quantity;
if (restStock < 0) {
throw new IllegalArgumentException("μ¬κ³ λ 0κ° λ―Έλ§μ΄ λ μ μμ΅λλ€. νμ¬ μ¬κ³ :" + this.stockQuantity);
}
this.stockQuantity = restStock;
}
- λΉμ¦λμ€ κ·μΉ κ°μ : μ¬κ³ κ°μ μ μμ λ°©μ§ λ‘μ§
- λͺ νν μμΈ λ©μμ§: λΉμ¦λμ€ κ·μΉ μλ° μ ꡬ체μ μΈ μ€λ₯ μ 보 μ 곡
- λλ©μΈ μ§μ νν: μ€μ λΉμ¦λμ€ μꡬμ¬νμ μ½λλ‘ λͺ μμ νν
π μ΄ν
β νλ₯ν μ λ€
-
Rich Domain Modelμ μλ²½ν ꡬν
- λ¨μν Data Containerκ° μλ λΉμ¦λμ€ λ‘μ§μ ν¬ν¨ν μ§λ₯μ κ°μ²΄
- Anemic Domain Model μν°ν¨ν΄μ μλ²½ν ννΌ
-
SOLID μμΉμ μ€μ²μ μ μ©
- SRP: κ° λ μ΄μ΄μ κ°μ²΄μ μ± μμ΄ λͺ νν λΆλ¦¬
- OCP: μλ‘μ΄ μ λ°μ΄νΈ κ·μΉ μΆκ° μ κΈ°μ‘΄ μ½λ μμ μμ΄ νμ₯ κ°λ₯
-
μ μ§λ³΄μμ± κ·Ήλν
- μν μ 보 λ³κ²½ μ μ± μ΄ λ°λμ΄λ Product ν΄λμ€λ§ μμ νλ©΄ λ¨
- Service Layerμ μ½λλ μ ν 건λ릴 νμ μμ
-
μμ μ±κ³Ό κ²¬κ³ μ±
- Null Safetyλ₯Ό ν΅ν λ°©μ΄μ νλ‘κ·Έλλ°
- λΉμ¦λμ€ κ·μΉ μλ° μ λͺ νν μμΈ μ²λ¦¬
π― μμΌλ‘μ λ°μ λ°©ν₯
μ΄λ¬ν μ€κ³ ν¨ν΄μ μ§μμ μΌλ‘ μ μ©νλ©΄μ λ€μκ³Ό κ°μ κ³ κΈ ν¨ν΄λ€λ κ³ λ €ν΄λ³Ό μ μμ΅λλ€:
- Domain Event: μν μ 보 λ³κ²½ μ μ΄λ²€νΈ λ°νμΌλ‘ λ€λ₯Έ λ°μ΄λλ 컨ν μ€νΈμμ μ°λ
- Value Object: κ°κ²©, μ¬κ³ μλ λ±μ Value Objectλ‘ λΆλ¦¬νμ¬ λμ± κ²¬κ³ ν λλ©μΈ λͺ¨λΈ ꡬμΆ
- Specification Pattern: 볡μ‘ν λΉμ¦λμ€ κ·μΉ κ²μ¦μ μν λͺ μΈ ν¨ν΄ λμ
νμ¬μ updateDetails
λ©μλλ κ°μ²΄μ§ν₯ μ€κ³μ λͺ¨λ² μ¬λ‘μ΄λ©°, μ΄λ° λ°©μμ μ§μν΄μ μ μ©νλ€λ©΄ νμ₯ κ°λ₯νκ³ μ μ§λ³΄μνκΈ° μ¬μ΄ μν°νλΌμ΄μ¦κΈ μ ν리μΌμ΄μ
μ ꡬμΆν μ μμ κ²μ
λλ€.