VO에 대해
DTO, VO, Entitiy에 대해 공부하다보니 뭔가 VO에 대한 개념이 잘 잡히지 않았다.
그래서 추가로 VO에 대해 찾아봤고 어느정도 개념이 잡힌 것 같다.
Intro
VO는 많은 사람들이 DTO와 혼용하는 경우가 많다.
하지만 DTO와 VO는 각기 다른 역할을 가지고 있다.
- DTO : 객체 간의 데이터 교환을 위한 객체
- VO : 객체를 값처럼 쓰기 위한 객체
위의 내용만 본다면 무슨 소리인지 알기 힘들기 때문에 예제를 통해 알아보자.
VO에 관련된 질문
만약 아래와 같은 질문을 받았다고 생각해보자.
1
2
3
4
사람의 나이를 나태내기 위한 변수에 어떤 타입을 지정해야될까?
1. Integer
2. Boolean
3. String
대부분의 사람들이 나이는 숫자로 나타낼 수 있으니 Integer가 아닐까?
라는 생각으로 Integer을 고를 것이라고 생각한다.
하지만 답은 3개 중에 없다이다. 답은 사람의 나이를 표현하기 위한 Age 타입을 직접 만들어 지정해줘야한다.
그 이유는 Integer 타입의 경우 age가 가지지 않은 속성과 연산들을 가지고 있기 때문
이다.
- 나이를 가지고 더하기나 빼기 등의 연산이 필요한가?
- 음수의 나이가 존재할 수 있는가?
위와 같이 생각해보면 Integer 타입이 뭔가 부적절하다고 느낄 수 있을 것이다.
그래서 Age는 정수가 아니라 Integer 타입으로 표현할 수 없다는 것이다.
도메인 객체를 나타내기 위해 primitive type으로 쓰는 나쁜 습관을 primitive obsession
이라고 부른다.
또 만약 나이를 나타내는데 Integer 타입을 사용했다면 그 변수가 알맞은 값으로 초기화
하거나 한번 할당된 나이가 변경되지 않는 것을 보장
하기 위해선 할당 전에 해야될 것
이다.
이런 이유 때문에 우리는 VO의 필요성에 대해 느낄 수 있다.
VO
- VO는
불변
하다 - 수정자(setter)가 없다. 같은 값을 가지는 VO는 같다고 취급
한다.- 자가 유효성 검사 -
생성자에서 유효성 검사
를 진행한다.
1. Immutability
VO는 불변
이다. 즉, 한번 생성한 후에는 내부 값을 바꿀 수 없게 setter를 허용하지 않는다.
이것을 얻을 수 있는 이점은 다음과 같다.
Hassle-free Sharing
VO는 다른 곳에서 수정할 수 없기 때문에 참조값으로 공유가 가능하다. side effect를 피하고 Multi-thread에서 이점이 있다.
Improved Semantics
무의미한 getter를 VO에 추가하지 말자. 그리고 다음과 같이 VO를 조작함으로써 VO가 더 명확하게 해줄 수 있다.
- 생성자 또는 정적 메소드를 통해 새 인스턴스 만들기
- 현재 객체에서 다른 객체를 생성하기
- 내부 데이터를 추출하여 다른 유형으로 변환하기
이 부분은 조금 어려워 다음으로 넘어가자
2. value equality
만약 친구와 카드 게임을 하고 있다고 상상해보자.
서로 카드를 뽑아들었는데 만약 친구와 내가 같은 카드를 뽑았다. 그럼 두 카드가 정말로 같은지 서로 확인할 것이다.
또 만약 두 카드를 서로 바꾼다고 어떤 결과가 달라지지 않는다. 즉, 두 카드가 같은 값을 가지고 있기 때문에, 두 카드를 구별할 수 없다.
결국 카드 객체는 VO라고 말할 수 있는 것이다.
내부의 값이 동일한 두 객체는 동일한 객체
라고 판단할 수 있다.
예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
final class Card {
private Rank rank;
private Suit suit;
public Card(Rank rank, Suit suit) {
this.rank = rank;
this.suit = suit;
}
public boolean sameAs(Card anotherCard) {
return rank.sameAs(anotherCard.rank) &&
suit.sameAs(anotherCard.suit);
}
}
3. self validation
VO는 유효한 값만 허용해야한다. 유효하지 않다면 객체를 만들 수 없다.
따라서 생성자가 주입될 때 값의 유효성 검사를 진행해야한다.
예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Numbers {
private List<Integer> numbers;
private Numbers(List<Integer> numbers) {
this.numbers = numbers;
validateNumbers();
}
public static Numbers of(List<Integer> numbers) {
return new Number(numbers);
}
private void validateNumbers() {
if (this.numbers.contains(0)) {
throw new IllegalArgumentException("0 이외의 값만 허용합니다");
}
}
}
정리
어떤 객체를 VO로 만들 때 그 해당 객체의 특성을 잘 이해하고 그에 맞는 VO를 만들어야한다.
그러기 위한 체크 리스트는 다음과 같다.
- 불변하며 수정자가 없음
- 도메인의 의미론을 반영
- 런타임 동안 정보의 흐름과 변환 방법을 보여줌.
- default나 쓸모없는 getter 메소드가 없다
- private 속성을 가지며 다른 Value Object와 비교할 수 있다.
출처)
[Value Objects Like a Pro. How to build the perfect class for your… | by Nicolò Pignatelli | Medium](https://medium.com/@nicolopigna/value-objects-like-a-pro-f1bfc1548c72) |