λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
  • μž₯원읡 κΈ°μˆ λΈ”λ‘œκ·Έ
πŸ”¬application/- DDD

[DDD] Value Object 에 λŒ€ν•œ μ—¬λŸ¬ 이야기

by Wonit 2023. 7. 23.

λͺ©μ°¨

  • entity 에 λŒ€ν•œ 짧은 μ†Œκ°œ
    • λͺ¨λ“  객체가 entity 일까?
  • value object
    • entity 와 value object
  • value object 의 3가지 νŠΉμ„±
    • μΈ‘λŸ‰, μΈ‘μ •, μˆ˜λŸ‰ν™”, ν‘œν˜„
    • λΆˆλ³€μ„±
    • λ“±κ°€μ„±
  • value object λŠ” μ΄λ ‡κ²Œ μ“΄λ‹€
    • java 에선 lombok 의 @Value κ°€ μžˆλ‹€
    • value object 와 context
    • κ°œλ…μ  ν•˜λ‚˜
    • μ—”ν‹°ν‹°μ˜ μ±…μž„ λΆ„μ‚°

 

μ„œλ‘ 

 

μ§€λ‚œμ‹œκ°„μ— μ—¬λŸ¬λ²ˆμ— κ±Έμ³μ„œ entity 에 λŒ€ν•΄μ„œ 이야기λ₯Ό ν•΄λ³΄μ•˜λ‹€. 기본적인 entity 의 κ°œλ…κ³Ό νŠΉμ„± λΆ€ν„° μ‹œμž‘ν•΄μ„œ entity 와 μ‹λ³„μžμ— λŒ€ν•œ μ΄μ•ΌκΈ°κΉŒμ§€..

 

 

μ΄λ²ˆμ‹œκ°„μ—λŠ” 엔티티와 λ”λΆˆμ–΄ 도메인 λͺ¨λΈμ˜ 핡심 κ΅¬μ„±μš”μ†ŒμΈ Value Object, κ°’ 객체에 λŒ€ν•΄μ„œ μ•Œμ•„λ³΄λ„λ‘ ν•˜κ² λ‹€

 

entity 에 λŒ€ν•œ 짧은 recap

 

value object λ₯Ό 이야기할 땐 항상 entity 도 ν•¨κ»˜ λ“±μž₯ν•œλ‹€.

 

value object λ₯Ό 잘 μ΄ν•΄ν•˜κΈ° μœ„ν•΄μ„œ entity 에 λŒ€ν•΄μ„œλ„ μ•Œμ•„λ³Ό ν•„μš”λŠ” μžˆμ§€λ§Œ, entity 에 λŒ€ν•΄μ„œλŠ” μ§€λ‚œ μ‹œκ°„ 이야기λ₯Ό 많이 λ‚˜λˆ΄κΈ° λ•Œλ¬Έμ— μžμ„Έν•œ μ„€λͺ…은 μƒλž΅ν•˜κ³  ν•΅μ‹¬λ§Œ μ΄μ•ΌκΈ°ν•΄λ³΄μž.

 

entity ν•œ λ¬Έμž₯으둜 ν‘œν˜„ν•˜μžλ©΄ μ΄λ ‡κ²Œ 말할 수 μžˆλ‹€

 

entity: 도메인 객체가 연속성을 가지기 μœ„ν•΄ μ‹λ³„μžλ₯Ό λΆ€μ—¬λ°›μ•„, μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ‚΄μ—μ„œ 식별이 될 수 μžˆλŠ” λŒ€μƒ 객체

 

μ΄λ ‡κ²Œ 식별이 ν•„μš”ν•œ μ΄μœ λŠ” λ°”λ‘œ μ‹œκ°„μ— 흐름에 λ”°λ₯Έ λ³€ν™”λ₯Ό μΆ”μ ν•˜κ³  μ‹ΆκΈ° λ•Œλ¬Έμ΄λ‹€.

 

일반적으둜 μ£Όλ¬Έ(Order) λ„λ©”μΈμ—μ„œ μœ μ €μ˜ μ£Όλ¬Έ ν•˜λ‚˜κ°€ μ—¬λŸ¬κ°€μ§€ μƒνƒœλ₯Ό κ°–κ²Œλœλ‹€.

 

  • μ£Όλ¬Έμš”μ²­
  • 결제 λŒ€κΈ°
  • 결제 μ™„λ£Œ
  • μ£Όλ¬Έ 성곡
  • 배솑쀑

 

각각의 μƒνƒœμ— λ”°λΌμ„œ μ£Όλ¬Έ 객체가 ν•΄μ•Όν•  일이 λ‹€λ₯΄λ‹€.

 

