시스템의 고유한 값을 만드는, 값 객체(Value Object)

박유민·2023년 12월 11일
1

Domain-Driven Design

목록 보기
1/2
post-thumbnail
post-custom-banner

값 객체란?

  • 시스템에서 어떤 처리를 해야하는지에 따라 적합하게 값을 나타내는 객체.

값 객체를 설명하기 위해 예시를 하나 들어보겠다. 여기 성명을 저장하기 위한 full_name이라는 변수가 있다.

full_name: str = "youmin park"

성명을 저장할때는 요구사항에 따라 달라 질 수 있는데, 단순하게 이름 전체만 저장해서 보여줘야 한다면 위와 같은 형태로 저장해도 문제가 없을 것이다.

그런데, 요구사항이 달라져서 성 또는 이름을 따로 각각 가져와야 하는 경우가 생기면 어떻게 해야할까? full_name이라는 변수에서 성과 이름을 따로 가져오려고 한다면, 아래와 같이 공백을 기준으로 가져와야 한다.

first_name, last_name = fullname.split(" ")

하지만 공백으로 해당 데이터를 가져오려고 한다면 아래와 같은 경우는 문제가 발생한다. 의도와 다르게 성 다음에 이름이 오는 경우이다.

full_name: str = "park youmin"

이런 문제를 해결하기 위해서 값객체를 만들게 된다.

from dataclasses import dataclass

@dataclass(frozen=True)
class FullName:
	first_name: str
    last_name: str

위에서는 full_name을 문자열 타입으로 다뤘지만, 새로 FullName이라는 클래스를 정의했다. 성이 필요하면 last_name을, 이름이 필요하면 first_name을 사용하면 된다.

FullName 클래스 처럼 시스템에서 어떤 처리를 해야하는지에 따라 적합하게 값을 나타내는 객체 를 값 객체라고 한다.

 

값 객체의 장점

표현력 증가

여기 어떤 사람의 사원번호이 있다.

employee_id: str = "20210101-001"

이 사원번호를 값 객체로 표현하면 이렇게 표현 할 수 있다.

from dataclasses import dataclass
from datetime import date

@dataclass(frozen=True)
class EmployeeId:
	hire_date: date
    entry_order: int
    
    def __str__(self):
        return f"{self.hire_date.strftime('%Y%m%d')}-{self.entry_order:03d}"

위의 정보를 통해서 사원번호는 들어온 날짜와 순서로 구성되어 있다는것을 알 수 있다.

무결성의 유지

  1. EmployeeId 클래스는 @dataclass(frozen=True) 데코레이터를 사용하므로써 변경 할 수 없다는것을 알 수 있다.
  2. hire_date는 date type으로 잘못된 date로 사원번호를 만들려고 할때 ValueError가 발생한다.

위의 2가지처럼 변경불가능한 사원번호의 특성과 date의 validation을 통해 데이터의 무결성이 보장된다. 만약 값객체 없이 string으로 사용을 했다면 사원번호를 사용할려는 곳마다 해당 로직을 구현해서 코드의 복잡도가 올라가게 될 것이다.

 

실생활에서 값 객체

2개의 421버스

오늘 421번 버스를 타고 출근했다. 어떤 사람이 421버스에 타고 버스카드를 찍는 순간 이미 처처리된 카드입니다 라는 음성이 들렸다. 이 사람은 방금 421번 버스를 탔는데, 왜 이미 처리된 카드라고 나왔을까?

그 이유는 우리가 환승을 할때 버스는 값 객체로 취급되기 때문이다. 값 객체에서는 값이 같으면 같은 객체로 취급된다.

from datetime import datetime, timedelta

class BusBoardingInformation:
    def __init__(self, card_id: int, last_get_off_time: datetime, bus_number: int):
        self.card_id = card_id
        self.last_get_off_time = last_get_off_time
        self.bus_number = bus_number

    def can_transfer(self, bus_number: int) -> bool:
        current_time = datetime.utcnow()
        time_since_last_get_off = current_time - self.last_get_off_time
        time_threshold = timedelta(minutes=30)
        
        if time_since_last_get_off < time_threshold:
        	return False
        
        if self.bus_number == bus_number:
        	voice_message("이미 처처리된 카드입니다")
            return False
 
        return True
  • can_transfer 함수는 환승이 가능한 여부를 알려주고, 만약 같은 버스번호로 요청하면 voice_message("이미 처처리된 카드입니다")를 호출하고 False로 리턴한다. 환승 시스템에서 같은 번호의 버스는 같은 버스로 취급하기 때문이고, 그래서 값객체라고 할 수 있다.

 

마무리

값 객체(Value Object)는 시스템에서 고유한 값을 나타내는 데 사용되며, 원시 데이터 타입만 사용하여 소프트웨어를 개발할 수 있지만, 값 객체를 도입함으로써 코드의 표현력과 유지보수성을 향상시킬 수 있다. 이는 값 객체가 특정 도메인 규칙을 코드로 명확하게 정의할 수 있기 때문이다.

profile
백엔드 엔지니어
post-custom-banner

0개의 댓글