DTO를 여러 개로 만들까 한 개로 만들까?

honggom·2023년 1월 18일
0

최근 프로젝트를 진행하는 중에 "API의 DTO를 어떻게 만들면 좋을까?"를 계속 고민했다.

고민거리는 DTO를 API마다 세분화해서 각 용도에 맞게 컴팩트하게 사용되게 여러 개를 만드느냐..
아니면 흔히 말하는 한방 DTO로 간단하게 처리하느냐.. 였다.

일단 처음에는 여러 개의 DTO를 만들어서 사용했지만... DTO를 만들면 만들수록 차라리 한방 DTO가 나을 것 같다는 게 내 생각이다.

DTO를 여러 개 만들어서 사용하는 경우

예를 들어 아래와 같이 Member라는 Entity가 있다고 치자.

@Entity
public class Member {

  private Long id;
  
  private String name;
  
  private String email;
  
  private String password;
  
  @ManyToOne
  private Team team;
  
  private Boolean deletedFlag;
  
  private Instant deletedAt;

}

그리고 여기서 가정을 하는 것이다.

프론트가 Member의 정보를 필요로 하는 곳이 3군데가 있는데, 각 화면에서 실질적으로 필요로하는 Member의 데이터는 조금씩 다르다.

예를 들면

  • A 화면에서는 Member의 name만 필요하고.
  • B 화면에서는 Member의 id, email이 필요하고.
  • C 화면에서는 Member의 모든 필드를 필요로 한다.

위 예시를 토대로 딱 필요로 하는 필드만 가지고 있는 DTO를 아래처럼 만들 수 있을 것이다.

public class MemberResponseDTO {

  @Getter
  @AllArgsConstructor(access = AccessLevel.PRIVATE)
  @NoArgsConstructor(access = AccessLevel.PRIVATE)
  public static class Name {
    
    private String name;
    
    public Name(final Member member) {
      this.name = member.getName();
    }
  
  }
  
  @Getter
  @AllArgsConstructor(access = AccessLevel.PRIVATE)
  @NoArgsConstructor(access = AccessLevel.PRIVATE)
  public static class IdAndEmail {
    
    private Long id;
    
    private String email;
    
    public IdAndEmail(final Member member) {
      this.id = member.getId();
      this.email = member.getEmail();
    }
  
  }
  
  @Getter
  @AllArgsConstructor(access = AccessLevel.PRIVATE)
  @NoArgsConstructor(access = AccessLevel.PRIVATE)
  public static class All {
    
    private Long id;
  
    private String name;
  
    private String email;
  
    private String password;
    
    ...
    
    public All(final Member member) {
      this.id = member.getId();
      this.name = member.getName();
      ...
    }
  
  }

}

자! 이렇게 static inner class로 DTO를 세분화 해놓으면 일단은 뭔가 마음이 편안하고 이쁘다.

우선 위와 같이 DTO를 여러 개로 만든 경우의 장점을 생각해보자..

장점

  • static inner class로 구성을 해놨기 때문에 큰 관점에서 하나의 DTO(MemberResponseDTO)로 보기 좋게 그룹화 해놓을 수 있고, 각각의 DTO가 유의미한 이름을 가질 수 있다.(Name, IdAndEmail, All)
  • 불필요한 필드를 프론트에게 노출 시키지 않는다.

일단 내가 생각한 장점은 이 정도다.

이제 단점을 살펴보자.. 실제로 위와 같이 DTO를 여러 개를 만들며 프로젝트를 진행했었는데 그래서 그런가? 단점이 더 잘 와닿는다.

단점

  • DTO가 많아진다.

사실 단점은 위 DTO가 많아진다.가 전부이지만, 이게 생각보다 크다. 왜냐하면 위와 같은 컨셉을 유지하면 필요에 따라 DTO가 끔찍하게 많이 늘어날 수 있다. 심지어 그 DTO마다 차이가 명확하지도 않게 된다. 결과적으로 비슷비슷하게 생긴 DTO가 우후죽순으로 늘어나게 되고, 유지보수에 악영향을 끼칠 것을 쉽게 예상할 수 있다.

DTO를 한 개만 만들어서 사용하는 경우

