|

DTO와 Entity 차이: 왜 같은 데이터처럼 보여도 역할은 달라야 할까

DTO와 Entity 차이를 전달 계약과 상태 규칙 관점으로 설명하는 대표 이미지
필드가 비슷해 보여도 DTO와 Entity는 같은 역할이 아닐 수 있다

DTO와 Entity 차이는 처음 배울 때보다 실무에서 더 헷갈립니다. 둘 다 결국 필드 몇 개를 가진 객체처럼 보이기 때문입니다. 결론부터 말하면 DTO는 데이터를 전달하는 계약에 가깝고, Entity는 식별자와 상태, 규칙을 가진 객체에 가깝습니다.

DTO와 Entity 차이를 비교한 요약 카드
먼저 큰 차이를 한눈에 잡는 요약 카드

왜 자꾸 같은 것으로 보일까

주문 조회 기능처럼 실제 예시를 보면 두 객체가 모두 id, status, totalAmount 같은 필드를 가질 수 있습니다. 그래서 표면만 보면 같은 주문 객체처럼 보입니다. 하지만 이 객체가 어디에서 어디로 이동하는지, 자기 상태를 바꾸는 규칙을 가져야 하는지, 식별자와 생명주기가 중요한지를 보면 역할이 갈립니다.

public class OrderEntity {
    private Long id;
    private OrderStatus status;
    private BigDecimal totalAmount;
    private Long customerId;

    public void cancel() {
        if (status == OrderStatus.SHIPPED) {
            throw new IllegalStateException("이미 출고된 주문은 취소할 수 없습니다.");
        }
        this.status = OrderStatus.CANCELED;
    }
}

public record OrderResponseDto(
    Long id,
    String status,
    BigDecimal totalAmount
) {}

DTO는 무엇인가

Martin Fowler의 DTO 설명은 DTO를 경계 사이 데이터를 한 번에 전달하기 위한 객체로 설명합니다. 현대 웹 API 문맥으로 옮겨도 감각은 비슷합니다. DTO는 요청 본문을 받거나, 응답 데이터를 정리해 내보내거나, 계층 사이 전달 계약을 표현하는 쪽에 가깝습니다.

  • 요청 본문을 받는다
  • 응답 데이터를 정리해 내보낸다
  • 계층 사이 전달 계약을 표현한다
  • 직렬화 구조를 안정적으로 유지한다

즉 DTO의 관심사는 보통 전달입니다.


Entity는 무엇인가

반면 Entity는 식별자, 상태 변화, 생명주기, 영속성 문맥과 더 강하게 연결됩니다. Jakarta Persistence 스펙이 entity identity, entity life cycle, managed instances를 별도로 다루는 것도 이 때문입니다.

Entity 쪽에서 더 중요한 것은 보통 식별성, 상태 전이, 무결성, 저장소와의 연결입니다. 즉 Entity는 자기 상태를 어떻게 지켜야 하는가에 더 가깝습니다.


가장 큰 차이는 전달 계약 vs 상태와 규칙

DTO는 바깥과 주고받는 계약이고, Entity는 식별자와 생명주기, 상태 변화 규칙이 중요한 객체입니다. DTO는 값 중심으로 다루기 쉽고 표현이 더 중요합니다. Entity는 표현보다 무결성과 상태 전이가 더 중요합니다.

DTO는 “어떻게 보여주고 옮길까”에 가깝고, Entity는 “어떤 상태가 가능하고 무엇을 지켜야 할까”에 가깝습니다.


왜 Entity를 API에 바로 내보내면 자주 불편할까

Entity를 그대로 응답으로 내보내면 처음에는 편해 보입니다. 하지만 시간이 지나면 불필요한 필드 노출, 응답 구조와 저장 구조의 강한 결합, 연관관계와 lazy loading 문제, 상태 표현 가공의 어려움 같은 문제가 자주 생깁니다. API 응답 모델은 보통 클라이언트 계약 관점으로 다시 정리될 필요가 있습니다.


그렇다고 DTO를 무조건 다 분리해야 할까

반대쪽 과설계도 자주 생깁니다. Request DTO, Response DTO, Entity, Domain Model, Query Model을 모두 따로 만들었지만 실제로는 필드 이름만 복사하는 코드만 늘어나는 경우입니다.

클린 아키텍처 글에서 다뤘듯 모델 분리는 경계 보호 가치가 복제 비용보다 클 때 정당화됩니다.


VO와 API 응답 모델은 어디쯤 있을까

VO는 보통 식별자보다 값 자체의 의미가 중요한 객체입니다. Money, Address, Period 같은 타입이 대표적입니다. 응답 모델은 DTO의 한 종류처럼 볼 수 있고, 특히 외부 클라이언트에 맞춘 표현 구조를 따로 두는 경우가 많습니다.


실무 예시로 보면 더 쉽다

public class OrderEntity {
    private Long id;
    private OrderStatus status;
    private BigDecimal totalAmount;

    public void approve() {
        if (status != OrderStatus.PAID) {
            throw new IllegalStateException("결제 완료 주문만 승인할 수 있습니다.");
        }
        this.status = OrderStatus.APPROVED;
    }
}

public record ApproveOrderResponseDto(
    Long orderId,
    String status,
    String message
) {}

OrderEntity는 승인 가능 상태인지 판단하고 상태를 바꿉니다. 반면 ApproveOrderResponseDto는 결과를 API 바깥으로 전달합니다. 둘 다 orderId와 status를 가질 수 있지만, 한쪽은 규칙을 실행하는 객체이고 다른 한쪽은 결과를 전달하는 객체입니다.


언제 분리가 특히 가치가 커질까

  • 외부 API 계약이 자주 바뀐다
  • 내부 저장 모델을 그대로 노출하면 안 된다
  • 응답용 가공 필드가 필요하다
  • 연관관계와 지연 로딩 문제가 생길 수 있다
  • 요청/응답 구조 수명과 도메인 모델 수명이 다르다

반대로 아주 작은 내부 도구이고 외부 계약도 거의 없고 실질적인 경계 보호 이점도 없다면, 무조건 복제 클래스를 늘리는 것이 더 좋은 선택은 아닐 수 있습니다.


헷갈릴 때 바로 쓰는 체크리스트

  1. 이 객체는 바깥과 주고받는 계약인가?
  2. 이 객체는 자기 상태를 바꾸는 규칙을 가져야 하는가?
  3. 식별자와 생명주기가 중요한가?
  4. 직렬화 구조와 표현 방식이 핵심인가?
  5. 내부 모델을 외부에 그대로 드러내면 결합이 커지는가?

1, 4, 5 쪽이면 DTO 쪽 질문일 가능성이 큽니다. 2, 3 쪽이면 Entity 쪽 질문일 가능성이 큽니다.


마무리

DTO와 Entity 차이의 핵심은 필드 개수가 아닙니다. 같은 데이터를 담아도 하나는 전달 계약이고, 다른 하나는 식별자와 상태, 규칙을 가진 객체일 수 있습니다.

같이 읽으면 좋은 글은 Application Service와 Domain Service 차이, 클린 아키텍처가 실무에서 어려운 이유, 레이어드 아키텍처는 왜 필요한가, 도메인 서비스는 언제 필요할까입니다.

함께보면 좋은 글