💡 한 줄 요약
Entity
의 변경은 최소화하고,Setter
사용을 지양해야한다.
Entity
는 실제 DB 테이블과 매핑되는 핵심 Class
로, DB 테이블에 존재하는 Column
들을 Field
로 가지는 객체이다. Entity
는 영속성(Persistent)을 목적으로 사용되는 객체이며, 때문에 Request(요청)
이나 Response(응답)
의 값을 전달하기 위해 사용하는 것은 좋지 않다. 특히 Service
의 비즈니스 로직들이 Entity
를 기준으로 동작하기 때문에, ✨ Entity
의 변경은 최소화해야 한다. ✨
이러한 원칙은 Setter
의 사용과도 연관된다. Entity
안에 Setter
가 존재할 경우, 인스턴스에 대해 Setter
를 통해 접근이 가능해진다. 이는 객체의 일관성과 안전성을 보장할 수 없음을 의미한다. 이 때문에 ✨ Entity
는 Setter
의 사용을 지양 ✨하며, 객체의 생성 단계에서 Field
의 Data를 구성한다. 주로 Constructor(생성자)
혹은 Builder
를 사용한다.
💡 한 줄 요약
DTO
는 계층 간 데이터 교환을 위한 객체이며, 비즈니스 로직과는 무관하다.
DTO
는 ✨ 계층(Layer) 간 데이터 교환이 이루어질 수 있도록 하는 객체 ✨다. DTO
는 DAO(Data Access Object)
패턴에서 유래된 단어로, DAO
에서 DB 처리 로직을 숨기고 DTO
라는 결과값을 Response(응답)
하는 데 사용됐다.
Layered Architecture
에서는 대표적으로, Controller
와 같이 ✨ Client
와 직접 마주하는 계층에서는 Entity
가 아닌 DTO
를 사용해 Data를 교환 ✨한다. 물론 Controller
외에도 여러 계층 간 Data 교환을 목적으로 사용될 수 있으나, 주로 View
와 Controller
간 데이터를 주고 받을 때 활용한다.
Entity
와 달리 DTO
는 비즈니스 로직과 전혀 연관이 없으며, 이 때문에 Setter
메소드 사용에 큰 제약은 없다.
Layered Architecture
에서 View
에서 받아온 Data를 Controller
를 거쳐 Service
에 전달하는 과정을 예를 들면 다음과 같다.
이러한 과정에서, DTO
에 포함된 Data를 Entity
에 전달하는 방식은 다양하다. 실제로 내가 작성했던 코드들을 통해 방식들을 비교해보고, ✨가장 좋다고 생각한 방법(!!)✨을 소개해보겠다.
Architecture를 공부하면서 가장 많이 드는 생각은, "정답은 없다"인 것 같습니다. 지금껏 봐온 자료들과 스터디 등을 통해서 내린 개인적인 견해이니, 개선점 혹은 다른 의견이 있으시다면 말씀주시면 감사하겠습니다! 😀
Entity
의 Constructor(생성자)
에 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);
(...생략...)
}
이러한 구조에서 Entity
는 DTO
에 과하게 의존하게 된다. Entity
는 해당 DTO
없이는 생성될 수 없을 뿐만 아니라, DTO
의 변경 사항들에 직접적으로 영향을 받게된다.
앞서 개념들을 상기해보면 Entity
의 변경은 최소화되어야 하며, DTO
는 비교적 자유롭게 Data를 변경할 수 있다. 현재 구조에서는 ✨ 일관성과 안전성을 보장받아야 하는 Entity
가 일관성 없는 DTO
에 의존하여 생성되고 있다. ✨ 피드백을 주셨던 분의 말씀을 빌리자면, Entity
입장에서 DTO
는 🔥알빠노?
여야 한다.
Entity
의 Constructor(생성자)
에 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);
(...생략...)
}
이러한 구조에서 Entity
는 DTO
에 의존 관계가 성립되지 않는다. Entity
는 생성 이후 해당 DTO
의 변경 사항들에 전혀 영향을 받지 않을 뿐만 아니라, Service
계층에서 DTO
의 Data를 DTO
에 영향을 주지 않고 마음대로 수정해 Entity
를 생성할 수도 있다.
하지만 추가적인 리소스를 필요로 한다는 단점이 있다.Service
계층에서 어떤 비즈니스 로직을 구현하기 위해 Data를 수정해야하는 경우라면 문제가 없지만, DTO
의 Data를 그대로 집어넣어도 괜찮은 경우 일일이 DTO
의 Data를 새로운 변수에 저장해주어야 한다.
물론 다음과 같은 방법으로 추가적인 리소스를 사용하지 않고 구현할 수도 있다.
User user = new User(requestDto.getUsername, requestDto.getPassword, requestDto.getRole);
하지만 이러한 방법은 ✨ 드럽게 귀찮을 뿐만 아니라, DTO
의 field
가 많아질 수록 코드의 재사용성 및 가독성을 크게 저하 시킬 우려가 있다.✨
DTO
에 Entity
를 생성하는 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를 주입하는 방식의 모든 이점을 갖는다. Entity
는 DTO
에 의존 관계가 성립되지 않으며, Entity
는 생성 이후 DTO
의 변경 사항들에 전혀 영향을 받지 않는다. 또한 Entity
의 생성자에 DTO
Data를 주입하는 방식의 단점 또한 간단하게 해결 가능하다.
DTO
는 자유롭게 Data를 변경할 수 있는 객체이다. 비즈니스 로직에 따라 DTO
의 Field
를 변경하는 Method를 구현하고, 변경된 Field
를 지닌 DTO
로 Entity
를 생성할 수 있다.
💡 No Silver Bullet - Essence and Accident in Software Engineering
프레드 브룩스가 1986년 작성한 논문이다. 그는 해당 논문에서 다음과 같이 이야기한다.
소프트웨어 개체의 본질은 데이터 세트, 데이터 항목 간 관계, 알고리즘, 함수 호출처럼 서로 맞물리는 개념으로 이루어진 구조물이다. 표현 방법은 여러 가지로 달리 하더라도 개념적 구조물은 동일하다는 점에서 이 본질은 추상적이다. 추상적이라 해도 그 구조물은 고도로 정밀하며 풍부한 세부 내용을 담고 있다.
요약하자면, 소프트웨어 개발은 본질적으로 엄청 복잡하고 추상적이라는 거다. 복잡하고 추상적인만큼 발전 속도는 더딜 수 밖에 없으며, 모든 문제를 해결할 수 있는 Silver Bullet은 존재하지 않는다.
Entity
와 DTO
도 마찬가지다. 어떤 상황에서도 어떤 방법이 꼭 옳다고 이야기 할 수 없다. 하지만 여러 방법을 분석하고 이해함으로써, 당면한 상황에 맞춰 사용할 수 있어야한다.
덕분에 dto로 옮기는거 성공했어요 정말 감사합니다!!