위에서 살펴본 경우와 반대로 DTO를 한 개만 만들어서 사용하면 어떨까?

@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MemberResponseDTO {

  private Long id;
  
  private String name;
  
  private String email;
  
  private String password;
    
  ...
    
  public MemberResponseDTO(final Member member) {
    this.id = member.getId();
    this.name = member.getName();
    ...
  }

}

단점

  • 여러 API에서 항상 같은 DTO로 값을 반환하다보면, 필드에 null이 들어가거나, 빈 값인 필드들이 많이 노출된다.
  • 프론트에서 실제로 사용하지 않는 필드여도 값을 반환받게 된다.
  • JPA등을 사용하게 되면 DTO가 가지고 있는 연관관계로 인해 N + 1, 불필요한 쿼리 호출 등이 일어날 수 있다.

장점

  • DTO 파일이 하나로 통합된다.
  • 프론트가 새로운 필드의 값이 필요해도 추가적인 백엔드 개발이 필요 없다.

사실 프로젝트를 진행할 때 여러 개의 DTO를 만든 이유가 DTO를 한 개만 만드는 경우의 단점이 크다고 생각했기 때문이다. 하지만 실상은 그렇지 않았다. 프론트에서 실제로 사용하지 않는 필드여도 값을 반환받게 된다.이 부분은 사실 장점이다. 왜냐하면 요구사항은 항상 바뀌기 때문이다. 이게 무슨 말이냐면..

현재 개발하고 있는 상황에서는 Member의 name만 딱 필요할 수 있다. 하지만 요구사항은 계속 바뀌고 추가 개발도 계속 생기기 마련이다. 그래서 Member의 id도 필요해지고.. email도 필요해지고 상황은 계속 바뀐다. 이렇게 되면 백엔드 입장에서는 DTO를 수정해줘야하고.. 바뀐 DTO에 맞게 컨트롤러, 서비스 등의 레이어도 수정을 해야될 수 있다.

하지만 처음부터 필요하든, 필요하지 않든 도메인과 1:1로 구성해놓은 DTO를 반환하게 된다면 프론트 입장에서는 원래 반환받던 값에서 필드만 추가로 이용해서 개발하면 되는 것이고 백엔드는 추가로 개발할 것이 없다.(이게 큼)

그렇다고 다른 단점을 무시할 수는 없다. DTO를 하나만 만들어서 사용하는 경우에 제일 크게 생각된 단점은 DTO가 가지고 있는 여러 연관관계들 때문에 불필요한 쿼리가 너무 많이 나간다는 점이다. 최적화 관점에서 좋지 않다. 여기서 두 가지 케이스를 생각해 볼 수 있다.

  1. 꼭 최적화가 되어야 하는 서비스다.(사용자가 굉장히 많음)
  2. 최적화가 필요할만큼 사용자가 많지 않고, 개발 퍼포먼스가 더 중요하다.

1번의 경우에는 당연히 DTO의 설계를 바꾸던가.. JPA의 fetchJoin을 잘 사용하던가.. 여러 가지 방법을 동원해서 최적화를 해야 될 것이다. 이견이 없다.

하지만 2번의 경우에는 꼭 최적화를 할 필요는 없다고 생각한다. 이유는 위에서도 적었지만 모든 프로그램 모든 개발에 모든 것을 최적화 할 수는 없다. 현실은 실전이니까! 당장의 0.001ms보다 코드를 한 줄 더 빨리쳐서 기능 하나를 더 빨리 만드는 것이 중요한 상황도 상당히 많다. 그렇기 때문에 뭐든지 당장에 처한 상황이 우선 중요하다.(급 마무리)

결론

  • 최적화가 필요한 서비스다. : DTO 상황에 맞게 잘 쪼갠다.
  • 세상에 나와서 일단 돌아가는 서비스가 되는 게 중요하다 : 한방 DTO로 편하고 빠르게 개발한다.

너무 한방 DTO가 좋은 듯 편향되게 쓴 것 같은데.. 일단 현재 내가 처한 상황에서는 확실히 한방 DTO가 좋다.

하지만 생각과 상황은 항상 바뀐다.

-끝-

profile
while alive: study()

0개의 댓글