[Spring JPA] Entity, DTO

Jay Mild Lee·2022년 12월 7일
1

Spring

목록 보기
3/3

I. 개념

1. Entity

💡 한 줄 요약
Entity의 변경은 최소화하고, Setter 사용을 지양해야한다.

Entity는 실제 DB 테이블과 매핑되는 핵심 Class로, DB 테이블에 존재하는 Column들을 Field로 가지는 객체이다. Entity영속성(Persistent)을 목적으로 사용되는 객체이며, 때문에 Request(요청)이나 Response(응답)의 값을 전달하기 위해 사용하는 것은 좋지 않다. 특히 Service의 비즈니스 로직들이 Entity를 기준으로 동작하기 때문에, Entity의 변경은 최소화해야 한다. ✨

이러한 원칙은 Setter의 사용과도 연관된다. Entity 안에 Setter가 존재할 경우, 인스턴스에 대해 Setter를 통해 접근이 가능해진다. 이는 객체의 일관성안전성을 보장할 수 없음을 의미한다. 이 때문에 EntitySetter의 사용을 지양 ✨하며, 객체의 생성 단계에서 Field의 Data를 구성한다. 주로 Constructor(생성자) 혹은 Builder를 사용한다.

2. DTO(Data Transfer Object)

💡 한 줄 요약
DTO는 계층 간 데이터 교환을 위한 객체이며, 비즈니스 로직과는 무관하다.

DTO✨ 계층(Layer) 간 데이터 교환이 이루어질 수 있도록 하는 객체 ✨다. DTODAO(Data Access Object) 패턴에서 유래된 단어로, DAO에서 DB 처리 로직을 숨기고 DTO라는 결과값을 Response(응답)하는 데 사용됐다.

Layered Architecture에서는 대표적으로, Controller와 같이 Client와 직접 마주하는 계층에서는 Entity가 아닌 DTO를 사용해 Data를 교환 ✨한다. 물론 Controller 외에도 여러 계층 간 Data 교환을 목적으로 사용될 수 있으나, 주로 ViewController 간 데이터를 주고 받을 때 활용한다.

Entity와 달리 DTO비즈니스 로직과 전혀 연관이 없으며, 이 때문에 Setter 메소드 사용에 큰 제약은 없다.

II. DTO의 Data를 Entity로

Layered Architecture에서 View에서 받아온 Data를 Controller를 거쳐 Service에 전달하는 과정을 예를 들면 다음과 같다.

이러한 과정에서, DTO에 포함된 Data를 Entity에 전달하는 방식은 다양하다. 실제로 내가 작성했던 코드들을 통해 방식들을 비교해보고, 가장 좋다고 생각한 방법(!!)을 소개해보겠다.

Architecture를 공부하면서 가장 많이 드는 생각은, "정답은 없다"인 것 같습니다. 지금껏 봐온 자료들과 스터디 등을 통해서 내린 개인적인 견해이니, 개선점 혹은 다른 의견이 있으시다면 말씀주시면 감사하겠습니다! 😀

1. Entity의 생성자에 DTO 주입(Worst Case)

EntityConstructor(생성자)에 DTO를 직접 주입하는 방식이며, 과정을 Diagram으로 표현하면 다음과 같다.

실제 코드는 다음과 같다.

  • Entity
public class Comment extends TimeStamp{
	...(field 생략)...
    public Comment(CommentRequestDto requestDto, Forum forum){
        this.username = forum.getUsername();
        this.contents = requestDto.getContents();

        // 양방향 연관관계 설정
        this.forum = forum;
        forum.getCommentList().add(this);
    }
    ...(method 생략)...
}
  • DTO
@Getter
public class CommentRequestDto {
    private String contents;
}
  • Service 계층에서의 사용
public (...생략...) createComment(CommentRequestDto requestDto, (...생략...)){
	(...생략...)
	Comment comment = new Comment(requestDto, forum);
    (...생략...)
}

이러한 구조에서 EntityDTO에 과하게 의존하게 된다. Entity는 해당 DTO 없이는 생성될 수 없을 뿐만 아니라, DTO의 변경 사항들에 직접적으로 영향을 받게된다.

앞서 개념들을 상기해보면 Entity의 변경은 최소화되어야 하며, DTO는 비교적 자유롭게 Data를 변경할 수 있다. 현재 구조에서는 ✨ 일관성안전성을 보장받아야 하는 Entity일관성 없는 DTO에 의존하여 생성되고 있다. ✨ 피드백을 주셨던 분의 말씀을 빌리자면, Entity 입장에서 DTO🔥알빠노?여야 한다.

2. Entity의 생성자에 DTO data 주입

EntityConstructor(생성자)에 DTO의 data를 각각 선언해 주입하는 방식이며, 과정을 Diagram으로 표현하면 다음과 같다.

실제 코드는 다음과 같다.

  • Entity
