Dto의 역할에 관하여

허진혁·2023년 5월 8일
0

의문점

기본적인 레이어드 아키텍처에서 도메인 데이터의 노출의 범위를 제한하기 위해 OOODto 클래스를 만들어야, 레이어드 간에 데이터 전송 객체를 만들었어요.

도메인 데이터는 DB와 데이터가 오가는 직접적인 데이터이고, dto는 레이어드 간에 데이터 전달을 위한 것이 목적으로 두었어요.

그렇다면 'dto -> 도메인'의 메서드와 '도메인 -> dto'의 메서드를 어디에 두어야 할까요?

여태까지 dto에 두 메서드를 두었는데, 그렇다면 도메인 데이터 노출 허용 범위에 dto가 들어가게 된 것인데, 이것은 최초의 목표로 했던 도메인 노출을 제한하는 것과 반대로 흘러가게 되었어요.

이 포스트에는 service 레이어까지 도메인 노출시킨다는 전제 조건을 달거에요. dto의 역할에 대해 다시 생각해보고, 위의 두 메서드를 어디에 두어야 되는지를 고민할 글이에요.

Dto 사용 이유

dto의 사용 원인을 찾아보면 Spring MVC 패턴과 관련이 있어요.

Model은 비지니스 로직이라는 책임, View는 화면에 뿌려준다는 책임, Controller는 Model과 View 사이에서 프로그램을 제어하는 책임을 갖고 있어요.

dto는 계층간 데이터 이동을 위해 존재해요. dto를 사용하면 민감한 비니지스 기능이 노출되는 것을 방지함으로써 Model과 View의 의존성을 낮출 수 있어요.

도메인 데이터로 직접 응답하지 않는 이유

  1. 도메인 데이터가 화면에 직접적으로 노출이 되요. 즉, 민감한 정보나 공개되지 않아야 할 데이터들도 포함해서 노출이 된다는 의미에요.

  2. 클라이언트가 요청한 데이터보다 더 많은 데이터를 응답하게 되요. 즉, 불필요한 데이터까지 응답한다는 의미에요.
    예를 들어, 로그인할 때 유저의 아이디와 비밀번호만 요청했는데, 유저의 아이디, 비밀번호, 나이, 주소, 이메일 등 초기에 설정한 유저의 모든 데이터가 응답하게 되는거에요.

도메인 데이터와 dto 변환 위치

간단하게 게시판 글쓰기 대한 로직으로만 예시를 들게요.

공통 로직

Post (도메인 데이터)

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Table(name = "posts")
@Where(clause = "deleted_date IS NULL")
public class Post extends BaseTime{

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

    @NotNull
    private String title;
    @NotNull
    private String body;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @OneToMany(mappedBy = "post")
    private List<Comment> comments = new ArrayList<>();

    @OneToMany(mappedBy = "post")
    private List<PostLike> postLikes = new ArrayList<>();

    private Integer likeCounts;

    @Version
    private Long version;

    @Builder
    public Post(Integer id, String title, String body, User user, Integer likeCounts, Long version) {
        this.id = id;
        this.title = title;
        this.body = body;
        this.user = user;
        this.likeCounts = likeCounts;
        this.version = version;
    }
    
    // 연관관계 메서드
    public void addUser(User user) {
    	this.post.user = user
    }
}

바꾸기 이전 로직

바꾸기 이전에는 requestDto와 ResponseDto 따로 두어
requestDto 에서 dto에서 도메인 데이터로 변환하는 toEntity() 메서드를 만들었고,
responseDto 에서 도메인 데이터에서 dto로 변환하는 from()메서드를 만들었어요.

PostCreateRequestDto

@Getter
@NoArgsConstructor
public class PostCreateRequestDto {

    @NotNull
    private String title;
    @NotNull
    private String body;

    public PostCreateRequestDto(String title, String body) {
        this.title = title;
        this.body = body;
    }

    public Post toEntity(User user) {
        return Post.builder()
                .title(this.title)
                .body(this.body)
                .user(user)
                .likeCounts(0)
                .build();
    }
}

