3대450(이었던) 킹갓인비의 2월 25일 DTO vs VO 테코톡을 정리해보자!

🏈 결론

  • DTO : 데이터 전달용 객체
  • VO : 값 표현용 객체

image

????

그렇다. 결론부터 이야기 해야 이해가 쉽다.
결론을 알았으니 이제부터 DTO와 VO에 대해 천천히 알아보자.


🏈 DTO 란?

Data Transfer Object
데이터를 전달하기 위해 사용하는 객체이자, 데이터를 담아서 전달하는 바구니. 계층간 데이터를 전달하기 위한 객체다. 주로 Entity class(DB 테이블과 직접적으로 매칭되는 클래스)와 함께 사용된다.

그대로 전달해도 될 데이터를 굳이 DTO, Entity class로 나누고, 묶는 이유는 무엇일까? 바로 View 때문이다.

View는 비즈니스 요구사항에서 가장 자주 변경되는 계층이다. View가 변경될 때마다 DB 테이블에 이를 반영하는 건 엄청난 오버헤드를 발생 시킨다. 그렇다고 DB 테이블과 직접적으로 매칭되는 Entity class에 이를 반영하기엔, Entity class 와 의존관계를 맺은 수 많은 클래스들에도 영향을 끼치게 된다. 즉, 매번 View의 변경사항을 DB나 Entity class에 반영하는 건 사실상 불가능하다.

이러한 문제점을 해결하기 위해 다른 클래스들로부터 완전히 독립적으로, 자유롭게 변환이 가능한 DTO 객체 개념이 탄생하게 된 것이다.

image

DTO 객체는 단순하다. 순수하게 데이터 전달만을 위해 사용되기 때문에 오직 getter/setter만 사용한다.
(순수하게 getter/setter만 가진 객체가 DTO라고 말하는 개발자들도 많지만, 간단한 형태변환 정도는 허용하는 개발자들도 존재한다. DTO도 결국 Object이므로 말이 된다. 적당한 타협이 필요하다.)

아래 예시코드로 구조를 살펴보자.

// DTO
public class CrewDto {
	private String name;
	private String nickname;

	public String getName() {
		return name;
	}

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

	public String getNickname() {
		return nickname;
	}

	public void setNickname(final String nickname) {
		this.nickname = nickname;
	}
}

정말 기본적인 getter/setter 만 가지고 있으며, 별도의 로직을 포함하지 않는다.

// Service Layer - SampleService
public CrewDto createNewCrew() {
	String newName = "김태희";
	String newNickname = "Inbi";

	CrewDto crewDto = new CrewDto();
	crewDto.setName(newName);
	crewDto.setNickname(newNickname);

	return crewDto;
}
// Web(Controller) Layer
public String createNewCrew() {
	CrewDto newCrewDto = sampleService.createNewCrew();
	
	String nameOfNewCrew = newCrewDto.getName();
	String nicknameOfNewCrew = newCrewDto.getNickname();

	return nameOfNewCrew + nicknameOfNewCrew;
}

Service 계층에서 Web(Controller) 계층으로 크루 정보를 전달 할 때 DTO를 통해서 받는다. 반대의 경우에도 마찬가지다.


🏈 DTO도 불변 객체로

객체지향 프로그래밍에 대해 공부하다보면 “가변 객체를 지양하라”는 말을 자주 볼 수 있다. 이 이야기는 DTO에도 예외 없이 적용된다. DTO 또한 setter 메서드를 제외시키고 생성자를 통해 값을 지정함으로서 불변 객체로 만들어 줄 수 있다.

// DTO
public class CrewDto {
	private final String name;
	private final String nickname;

	public CrewDto(final String name, final String nickname) {
        this.name = name;
        this.nickname = nickname;
    }
	
	public String getName() {
		return name;
	}

	public String getNickname() {
		return nickname;
	}
}

setter를 사용할 때보다 코드도 간결해졌다. 역시 불변 객체가 짱이다.


🏈 VO 란?

Value Object
말 그대로 값을 표현하는 객체다. (돈, 이름 등…) 현실세계에도 VO 개념이 존재한다.

image

DK0577701L, AB0688463C, BH0640025K, BJ7783675C 지폐의 일련번호는 모두 다르지만, 우리는 이것들을 모두 “50,000원권” 이라고 부른다. 이 “50,000원권” 개념이 바로 VO다.

아래 예시 코드로 구조를 살펴보자.

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

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

VO는 값 그 자체를 표현하는 객체이므로 무조건 불변이어야한다. 그래서 setter가 없어야 한다. 그렇지만 VO는 getter 이외의 별도 로직을 가질 수 있다!

앞서 VO는 “50,000원권” 개념이라고 했다. 그런데 자바 컴파일러는 기본적으로 인스턴스간 비교를 해시코드를 통해서 진행한다. 즉, 현재 Money 인스턴스끼리 동등한가를 물으면 컴파일러는 “다르다!” 라고 대답할 것이다.

이 때문에 VO는 자바 컴파일러가 인스턴스간 동등성 비교를 해시코드가 아닌 필드 값으로 하도록 유도하기 위해서 equals, hashCode 메서드를 필수로 구현(오버라이딩)해야한다!

image

equals, hashCode 메서드 오버라이딩을 통해서 VO 끼리는 서로 다른 인스턴스더라도, 가지고 있는 값(value)가 같다면 같은 인스턴스로 취급하게 된다.

public class Money {
    private final int value;
    
    public Money(final int value) {
        this.value = value;
    }
    
    public int getHalfValue() {
        return value / 2;
    }
    
    @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);
    }
}


🏈 DTO vs VO

DTO와 VO에 정리가 끝났으니, 포스팅의 주제였던 DTO vs VO로 되돌아와보자.

DTO vs VO 주제는 많은 개발자들이 두 개념을 혼동하다보니 자주 떠오른다. 분명한 차이를 가지고 있음에도 많인 개발자들이 혼동하는 이유는 무엇일까?

image

… 이 책이 원인일 가능성이 농후하다고 한다.

우리는 이미 DTO와 VO의 차이를 명확하게 이해했다. 마지막 표로 다시 한 번 정리해보자.

  DTO VO
용도 계층간 데이터 전달 값 표현
동등성 결정 값이 같다고 같은 객체는 아님 값이 같으면 같은 객체
가변 객체 (권장하지 않지만) setter를 포함시키면 가변 객체 가능 절대 안됨. 무조건 불변
로직 (취향차이지만) getter/setter 외 로직이 없다 getter/setter 외 로직을 가질 수 있다


끗!

References

태그:

업데이트:

댓글남기기