getter/setter만 있는, 값을 실어 나르는 객체를 VO(Value Object)라고 부르는 사례를 실무에서 종종 접합니다. 프로세스 사이의 원격 호출에서 데이터를 운반하는 역할이라면 그 객체는 DTO(Data Transfer Object)라고 부르는 편이 혼란의 여지가 적습니다. Value Object는 Martin Fowler의 글과 Eric Evans의 DDD(Domain-Driven Design)에서 이미 다른 뜻으로 정의한 용어이기 때문입니다. 이 글에서는 두 패턴의 정의와 용어가 뒤섞인 역사를 정리합니다.
Value Object의 정의
Value Object는 식별성(identity)이 없고, 담고 있는 값으로 동등성(equality)을 판단하는 객체입니다.
식별성은 속성 값과 상관없이 한 객체를 다른 객체와 구별해 주는 성질입니다. 회원은 이름이나 주소가 바뀌어도 같은 회원이고, 이름과 주소가 모두 같은 두 회원도 서로 다른 사람입니다. 이런 객체는 회원 번호 같은 식별자로 구별하며, DDD에서는 ENTITY라고 부릅니다.
동등성은 두 객체를 같은 것으로 볼지 판단하는 기준입니다. Value Object는 이 기준을 식별자가 아니라 담고 있는 값에 둡니다. 돈, 색상, 날짜 같은 개념이 대표적인 예입니다. 예를 들어 Java에서 시간선 위의 한 시점을 나타내는 Instant는 서로 다른 방법으로 따로 만들었더라도 같은 시점을 가리키면 equals()로 비교했을 때 같습니다.
Instant fromText = Instant.parse("2026-09-24T20:00:00Z");
Instant fromSeoulTime = ZonedDateTime.of(2026, 9, 25, 5, 0, 0, 0, ZoneId.of("Asia/Seoul"))
.toInstant();
assertThat(fromText).isEqualTo(fromSeoulTime); // UTC 24일 20시와 서울 25일 5시는 같은 시점
아래 세 자료에서 이 정의를 확인할 수 있습니다.
-
Martin Fowler의 글: 속성 값이 같아서 같다고 보는 객체. x, y 좌표로 이루어진 점(point)을 예로 들어 설명합니다.
Objects that are equal due to the value of their properties, in this case their x and y coordinates, are called value objects.
속성 값, 이 예에서는 x와 y 좌표 값이 같아서 같은 객체를 value object라고 부른다.
-
Wikipedia: 동등성이 식별성에 기반하지 않고, 같은 값을 가지면 같은 것으로 보는 객체
-
Microsoft의 .NET 아키텍처 문서: 식별성이 없는 객체
Eric Evans도 같은 관점입니다. Evans가 Domain-Driven Design의 패턴 정의를 요약해 공개한 Domain-Driven Design Reference(2015)의 Value Objects 항목은 문제를 서술하는 부분에서 많은 객체에 개념적 식별성이 없다고 전제하고, 속성과 로직에만 관심이 있는 모델 요소를 value object로 분류하라고 말합니다.
Some objects describe or compute some characteristic of a thing. Many objects have no conceptual identity. …
Therefore:
When you care only about the attributes and logic of an element of the model, classify it as a value object. Make it express the meaning of the attributes it conveys and give it related functionality.
어떤 객체는 사물의 특성을 기술하거나 계산한다. 많은 객체에는 개념적 식별성이 없다. …
그러므로:
모델 요소의 속성과 로직에만 관심이 있다면 그것을 value object로 분류하라. Value object가 담은 속성의 의미를 드러내게 하고 관련 기능을 부여하라.
Domain-Driven Design Reference: Value Objects
Java 코드에서는 값 개념을 표현하는 클래스에 동등성의 기준이 되는 속성으로 equals()와 hashCode()를 구현하면 Value Object가 됩니다. 이때 지켜야 할 두 메서드의 규약은 Joshua Bloch의 Effective Java 3판 Item 10과 Item 11에 정리되어 있습니다.
-
Item 10:
equals()의 일반 규약-
반사성(reflexive):
x.equals(x)는true를 반환합니다. -
대칭성(symmetric):
x.equals(y)가true이면y.equals(x)도true를 반환합니다. -
추이성(transitive):
x.equals(y)와y.equals(z)가true이면x.equals(z)도true를 반환합니다. -
일관성(consistent): 비교에 쓰는 정보가 바뀌지 않는 한
x.equals(y)는 여러 번 호출해도 같은 결과를 반환합니다. -
null 아님(non-null):
x.equals(null)은false를 반환합니다.
-
-
Item 11:
equals()를 재정의하면hashCode()도 재정의-
equals()비교에 쓰는 정보가 바뀌지 않는 한hashCode()는 여러 번 호출해도 같은 값을 반환합니다. -
equals()로 같은 두 객체는 같은hashCode()값을 반환합니다. -
equals()로 다른 두 객체가 서로 다른hashCode()값을 반환할 필요는 없지만, 다른 값을 반환하면 해시 테이블의 성능이 좋아집니다.
-
Java 16부터 정식 기능이 된 record를 쓰면 이 두 메서드를 직접 작성하지 않아도 됩니다. record를 도입한 JEP 395는 record의 equals()와 hashCode()가 모든 구성 요소의 값을 기준으로 자동 생성된다고 명시합니다. 두 record 인스턴스는 타입이 같고 구성 요소의 값이 모두 같을 때 동등합니다.
public record Money(BigDecimal amount, Currency currency) {
}
이 클래스의 두 인스턴스는 담고 있는 값이 같으면 동등합니다.
Currency krw = Currency.getInstance("KRW");
Money price1 = new Money(BigDecimal.valueOf(10000), krw);
Money price2 = new Money(BigDecimal.valueOf(10000), krw);
assertThat(price1).isEqualTo(price2); // 참조가 달라도 값이 같으면 동등
다만 record는 구성 요소 타입의 equals()를 그대로 쓰므로, 자동 생성된 동등성이 도메인에서 원하는 동등성과 다를 수 있습니다. 예를 들어 BigDecimal의 equals()는 scale까지 비교하므로 new BigDecimal("10000")과 new BigDecimal("10000.0")으로 만든 Money는 서로 다른 값이 됩니다. 이럴 때는 생성자에서 값을 정규화하거나 equals()를 직접 정의합니다.
Java 언어와 JVM에도 식별성 없는 value object 개념이 도입되고 있습니다. DDD와 Fowler가 말하는 Value Object와는 식별성 없이 값으로 구분된다는 공통점이 있습니다. Project Valhalla의 JEP 401: Value Objects (Preview)는 불변이고 객체 식별성이 없으며 필드 값으로만 구분되는 value object를 제안합니다. 2026년 8월 현재 이 JEP는 2027년 3월 출시 예정인 JDK 28에 미리보기 기능으로 통합되어 있습니다. JEP 169: Larval State for Value Objects는 이러한 불변 value object의 임시 가변 상태를 다루는 별도의 Draft 제안입니다. 다만 DDD의 식별성은 도메인에서 개념적으로 구별할 필요가 있는가의 문제이고, Valhalla가 없애는 식별성은 JVM이 객체를 구분하는 런타임 성질이라서 두 value object는 같은 개념이 아닙니다. 그래도 어느 쪽에서도 value object를 getter/setter만 가진 객체라는 의미로 사용하지 않습니다.
한편 실무에서는 앞의 정의와 무관하게 VO를 데이터를 담는 객체(data holder)라는 넓은 의미로 해석해서, getter/setter만 가진 운반용 객체를 VO라고 부르는 관례도 퍼져 있습니다. 이 관례를 확산시킨 대표적인 출처는 뒤의 Core J2EE Patterns 절에서 살펴봅니다.
불변성의 위상: 정의 요건 vs 바람직한 설계
여러 책과 글이 Value Object를 완전한 불변 객체로 만들라고 권고합니다.
-
Fowler는 Patterns of Enterprise Application Architecture 486쪽에서 Value Object를 불변으로 만드는 것이 매우 좋은 방법이라고("it’s a very good idea to make them immutable") 권고합니다.
-
Fowler의 Value Object 글도 마찬가지입니다. 2016년 개정 전 버전에서는 value object를 완전히 불변으로 만드는 것을 일반적인 경험칙으로 제시했고, 개정된 현재 버전에서도 "value objects should be immutable"을 중요한 규칙으로 제시합니다.
A general heuristic is that value objects should be entirely immutable.
일반적인 경험칙은 value object를 완전히 불변으로 만들어야 한다는 것이다.
-
Eric Evans는 앞에서 인용한 DDD Reference의 Value Objects 항목에서 value object를 불변으로 다루라는 설계 지침을 제시합니다.
Treat the value object as immutable. Make all operations Side-effect-free Functions that don’t depend on any mutable state. Don’t give a value object any identity and avoid the design complexities necessary to maintain entities.
Value object를 불변으로 다루라. 모든 연산을 가변 상태에 의존하지 않는 부수 효과 없는 함수(Side-effect-free Function)로 만들라. Value object에는 식별성을 부여하지 말고, entity를 유지하는 데 필요한 설계 복잡성을 피하라.
-
Joshua Bloch는 Effective Java 3판의 Item 17 "Minimize mutability"에서 Value Object에 한정하지 않고 클래스 일반에 대해 가변성을 최소화하고 가능하면 불변으로 만들라고 권합니다.
VO가 불변이면 여러 객체가 같은 인스턴스를 공유해도 별칭 문제(aliasing bug)가 생기지 않습니다. 별칭 문제는 한쪽에서 값을 바꾸면 같은 인스턴스를 참조하는 다른 쪽의 값까지 바뀌는 문제입니다. Java의 java.util.Date와 Calendar는 값의 성격을 가진 객체인데도 가변으로 설계되어서 이런 버그의 원인이 되었습니다. 예를 들어 두 객체가 회의 시작 시각을 담은 Date 인스턴스 하나를 공유할 때 한쪽에서 setTime()을 호출하면 다른 쪽의 시작 시각도 바뀝니다.
record Meeting(String title, Date start) {
}
Date start = new Date();
Meeting review = new Meeting("설계 리뷰", start);
Meeting retro = new Meeting("회고", start);
long oneHourLater = start.getTime() + Duration.ofHours(1).toMillis();
review.start().setTime(oneHourLater); // (1)
assertThat(retro.start().getTime()).isEqualTo(oneHourLater); // (2)
-
설계 리뷰만 1시간 미루려고 함
-
회고 시작 시각도 바뀜
Meeting을 record로 선언하는 것만으로는 이 문제를 막을 수 없습니다. record는 필드에 다른 인스턴스를 대입하지 못하게 할 뿐이고, 필드가 참조하는 Date의 내부 상태는 여전히 바꿀 수 있기 때문입니다.
Java 8의 java.time 패키지가 LocalDate, Instant 같은 날짜·시간 클래스를 모두 불변으로 만든 것도 이 교훈을 반영한 결과입니다. 시작 시각을 시간대 없는 날짜·시각인 LocalDateTime으로 표현하면 plusHours()가 기존 인스턴스를 고치지 않고 새 인스턴스를 반환하므로, 인스턴스를 공유해도 다른 회의의 시작 시각은 바뀌지 않습니다.
record Meeting(String title, LocalDateTime start) {
}
LocalDateTime start = LocalDateTime.of(2026, 9, 25, 14, 0);
Meeting review = new Meeting("설계 리뷰", start);
Meeting retro = new Meeting("회고", start);
Meeting delayedReview = new Meeting(review.title(), review.start().plusHours(1));
assertThat(delayedReview.start()).isEqualTo(LocalDateTime.of(2026, 9, 25, 15, 0));
assertThat(retro.start()).isEqualTo(start); // (1)
-
회고 시작 시각은 그대로
그런데 Fowler와 Evans의 문장을 자세히 보면 불변성은 VO의 정의가 아니라 지침으로 나옵니다. Fowler는 별칭 문제를 피하기 위한 규칙으로, Evans는 명령형 설계 지침으로 불변성을 제시합니다. Fowler가 Value Object의 정의로 제시하는 성질은 값에 의한 동등성입니다. 앞에서 인용한 "value objects should be immutable"도 전체 문장을 보면 별칭 문제를 피하기 위해 따르는 규칙으로 등장합니다.
To avoid aliasing bugs I follow a simple but important rule: value objects should be immutable.
별칭 버그를 피하려고 나는 단순하지만 중요한 규칙 하나를 따른다. Value object는 불변이어야 한다.
Value Object
같은 글에서 C#의 struct처럼 언어가 대입할 때마다 값을 복사하는 방식으로도 별칭 문제를 피할 수 있다고 대안까지 언급합니다.
While immutability is my favorite technique to avoid aliasing bugs, it’s also possible to avoid them by ensuring assignments always make a copy. Some languages provide this ability, such as structs in C#.
별칭 버그를 피하는 방법으로 내가 가장 좋아하는 것은 불변성이지만, 대입할 때마다 항상 복사본을 만들도록 보장해서 피할 수도 있다. C#의 struct처럼 이런 기능을 제공하는 언어도 있다.
Value Object
Evans는 불변성을 명령형 문장으로 썼습니다. 앞에서 인용한 DDD Reference의 해법 부분에서 value object로 분류하는 기준은 모델 요소의 속성과 로직에만 관심이 있는가입니다. 그 뒤에 이어지는 "Treat the value object as immutable."은 그렇게 분류한 객체를 불변으로 다루라는 명령형 문장이고, 바로 뒤의 문장도 모든 연산을 부수 효과 없는 함수로 만들라는 명령형 문장입니다. 저는 이 문장들을 분류 기준이 아니라, 분류한 객체를 다루는 설계 지침으로 읽습니다.
Ward Cunningham의 wiki에 있는 ValueObjectsShouldBeImmutable 페이지에 Fowler가 남긴 글은 정의와 지침의 구분을 더 분명하게 보여 줍니다. 새로 설계하는 객체에는 상태를 바꾸는 메서드를 두지 말라고 합니다.
So if you design an object that should be a value object, don’t provide any methods that change its state, ie make it immutable.
그러니 value object여야 하는 객체를 설계한다면 상태를 바꾸는 메서드를 제공하지 말라. 즉, 불변으로 만들라.
c2 wiki: ValueObjectsShouldBeImmutable
그리고 이미 가변으로 만들어진 Value Object에 대해서는 다음과 같이 말합니다.
If you are using a ValueObject that is mutable, treat it like it is immutable. You may not realize why, but you will save a lot of time and money.
가변인 ValueObject를 쓰고 있다면 불변인 것처럼 다루라. 이유를 깨닫지 못할 수도 있지만, 많은 시간과 돈을 아끼게 될 것이다.
c2 wiki: ValueObjectsShouldBeImmutable
"가변인 ValueObject를 쓰고 있다면"이라는 가정 자체가 불변이 아닌 Value Object의 존재를 전제합니다. 페이지 이름도 MustBeImmutable이 아니라 ShouldBeImmutable입니다. Cambridge Dictionary는 should의 첫 번째 뜻을 다음과 같이 풉니다.
used to say or ask what is the correct or best thing to do
무엇이 옳거나 가장 좋은 일인지 말하거나 물을 때 쓴다.
should
같은 사전은 must를 어떤 일이 일어나는 것이 필요하거나 매우 중요하다는 것을 나타낼 때 쓴다고 풉니다. must가 반드시 그래야 하는 필요를 나타낸다면, should는 그렇게 하는 편이 옳다는 권고를 나타냅니다.
반면 불변성을 정의의 일부로 서술하는 자료도 있습니다.
-
앞에서 언급한 Microsoft의 .NET 아키텍처 문서는 value object의 두 가지 주요 특성으로 식별성 없음과 불변성을 나란히 들고, 불변성을 중요한 요건이라고 밝힙니다.
There are two main characteristics for value objects: They have no identity. They are immutable. The first characteristic was already discussed. Immutability is an important requirement.
Value object의 주요 특성은 두 가지다. 식별성이 없고, 불변이다. 첫 번째 특성은 이미 다루었다. 불변성은 중요한 요건이다.
-
Wikipedia는 value object가 불변이어야 한다(should)고 쓰면서, 같은 값으로 생성된 두 value object가 계속 같아야 한다는 암묵적 계약을 위해 불변성이 필요하다(required)고 설명합니다. should를 쓰면서도 불변성을 계약의 전제로 둔다는 점에서 정의에 가까운 위상을 부여합니다.
-
Vaughn Vernon의 Implementing Domain-Driven Design(2013) 6장도 Value Object의 특성을 열거하면서 불변성을 그중 하나로 포함합니다.
-
김우근 님의 자바/스프링 개발자를 위한 실용주의 프로그래밍(2024)도 "VO는 이러한 불변성이라는 특징을 갖고 있는 객체를 말합니다"(43쪽)라고 설명하고, 불변성·동등성·자가 검증이라는 세 특성을 만족하는 객체를 VO로 정의합니다.
-
JEP 401의 value object처럼 필드가 암묵적으로 final이 되어 얕은 불변성이 언어 차원에서 강제되는 개념도 있습니다. 필드에 다른 인스턴스를 대입할 수는 없지만, 필드가 참조하는 객체의 내부 상태까지 불변이 되지는 않습니다.
저는 불변성을 VO의 정의 요건이라기보다는 바람직한 설계 규범으로 봅니다. c2 wiki에서 Fowler도 새로 설계하는 객체는 불변으로 만들라고 썼습니다. 새로 만드는 Value Object는 어느 관점을 따르든 불변으로 설계하므로, 실무에서 should와 must의 차이가 드러나는 경우는 많지 않습니다. 그래도 이 구분이 무의미하지는 않습니다. java.util.Date처럼 가변으로 만들어진 값 성격의 객체를 만났을 때, 불변성을 정의에 포함하면 그 객체는 VO가 아닌 무언가가 되고, 지침으로 보면 불변 지침을 지키지 못한 VO가 됩니다. c2 wiki의 Fowler 문장은 후자의 관점에서 나온 실용적인 조언입니다. 불변 지침을 지키지 못한 VO를 이미 마주쳤다면, 그것을 VO가 아니라고 배제하는 대신 최소한 불변인 것처럼 다루라는 뜻입니다.
'술을 마시고 운전하지 않는다’를 운전자의 정의에 포함할 것인지, 운전자가 지켜야 할 규범으로 볼 것인지의 차이와 비슷합니다. 어느 쪽이든 술을 마시고 운전하면 안 된다는 결론은 같지만, 규범까지 정의에 포함하면 현실에 존재하는 위반 사례를 부를 이름이 사라져서 그런 사례를 논의하기가 어려워집니다.
DTO(Data Transfer Object)의 정의
Fowler의 원래 정의에서 DTO는 원격 호출의 비용을 줄이기 위한 객체입니다. Martin Fowler의 Patterns of Enterprise Application Architecture 카탈로그는 DTO를 다음과 같이 정의합니다.
An object that carries data between processes in order to reduce the number of method calls.
메서드 호출 횟수를 줄이기 위해 프로세스 사이에서 데이터를 나르는 객체.
Patterns of Enterprise Application Architecture 카탈로그: Data Transfer Object
책의 401쪽에서도 같은 정의를 볼 수 있습니다. 원격 호출은 매번 네트워크 왕복과 직렬화 비용이 들므로 한 번의 호출로 필요한 데이터를 모두 전달하려는 의도에서 나온 패턴입니다. 예를 들어 고객의 이름, 주소, 주문 목록을 getter 원격 호출 세 번으로 가져오는 대신, 세 값을 담은 DTO 하나를 한 번의 호출로 받습니다.
HTTP API의 요청과 응답처럼 네트워크를 건너는 데이터를 담는 객체는 프로세스 경계를 넘고 직렬화된다는 점에서 이 정의와 통합니다. 다만 모든 HTTP API가 원격 호출 횟수를 줄이기 위해 설계되는 것은 아니므로, 이를 DTO라고 부르는 용법은 원래 정의를 현대적인 API 경계에 확장한 것입니다.
실무에서는 여기에서 더 나아가 이 정의를 폭넓게 해석해서, 원격 호출과 무관하게 같은 프로세스 안에서 계층의 경계를 넘어 데이터를 운반하는 객체까지 DTO라고 부르는 관례가 생겼습니다. DB 조회 결과를 담는 객체, 서비스 계층에서 뷰 렌더링 계층으로 전달하는 객체, JPA 엔티티를 서비스 계층 바깥에 직접 노출하지 않도록 변환한 응답 전용 객체가 그런 예입니다. 이런 용법은 원격 호출 횟수를 줄인다는 원래 정의와 거리가 멀어서, DTO라고 부르기 어렵다는 주장도 나올 수 있습니다.
이름을 둘러싼 논쟁과는 별개로, 로컬 맥락에서 그런 객체를 두는 것이 설계상 바람직한지도 따져 볼 수 있습니다. Fowler는 LocalDTO에서 이름이 아니라 이 쓰임새를 문제 삼습니다. DTO 패턴의 존재 이유가 원격 호출 비용을 줄이는 것이므로, 원격 호출이 없는 로컬 맥락에서는 그 이유가 사라진다는 것이 출발점입니다. 그래서 로컬 맥락에서는 DTO가 필요하지 않을 뿐 아니라 오히려 해롭다고 말합니다. 한 번의 호출로 많은 데이터를 주고받는 coarse-grained API는 사용하기 불편하고, 도메인 계층이나 데이터 소스 계층에서 DTO로 데이터를 옮기는 작업이 모두 추가 비용이라는 이유입니다.
Not just do you not need them in a local context, they are actually harmful both because a coarse-grained API is more difficult to use and because you have to do all the work moving data from your domain or data source layer into the DTOs.
로컬 맥락에서는 DTO가 필요 없을 뿐 아니라 실제로 해롭다. Coarse-grained API는 사용하기 더 어렵고, 도메인 계층이나 데이터 소스 계층의 데이터를 DTO로 옮기는 작업을 모두 해야 하기 때문이다.
LocalDTO
서비스 계층의 클라이언트가 도메인 모델에 의존하지 않도록 서비스 계층 API에 DTO를 두자는 주장에 대해서도, 편리할 수는 있지만 그 모든 데이터 매핑 비용을 들일 가치는 없다고 답합니다.
다만 같은 글에서 프레젠테이션 계층의 모델과 도메인 모델의 차이가 클 때는 로컬에서도 DTO와 비슷한 객체가 유용하다고 인정합니다.
One case where it is useful to use something like a DTO is when you have a significant mismatch between the model in your presentation layer and the underlying domain model.
DTO 같은 것을 쓰는 것이 유용한 경우 하나는 프레젠테이션 계층의 모델과 그 아래의 도메인 모델 사이에 큰 불일치가 있을 때다.
LocalDTO
그런 경우에는 어차피 두 모델 사이의 매핑이 필요하므로 DTO가 추가 비용이 아니기 때문입니다. 같은 글의 뒷부분에는 멀티스레드 애플리케이션에서 격리된 영역 사이에 메시지로 데이터를 주고받을 때 DTO를 쓰는 용도도 덧붙여 있습니다. 정리하면 Fowler의 비판은 도메인 모델과의 분리 자체를 목적으로 DTO를 두는 경우를 향한 것이고, 모델의 차이나 격리된 영역 사이의 메시지 전달처럼 실제 필요가 있는 경우까지 부정하지는 않습니다.
이름 논쟁과 쓰임새 논쟁은 서로 다른 질문이지만 같은 전제에서 출발합니다. 로컬 맥락에는 원격 호출이 없다는 사실이, 한쪽에서는 DTO라는 이름이 원래 정의와 맞지 않는다는 근거가 되고, 다른 쪽에서는 DTO를 둘 이유가 약해진다는 근거가 됩니다. 뒤에서 제안하는 역할별 접미어 분리는 이 중 이름 논쟁에 대한 답입니다. 그런 객체를 둘지는 쓰임새 논쟁에서 따로 판단할 문제이고, 두기로 결정했다면 Dto보다 역할이 드러나는 이름을 붙이자는 제안입니다.
결국 VO와 DTO를 구분하는 기준은 객체의 특성과 역할입니다. 값 개념을 표현하며 동등성을 값으로 판단하는 것은 VO의 성질이고, 경계를 넘어 데이터를 운반하는 것은 DTO의 역할입니다. 이 둘은 서로 배타적인 분류가 아닙니다. 값 동등성이 있는 불변 DTO는 VO이기도 합니다. 반대로 모든 DTO에 값 동등성이 필요한 것은 아닙니다.
Core J2EE Patterns 초판의 VO와 개정판의 TO
Java/J2EE(현 Jakarta EE) 진영에서 두 용어의 혼용을 확산시킨 대표적인 초기 출처는 Deepak Alur 등이 쓴 Core J2EE Patterns: Best Practices and Design Strategies입니다. 2001년에 나온 초판에서는 티어(tier) 사이에서 데이터를 전송하는 객체를 Value Object라는 이름의 패턴으로 정의했습니다. 2003년의 2판에서는 같은 패턴을 TO(Transfer Object)로 개칭했습니다. 값 동등성 객체로서의 Value Object와의 혼동을 피하려는 개칭으로 보입니다. Martin Fowler는 같은 역할의 패턴을 DTO라고 부릅니다(Patterns of Enterprise Application Architecture 401쪽). 정리하면 다음 세 가지는 같은 전송 패턴을 가리킵니다.
Core J2EE Patterns 초판의 VO = 2판의 TO = Martin Fowler의 DTO
Oracle의 Transfer Object 문서는 개칭된 이름인 Transfer Object로 이 패턴을 설명합니다.
다른 여러 책도 VO와 DTO가 같은 의미이거나 매우 가까운 개념이라고 썼습니다.
-
Rod Johnson, Expert One-on-One J2EE Design and Development, Wrox, 2002, 265쪽
Value objects are sometimes referred to as Data Transfer Objects (DTOs).
Value object는 DTO(Data Transfer Object)라고 불리기도 한다.
-
Rod Johnson·Juergen Hoeller, Expert One-on-One J2EE Development without EJB, Wrox, 2004, 27쪽
Transfer objects, often referred to as Data Transfer Objects (DTOs) or Value Objects.
흔히 DTO(Data Transfer Object)나 Value Object라고도 부르는 Transfer object.
-
Murat Yener·Alex Theedom, Professional Java EE Design Patterns, Wrox, 2014, 12장
The DTO is also referred to as the Value Object
DTO는 Value Object라고도 불린다
-
Derek C. Ashmore, The Java EE Architect’s Handbook, Second Edition, DVT Press, 2014, 5장
My definition of 'value object' is very close to a Data Transfer Object (DTO)
내가 정의하는 'value object’는 DTO(Data Transfer Object)에 매우 가깝다
이 책들이 모두 Core J2EE Patterns 초판의 직접적인 영향을 받았다고 단정할 수는 없습니다. 다만 등장 시기와 여러 문헌에서 반복되는 표현을 고려하면, 초판의 용법이 이후 Java/J2EE 문헌의 표현에도 어느 정도 영향을 미치지 않았을까 추정합니다. 2판에서 이름을 바꾼 지 20년이 넘은 지금까지도 그 관행은 남아 있습니다.
데이터 운반 객체를 VO라고 부를 때의 비용
이미 팀 안에서 통용되는 이름을 굳이 바꿔야 하는지 의문이 들 수도 있습니다. 그러나 데이터 운반 객체를 VO라고 부르는 관행에는 실질적인 비용이 있습니다. 값 동등성 객체로서의 Value Object가 DDD, ORM, Java 언어·JVM 기능이라는 세 맥락에서 계속 등장하기 때문입니다.
-
DDD(Domain-Driven Design): VALUE OBJECT는 ENTITY와 함께 도메인 모델을 구성하는 핵심 요소입니다. AGGREGATE는 하나의 ENTITY를 루트로 삼는 데이터 변경의 일관성 경계이며, 내부에 다른 ENTITY와 VALUE OBJECT를 포함할 수 있습니다.
-
ORM: Hibernate와 JPA의
@Embeddable은 자체 영속 식별성 없이 그것을 소유하는 엔티티에 종속되는 객체를 매핑합니다. DDD의 VALUE OBJECT를 매핑할 때 흔히 사용하는 구조이지만, JPA가 값 동등성이나 불변성을 강제하지는 않으므로 모든@Embeddable이 곧 DDD VALUE OBJECT인 것은 아닙니다. -
Java 언어·JVM 기능: JEP 401을 비롯한 Project Valhalla의 문서들은 value object라는 용어를 불변이고 객체 식별성이 없으며 필드 값으로 구분되는 객체라는 의미로 씁니다.
getter/setter만 가진 데이터 홀더를 VO라고 이해하고 있다면 이런 자료를 읽을 때 용어가 어긋나서 혼란이 생깁니다. 예를 들어 VO를 @Embeddable로 매핑하라는 설명을 읽으면서 setter가 가득 정의된 요청 파라미터 객체를 떠올리게 됩니다. 원격 프로세스 경계를 넘는 데이터 운반 객체를 DTO라고 부르면 Fowler의 정의와도, Core J2EE Patterns 2판 이후의 개칭된 이름과도 자연스럽게 연결됩니다.
앞의 DTO 정의 절에서 본 것처럼 근래의 관례로는 계층 경계를 넘는 데이터 홀더라는 넓은 의미로 DTO를 쓰기도 합니다. 그러나 DTO 접미어를 모든 운반 객체에 붙이면 단점이 있습니다. HTTP 요청 파라미터를 담는 객체, 통계 쿼리의 결과를 담는 객체까지 모두 DTO라고 부르면 이름만으로는 역할이 드러나지 않습니다. 예를 들어 IssueDto라는 이름만 보고는 요청 본문인지, 조회 응답인지, 통계 쿼리의 결과인지 알 수 없습니다.
저는 객체의 역할에 따라 접미어를 나누는 방법을 권합니다. 이렇게 하면 이름만으로 역할이 드러나고, 원격 호출과 관련되지 않은 계층에서 쓰이는 객체를 DTO라고 부를 때 엄밀한 정의에서 벗어난다는 논쟁도 피할 수 있습니다. 예를 들면 다음과 같은 이름입니다.
| 역할 | 클래스 이름 예 |
|---|---|
이슈 조회 JSON 응답 |
|
이슈 생성 JSON 요청 |
|
이슈 조회 조건 |
|
이슈 DB 통계 조회 결과 |
|
참고 자료
Value Object
-
Martin Fowler: Value Object (2016년 개정 전 버전, Wayback Machine)
-
Martin Fowler, Patterns of Enterprise Application Architecture, Addison-Wesley, 2002, 486쪽
-
Eric Evans, Domain-Driven Design, Addison-Wesley, 2003
-
Eric Evans: Domain-Driven Design Reference (2015), Value Objects
-
김우근, 자바/스프링 개발자를 위한 실용주의 프로그래밍, 위키북스, 2024, 43쪽
-
Joshua Bloch, Effective Java 3판, Addison-Wesley, 2018, Item 10·11·17
-
Vaughn Vernon, Implementing Domain-Driven Design, Addison-Wesley, 2013, 6장
-
Ward Cunningham: The CHECKS Pattern Language of Information Integrity - Whole Value
Transfer Object / DTO
-
Martin Fowler, Patterns of Enterprise Application Architecture, Addison-Wesley, 2002, 401쪽
-
Deepak Alur·John Crupi·Dan Malks, Core J2EE Patterns: Best Practices and Design Strategies 초판, Prentice Hall, 2001
-
Rod Johnson, Expert One-on-One J2EE Design and Development, Wrox, 2002, 265쪽
-
Rod Johnson·Juergen Hoeller, Expert One-on-One J2EE Development without EJB, Wrox, 2004, 27쪽
-
Murat Yener·Alex Theedom, Professional Java EE Design Patterns, Wrox, 2014, 12장
-
Derek C. Ashmore, The Java EE Architect’s Handbook, Second Edition, DVT Press, 2014, 5장
-
Adam Bien: Value Object vs. Data Transfer Object (VO vs. DTO)
Twitter
Facebook
Reddit
LinkedIn
Email