μœ μ € ν”Œλ‘œμš°μƒ μœ μ €κ°€ 결제λ₯Ό 마치면 결제 λŒ€κΈ° μ€‘μ΄λ˜ μ£Όλ¬Έ 객체λ₯Ό μ‹λ³„ν•˜κ³  결제 μ™„λ£Œ μƒνƒœλ‘œ λ³€κ²½ν•˜λŠ” 일반적인 흐름을 κ°–λŠ”λ‹€.

 

그럼 λͺ¨λ“  객체가 entity 처럼 식별성이 ν•„μš”ν• κΉŒ?

 

그렇지 μ•Šλ‹€. 도메인 λͺ¨λΈμ— μ‘΄μž¬ν•˜λŠ” λͺ¨λ“  객체가 식별성을 가지면 맀우 λ³΅μž‘ν•œ ꡬ쑰가 될 것이닀.

 

value object λŠ” 식별해야할 ν•„μš”κ°€ μ—†λŠ” κ°’ κ·Έ 자체λ₯Ό μ˜λ―Έν•œλ‹€.

 

entity 와 value object λ₯Ό μ μ ˆν•˜κ²Œ μ„žμ–΄ 도메인을 λͺ¨λΈλ§ν•˜λŠ”것이 μΌλ°˜μ μ΄λ‹€.

 

value object

 

Value Object λŠ” 도메인 λͺ¨λΈ λ‚΄μ—μ„œ νŠΉμ •ν•œ 객체λ₯Ό ν‘œν˜„ν•˜κ³ , μˆ˜μΉ˜ν™”ν•˜κ±°λ‚˜ μΈ‘λŸ‰ν•˜λŠ” 일만 μˆ˜ν–‰ν•œλ‹€.

 

value λŠ” μ–Έμ œ, μ–΄λ””μ„œ, λˆ„κ΅¬μ—μ˜ν•΄ μƒμ„±λ˜μ—ˆλ“  κ·Έλƒ₯ 값일 뿐이닀.

 

μš°λ¦¬κ°€ μ˜μ† κ³Όμ •μ—μ„œ value 에 λŒ€ν•œ 식별을 μžƒμ–΄λ²„λ¦°λ‹€κ³  ν•˜λ”λΌλ„ entity 와 달리 λ¬Έμ œκ°€ λ˜μ§€ μ•ŠλŠ”λ‹€.

 

entity 와 value object κ΅¬λΆ„ν•˜κΈ°

 

entity 와 value λŠ” 도메인 λͺ¨λΈ λ‚΄μ—μ„œ μ€‘μš”ν•œ 역할을 μˆ˜ν–‰ν•˜κ³ , μ„œλ‘œ μƒν˜Έ 보완적인 역할을 μˆ˜ν–‰ν•œλ‹€.

 

μ•žμ„œ μ΄μ•ΌκΈ°ν•œ λ‚΄μš©μ„ ν† λŒ€λ‘œ 이해λ₯Ό 돕기 μœ„ν•΄ ν•œκ°€μ§€ 상황을 가정해보도둝 ν•˜κ² λ‹€.

 

λ‚˜λŠ” μ„Έμƒμ—μ„œ λ‹€λ₯Έ μ‚¬λžŒλ“€κ³Ό ꡬ뢄이 λœλ‹€. 'λ‚˜' λΌλŠ” μ‚¬λžŒμ€ λ‹€λ₯Έ μ‚¬λžŒλ“€κ³Ό μ‹λ³„λ˜μ–΄ 독립적인 개체둜써 μ‚΄μ•„κ°€κ²Œ λœλ‹€.

 

그리고 λ‚˜ λ₯Ό λ‹€λ₯Έ μ‚¬λžŒλ“€κ³Ό κ΅¬λΆ„ν•˜κΈ° λΆ€λͺ¨λ‹˜μ€ λ‚˜μ—κ²Œ 이름 을 지어주셨고, λ‚˜λΌλŠ” λ‚˜μ—κ²Œ κ³ μœ ν•œ μ‹œλ―Όμž„μ„ 증λͺ…ν•˜κΈ° μœ„ν•œ μ£Όλ―Όλ“±λ‘λ²ˆν˜Έ λ₯Ό λ°œκΈ‰ν•΄μ£Όμ—ˆλ‹€.

 

μ—¬κΈ°μ„œ entity 와 value λ₯Ό κ΅¬λΆ„ν•΄λ³΄μž.

 

  • entity: 식별해야할 λŒ€μƒ
  • value: μ–΄λ–€ λŒ€μƒμ„ ν‘œν˜„ν•˜κ³  μˆ˜μΉ˜ν™”ν•˜κ±°λ‚˜ μΈ‘λŸ‰ν•˜λŠ” 쑴재

 

