본문 바로가기

Development/Spring

DDD(Domain Driven Design)에서 Entity, DTO, VO 비교

 

 

Entity

 

 

Entity는 주로 데이터베이스테이블매핑되는 객체다.

 

값이 쉽게 변경되면 객체일관성이 유지되지 않으며 다른 객체들에도 영향을 끼치게 되므로, Setter가 아닌 생성자사용하는 것이 바람직하며 데이터 전송용으로는 적합하지 않다.

 

Entity는 Business layer에 속하지는 않지만, 도메인에 관계되는 일부 복잡한 로직은 DDD의 Rich Model 개념에 의거하여 Entity에서 구현할 수도 있다. 이는 Entity가 스스로의 상태를 관리하기 위함이다. 하지만 이것이 주 목적은 될 수 없으며, 기본적으로는 데이터베이스조작을 수행하기 위한 매개체로서 기능한다.

 

Entity 객체는 식별성을 가지고 있으므로, 반드시 식별자(ID)를 포함해야 한다.

 

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "member")
public class Member {

    @Id
    @Column(name = "member_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, length = 10)
    private String name;
    
    @Builder
    private Member(Long id, String name) {
        this.id = id;
        this.name = name;
    }

}

 

id와 name을 column값으로 가지고 있는 Member 테이블에 대응하는 Entity 객체의 예시다. 데이터베이스의 조작을 수행할 클라이언트의 요청값은 주로 business layer에서 Entity로 변환된다.

 

클래스명테이블명, field값은 테이블의 각 column값에 대응한다. Setter 메소드를 사용하게 되면 객체일관성깨지기 쉬우므로, 생성자를 통해 Entity의 field값을 갱신한다. 데이터베이스의 데이터를 갱신할 때는 Getter 메소드를 사용한다.

 

 

DTO

 

 

DTO(Data Transfer Object)는 데이터 주고받기 위한 객체다. 비즈니스 로직은 포함하지 않으며, 오직 전송을 위한 기능만을 담당한다.

 

필드값 초기화 시 Setter 메소드를 이용하기도 하기 때문에 field값가변적일 수 있지만, 경우에 따라 생성자를 이용해 불변성을 보장할 수도 있다.

 

@Data
public class MemberDto {

    private Long id;
    private String name;
    
}

 

id와 name값을 전달하는 DTO 객체의 예시다. 클라이언트에게 전송될 응답값은 주로 business layer에서 DTO로 변환되며, 매개변수로 사용되어 단순히 Layer(계층) 간, Component(구성요소) 간 데이터 전송 역할을 담당한다.

 

 

VO

 

 

VO(Value Object)는 데이터를 주고받을 수 있으며, 표현하는 불변 객체다. 따라서 식별자(ID)를 소유하지 않으며, 같은 값을 가지는 두 VO는 동일 객체로 간주된다.

 

객체들이 참조하는 메모리 번지가 달라도 같으면 같은 객체로 간주한다. 이를 위해 hashCode()(객체 식별 정수)와 equals()(객체의 메모리 주소 비교 연산) 메소드를 이용하는데, 두 메소드는 원래 객체의 메모리 주소를 기준으로 객체를 식별하므로, 값을 기준으로 비교(동등 비교)하기 위해서는 method overriding이 필요하다.

 

생성자를 이용해 불변성이 보장되고 DTO와 달리 표현하므로, 객체 그 자체로서 가치를 지닌다. 또한 비즈니스 로직포함할 수 있다.

 

 

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class MemberVo {

    @Column(nullable = false, length = 10)
    private String name;

    @Embedded
    private Address address;

    @Builder
    public MemberVo(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, address);
    }

    @Override
    public boolean equals(Object obj) {

        if (obj instanceof MemberVo) {

            MemberVo target = (MemberVo) obj;

            if (name.equals(target.getName()) && address.equals(target.getAddress())) {
                return true;
            }

        }

        return false;

    }

}

 

 

위의 예시처럼 hashCode() 메소드와 equals() 메소드를 overriding해 동등 비교를 할 수 있다. id와 name을 통합한 해시코드 값데이터비교하고, 둘 중 하나라도 다르면 동등 객체로 인정되지 않는다.

 

다만 현재 VO는 비즈니스 소프트웨어 개발 과정에서 잘 사용되지 않는데, 불변성으로 인해 변화하는 상태를 반영하기 어렵기 때문이다. 이는 상태 변화가 빈번한 비즈니스 로직에서 Entity나 DTO에 비해 불리한 특성으로 작용한다.

 

또한 값을 통해 동등성을 판단한다는 것은 식별자(ID)를 통해 동등성을 판단하는 Entity에 비해 대량 데이터 처리 혹은 복잡한 데이터 구조를 다루는 데 있어 비효율적일 수 있다는 뜻이 된다.

 

다만 실제 프로그래밍에서 불변성은 많은 이점을 제공하는 특성으로서, 동일성에 비해 불변성이나 동등성중요한 도메인 로직의 경우에는 Entity 대신 VO를 사용하기도 한다.