#spring,  #dto,  #vo,  #entity

DTO vs VO vs Entity

DTO vs VO vs Entity

DTO와 VO는 분명히 다른 개념이다. 그런데, 같은 개념으로 생각해서 사용하는 경우가 많다. 왜일까?
⌜Core J2EE Patterns: Best Practices and Design Strategies⌟ 책의 초판에서는 데이터 전송용 객체를 VO로 정의했다. 그 이후 2판에서는 해당 객체를 TO로 정정해서 작성했다. 이 때문에 DTO와 VO를 혼동하게 된 것 같다.
이번 글에서는 DTO, VO, Entity의 정의와 특징을 살펴본다. 마지막에는 세 객체를 도표로 비교하며 정리를 한다.


DTO(Data Transfer Object)

DTO는 데이터를 전달하기 위한 객체이다. 계층간 데이터를 주고 받을 때, 데이터를 담아서 전달하는 바구니로 생각할 수 있다. 여러 레이어 사이에서 DTO를 사용할 수 있지만, 주로 View와 Controller 사이에서 데이터를 주고 받을 때 활용한다.
DTO는 getter/setter 메소드를 포함한다. 이 외의 비즈니스 로직은 포함하지 않는다.

아래 코드처럼 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(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

VO(Value Object)

VO는 값 자체를 표현하는 객체이다. VO는 객체들의 주소가 달라도 값이 같으면 동일한 것으로 여긴다. 예를 들어, 고유번호가 서로 다른 만원 2장이 있다고 생각하자. 이 둘은 고유번호(주소)는 다르지만 10000원(값)은 동일하다.
VO는 getter 메소드와 함께 비즈니스 로직도 포함할 수 있다. 단, setter 메소드는 가지지 않는다. 또, 값 비교를 위해 equals()hashCode() 메소드를 오버라이딩 해줘야 한다.

아래 코드처럼 equals()와 hashCode() 메소드를 오버라이딩 하지 않으면 테스트가 실패한다.

// Money.java
public class Money {
    private final String currency;
    private final int value;

    public Money(String currency, int value) {
        this.currency = currency;
        this.value = value;
    }

    public String getCurrency() {
        return currency;
    }

    public int getValue() {
        return value;
    }
}

// MoneyTest.java
public class MoneyTest {
    @DisplayName("VO 동등비교를 한다.")
    @Test
    void isSameObjects() {
        Money money1 = new Money("원", 10000);
        Money money2 = new Money("원", 10000);

        assertThat(money1).isEqualTo(money2);
        assertThat(money1).hasSameHashCodeAs(money2);
    }
}

다음은 equals()와 hashCode() 메소드를 오버라이딩 하지 않았을 때의 테스트 결과이다.

money_test_fail


한편, 두 메소드를 오버라이딩 하면 테스트가 통과한다. 앞서 말했듯이 VO는 주소가 아닌 값을 비교하기 때문이다.

// Money.java
public class Money {
    private final String currency;
    private final int value;

    public Money(String currency, int value) {
        this.currency = currency;
        this.value = value;
    }

    public String getCurrency() {
        return currency;
    }

    public int getValue() {
        return value;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Money money = (Money) o;
        return value == money.value && Objects.equals(currency, money.currency);
    }

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

// MoneyTest.java
public class MoneyTest {
    @DisplayName("VO 동등비교를 한다.")
    @Test
    void isSameObjects() {
        Money money1 = new Money("원", 10000);
        Money money2 = new Money("원", 10000);

        assertThat(money1).isEqualTo(money2);
        assertThat(money1).hasSameHashCodeAs(money2);
    }
}

다음은 두 메소드를 오버라이딩 했을 때의 테스트 결과이다.

money_test_pass


Entity

Entity는 실제 DB 테이블과 매핑되는 핵심 클래스이다. 이를 기준으로 테이블이 생성되고 스키마가 변경된다. 따라서, 절대로 Entity를 요청이나 응답값을 전달하는 클래스로 사용해서는 안 된다.
Entity는 id로 구분된다. 그리고 비즈니스 로직을 포함할 수 있다.

Entity는 DTO처럼 setter를 가지는 경우 가변 객체로 활용할 수 있다.

public class Member {
    private final Long id;
    private final String email;
    private final String password;
    private final Integer age;

    public Member() {
    }

    public Member(Long id, String email, String password, Integer age) {
        this.id = id;
        this.email = email;
        this.password = password;
        this.age = age;
    }
}

세 객체 비교

분류DTOVOEntity
정의레이어간 데이터 전송용 객체값 표현용 객체DB 테이블 매핑용 객체
상태 변경 여부가변 또는 불변 객체불변 객체가변 또는 불변 객체
로직 포함 여부로직을 포함할 수 없다.로직을 포함할 수 있다.로직을 포함할 수 있다.

References