4 minute read

DTO vs VO vs Entity 그리고 POJO 의 차이를 아시나요?

실무에서 가끔 위의 용어들을 혼동해서 사용할 때가 있다. 왜그럴까?

나는 학원에서 처음 MVC를 배울때 모든 response 나 계층 간의 데이터 교환을 모두 VO로 사용하였다.

그땐, 화면이든 DB든 다 내가 만드는것이였으니깐,,

그렇지만 실무에선 이에 대한 구분히 명확하고 사용처가 나뉘어야 한다, 그래야 혼동이 없고 적절히 사용이 가능하기 때문이다.

이에 대해 알아보도록 하자,

1. DTO 란?

DTO (Data Transfer Object) : 데이터 전송 객체

계층(Layer) 간 데이터 교환을 위해 사용하는 객체이고, 로직을 갖고 있지 않는 순수하 데이터 객체이며, getter/setter 메서드만을 갖는다.

DTO

DTO 정리

  • 프로세스간 데이터를 전달하는 객체
    • Controller, View, Business Layer, Persistence Layer 등의 데이터 교한을 위한 Java beans
    • VO와 비교하면 DTO는 같은 시스템에서 사용되는것이 아닌, 다른 시스템으로 전달하는 작업을 처리하는 객체!
    • 마틴 파울러 페이지 에서 용어의 역사까지 설명
    • Core J2EE Pattern 2판 (2003년) 에서 TO라는 이름을 붙이기 시작
  • 네트워크 전송시의 Data holder 역할로 요즘은 쓰이는 느낌
    • MSDN의 DTO 만들기 에서는 네트워크를 통해 데이터를 전송하는 방법을 정의하는 개체로 사용
  • 도메인단위의 데이터를 저장
  • 상태 변경 가능
  • getter/setter 메서드만을 갖는 순수한 데이터 객체
    • 비즈니스 로직을 가지지 안흔 순수 데이터 객체,
    • 가변의 성격을 가지고 있다 = (getter/setter) 메서드를 갖는다
  • 해결하는 문제와 맥락이 달라졌는데 같은 패턴 이름을 쓸까?
    • 레이어 간의 경계를 넘어 데이터를 전달 하는 역할은 과거와 동일
      • 원격 호출 레이어에 국한되지 않게 쓰는 경우도 넓게 보면 이용어로 이해 가능
    • 다만 다양한 객체의 역할을 다 DTO로 이름 붙이는 건 혼라이 올 수도 있다.
      • EX) Http 요청으로 오는 파라미터를 담을 객체, 통계 쿼리의 결과를 담을 객체
      • EX) QueryDSL 에서 DB조회 결과를 담을 객체

QueryDSL 메뉴얼 예제

List<UserDTO> dtos = query.list(
    Projections.bean(UserDTO.class, user.firstName, user.lastName));

DTO 예제


public class MoveDto {
    private String source;
    private String target;
    
    public MoveDto(String source, String target) {
        this.source = resource;
        this.target = target;
    }
    
    public String getSource() {
        return source;
    }
    
    public String getTarget() {
        return target;
    }
    
    public void setSource(String source) {
        this.source = source;
    }
    
    public void setTarget(String target) {
        this.target = target;
    }
}

2. VO 란?

VO (Value Object) : 값 객체

값 그 자체르르 표현하는 객체, 서로 다른 이름을 각진 VO의 인스턴스가 모든 속성 값이 같다면 같은 객체이다. 객체의 불변성을 보장한다. 로직을 포함한다.

VO (Value Object) 정리

  • 값이 같으면 동일하다고 간주되는 식별성이 없는 작은 객체 (EX : Money, Color)
  • DTO와 혼용되어 쓰여옴
    • Core J2EE Pattern 1판 (2001년) 에서 위 2003 년 버전의 TO를 VO라 적어 사용
    • DTO와 동일하다고 적는 서적도 있다.
    • 보통 학원에서 알려줄때 명확한 구분을 지어주지 않음,
  • Data Holder의 의미로 폭 넓게 생각하는 경향이 있다.

VO

public class RGBColorVO {

    private final double red;
    private final double green;
    private final double blue;

    private RGBColorVO(double red, double green, double blue) {
        this.red = red;
        this.blue = blue;
        this.green = green;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        RGBColor rgbColor = (RGBColor) o;
        return Double.compare(rgbColor.red, red) == 0 && Double.compare(rgbColor.green, green) == 0 && Double.compare(rgbColor.blue, blue) == 0;
    }

    @Override
    public int hashCode() {
        return Objects.hash(red, green, blue);
    }

    public double getRed() {
        return red;
    }

    public double getGreen() {
        return green;
    }

    public double getBlue() {
        return blue;
    }
}