κ·Έλ ‡λ‹€λ©΄ 'λ‚˜' λŠ” entity κ°€ 될 것이고, λ‚˜λ₯Ό μ„€λͺ…ν•˜λŠ” '이름' μ΄λ‚˜ '킀와 λͺΈλ¬΄κ²Œ' ν˜Ήμ€ 'μ£Όλ―Όλ“±λ‘λ²ˆν˜Έ' λŠ” value κ°€ 될 수 μžˆλ‹€.

 

μš°λ¦¬κ°€ 이름에 일련번호λ₯Ό λΆ€μ—¬ν•˜λŠ” 일은 μ—†λ‹€. 이름은 κ·Έλƒ₯ 이름이닀.

 

Value 의 νŠΉμ„±

 

일반적으둜 Value Object λŠ” μ½”λ“œλ‘œμ„œ primitive value κ°€ μ•„λ‹Œ ν•΄λ‹Ή value λ₯Ό Wrapping ν•œ 객체둜 ν‘œν˜„ν•œλ‹€.

 

class Username {
  private final String value;

  public Username(String value) {
    this.value = value;
  }
}

 

μ΄λ ‡κ²Œ Value λ₯Ό Object 둜 μ‚¬μš©ν•˜λ©΄ primitive type 의 value 보닀 훨씬 더 readable ν•˜λ©°, type safe ν•˜λ‹€.

 

이제 본격적으둜 Value 의 3κ°€μ§€μ˜ νŠΉμ„±μ— λŒ€ν•΄μ„œ μ•Œμ•„λ³΄μž

 

  1. λΆˆλ³€μ„±
  2. κ°’ λ“±κ°€μ„±

 

value 의 νŠΉμ„± 1. λΆˆλ³€μ„±

 

value 와 entity 의 κ°€μž₯ 큰 νŠΉμ„±μ΄λΌκ³  ν•œλ‹€λ©΄ mutable κ³Ό immutable 이라고 ν•  수 μžˆλ‹€.

 

μš°μ„  질문 ν•˜λ‚˜λ₯Ό 해보겠닀.

 

μ–΄λ–€ 데이터가 mutable ν•΄μ•Όν•˜λŠ” μ΄μœ λŠ” 뭘까?

 

entity 의 쑴재 μ΄μœ μ™€λ„ λ™μΌν•˜λ‹€.

 

νŠΉμ • 객체가 연속성을 가지고 연속성에 따라 λ‹€λ₯Έ μƒνƒœ (state) λ₯Ό κ°€μ Έμ•Ό ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

 

κ·Έ 연속성을 μœ„ν•΄μ„œ μš°λ¦¬λŠ” μ‹λ³„μžλΌλŠ” 것을 λΆ€μ—¬ν•˜κ³  μ–΄λŠ μ‹œμ μ—” μ‹λ³„μžλ₯Ό ν†΅ν•΄μ„œ κ³Όκ±° μƒνƒœλ₯Ό load ν•˜μ—¬ μƒˆλ‘œμš΄ μƒνƒœλ‘œ λ³€ν™”μ‹œμΌœμ€€λ‹€.

 

ν•˜μ§€λ§Œ μ–΄λ–€ 데이터가 immutable ν•˜λ‹€λ©΄?

 

immutable 이라면 μ–Έμ œλ“ , μ–΄λ–€ μ‹œμ μ΄λ“  μƒμ„±ν•˜κ³  μžŠμ–΄λ²„λ¦΄ 수 μžˆλ‹€. 변경될 일이 μ—†κΈ° λ•Œλ¬Έμ— μ‹λ³„λ˜μ–΄μ•Όν•  μ΄μœ λ„ μ—†λ‹€.

 

변경될 ν•„μš”κ°€ μ—†λŠ” 데이터라면 였히렀 μ‹œμŠ€ν…œ λ‚΄μ—μ„œ 변경될 수 μ—†μŒμ„ 보μž₯ν•˜λŠ” 편이 훨씬 λ‚«λ‹€.

 

μ–΄λ–»κ²Œ immutable ν•˜κ²Œ λ§Œλ“€κΉŒ?

 

java μ—μ„œ immutable ν•˜κ²Œ λ§Œλ“œλŠ” 방법은 κ°„λ‹¨ν•˜λ‹€.

 

값을 λ³€κ²½ν•  수 μ—†λŠ” μƒμˆ˜λ‘œ λ§Œλ“€μ–΄λ²„λ¦¬κ±°λ‚˜, ν•œ 번 μƒμ„±λ˜λ©΄ νŒŒκ΄΄λ λ•ŒκΉŒμ§€ λ³€κ²½λ˜μ§€ μ•Šλ„λ‘ λ§Œλ“€λ©΄ λœλ‹€

 

  • final
    • λ³€μˆ˜λ₯Ό μƒμˆ˜λ‘œ λ§Œλ“€μ–΄λ²„λ¦°λ‹€.
  • no setter
    • 객체가 μ†Œλ©Έν•  λ•Œ κΉŒμ§€ 변경될 수 μ—†λ‹€.

 