PostCreateResponseDto

@Getter
public class PostCreateResponseDto {

    private Integer postId;
    private String title;
    private String body;

    @Builder
    public PostCreateResponseDto(Integer postId, String title, String body) {
        this.postId = postId;
        this.title = title;
        this.body = body;
    }

    public static PostCreateResponseDto from(Post post) {
        return PostCreateResponseDto.builder()
                .postId(post.getId())
                .title(post.getTitle())
                .body(post.getBody())
                .build();
    }
}

바뀐 이후 로직

서비스 로직까지 데이터의 노출 범위를 제한할 목적이기 때문에, service 로직에서 responseDto에 from()메서드를 toDto()라는 메서드로 이름을 바꾸어 정의해둘 것이에요.

그리고 다음과 같은 순서로 진행할 거에요.
1. PostCreateRequestDto 객체는 toEntity() 메서드를 정의한다. (이전과 동일하나 user부분은 제거)
2. PostCreateResponseDto 객체에서 from() 메서드를 제거할 거에요.
3. PostService 에서 toDto() 메서드를 정의한다.

PostCreateRequestDto

@Getter
@NoArgsConstructor
public class PostCreateRequestDto {

    @NotNull
    private String title;
    @NotNull
    private String body;

    public PostCreateRequestDto(String title, String body) {
        this.title = title;
        this.body = body;
    }

    public Post toEntity(User user) {
        return Post.builder()
                .title(this.title)
                .body(this.body)
                .likeCounts(0)
                .build();
    }
}

PostCreateResponseDto

@Getter
public class PostCreateResponseDto {

    private Integer postId;
    private String title;
    private String body;

    @Builder
    public PostCreateResponseDto(Integer postId, String title, String body) {
        this.postId = postId;
        this.title = title;
        this.body = body;
    }
}

PostService

@Service
@RequiredArgsConstructor
public class PostService {

    private final PostRepository postRepository;
    private final UserRepository userRepository;
    private final PostLikeRepository postLikeRepository;
    private final CommentRepository commentRepository;
    private final ApplicationEventPublisher publisher;

    /**
     * 비지니스 로직
     */
     @Transactional
    public PostCreateResponseDto createPost(PostCreateRequestDto requestDto, String userName) {
        User user = findUser(userName);
        Post post = requestDto.toEntity(user);
        // createRequestDto에서 유저를 추가하는 것이 아니라 서비스에서 추가함
		post.addUser(user)
        
        postRepository.save(post);
        return PostCreateResponseDto.from(post);
    }

	// 비지니스 로직 ...
    
    /**
     * 중복메서드
     */
     private PostCreateDto toDto(Post post) {
     	return PostCreateResponseDto.builder()
                .postId(post.getId())
                .title(post.getTitle())
                .body(post.getBody())
                .user(post.getUser())
                .build();
     
     }
     
    // 중복 메서드 ...

}

정리

정리해보면,

  1. Dto -> 도메인 데이터로 변환하는 toEntity() 메서드는 Dto에 메서드로 정의하는 것이 좋아 보아요.
  2. 도메인 데이터 -> Dto로 변환하는 toDto()메서드는 Service레이어에서 정의하는 것이 좋아 보여요. 왜냐하면 toDto를 사용하는 곳은 보통 Service 레이어 이니까요.
  3. 연관관계 메서드는 도메인에 정의해 두는 것이 좋아요. (Post 객체에서 addUser())

dto의 사용 범위를 서비스 레이어까지만 제한해야 할까요? 그렇다면 도메인 데이터 사용을 컨트롤러에서 하면 dto의 목적과 반대가 되기 때문에 사용하지 않아야 하나요?

하나의 궁금점을 끝내니 또 다른 궁금점이 찾아왔어요. 개발에는 "절대"라는 단어를 용서하면 안되니, 컨트롤러에서도 dto사용하는 경우를 다음 글에서 고민해 볼게요 !!

profile
Don't ever say it's over if I'm breathing

0개의 댓글