martinfowler.com 블로그 포스트 번역
프로그래밍할 때 저는 종종 여러 값을 조합하여 표현하는 것이 유용하다는 것을 발견합니다. 2D 좌표는 x 값과 y 값으로 구성됩니다. 금액은 숫자와 통화로 구성됩니다. 날짜 범위는 시작 날짜와 종료 날짜로 구성되며, 이들 자체도 연도, 월, 일의 조합일 수 있습니다.
이렇게 작업하다 보면 두 개의 복합 객체가 동일한지 여부를 판단해야 하는 질문에 직면합니다. 데카르트 좌표 (2,3)을 나타내는 두 개의 point 객체가 있다면, 이들을 동일한 것으로 취급하는 것이 합리적입니다. 이 경우 x와 y 좌표인 속성의 값으로 인해 동일한 객체를 value object라고 부릅니다.
하지만 프로그래밍할 때 주의하지 않으면 프로그램에서 그러한 동작을 얻지 못할 수 있습니다.
JavaScript에서 point를 표현하고 싶다고 가정해봅시다.
안타깝게도 이 테스트는 통과합니다. JavaScript는 객체의 동등성을 참조를 보고 판단하기 때문에, 포함된 값은 무시합니다.
많은 상황에서 값 대신 참조를 사용하는 것이 합리적입니다. 많은 판매 주문을 로드하고 조작하는 경우, 각 주문을 한 곳에 로드하는 것이 합리적입니다. 그 후 Alice의 최신 주문이 다음 배송에 포함되어 있는지 확인해야 한다면, Alice의 주문의 메모리 참조 또는 identity를 가져와서 그 참조가 배송의 주문 목록에 있는지 확인할 수 있습니다. 이 테스트의 경우 주문에 무엇이 있는지 걱정할 필요가 없습니다. 마찬가지로 고유한 주문 번호에 의존하여 Alice의 주문 번호가 배송 목록에 있는지 확인할 수 있습니다.
따라서 저는 두 가지 클래스의 객체를 생각하는 것이 유용하다고 생각합니다: value object와 reference object이며, 이는 이들을 구별하는 방식에 따라 달라집니다¹. 각 객체가 동등성을 어떻게 처리할 것으로 예상하는지 알아야 하며, 예상에 따라 동작하도록 프로그래밍해야 합니다. 이를 수행하는 방법은 작업 중인 프로그래밍 언어에 따라 다릅니다.
1: Domain-Driven Design에서 Evans Classification은 value object를 entity와 대조합니다. 저는 entity를 reference object의 일반적인 형태로 간주하지만, "entity"라는 용어는 domain model 내에서만 사용하고 reference/value object 이분법은 모든 코드에 유용합니다.
일부 언어는 모든 복합 데이터를 값으로 취급합니다. Clojure에서 간단한 복합을 만들면 다음과 같습니다.
이것이 functional style입니다 - 모든 것을 immutable 값으로 취급합니다.
하지만 functional language가 아니더라도 여전히 value object를 만들 수 있습니다. 예를 들어 Java에서는 기본 point 클래스가 제가 원하는 방식으로 동작합니다.
이것이 작동하는 방식은 point 클래스가 기본 equals 메서드를 값에 대한 테스트로 재정의하기 때문입니다.²³
2: 엄밀히 말하면 이는 awt.Point의 상위 클래스인 awt.geom.Point2D에서 수행됩니다.
3: Java의 대부분의 객체 비교는 equals로 수행됩니다 - 이는 equals 연산자 == 대신 그것을 사용해야 한다는 것을 기억해야 하기 때문에 다소 어색합니다. 이는 성가시지만 String이 동일한 방식으로 동작하기 때문에 Java 프로그래머는 곧 익숙해집니다. 다른 OO 언어는 이를 피할 수 있습니다 - Ruby는 == 연산자를 사용하지만 이를 재정의할 수 있습니다.
JavaScript에서도 비슷한 작업을 할 수 있습니다.
JavaScript의 문제는 제가 정의한 이 equals 메서드가 다른 JavaScript 라이브러리에는 미스터리라는 것입니다.
이는 Java에서는 문제가 아닙니다. Object.equals가 핵심 라이브러리에 정의되어 있고 다른 모든 라이브러리가 비교에 이를 사용하기 때문입니다 (==는 보통 primitives에만 사용됩니다).
value object의 좋은 결과 중 하나는 메모리에서 동일한 객체에 대한 참조를 가지고 있는지 또는 동일한 값을 가진 다른 참조를 가지고 있는지 신경 쓸 필요가 없다는 것입니다. 하지만 주의하지 않으면 그러한 행복한 무지가 문제로 이어질 수 있으며, 이를 Java의 예로 설명하겠습니다.
이는 Aliasing Bug의 예입니다. 한 곳에서 날짜를 변경하면 예상 이상의 결과가 발생합니다⁴. aliasing bug를 피하기 위해 저는 간단하지만 중요한 규칙을 따릅니다: value object는 immutable이어야 합니다. 파티 날짜를 변경하고 싶다면 대신 새로운 객체를 만듭니다. 4: pre-Java-8 date와 time 시스템의 최악의 기능에 대해 강력한 경쟁이 있습니다 - 하지만 제 투표는 이것입니다. 다행히 Java 8의 java.time 패키지로 대부분을 피할 수 있습니다.
물론 value object를 immutable로 취급하는 것이 훨씬 더 쉬워집니다. 객체의 경우 일반적으로 단순히 설정 메서드를 제공하지 않음으로써 이를 수행할 수 있습니다. 따라서 제 이전 JavaScript 클래스는 다음과 같이 보입니다:⁵
5: 클라이언트가 _data 속성을 조작할 수 있기 때문에 이는 엄밀히 말해 immutable이 아닙니다. 하지만 적절히 훈련된 팀은 실제로 이를 immutable로 만들 수 있습니다. 팀이 충분히 훈련되지 않을까봐 걱정된다면 freeze를 사용할 수 있습니다. 실제로 간단한 JavaScript 객체에 freeze를 사용할 수 있지만, 저는 선언된 accessor를 가진 클래스의 명시성을 선호합니다.
immutability는 aliasing bug를 피하기 위한 제 가장 좋아하는 기법이지만, 할당이 항상 복사본을 만들도록 보장함으로써 이를 피할 수도 있습니다. C#의 struct와 같은 일부 언어는 이 기능을 제공합니다.
개념을 reference object로 취급할지 value object로 취급할지는 문맥에 따라 다릅니다. 많은 상황에서 우편 주소를 값 동등성을 가진 간단한 텍스트 구조로 취급하는 것이 가치가 있습니다. 하지만 더 정교한 매핑 시스템은 우편 주소를 참조가 더 합리적인 정교한 계층 모델에 연결할 수 있습니다. 대부분의 모델링 문제와 마찬가지로 다른 문맥은 다른 솔루션으로 이어집니다.⁶
6: 이에 대한 더 많은 논의는 Evans의 DDD 책에 있습니다.
문자열과 같은 일반적인 primitives를 적절한 value object로 바꾸는 것이 종종 좋은 생각입니다. 전화번호를 문자열로 표현할 수 있지만, 전화번호 객체로 변환하면 변수와 매개변수가 더 명시적이 되고 (언어가 지원할 때 타입 체크), 검증을 위한 자연스러운 초점이 되며, 적용할 수 없는 동작(예: 정수 id 번호에 대한 산술)을 피합니다.
point, money, range와 같은 작은 객체는 value object의 좋은 예입니다. 하지만 더 큰 구조도 개념적 identity가 없거나 프로그램 전체에서 참조를 공유할 필요가 없다면 종종 value object로 프로그래밍할 수 있습니다. 이는 immutability를 기본값으로 하는 functional language와 더 자연스러운 적합입니다.⁷
7: Immutability는 reference object에도 가치가 있습니다 - 판매 주문이 get 요청 중에 변경되지 않으면 이를 immutable로 만드는 것이 가치가 있습니다. 그리고 그것이 유용하다면 복사하기에 안전하게 만들 것입니다. 하지만 고유한 주문 번호를 기반으로 동등성을 결정하는 경우 판매 주문을 value object로 만들지는 않습니다.
저는 value object, 특히 작은 것들이 종종 간과된다는 것을 발견합니다 - 생각할 가치가 없을 정도로 사소한 것으로 여겨집니다. 하지만 좋은 value object 집합을 발견하면 그 위에 풍부한 동작을 만들 수 있습니다. 이를 맛보기 위해 Range 클래스를 사용해보고 더 풍부한 동작을 사용하여 시작 및 종료 속성으로 모든 종류의 중복 작업을 어떻게 방지하는지 확인해보세요. 저는 종종 이러한 domain-specific value object가 리팩토링의 초점이 될 수 있는 코드베이스를 만나며, 이는 시스템의 급격한 단순화로 이어집니다. 그러한 단순화는 종종 사람들을 놀라게 하지만, 몇 번 본 후에는 좋은 친구가 됩니다. 감사의 말
James Shore, Beth Andres-Beck, Pete Hodgson은 JavaScript에서 value object 사용에 대한 경험을 공유했습니다.
Graham Brooks, James Birnie, Jeroen Soeters, Mariano Giuffrida, Matteo Vaccari, Ricardo Cavalcanti, Steven Lowe는 내부 메일링 리스트에서 귀중한 의견을 제공했습니다.
추가 읽을거리
이 용어는 2000년대 초반에 인기를 얻기 시작했습니다. 그 시대의 두 권의 책이 이를 다룹니다: PoEAA와 DDD. Ward's Wiki에서도 흥미로운 논의가 있었습니다. 용어 혼동의 한 가지 원인은 세기 초 일부 J2EE 문헌에서 "value object"를 Data Transfer Object로 사용했다는 것입니다. 그 사용법은 대부분 사라졌지만 여전히 마주칠 수 있습니다.