3. Entity?

  • 실제 DB의 테이블과 매칭될 클래스
    • 즉, 테이블과 링크될 클래스임을 나타냄
    • Entity 클래스 또는 가장 Core한 클래스라고 부른다.
    • @Entity, @Column, @Id 등을 이용
  • 최대한 외부에서 Entity의 클래스의 getter method를 사용하지 않도록 해당 클래스 안에서 필요한 로직 method를 구현 (가변적)
    • 단, Domain Logic 만 가지고 있어야 하고, Presentation Logic 을 가지고 있어서는 안된다.
    • 여기서 구현한 method는 주로 Service Layer에서 사용. ```text Domain Logic

Presentation Logic

- __Entity와 Dto 클래스를 분리하는 이유__
    - View Layer 와 DB Layer 역할을 철저하게 분리하기 위함
    - 테이블과 매핑되는 Entity 클래스가 변경되면 여러 클래스에 영향을 끼치게 되는 반면 View와 통신하는 DTO 클래스는 자주 변경이 되므로 분리가 필요하다.
    - Domain Model을 아무리 잘 설계 했다하여도 각 View 내에 Domain Model의 Getter만을 이용해서 원하는 정보를 표시하기 어려운 경우가 있음, 이런 경우 Domain model내에 Presentation을 위한 필드나 로직을 추가하게 되는데 이러한 방식이 
    모델의 순수성을 해치고 Domain Model 객체를 망가뜨린다.
    - Domain Model을 복잡하게 조합한 형태의 Presentation 요구 사항들이 있기 때문에 Domain Model 을 직접 사용하는것은 어려움
    - 즉 DTO는 Domain Model 을 복사한 형태로, 다양한 Presentation Login을 추가한 정도로 사용하며, Domain Model 객체는 Persistent만을 위해서 사용한다.

```java
@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
public class User implements Serializable {
    private static final long serialVersionUID = 7342736640368461848L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @JsonProperty
    private Long id;

    @Column(nullable = false)
    @JsonProperty
    private String email;

    @Column(nullable = false)
    @JsonIgnore
    private String password;

    // @Override 
    // public boolean equals(Object o) { ... }
    // @Override
    // public int hashCode() { ... }
    // @Override
    // public String toString() { ... }
}
//  https://gmlwjd9405.github.io/2018/12/25/difference-dao-dto-entity.html
  • Entity를 감추기
    • 위에서 말한 내용이지만 중요하므로 다시 한번 정리
    • Entity가 뷰, API 응답에 바로 노출될 떄의 비용
      • 캡슐화를 지키기가 어려워짐
        • 꼭 필요하지 않는 속성도 외부로 노출되어 향후 수정하기 어려워진다.
      • 컴파일 시점의 검사 범위가 좁다. -> Entity 클래스를 수정했을때 뷰에서 에러가 나는 경우가 뒤늦게 발견된다.
      • JPA 를 쓴다면 OpenEntityManagerInViewFilter 를 고려해야 함
        • 쿼리 시점을 예상하기 힘듬
      • JSON 응답
        • @JsonIgnore, @JsonView 같은 선언이 많아지면 JSON 형태를 클래스만 보고 예측하는 난이도가 올라간다.
  • 외부 노출용 DTO를 따로 만들기
    • ENTITY -> DTO 변환 로직은 컴파일 타임에 체크된다.
    • DTO는 구조를 단순하게 가져갈수 있다.
      • 더 단순한 JSON 응답, JSP에서 쓰기 좋은 궂를 만들기에 유리
    • DTO의 변화는 외부 인터페이스로 의식해서 관리하는 범위가 된다.
    • 여러 Entity를 조합할 수 있는 여지가 생김
  • 자바로 말하자면 VO는 equal을 사용할때, 모든 값들을 비교하는 반면, entity는 id값만을 비교한다는 차이가 있다.

4. POJO?

POJO (Plain Old Java Object) 말그대로 오래된 방식의 자바 오브젝트라는 말로, Java EE등의 중량 프레임워크들을 사용하게 되면서 해당 프레임워크에 종속된 무거운 객체를 만들게 된것에 반발하여 사용하게된 용어

즉, 특정 클래스에 종속되지 않는 객체

5. 간략 정리

DTO : “데이터 전송 개체”는 소프트웨어 아키텍처에서 별도의 계층간에 이동할 수 있습니다.
VO : “값 개체”는 정수, 머니 등과 같은 개체를 보유합니다.
POJO : 특수 객체가 아닌 일반 오래된 Java 객체.
Java Beans : Java Class직렬화 가능해야하며 no-arg각 필드마다 생성자와 게터 및 세터가 있어야합니다.

Categories:

Updated: