🧐TIL

[DDD] 값 객체, Value Object

date
Feb 20, 2023
thumbnail
slug
ddd-vo
author
status
Public
summary
“도메인 주도 설계 철저 입문”을 읽으며 공부한 내용을 정리합니다.
type
Post
category
🧐TIL
tags
TIL
DDD
Architecture
updatedAt
Jun 18, 2023 05:57 AM

값 객체란

간단하게는 도메인의 고유 개념을 으로 표현하기 위해 정의하는 객체.
여기서 ‘값’은 말그대로 변수나 다른 클래스의 속성으로 대입할 수 있는 값을 의미한다.
일반적으로는 파이썬의 str, int, float 처럼 프로그래밍 언어별로 존재하는 원시 데이터가 값으로 많이 사용되지만 시스템에 따라 사용할 수 있는 값의 조건이 달라질 수도 있다.
예를 들어 ‘이름’은 단순 문자열로 볼 수도 있지만 성과 이름이 분리되어야 할 수도 있고 특수문자가 사용되면 안될 수도 있다. 그리고 ‘나이’는 단순 숫자로 생각할 수도 있지만 0세 미만은 존재 할 수 없고 만 나이를 구분해야 하는 경우도 있다.
그래서 위와 같은 경우에는 이름에 str 타입을 쓰는 대신 Name 이라는 클래스를 정의하거나 나이에 int 대신 Age 라는 클래스를 정의하고 각각 속성과 조건을 부여해 줄 수도 있다.
이처럼 값 객체는 도메인 특유의 조건, 규칙 등이 담긴 객체이자 동시에 값이다.

값의 특성

값은 대표적으로 세 가지 성질을 가지고 있다.
  • 변하지 않는다.
  • 주고받을 수 있다.
  • 등가성을 비교할 수 있다.

값의 불변성

변수에 새로운 값을 대입하는 것은 가능하지만 값 자체는 바뀌어서는 안되며 값이 바뀐다는 것은 버그가 발생활 확률을 높인다.
클래스의 속성을 수정하는 메소드를 만드는 것은 어렵지 않기 때문에 문제를 느끼지 못할 수도 있지만 숫자 2가 갑자기 3이 된다거나 “안녕하세요”가 마음대로 “Hello”로 바뀌면 안되듯이 값은 바뀌지 않는다는 것이 전제되어야 안심하고 값을 사용할 수 있기 때문에 값 객체에서도 이미 정의된 값은 바뀌어서는 안된다.

값의 교환 가능성

값은 변해서는 안되지만 만약 회원가입시 입력한 이름을 수정하는 것 처럼 값을 수정해야 하는 경우에는 어떻게 할까? 이런 경우에는 값 자체를 수정하는 것이 아니라 대입문을 통해 값을 교환하는 방식을 사용한다.
회원정보의 이름이라는 속성에 기존에는 “John”이라는 값이 있었다면 이름을 바꾸기 위해 “John” 이라는 문자열을 수정하는 것이 아니라 “Tom” 이라는 새로운 문자열을 이름에 대입해주는 것이다.
값 객체는 불변이기 때문에 대입문을 통한 교환 외의 수단으로는 수정을 나타낼 수 없다.

등가성 비교 가능

숫자 혹은 문자끼리 비교가 가능하듯이 같은 종류의 값끼리는 비교할 수 있어야 한다.
이때, 값 객체간의 비교를 위해서는 객체의 속성을 꺼내서 직접 비교하는 것이 아니라 객체간 비교를 할 수 있는 메서드 정의가 필요하다.
메서드를 정의하는 이유에는 값은 값 자체로 비교를 하는 것이 자연스럽기 때문이라는 점도 있지만, 메서드가 없을 경우 객체간 비교를 위한 코드를 필요할 때마다 작성해줘야 하는데 이로 인해 객체 비교 코드가 여러 곳에 작성되어 있으면 나중에 값 객체에 속성이 추가됐을 때 산개한 모든 코드를 수정해줘야 하는 어려움이 생기기 때문이라는 점도 있다.

값 객체를 정의하는 기준

필요에 의해 값 객체를 정의하는 것은 좋지만 따지고 들어가면 이름은 성과 이름으로 나뉠 수도 있고 성과 이름에도 규칙이 필요할 수 있다. 숫자가 사용되는 곳은 나이 외에도 가격, 개수, 반 번호 등이 있을 수 있는데 각 사용처마다 모두 다른 규칙이 존재한다.
그렇다면 프로그래밍을 할 때 원시 데이터가 사용되는 모든 개념을 값 객체로 만들어야 하는 것 아닐까?
‘도메인 주도 설계 철저 입문’에서는 값 객체에 대한 정의 기준으로 “규칙이 존재하는가”와 “낱개로 다루어야 하는가”를 제시한다.
현실에서는 규칙이 존재하지만 시스템 상 규칙을 적용할 필요가 없다면 당장 값 객체로 정의할 필요는 없을 것이다. 마찬가지로 이름은 성과 이름으로 나눌 수 있지만 시스템 상 굳이 구분해서 낱개로 다룰 필요가 없다면 값 객체로 정의하지 않아도 된다.
만약 두 기준 중 한 가지만 부합하는 경우라면 값 객체를 정의하지 않더라도 속성을 낱개로 분리하거나 속성에 대한 조건을 상위 객체에서 강제하는 방법이 있다. 물론 한 가지 기준만 부합해도 값 객체를 정의할 수도 있으며 중요한 것은 값 객체의 필요성을 정확히 이해하고 판단에 따라 사용하는 것이다.

값 객체를 사용하는 이유

행동 정의

— 값 객체는 데이터를 담는 것만이 목적이 아니다. 데이터와 더불어 데이터에 대한 행동을 한 곳에 모아 자신만의 규칙을 갖는 도메인 객체를 정의하는 것이 값 객체를 정의하는 목적이다.
객체에 정의된 행위를 통해 해당 객체가 어떤 일을 할 수 있는지 알 수 있으며, 반대로 정의 되지 않는 행동은 객체가 할 수 없으며 해당 객체가 존재하는 목적이 아님을 알 수 있기도 하다.

표현력 증가

— 값 객체는 자기 정의를 통해 자신이 무엇인지에 대한 정보를 제공하는 자기 문서화를 돕는다.

무결성 유지

— 처리 대상이 아닌 입력을 확인해 걸러내는 코드인 ‘방어 코드’를 값 객체에 적용하면 규칙을 위반하는 유효하지 않은 값을 처음부터 방지할 수 있어 무결성을 유지할 수 있으며 값이 사용될 때마다 유효성 검증을 해야 할 필요도 없애준다.

잘못된 대입 방지

— ‘표현력 증가’를 통해 값을 대입할 때 컨텍스트에 대한 이해가 없어도 올바른 코드인지 판단하기 쉬워지고, 실수로 잘못된 대입을 한다해도 ‘무결성 유지’의 장점을 살려 에러가 발생하는 곳을 쉽게 파악할 수 있게 된다.

코드의 반복 방지

— 앞서 나왔던 등가성 비교를 위한 메서드나 유효성 검증 코드 등 값의 행동과 관련된 코드를 값 객체에 정의하게 되면 로직을 한 곳에 모아두고 반복해서 코드를 작성하게 되는 경우를 크게 줄일 수 있다. 한 곳에 모여있는 코드는 수정도 한 곳에서만 하면 되기 때문에 작업의 불편함을 줄이고 소프트웨어의 유연성도 높여주게 된다.