[spring] DTO와 빌더패턴

김기현·2023년 1월 15일
0
post-thumbnail

DTO와 빌더패턴

DTO_Data Transfer Object, 전송객체

DTO의 정의는 아래와 같다.

데이터 전송 객체(DTO)는 프로세스 간 데이터를 전달하는 객체이다.

프로세스 간 통신이 일반적으로 원격 인터페이스로 재정렬하면서 이루어지는데, 여기서 각 호출의 비용이 많다는 점을 동기로 하여 이용하게 된다. 하나의 호출만으로 서비스되는 객체인 DTO를 사용해 호출의 수를 줄일 수 있게 된다.

DTO의 특징은 스토리지, 그리고 자체 데이터(접근자 등)의 조회를 제외하고 어떻한 동작도 하지 않는다. 즉 어떠한 비즈니스 로직도 포함되지 않아도 되는 단순한 객체이다.

DTO는 순수하게 데이터를 저장하고 데이터에 대한 getter, setter만을 가져와야 한다. 어떠한 비즈니스 로직을 가져서는 안되며, 저장, 검색, 직렬화, 역직렬화 로직만을 가져야 한다.

위 글의 설명만으로는 DTO와 Domain이 크게 다르지 않다고 생각이 든다.

아래의 코드를 보자.

domain

@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User extends BaseTimeEntity{

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Long id;

    @Column(length = 45)
    private String username;

    @Column(length = 45)
    private String nickname;

    @Column(length = 45)
    private String birth;

    @Column(length = 45, nullable = false)
    private String email;

    private String password;

    private boolean privacyAgreement;
    private boolean marketingAgreement;
    private boolean hostPermission;

    @Enumerated(EnumType.STRING)
    @Column(length = 10, nullable = false)
    private OauthProvider oauthProvider;

    @Enumerated(EnumType.STRING)
    @Column(length = 45, nullable = false)
    private Status status;

}

Dto

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserJoinRequestDto {

    private String email;
    private String password;
    private String username;
    private String nickname;
    private String birth;
    private boolean marketingAgreement;
}

어노테이션을 제외하고 크게 다르지가 않다.

WHY DTO?

그렇다면 왜 DTO를 사용해야 할까?

DTO는 데이터베이스에서 전달된 데이터를 저장하는, Entity를 가지고 만드는 일종의 Wrapper Class이다. 보안상의 문제로 Entity를 Controller와 같이 클라이언트단과 직접 마주하는 계층에 전달하지 않는다.

DTO를 사용해야 하는 이유는 아래와 같다.

  • Entity의 값이 변하면 Repository 클래스의 flush()가 호출될 때 그 값이 DB에 반영되고, 동시에 다른 로직에 영향을 준다.
  • Entity 중 getter를 사용해 원하는 데이터를 표시하기가 어려운 경우도 있다. (예를 들어 로그인 이후 토큰 발급 등과 같은 것이다. 발급된 엑세스 토큰은 데이터베이스에 저장하지 않는다.) 이 경우 Entity와 DTO가 분리되어 있다면 DTO에 추가적으로 표시할 필드나 로직을 추가하고 Entity에는 변경 사항을 만들지 않아 도메인 모델링을 건들이지 않을 수 있다.
  • (표현 계층에) 필요하지 않은 도메인 모델의 정보를 외부로 노출시키지 않아 캡슐화를 통해 보안을 강화할 수 있다.
  • Entity, 도메인 모델을 계층 간 전송에 사용하면 모델/뷰가 강하게 결합된다. DTO를 사용하면 결합을 느슨하게 할 수 있다.

Builder 패턴

DTO를 사용할 때 생성자를 사용할 수도 있지만 Builder 패턴을 사용하게 된다.

여기서 Builder 패턴을 위키피디아는 아래와 같이 표현한다.

빌더 패턴이란 복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게 하는 패턴이다

빌더 패턴은 객체지향 프로그래밍 환경에서 다양한 객체 생성에 대한 유연한 해결책으로 제공하기 위해 설계된 생성패턴이다. 빌더 패턴을 사용하는 이유는 크게 불변성, 가독성 때문이다.

우선 생성자와 빌더 패턴으로 객체를 만드는 과정을 비교하자.

// 생성자 
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserJoinRequestDto {

    private Reservation reservation;
    private ReviewImage reviewImages;
    private int score;
    private String content;
    private Status status;
}

Review review = new Review(reservation, reviewImages, score, content, Status.ACTIVE);

예시 코드에서는 인자 수가 작은 편이지만, 인자들의 수가 늘어난다면 생성자의 변수 순서에 따라 하나씩 맞춰 넣어주고, 타입 오류 때문에 쉽게 어려움이 생긴다.
뿐만 아니라 동일한 타입인 경우 컴파일러에서 오류를 찾지 못하기 때문에 더욱 오류를 발견하기 어려워진다.
마지막으로 setter()로 열어두고 생성자로 만든 객체에 접근을 해 도중에 데이터가 변경될 수 있는 위험이 있다.

// 빌더
Review review = Review.builder()
                .reservation(reservation)
                .status(Status.ACTIVE)
                .score(score)
                .content(content)
                .build();

빌더 패턴으로 만들어진 객체는 어떤 데이터를 넣어주어야 할지 쉽게 판단할 수 있다. 또 Builder 패턴은 객체가 Immutable하다는 특징이 있는데, 그 데이터를 아무나 바꿀 수 없다는 장점을 가지고 있다.

이러한 장점으로 Builder 패턴은 생성자의 인수가 많을 때 더욱 편하게 개발이 가능하다.

profile
피자, 코드, 커피를 사랑하는 피코커

0개의 댓글