λΆˆλ³€μ„±μ„ μ΄μš©ν•œ 예

 

immutable ν•œ νŠΉμ„±μ„ κ°€μž₯ 잘 μ΄μš©ν•œ μΌλ‘€λŠ” λ°”λ‘œ μ—”ν‹°ν‹° μ‹λ³„μžλ₯Ό κ°’κ°μ²΄λ‘œ λ§Œλ“œλŠ” 것이닀.

 

μ—”ν‹°ν‹°μ˜ μ‹λ³„μžλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ‚΄μ—μ„œ 변경될 수 μ—†μŒμ„ 보μž₯ν•΄μ•Όν•œλ‹€.

 

μ§€λ‚œμ‹œκ°„ 엔티티와 μ‹λ³„μžμ— λŒ€ν•΄ μ΄μ•ΌκΈ°ν•˜λ©° μ‹λ³„μž μ•ˆμ •μ„±μ— λŒ€ν•œ 이야기λ₯Ό ν–ˆμ—ˆλ‹€. μ‹λ³„μž μ•ˆμ „μ„±μ€ μ‹λ³„μžκ°€ ν•œ 번 ν• λ‹Ήλ˜λ©΄ 변경될 수 없도둝 ν•˜μ—¬ μ‹λ³„μžμ— λŒ€ν•΄ side effect free ν•œ ν™˜κ²½μ„ λ§Œλ“€μ–΄μ£ΌλŠ” 것을 μ˜λ―Έν•œλ‹€

 

User λΌλŠ” μ—”ν‹°ν‹°κ°€ μ‘΄μž¬ν•œλ‹€λ©΄, μ—”ν‹°ν‹°μ˜ μ‹λ³„μžλ‘œ primitive type 을 μ΄μš©ν–ˆμ„ λ•Œ, μ‹λ³„μž μ•ˆμ •μ„±μ΄ μ§€μΌœμ§€μ§€ μ•Šμ„ 수 μžˆλ‹€.

 

κ²°κ΅­ μ‹λ³„μž 객체λ₯Ό immutable 함을 보μž₯ν•˜λŠ” Value Object 둜 λ§Œλ“€μ–΄μ„œ μ‹λ³„μž μ•ˆμ •μ„±μ„ μ œκ³΅ν•  수 μžˆλ‹€.

 

// μ‹λ³„μž μ•ˆμ •μ„± x
public class User {
  Long id;
}

// μ‹λ³„μž μ•ˆμ •μ„± o
public class User {
  final UserId id; // λ³€κ²½ λΆˆκ°€
}

 

 

value 의 νŠΉμ„± 2. κ°’ λ“±κ°€μ„±

 

예λ₯Όλ“€μ–΄ λ‚΄κ°€ 가진 5λ§Œμ› 지폐와 λ‚΄ 톡μž₯에 μžˆλŠ” 5λ§Œμ›μ€ μ„œλ‘œ λ™μΌν•œ κ°€μΉ˜λ₯Ό λ‚˜νƒ€λ‚Έλ‹€.

 

이렇듯 값은 속성과 ꡬ쑰가 λ™μΌν•˜λ‹€λ©΄ μ„œλ‘œ κ°™λ‹€κ³  이야기할 수 μžˆλ‹€.

 

μ΄λ ‡κ²Œ 말할 수 μžˆλŠ” μ΄μœ λŠ” λ°”λ‘œ 값은 등가성이 μ‘΄μž¬ν•˜κΈ° λ•Œλ¬Έμ΄κ³  value object μ—­μ‹œ κ°’ λ“±κ°€μ„±, 동등성(equality) 비ꡐλ₯Ό 지원해야 ν•œλ‹€.

 

일반적으둜 java μ—μ„œ equals() λ©”μ„œλ“œλŠ” 기본적으둜 객체의 동일성을 λΉ„κ΅ν•˜κΈ° μœ„ν•΄ reference λ₯Ό ν™•μΈν•œλ‹€.

 

즉, 두 객체의 λ©”λͺ¨λ¦¬ μ£Όμ†Œκ°€ 같은지λ₯Ό ν™•μΈν•˜κΈ° λ•Œλ¬Έμ— λ™μΌν•œ 속성과 값을 가지더라도 λ‹€λ₯Έ 객체라고 ν‘œν˜„ν•˜λŠ”λ°, Value λŠ” reference 비ꡐ가 μ•„λ‹Œ property value, structure 비ꡐλ₯Ό μˆ˜ν–‰ν•΄μ•Ό ν•œλ‹€.

 

κ²°κ΅­ equals() 와 hashcode() λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ν•΄μ€˜μ•Ό ν•œλ‹€.

 

