얼마전 회의중 개발자들 사이에서도 해당 패턴의 이해도에 따라 의견이 분분했던 내용 이었는데, 최근 유명 개발사의 테크유튜브에서 VO 와 DTO 에 대한 발표가 있어서 내용을 정리하고 관련하여 다른 개발자들은 어떻게 생각하고 있는지 찾아서 정리해 보았습니다.

결론

DTO (Data Transfer Object) : 데이터를 전달하기 위한 객체

VO (Value Object) : 값 전달을 위한 객체

혼동의 이유는 무엇일까?

DTO 와 VO 라는 키워드로 검색해보면 많은 글들이 보이고, 이러한 글을 쭉 읽어보면 CORE J2EE PATTERNS 책에서 그 원인이 발생했을 가능성이 있다는 내용이 많이 보인다.

책의 초판 발행 시점에는 getter/setter 만이 존재하며 오로지 데이터 전달용 으로만 사용하는 객체에 대한 정의를 VO 로 정의 하였지만, 이후 혼동의 여지가 있다고 판단하여 책의 2판부터는 VO 로 표현 했던것을 TO 로 변경 하였는데, 이것이 사람들에게 혼동을 유발하였다고 한다.

이후 TO 는 Data 를 뜻하는 D 를 붙여 DTO 로 사용 되고 있다. 그리고 VO는 DDD나 ORM의 맥락에서는 다른 의미로 사용되고 있는데 이는 아래에서 DTO 와 VO 에 대해서 자세히 설명하도록 하겠다.

DTO (Data Transfer Object)

DTO 는 계층간 데이터를 전달하기 위한 객체이다. 순수하게 데이터 전달만을 위한 객체이므로 오직 getter/setter 만 갖으며 별도의 데이터 조작을 위한 로직은 갖지 않는다.

public class MemberDto {
	private String name;
	private int age;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
}

setter 메소드가 존재함으로 인해서 가변적인 객체일 수 있는 부분을 생성자를 통해 값을 초기화하여 전달 하게 바꾸면 데이터 불변성을 보장 할 수 있어 안정적이다.

public class MemberDto {
	private final String name;
	private final int age;

	public MemberDto(final String name, final int age) {
        this.name = name;
        this.age = age;
    }

	public String getName() {
		return name;
	}

	public int getAge() {
		return age;
	}
}

(물론 위 정의된 내용이 DTO 의 정답이라고는 생각하지 않는다. 개인의 사용 방법에 따라 환경에 따라 차이는 존재한다고 생각한다.)

Entity 와 DTO 의 분리

DTO 와 혼동되어 사용 되는 개념으로 Entity 가 있다. 내경우 JPA 를 사용하며 처음 Entity 라는 개념을 접하게 되었는데 Entity 를 요청 또는 응답값을 전달하는 클래스로 사용 해서는 안된다

View 는 비지니스 요구사항에 따라 가장 빈번하게 변경 되는 영역이다. 만약 Entity를 DTO 처럼 응답 객체로 사용하면 View의 변화에 대응하기 위해서 Entity 를 변경해줘야 하는 경우가 빈번하게 발생할 것이다.

Entity 는 수많은 비지니스로직과 서비스 및 클래스와 의존 관계를 맺고 있기 때문에 Entity 를 매번 View 의 요구사항에 맞춰 계속 변경하는 것은 불가능에 가깝다. 또한, DB 의 데이터 조인한 결과 값을 전달하여 VIEW 에 표현하기에도 한계가 있다.

이러한 이유로 데이터 응답에 대한 객체로 자유롭고 독립적으로 변경이 가능한 DTO 가 사용 되어야 한다

VO (Value Object)

VO 는 값에 의해 동등성이 판단되는 객체이며 대표적으로 날짜, 돈 등의 객체 표현이 VO의 예로 소개 되기도 한다. 값 자체를 표현하는 VO는 불변 객체여야 하므로 setter 메소드를 가지면 안되고 생성자로 값을 초기화해야한다. VO 는 값의 표현을 위해서 별도의 로직을 사용할 수 있다.

// VO
public class Money {
	private final int value;

	public Money(final int value) {
		this.value = value;
	}

	public int getHalfValue() {
		return value / 2;
	}

	...
}

완벽한 VO 객체를 만들기 위해서는 속성값의 동등성을 비교를 해줘야 하며 이를 위해서 equals(), hashCode() 를 오버라이딩 해줘야 한다.

...
    @Override
    public boolean equals(Object o) {
        if(this == o) {
            return true;
        }
        if (!(o instanceof Money)) {
            return false;
        }
        Money money = (Money) o;
        return value == money.value;
    }

    @Override
    public int hashCode() {
        return Objects.hash(value);
    }
...

정리

fig1.우아한형제들 테코톡 - 인비

Ref.

ryan's profile image

ryan

2021-05-04 10:00

ryan 님이 작성하신 글 더 보기