왜?
DDD관점에서 도메인 및 비즈니스 로직 설계 시
도메인 모델은 크게 Entity와 VO로 구성되며,
이때 Entity, VO를 구분하기 위해 특성을 기록해놓는다.
Entity란?
Entity는 “고유한 식별자에 의해 특정된다”라는 특징을 가지고 있다.
생명 주기 동안 연속성을 가지고, 독립적으로 동일성을 유지하고있다면, Entity이다.
그렇다면 Entity 는 어떤 조건들에 의해 VO와 구분되는지 알아봅시다.
- ID로 구별되는 동등성 -> 반드시 ID값을 갖는다.
- 가변성 : ID를 제외한 속성값들은 생명주기 동안 변경될 수 있습니다.(setter 등등)
- 비즈니스 규칙 포함 : Entity 는 비즈니스 로직을 포함하게 되므로 주어진 상황에서 검증 과정을 거칠 수 있게됩니다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Getter @Entity @NoArgsConstructor public class UserPoint { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long userId; @Convert(converter = MoneyConverter.class) @Column(name = "money") private Money money; public UserPoint(Long userId, Money money) { this.userId = userId; this.money = money; } public void charge(Money money) { this.money = this.money.plus(money); } public void use(Money money) { if(this.money.isLessThan(money)) { throw new IllegalArgumentException("Insufficient points"); } this.money = this.money.minus(money); } public Money getBalance() { return this.money; } }
|
(DDD가 아닌) 기존 JPA에서의 Entity 의미
DB 테이블과 매핑되는, 영속성 컨텍스트가 관리하는 POJO 클래스
VO(Value Object)란?
VO 는 도메인에서 1개 또는 그 이상의 속성들을 묶어서 특정 값을 나타내는 객체입니다.
도메인 객체의 일종이고 보통 PK 로 식별값을 가지는 엔티티(Entity) 와 구분해서 사용합니다.
그렇다면 VO 는 어떤 조건들에 의해 Entity와 구분되는지 알아보자
- 동등성(equality): Id가 없고 단순히 모든 속성값들로 동등성을 구별 -> equals(), hashCode를 재정의해야한다.
- 불변성(immutable) : 수정자(setter) 를 가지지 않습니다. 즉, VO 는 불변하다는 특징이 있기 떄문에 내부의 모든 필드는
final 로써 변경되지 않음을 보장, 새로운 값이 필요하면 새로운 객체가 생성된다.
- 비즈니스 규칙 포함 : VO 는 비즈니스 로직을 포함하게 되므로 주어진 상황에서 검증 과정을 거칠 수 있게됩니다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| public class Money implements Comparable { private final long amount; private Money(Long amount) { this.amount = amount; } public static Money of(Long amount) { if (amount < 0) { throw new IllegalArgumentException("금액은 음수일 수 없습니다: " + amount); } return new Money(amount); } @Override public boolean equals(Object object) { if (object == this) { return true; } if (object == null) { return false; } if (!(object instanceof Money)) { return false; } if (getClass() != object.getClass()) { return false; } Money otherMoney = (Money) object; return amount == otherMoney.amount; } @Override public int hashCode() { return Long.hashCode(amount); } public Money plus(Money other) { return new Money(this.amount + other.amount); } public Money minus(Money other) { return new Money(this.amount - other.amount); } public boolean isLessThan(Money other) { return this.amount < other.amount; } public boolean isGreaterThan(Money other) { return this.amount > other.amount; } @Override public int compareTo(Object o) { if (!(o instanceof Money)) { throw new IllegalArgumentException("Money 타입이 아니기 때문에, 비교할 수 없습니다."); } Money otherMoney = (Money) o; return Long.compare(this.amount, otherMoney.amount); } public long getAmount() { return amount; } }
|