equals 와 hashcode overriding 에 λŒ€ν•΄μ„œλŠ” equals와 hashCodeλŠ” μ™œ 같이 μž¬μ •μ˜ν•΄μ•Ό ν• κΉŒ? μ—μ„œ μžμ„Ένžˆ 확인할 수 μžˆλ‹€.

 

Value λŠ” μ΄λ ‡κ²Œ μ“΄λ‹€

 

μ•žμ„œμ„œ value object 에 λŒ€ν•œ νŠΉμ„±μ„ μ‚΄νŽ΄λ³΄μ•˜μœΌλ‹ˆ 이제 Value Object λ₯Ό μ–΄λ–»κ²Œ μ‚¬μš©ν• μ§€ μ΄μ•ΌκΈ°ν•΄λ³΄μž.

 

  1. java 에선 lombok 의 @Value κ°€ μžˆλ‹€
  2. value object 도 context κ°€ μ€‘μš”ν•˜λ‹€
  3. κ°œλ…μ  ν•˜λ‚˜
  4. entity 의 μ±…μž„ λΆ„μ‚°

value μ‚¬μš©ν•˜κΈ° 1. java 에선 lombok 의 @Value κ°€ μžˆλ‹€.

 

μ•žμ„œ μ•Œμ•„λ³Έ Value Object 의 3가지 νŠΉμ„±μ„ κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄μ„œ λ‚˜λŠ” lombok 의 @Value μ–΄λ…Έν…Œμ΄μ…˜μ„ 주둜 μ‚¬μš©ν•œλ‹€.

 

import lombok.Value;

@Value
class Username {
  String value;
}

@Value λ₯Ό μ‚¬μš©ν•˜λ©΄ μš°μ„  ν•΄λ‹Ή 객체가 값객체인지 μ•„λ‹Œμ§€ μ–΄λ…Έν…Œμ΄μ…˜ λ©”νƒ€λ‘œ μ‰½κ²Œ λͺ…μ‹œν•  수 μžˆλ‹€λŠ” μž₯점이 μžˆλ‹€.

 

λ˜ν•œ Value 의 λΆˆλ³€ νŠΉμ„±μ„ λ§Œμ‘±μ‹œν‚€κΈ° μœ„ν•΄μ„œ 항상 반볡적으둜 μž‘μ„±ν•˜λŠ” private final μ½”λ“œλ₯Ό λŒ€μ‹  μž‘μ„±ν•΄μ£Όλ©° κ°’ 등가성을 μœ„ν•œ equals and hash code 도 λŒ€μ‹ ν•΄μ„œ μž‘μ„±ν•΄μ€€λ‹€.

 

equals κ°€ 참쑰값에 λŒ€ν•œ 비ꡐ가 μ•„λ‹Œ νŠΉμ„±μœΌλ‘œ toString() μ—μ„œ 더 이상 객체 μ£Όμ†Œκ°’μ„ 좜λ ₯ν•  ν•„μš”κ°€ μ—†λŠ”λ°, μ΄λ˜ν•œ @Value κ°€ toString() 을 override ν•΄μ£ΌκΈ° λ•Œλ¬Έμ— κ°„νŽΈν•˜λ‹€.

 

value μ‚¬μš©ν•˜κΈ° 2. value object 도 context κ°€ μ€‘μš”ν•˜λ‹€

λ‹€λ₯Έ DDD 의 μ „μˆ μ  μš”μ†Œλ“€κ³Ό λ§ˆμ°¬κ°€μ§€λ‘œ value object 도 μ—­μ‹œ context κ°€ μ€‘μš”ν•˜λ‹€.

 

즉, context 에 λ”°λΌμ„œ value object 냐 μ•„λ‹ˆλƒκ°€ κ²°μ •λœλ‹€λŠ” 것이닀

 

μš°λ¦¬κ°€ 5λ§Œμ›κΆŒ 지폐λ₯Ό ν•œμž₯ 가지고 μžˆλ‹€κ³  ν•΄λ³΄μž. 그럼 κ·Έ λˆμ„ λˆ„κ°€ λ§Œλ“€μ—ˆκ³  μ–Έμ œ λ§Œλ“€μ—ˆλŠ”μ§€ μ€‘μš”ν• κΉŒ?

 

λ‚΄ μ˜†μ— μžˆλŠ” λ™λ£Œ 지갑에 μžˆλŠ” 5λ§Œμ›κΆŒκ³Ό λ‚΄κ°€ 가지고 μžˆλŠ” 5λ§Œμ›κΆŒμ„ λ°”κΎΈμžκ³  ν•˜λ©΄ 별 μ˜μ‹¬μ—†μ΄ λ°”κΏ€ 수 μžˆλ‹€.

 

μ™œμΌκΉŒ? λ°”λ‘œ λ™μΌν•œ κ°€μΉ˜λ₯Ό μ§€λ‹ˆκΈ° λ•Œλ¬Έμ΄λ‹€.

 