public class User {
	...(field 생략)...
    public User(String username, String password, UserRoleEnum role) {
        this.username = username;
        this.password = password;
        this.role = role;
    }
  • DTO
public class UserRegiRequestDto {
    private String username;
    private String password;
    private boolean admin = false;
    private String adminToken = "";
}
  • Service 계층에서의 사용
public (...생략...) register(UserRegiRequestDto requestDto) {
		(...생략...)
        String username = requestDto.getUsername();
        String password = requestDto.getPassword();
        UserRoleEnum role = UserRoleEnum.USER;
		(...생략...)
        User user = new User(username, password, role);
        (...생략...)
}

이러한 구조에서 EntityDTO에 의존 관계가 성립되지 않는다. Entity는 생성 이후 해당 DTO의 변경 사항들에 전혀 영향을 받지 않을 뿐만 아니라, Service 계층에서 DTO의 Data를 DTO에 영향을 주지 않고 마음대로 수정해 Entity를 생성할 수도 있다.

하지만 추가적인 리소스를 필요로 한다는 단점이 있다.Service 계층에서 어떤 비즈니스 로직을 구현하기 위해 Data를 수정해야하는 경우라면 문제가 없지만, DTO의 Data를 그대로 집어넣어도 괜찮은 경우 일일이 DTO의 Data를 새로운 변수에 저장해주어야 한다.

물론 다음과 같은 방법으로 추가적인 리소스를 사용하지 않고 구현할 수도 있다.

User user = new User(requestDto.getUsername, requestDto.getPassword, requestDto.getRole);

하지만 이러한 방법은 드럽게 귀찮을 뿐만 아니라, DTOfield가 많아질 수록 코드의 재사용성 및 가독성을 크게 저하 시킬 우려가 있다.✨

3. DTO에서 Entity 생성

DTOEntity를 생성하는 Method를 구현하는 방식으로, 과정을 Diagram으로 표현하면 다음과 같다.

실제 코드는 다음과 같다.

  • Entity
public class Forum extends TimeStamp{
    (...생략...)
    @Builder
    public Forum(String username, String title, String contents, User user){
        this.username = user.getUsername();

        this.title = title;
        this.contents = contents;

        // 양방향 연관관계 설정
        this.user = user;
        user.getForumList().add(this);
    }
    (...생략...)
}
  • DTO
public class ForumRequestDto{
	(...생략...)
    public Forum toForum(User user){
        return Forum.builder()
                .user(user)
                .title(title)
                .contents(contents)
                .build();
    }
}
  • Service 계층에서의 사용
    public (...생략...) createForum(ForumRequestDto requestDto, (...생략...)) {
        (...생략...)
        Forum forum = requestDto.toForum(user);
        (...생략...)
    }

이 구조는 Entity의 생성자에 DTO Data를 주입하는 방식의 모든 이점을 갖는다. EntityDTO에 의존 관계가 성립되지 않으며, Entity는 생성 이후 DTO의 변경 사항들에 전혀 영향을 받지 않는다. 또한 Entity의 생성자에 DTO Data를 주입하는 방식의 단점 또한 간단하게 해결 가능하다.

DTO는 자유롭게 Data를 변경할 수 있는 객체이다. 비즈니스 로직에 따라 DTOField를 변경하는 Method를 구현하고, 변경된 Field를 지닌 DTOEntity를 생성할 수 있다.

III. 결론

💡 No Silver Bullet - Essence and Accident in Software Engineering

프레드 브룩스가 1986년 작성한 논문이다. 그는 해당 논문에서 다음과 같이 이야기한다.

소프트웨어 개체의 본질은 데이터 세트, 데이터 항목 간 관계, 알고리즘, 함수 호출처럼 서로 맞물리는 개념으로 이루어진 구조물이다. 표현 방법은 여러 가지로 달리 하더라도 개념적 구조물은 동일하다는 점에서 이 본질은 추상적이다. 추상적이라 해도 그 구조물은 고도로 정밀하며 풍부한 세부 내용을 담고 있다.

요약하자면, 소프트웨어 개발은 본질적으로 엄청 복잡하고 추상적이라는 거다. 복잡하고 추상적인만큼 발전 속도는 더딜 수 밖에 없으며, 모든 문제를 해결할 수 있는 Silver Bullet은 존재하지 않는다.

EntityDTO도 마찬가지다. 어떤 상황에서도 어떤 방법이 꼭 옳다고 이야기 할 수 없다. 하지만 여러 방법을 분석하고 이해함으로써, 당면한 상황에 맞춰 사용할 수 있어야한다.

2개의 댓글

comment-user-thumbnail
2022년 12월 7일

덕분에 dto로 옮기는거 성공했어요 정말 감사합니다!!

답글 달기
comment-user-thumbnail
2024년 11월 8일

단계적으로 이유까지 명확하게 설명해주셔서 쉽게 이해했네요. 좋은 글 감사합니다.

답글 달기