- Today
- Total
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- redis
- 브랜치전략
- 프로젝트
- network
- 캐싱전략
- Kotlin
- Project
- Spring Security
- JPA
- JWT
- SPRING JWT
- websocket
- EntityTransaction
- spring
- 후기
- 팀네이버
- 책
- chrome80
- container
- 스프링
- jenkins
- infra
- docker
- LazyInitialization
- 만들면서 배우는 클린 아키텍처
- 리뷰
- 젠킨스
- Java
- 팀네이버 공채
- SpringBoot
PPAK
[Java/Spring] VO(Value Object) 란? 본문
도메인을 설계하다보면 Value Object 를 자주 접하게 된다.
값 객체? 값 타입?? 알쏭달쏭한 VO 에 대해서 알아보자
우선 VO 는 아래의 두 가지 아이디어에 기반한다고 생각한다.
1. 연관있는 데이터의 집합이 분명 존재한다.
2. 그 데이터의 집합은 그 자체로만 식별이 가능하고, 어느 한 속성값이 수정된다면 더 이상 이전과 같은 데이터라고 할 수 없다.
아래는 직접 작성한 VO 이다
class Reference {
int age;
String course;
String job;
public Reference(int age, String course, String job) {
this.age = age;
this.course = course;
this.job = job;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || this.getClass() != obj.getClass()) return false;
final Reference r = (Reference) obj;
return (this.age == r.age &&
this.course.equals(r.course) &&
this.job.equals(r.job));
}
@Override
public int hashCode() {
return Objects.hash(this.age, this.course, this.job);
}
}
위의 작성한 코드에서 VO 의 첫 번째 특징을 확인할 수 있다.
1. VO 는 equals(), hashCode() 를 의미에 맞게 재작성 해야한다.
잠깐 Database Entity 에 대해서 살펴본다면, Entity 라고 하는 것은 pk(primary key) 를 통해 테이블 상에서 유일하게 식별이 되는 데이터 집합이다.
이와 다르게 VO 는 따로 pk 를 두지 않고, 속성값을 통해서만 데이터를 식별할 수 있는 데이터 집합을 의미한다고 볼 수 있다.
우리는 이미 자바에서 VO 를 사용한 적이 있는데, 대표적으로 Integer, String 을 예로 들 수 있다.
String s1 = "sample";
String s2 = "sample";
Integer i1 = 10;
Integer i2 = 10;
System.out.println(s1.equals(s2));
System.out.println(i1.equals(i2));
위 코드의 결과는 true, true 로 나오는데, 이를 통해 String 과 Integer 는 값을 통해서 동등성을 비교한다는 사실을 확인할 수 있다
class Sample {
String s;
Sample(String s) {
this.s = s;
}
}
Sample s1 = new Sample("sample");
Sample s2 = new Sample("sample");
System.out.println(s1.equals(s2));
위의 코드의 결과는 false 이다.
VO 가 아닌 객체는 위와 같이 내부에 포함한 속성값이 같더라도 동등하지 않다라고 판단한다.
또한 VO 는 equals() 외에도 hashCode() 에 대해서도 동등성을 보장한다
hashCode() 의 의미가 메모리상에서 객체를 식별할 값을 의미하기 때문에(실제로 컬렉션 객체를 사용할 때, 이 hashCode() 값을 통해 동등성을 비교한다) 당연히 동등한 속성값을 가지는 VO 에 대해서는 같은 값을 반환하는 것이 옳은 설계일 것이다.
다시 위의 작성한 Reference 코드를 살펴보면, equals(), hashCode() 에 대해서 동등성을 부여하기 위해서, VO 와 동등성을 비교할 VO 객체의 모든 속성값을 비교한다는 사실을 확인할 수 있다.
여기까지 VO 는 속성값이 같은 객체에 대해서 동등하다고 판단한다는 것을 확인했다.
즉, VO 의 속성값은 Entity 의 pk 와 같이 데이터 집합을 식별할 수 있는 데이터라는 것을 알 수 있다.
그렇다면, VO 의 속성값이 변경된다면 여전히 이전이랑 동일한 객체라고 말할 수 있는가?
다시 말해 Entity 의 pk 값이 임의로 변경되었을 때, 그 Entity 를 같은 데이터로 보는가?
여기서 VO 의 두 번째 특징을 확인할 수 있다.
2. VO 의 속성값은 불변(Immutable) 하다 (= setter 가 존재하지 않는다)
VO 의 속성값이 변한다는 것은 Entity 의 pk 값이 임의로 변경되는 것과 같은 의미다 (이런일이 있으면 안된다)
VO 는 속성값 그 자체가 식별값으로 동작하기 때문에 한번 생성되고 나서 변경되면 안된다.
따라서 생성 이후에 값을 수정하는 setter 와 같은 메소드가 존재해서는 안된다.
그렇다면 VO 를 변경하고 싶을 때는 어떻게 하면 좋을까?
예를들어, 위의 Reference Class 에서 (30살, "상급자코스", "요리사") 에서 (30살, "중급자코스", "요리사") 로 나의 Reference 를 변경하고 싶을 때 이야기이다.
정답은 새로운 VO 인스턴스를 생성하는 것이다.
의미상으로도 두 가지 Reference 는 속성값이 다르기 때문에 서로 다른 객체가 맞고, VO 는 불변성을 토대로 하기 때문에 새로운 객체를 생성하는 방법이 가장 적절하다.
3. 의문
언뜻 보기에 VO 는 생성 후 변경 불가능한 데이터의 집합으로 보인다.
그러면 이런 의문이 들 수 있다. (물론 너무 넘겨짚는 이야기 일 수도 있다)
VO 로 묶지 않고, 각각의 속성값을 따로 선언하면 되지않나? (해당 속성값에 대한 setter 는 없애고, equals() 와 hashCode() 는 해당 속성값을 비교할 때 적절히 구현하면 되고... 등등)
우선 답을 해보자면
1. VO 로 연관있는 데이터의 집합을 만드는 것에서 부터 의미를 가진다.
VO 를 생성함으로써 각각의 속성값과, VO 와 연관된 class 간의 결합도를 낮추고, Entity 의 속성값이 불필요하게 커지는 것을 방지할 수 있다.
2. VO 속성값에 대한 제약조건을 만들 수 있다
가령 Reference Class 같은 경우에는 나이가 20세 미만인 정보는 담을 수 없는다던지 등의 의도치 않은 값이 생성되는 것을 사전에 방지할 수 있다. 즉 새로운 값 타입의 데이터 집합을 정의할 수 있다는 의미이다.
이는 일급 컬렉션을 생성하는 이유 중 하나와 같은데, 속성값에 제한을 둔 Class 를 정의함으로써 완전 새로운 타입의 객체를 생성하거나 기존의 검증과 제약이 필요했던 클래스의 내부에서 해당 로직을 수행할 수 있다는 장점과 맥락을 같이한다.
VO 는 불변한 데이터 집합으로 속성값을 통한 동등성을 비교하고, 속성값에 대한 검증과 같은 제약조건을 걸 수 있다.
잘못된 정보가 있다면 댓글로 알려주시면 감사하겠습니다!!