λ°˜λ©΄μ— 범죄에 μ—°λ₯˜λœ μ§€νλŠ” λ‚˜λΌμ—μ„œ 일련번호λ₯Ό 톡해 식별할 수 μžˆλ‹€.

 

μš°λ¦¬κ°€ 범죄에 μ—°λ₯˜λœ λˆμ„ value object 둜 λ§Œλ“€λ©΄ μ–΄λ–»κ²Œ 될까? 그럼 μ•žμ„  νŠΉμ„±μ— μ˜ν•΄μ„œ λˆμ΄λΌλŠ” value λŠ” μ‹λ³„μžκ°€ 없기에 연속성을 μžƒμ–΄λ²„λ € 좔적이 λΆˆκ°€λŠ₯ν•˜κ²Œ 될 수 μžˆλ‹€.

 

이렇듯 값을 λ‚˜νƒ€λ‚΄λŠ” value object 더라도 상황에 λ”°λΌμ„œ entity κ°€ 될 μˆ˜λ„ 있고 value object κ°€ 될 μˆ˜λ„ μžˆλ‹€.

 

value μ‚¬μš©ν•˜κΈ° 3. κ°œλ…μ  ν•˜λ‚˜

Value Object λŠ” κ°’ 속성을 ν•˜λ‚˜ λ˜λŠ” μ—¬λŸ¬κ°œλ₯Ό κ°€μ§ˆ 수 μžˆλ‹€.

 

μ—¬λŸ¬κ°€μ§€ 속성이 μ‘΄μž¬ν•  수 있기 떄문에 잘λͺ»λœ 섀계에 λ”°λΌμ„œλŠ” Value Object κ°€ λͺ¨ν˜Έν•œ κ°œλ…μœΌλ‘œ 진화할 κ°€λŠ₯성이 μžˆλ‹€.

 

κ°œλ…μ μœΌλ‘œ ν•˜λ‚˜μ—¬μ•Ό ν•œλ‹€.

 

// 5,000,000β‚© κ°œλ…μ  ν•˜λ‚˜, 두 νŠΉμ„±μ„ ν•˜λ‚˜λ‘œ 봐야 ν•˜λ‚˜κ°€ 됨
class Money {
  long value; // 5,000,000
  String currency; // 원, λ‹¬λŸ¬
}

value μ‚¬μš©ν•˜κΈ° 4. entity 의 μ±…μž„ λΆ„μ‚°

 

이 κ°œλ…μ΄ κ°€μž₯ μ€‘μš”ν•˜λ‹€.

 

value λ₯Ό 단지 entity λ₯Ό ν‘œν˜„ν•˜λŠ” κ΅¬μ„±μš”λ‘œμ†Œλ§Œ μ‚¬μš©ν•œλ‹€λŠ” 것은 anemic ν•œ 도메인 λͺ¨λΈμ΄ 될 κ°€λŠ₯성이 λ†’λ‹€.

 

μ˜ˆμ „μ—λŠ” value λ₯Ό 단지 primitive type λŒ€μ‹ ν•΄μ„œ 이름을 λΆ€μ—¬ν•˜λŠ” λͺ©μ μœΌλ‘œ μ‚¬μš©ν–ˆμ—ˆλŠ”λ°, 이렇닀면 primitive type 을 μ‚¬μš©ν•˜λŠ”νŽΈμ΄ 훨씬 λ‚«λ‹€. μ μ ˆν•œ μ±…μž„μ΄ μ—†λŠ” value object λŠ” 쑴재 μ΄μœ κ°€ μ—†λ‹€.

 

λ‹€μŒ ꡬ독에 λŒ€ν•œ λ‹€μŒ 청ꡬλ₯Ό μƒμ„±ν•˜λŠ” 도메인 λͺ¨λΈλ§μ„ 봐보자

 

 

μ‚¬μš©μžλŠ” Subscription κ°μ²΄μ—κ²Œ λ‹€μŒ 청ꡬ 생성을 μš”μ²­ν•œλ‹€.

 

κ·Έ λ•Œμ˜ μ°Έμ—¬ 객체듀을 Value Object λ₯Ό μ΄μš©ν•˜μ—¬ λͺ¨λΈλ§ν•œ μ½”λ“œμ΄λ‹€.

 

public class Subscription {
    ReservedBill bill; // λ‹€μŒ 청ꡬ
}

@Value(staticConstructor = "of")
public class ReservedBill {
    ReservedAt reservedAt; // 청ꡬ μ˜ˆμ•½μΌ
    BillInterval interval; // 청ꡬ 주기
}

@Value(staticConstructor = "of")
public class ReservedAt { // 청ꡬ μ˜ˆμ•½μΌ
    LocalDateTime value; // 청ꡬ μ˜ˆμ•½μΌμ˜ value
}

