오늘의하루

DTO(Data Transfer Object)를 쓰는 이유? 본문

Spring

DTO(Data Transfer Object)를 쓰는 이유?

오늘의하루_master 2024. 11. 11. 12:50
반응형

"Controller에서는 DTO로 요청 정보를 받는 게 좋다."라는 말을 개발 공부를 시작했을 무렵 접하게 되었습니다.

하지만 이유를 정확히 알지 못한 상태에서 사용해 오다가 이번에 JPA 공부를 하면서 그 의미를 더 깊이 이해하게 되었습니다. 해당 글에서는 Entity 대신 DTO를 사용해야 하는 이유를 정리했습니다.

Entity와 DTO란 무엇일까?

JPA와 같은 ORM(Object Relational Mapping) 기술을 사용할 때 Entity란 DB 테이블과 1:1로 매핑되는 클래스를 의미하며 각 필드는 테이블의 컬럼과 연결되어 애플리케이션에서 DB와 상호작용하는 도메인 모델의 역할을 합니다.

DTO(Data Transfer Object)는 데이터 전송을 위한 전용 객체로써 주로 API 요청과 응답 데이터를 담는 데 사용하게 됩니다.

왜 Entity를 쓰면 안 되는 걸까?

솔직히 DTO를 매번 따로 만드는 작업은 정말 귀찮게 느껴질 수 있지만 아래 예시를 본다면 DTO를 사용하면 좋은 이유에 대해 쉽게 이해할 수 있습니다.

@Entity
@Getter
public class TestEntity {
  @Id @GenerateValue
  private Long id;
  private String name; // 요구 사항: username으로 바꿔주세요.
  private String password;
  private String phoneNumber;
}

1. API 스펙

만약 위와 같은 Entity에서 아주 사소한 요구로 name을 username으로 변경하게 되었다고 가정해 본다면 이로 인해 모든 API 스펙과 클라이언트 코드를 수정해야하는데 만약 이 Entity를 사용하는 곳이 1억 곳이라면 차라리 퇴사가 생각하는 게 빠를 수도 있습니다.

이처럼 Entity를 직접 사용하면 작은 변경이 예상치 못한 여러 문제를 일으킬 수 있으므로 DTO를 사용해 Entity와 API 스펙을 분리하는 것이 좋습니다.

2. 보안과 성능

Entity에는 해당 응답에 필요없지만 민감한 데이터가 포함되는 경우가 있습니다.

예를 들어 Entity를 사용해서 사용자를 찾는API를 만드는 경우 클라이언트에게 사용자들의 비밀번호와 전화번호까지 노출되는 위험이 있습니다.

@Getter @Setter
public class TestDto {
  private String name;
}

만약 DTO로 이름 필드만 가진 전용 객체를 만들어서 응답한다면 위와 같은 문제도 발생하지 않을뿐더러 전송되는 데이터의 크기가 줄어들어서 성능도 개선될 수 있습니다.

3. 데이터 유효성 검증

요청 데이터를 유효성 검증하는 데 있어 Entity에 검증 로직을 넣으면 다양한 문제를 일으킬 수 있습니다.

예를 들어 회원가입과 로그인에서 필요한 필드가 다를 때 Entity에 검증 로직을 넣으면 두 서비스가 동일한 검증 로직을 사용하게 되어 불필요한 필드가 포함될 수 있습니다.

@Entity
@Getter
public class TestEntity {
  @Id @GenerateValue
  private Long id;
  @NotEmpty(message = "이름은 필수 사항입니다.")
  private String name;
  @NotEmpty(message = "비밀번호는 필수 사항입니다.")
  @Size(min = 5, max = 10, message = "비밀번호는 5자 이상, 10자 이하로 입력해야 합니다.")
  private String password;
  @NotEmpty(message = "전화번호는 필수 사항입니다.")
  @Pattern(regexp = "^010-\\d{4}-\\d{4}$", message = "전화번호 형식이 올바르지 않습니다.")
  private String phoneNumber;
}

위 예시처럼 회원가입 서비스에는 전화번호가 필요하지만 로그인 서비스에서는 전화번호가 필요하지 않다면 Entity에서 공통적으로 검증하는 방식은 불필요한 데이터를 입력받게 되어 각각의 API를 구분해서 사용하기 어려워집니다.

@Getter @Setter
public class JoinDTO {
  @NotEmpty(message = "이름은 필수 사항입니다.")
  private String name;
  @NotEmpty(message = "비밀번호는 필수 사항입니다.")
  @Size(min = 5, max = 10, message = "비밀번호는 5자 이상, 10자 이하로 입력해야 합니다.")
  private String password;
  @NotEmpty(message = "전화번호는 필수 사항입니다.")
  @Pattern(regexp = "^010-\\d{4}-\\d{4}$", message = "전화번호 형식이 올바르지 않습니다.")
  private String phoneNumber;
}

@Getter @Setter
public class LoginDTO {
  @NotEmpty(message = "이름은 필수 사항입니다.")
  private String name;
  @NotEmpty(message = "비밀번호는 필수 사항입니다.")
  private String password;
}

DTO는 데이터 전송에만 집중하기 때문에 입력값을 검증하는 역할을 별도로 처리하여 코드의 책임을 분리하고 불필요한 데이터가 애플리케이션에 들어오는 것을 차단할 수 있습니다.

반응형
Comments