마틴 파울러의 책, PoEAA (Pattern of Enterprise Application Architecture: 엔터프라이즈 애플리케이션 아키텍처 패턴) 을 보면 대표적인 3계층을 소개한다.
여기서 나의 의문. 내가 실제로 작성해본 스프링 메모 프로젝트의 구조는 다음과 같다.
아니 내가 짠 구조에서는 dto, entity 라는 애들도 있는데 대체 얘들은 어떤 역할을 하는거야?
여기서의 DTO는 기능이 제한된 도메인 객체, 혹은 View 전용 등의 도메인과 무관하게 데이터 전달만을 위한 객체를 뜻한다.
- 위와 같이 DTO/Entity는 모든 레이어와 직/간접적으로 연결은 되지만...정말 그래도 되는걸까?
- 그리고 DTO와 Entity의 차이는 뭘까? 좋은 블로그를 발견해서 그 내용을 내 언어로 바꿔본다.
Entity 클래스는 실제 DB 테이블과 매핑되는 핵심 클래스로, 데이터베이스의 테이블에 존재하는 컬럼들을 필드로 가지는 객체(Id, userName, Contents등등...)
Entity는 데이터베이스 영속성(persistent)의 목적으로 사용되는 객체이며, 때문에 요청(Request)이나 응답(Response) 값을 전달하는 클래스로 사용하면 안된다.
또 많은 서비스 클래스와 비즈니스 로직들이 Entity 클래스를 기준으로 동작하기 때문에 Entity 클래스가 변경되면 여러 클래스에 영향을 줄 수 있습니다.
Entity에서는 setter 메서드의 사용을 지양해야 합니다.
이유는 변경되지 않는 인스턴스에 대해서도 setter로 접근이 가능해지기 때문에 객체의 일관성, 안전성을 보장하기 힘들어집니다.
(setter 메서드가 있다는 것은 불변하지 않다는 것이 됩니다.)
따라서 Entity에는 setter 대신 Constructor(생성자) 또는 Builder를 사용하게 되는데요.
setter 메서드가 아닌 생성자(Constructor)를 이용해서 초기화하는 경우 불변 객체로 활용할 수 있고, 불변 객체로 만들면 데이터를 전달하는 과정에서 데이터가 변조되지 않음을 보장할 수 있습니다.
그리고 아래 코드처럼 Builder를 사용하면 멤버 변수가 많아지더라도 어떤 값을 어떤 필드에 넣는지 코드를 통해 확인할 수 있고, 필요한 값만 넣는 것이 가능하다는 장점이 있습니다.
@Builder // 이 어노테이션의 사용으로 Builder 사용 가능
@Getter
@Entity
@NoArgsConstructor
public class Membmer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idx;
private String name; // 여기서는 @column 어노테이션을 안쓰시네. 왜지?
private String email;
public Member(Long idx, String name, String email) {
this.idx = idx;
this.name = name;
this.email = email;
}
}//이같이 생성자만 사용한다. 그리고 아래에 builder 사용. Class 인스턴스 = Class.builder().변수(~)
// 사용 방법
Member member = Member.builder()
.name("Jan")
.email("Jan@Jan.com")
.build();
(Builder Pattern 사용)
DTO는 계층(Layer) 간 데이터 교환이 이루어질 수 있도록 하는 객체!!!로 JSON serialization과 같은 직렬화에도 사용되는 객체입니다. DTO는 원래 DAO(Data Access Object) 패턴에서 유래된 단어로 DAO에서 DB 처리 로직을 숨기고 DTO라는 결괏값을 내보내는 용도로 활용했습니다.
Controller 같은 클라이언트 단과 직접 마주하는 계층에서는 Entity 대신 DTO를 사용해서 데이터를 교환하며, Controller 외에도 여러 레이어 사이에서 DTO를 사용할 수 있지만 주로 View와 Controller 사이에서 데이터를 주고받을 때 활용성이 높습니다.
DTO는 getter, setter 메서드를 포함하며, 이 외의 비즈니스 로직은 포함하지 않습니다.
setter 메서드를 가지는 경우 가변 객체로 활용할 수 있습니다.
public class MemberDTO {
pulic MemberDTO() { }
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
자바는 데이터 자동화 처리를 위해 리플렉션 기법을 사용합니다. 예시의 DTO에서 *프로퍼티(Property)가 name이라면 name의 키값으로 들어온 데이터는 리플렉션 기법으로 setter를 실행시켜 데이터를 넣을 수 있습니다.
객체지향 언어인 자바에서 객체는 고유한 속성(특징)을 가지는데, 그 속성을 칭하는 단어를 프로퍼티(Property)라고 합니다. 프로퍼티는 필드(데이터 멤버)와 메서드의 중간에 있는 클래스 멤버의 특수한 유형입니다.
프로퍼티의 읽기와 쓰기는 일반적으로 getter, setter 메소드 호출로 변환되며, 이 속성의 실제 값을 담는 곳을 필드(field, 멤버 변수)라고 합니다.
(직접 setter를 요청하는 것이 아닌 프레임워크 내부에서 setter가 실행된다는 것이 중요한 포인트)
특히 레이어 간(View에서 Controller로 이동)에 데이터를 넘길 때 DTO를 사용하면 편하다는 것이 이런 이유에서 입니다.
View에서 name 필드 값을 프로퍼티에 맞춰서 넘겼을 때, 받아야 하는 곳에서 하나하나 처리하는 것이 아니라 name 속성의 이름과 매칭 되는 프로퍼티에 자동적으로 DTO가 인스턴스화 되어 MemberDTO의 자료형으로 값을 받을 수 있습니다.
위에 MemberDTO 클래스를 예로 들면 MemberDTO는 name의 속성을 가지고 있는 객체이고, 자바 빈 정의에 의해 getter(), setter() 메서드로 실제 값(field)에 접근할 수 있습니다.
JavaBean 규약에 따라 작성된 자바 클래스로 클래스 외부에서 필드에 접근할 때는 반드시 메서드를 통해 접근해야 하며, 이때 get, set 으로 시작하는 메소드를 이용합니다.
'Entity와 DTO를 분리하는 이유는 DB와 View 사이의 역할 분리를 위해서'
DTO(Data Transfer Object)는 Entity 객체와 달리 각 계층끼리 주고받는 우편물이나 상자의 개념입니다.
특히 JPA를 이용하게 되면
테이블에 매핑되는 정보와 실제 View에서 요청되는 정보가 다를 경우 테이블에 필요한 정보에 맞게 데이터를 변환하는 로직이 필요할 수 있는데, 해당 로직이 Entity에 들어가게 되는 것은 일반적으로 생각해도 깔끔하지 못합니다.
DB로부터 조회된 Entity를 그대로 View로 넘기게 되었을 때 불필요한 정보 및 노출되면 안 되는 정보까지 노출될 수 있고, 이를 막기 위한 로직을 따로 구현해야 한다는 점도 이유가 됩니다.