@Value(staticConstructor = "of")
public class BillInterval { // 청ꡬ 주기
    long value; // μ£ΌκΈ° value
    IntervalUnit unit; // 주기의 λ‹¨μœ„
}

public enum IntervalUnit {
    DAY,
    MONTH
}

 

클래슀λͺ…λ§Œ 보더라도 Value Object 에 λŒ€ν•œ 도메인 λͺ¨λΈλ§μ΄ 잘 λ˜μ–΄μžˆκ³ , 각각의 Value 듀이 κ°œλ…μ  ν•˜λ‚˜λ₯Ό ν‘œν˜„ν•˜κ³  μžˆλ‹€.

 

그리고 μ•žμ„  상황을 κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄μ„œ λ‚˜λŠ” μ΄λ ‡κ²Œ μ½”λ“œλ₯Ό μ§œλ΄€λ‹€.

 

public class Subscription {
  ReservedBill bill;

  public void reserveNextBill() {

    ReservedAt reservedAt = bill.getReservedAt();

    BillInterval interval = bill.getInterval();

    if (MONTH.equals(interval.getUnit())) { // 청ꡬ μ£ΌκΈ°κ°€ 'μ›”' 이라면
      LocalDateTime past = reservedAt.getValue();

      // interval 만큼 μ˜ˆμ•½μΌμ„ plus
      LocalDateTime next = past.plusMonths(interval.getValue());

      ReservedAt nextReservedAt = ReservedAt.of(next);

      // λ‹€μŒ bill 생성
      this.bill = ReservedBill.of(nextReservedAt, interval);

    } else if (DAY.equals(interval.getUnit())) {
      // TODO impl
    }
  }
}

 

μœ„ μ½”λ“œλŠ” value object 의 μ±…μž„ 뢄산이 μ μ ˆν•˜κ²Œ μˆ˜ν–‰λ˜μ§€ μ•Šμ•˜λ‹€κ³  ν•  수 μžˆλ‹€. κ·Έλƒ₯ primitive value λ₯Ό wrpping ν•œκ²ƒ κ·Έ 이상도 μ΄ν•˜λ„ μ•„λ‹ˆλ‹€.

 

Subscription μ—μ„œ reserveNextBill() μ΄λΌλŠ” λ©”μ„œλ“œλ₯Ό 톡해 청ꡬλ₯Ό μ˜ˆμ•½ν•˜λŠ” ν΄λΌμ΄μ–ΈνŠΈ μž…μž₯μ—μ„œλŠ” μΊ‘μŠν™”κ°€ 잘 λ˜μ—ˆμ„μ§€λŠ” λͺ°λΌλ„, entity 와 value object κ°„μ˜ μ±…μž„ λΆ„λ°°κ°€ μ μ ˆν•˜κ²Œ 이뀄지지 μ•Šμ•˜λ‹€.

 

μ΄λŠ” Value Object λ₯Ό λ‹¨μˆœνžˆ value data holder 둜만 λ°”λΌλ³΄μ•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

 

Value Object κ°€ μ§„μ •ν•œ 빛을 λ‚΄κΈ° μœ„ν•΄μ„œλŠ” Value 만의 μ±…μž„μ„ λΆ€μ—¬ν•΄μ•Όν•œλ‹€.

 

Value Object λ₯Ό data holder 둜 μ‚¬μš©ν–ˆλ˜ μ•žμ„  예제λ₯Ό μ±…μž„κ³Ό 역할에 따라 λ‹€μŒκ³Ό 같이 λ¦¬νŒ©ν† λ§ν•  수 μžˆλ‹€.

 

public class Subscription {
  private ReservedBill bill;

  public void reserveNextBill() {
      this.bill = bill.reserve(); // μ˜ˆμ•½ μš”μ²­
  }
}

 

Subscription 은 ReservedBill μ—κ²Œ λ‹€μŒ 청ꡬλ₯Ό μƒμ„±ν•˜λΌκ³  μš”μ²­ν•œλ‹€.

 

Subscription μž…μž₯μ—μ„œλŠ” 세뢀적인 λ‚΄μš©μ„ μ•Œ ν•„μš”κ°€ μ—†λ‹€. 단지 μš”μ²­ν•  뿐이닀.

 

그럼 ReservedBill 이 λ‹€μŒ 과정을 κ±°μ³μ„œ nextBill 을 μƒμ„±ν•œλ‹€

 

@Value(staticConstructor = "of")
public class ReservedBill {
  ReservedAt reservedAt;
  BillInterval interval;

  public ReservedBill reserve() {
    ReservedAt nextReservedAt = reservedAt.next(interval); // reservedAt μ—κ²Œ 계산 μš”μ²­
    return ReservedBill.of(nextReservedAt, this.interval); // value λ°˜ν™˜
  }
}

 

nextBill 을 생성할 λ•Œμ—λ„ ReservedAt 을 μ–΄λ–»κ²Œ κ³„μ‚°ν•˜λŠ”μ§€ μ•Œ ν•„μš”κ°€ μ—†λ‹€.

 

μ‹€μ œλ‘œ ReservedAt μ—κ²Œ μ μ ˆν•œ κ·Όκ±°λ₯Ό νŒŒλΌλ―Έν„°λ‘œ λ„˜κ²¨ ν˜‘λ ₯ν•  뿐이닀

 

@Value(staticConstructor = "of")
public class ReservedAt {
  LocalDateTime value;

  public ReservedAt next(BillInterval interval) {
    if (MONTH.equals(interval.getUnit())) {
      return ReservedAt.of(value.plusMonths(interval.getValue()));
    }

    throw new UnsupportedOperationException();
  }
}

 

μ΄λ ‡κ²Œ ν•¨μœΌλ‘œμ¨ 각각의 Value κ°€ μ±…μž„μ„ κ°–κ³  entity 와 ν˜‘λ ₯을 ν•˜κ²Œ λ˜λŠ”λ°, 이것이 λ°”λ‘œ Value Object 의 λ³Έμ§ˆμ΄λ‹€.

 

μ–΄μ©Œλ©΄ DDD 의 λͺ‡λͺ‡ μ „μˆ μ  νŒ¨ν„΄μ€ 객체λ₯Ό 잘 지ν–₯ν•˜λŠ” 방법을 λ§ν•˜λŠ”κ²ƒ 같기도 ν•˜λ‹€. 객체의 μ—­ν• κ³Ό μ±…μž„, ν˜‘λ ₯κ³Ό μΊ‘μŠν™”κ°€ DDD 의 근간이라 μƒκ°ν•œλ‹€

 

마치며

 

value object λŠ” μ–΄μ©Œλ©΄ DDD 의 μ—¬λŸ¬ μ „μˆ μ€‘μ— κ°€μž₯ 많이 μ‚¬μš©λ˜λŠ” μ „μˆ μ΄ μ•„λ‹κΉŒ μƒκ°ν•œλ‹€.

 

과거에 DDD 에 κ²½ν—˜μ΄ ν’λΆ€ν•œ ν•œ μ‹œλ‹ˆμ–΄ κ°œλ°œμžλ‘œλΆ€ν„° λ‹Ήμ‹ μ˜ Value Object λŠ” λ„ˆλ¬΄ λΉˆμ•½ν•΄μš”, κ·Έλƒ₯ 값듀을 객체둜 λ§Œλ“ κ²ƒ λΏμ΄μ—μš”. λΌλŠ” ν”Όλ“œλ°±μ„ λ°›μ•˜λ‹€.

 

κ·Έ λ‹Ήμ‹œμ—λŠ” λ‚΄κ°€ λ§Œλ“  Value Object κ°€ 뭘 더 ν•  수 μžˆμ„μ§€ λͺ°λžλŠ”데, 점점 μ‹œκ°„μ΄ μ§€λ‚ μˆ˜λ‘ κ·Έ 차이가 느껴쑌고 이번 글을 ν†΅ν•΄μ„œ λ‚˜λ„ Value Object 에 λŒ€ν•΄ 더 κ°€κΉŒμ›Œμ§„κ²ƒ κ°™λ‹€.

 

Value Object μ•Ό 말둜 rich domain model 을 λ§Œλ“€ 수 μžˆλŠ” κ°€μž₯ 쉽고 μ•ˆμ „ν•œ 길이 μ•„λ‹κΉŒ μƒκ°ν•˜κΈ°λ„ ν•œλ‹€.

 

이 글을 λ³΄λŠ” μ—¬λŸ¬λΆ„λ„ κ·ΈλŸ¬ν•œ μΈμ‚¬μ΄νŠΈκ°€ λ˜μ—ˆμœΌλ©΄ μ’‹κ² λ‹€.

 

Value Object 에 λŒ€ν•΄μ„œλŠ” 아직 ν•˜μ§€ λͺ»ν•œ 이야기가 λ§Žλ‹€.

 

ORM 이 defacto κ°€ 된 ν˜„μž¬, Value Object λ₯Ό μ €μž₯ν•˜κΈ° μœ„ν•΄μ„œ κ²ͺμ–΄μ•Ό ν•˜λŠ” λͺ‡κ°€μ§€μ˜ λΆˆνŽΈν•œ 점듀과 κ·Έ νšŒν”Ό 기법듀에 λŒ€ν•΄μ„œ κΈ°νšŒκ°€ λœλ‹€λ©΄ 글을 써보도둝 ν•˜κ² λ‹€.

 

λŒ“κΈ€