지금까지 DTO와 Entity를 잘 구분하냐? 하면 말못함.. 그래서 프로젝트를 새로하는 계기가 된거기도하고.. 😅 이번기회에 각잡고 정리하는거다?!
Entity
: db에 저장되는 데이터 객체
DTO
: 클라이언트와 서버 간 데이터 전송을 위한 객체
그림에서 보이듯 Entity
는 데이터베이스와 매핑되는 객체이며Entity
를 DTO
로 변환해서 Client(View)
까지 전달해야하는것이 DTO
의 역할이다!!
그냥 Entity
를 Client까지 전달해도되지않음? 이라고 생각할수도있다.
하지만 효과적인 개발측면에서는 문제가 발생할 수 있다. 그 문제점은 무엇일까?
Entity
는 단순히 데이터를 담는 역할만 하지않는다. 실제 db의 테이블과 매핑되며 Entity Manager
에 의해 관리되는 db와 상호작용을 위한 객체다.
즉, 전달까지 맡기기엔 역할이 많기때문에 DB와 View의 역할을 분리하기위해서 DTO
를 만든다고 볼 수 있다.
Entity
는 테이블과 1:1 매핑되는 객체다. 만일 Entity
를 DTO
대신 사용할 때 요구사항이 변경되면 데이터가 변질될 수 있으며 데이터가 노출될수있기때문에 DTO
를 변경시키는게 좋다.
Entity
는 비즈니스로직이 포함될 수 있고 칼럼의 속성을 정의할 수 있다. 때문에 Entity의 로직이 무거워지면 직접 전송하기에 비효율적일 수 있으므로 데이터만 전송하는 DTO
를 쓰는게 좋다.
가장 큰 이유는 DB layer와 View layer의 역할을 분리하기위함이라 생각하면된다.
코드가 너무 길어지니까 lombok
을 사용했다 😅
@Entity
@Getter
@NoArgsConstructor
public class Product {
// 실제 db와 매핑되는 칼럼만 있어야함
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "product_name")
private String productName;
@Column(name = "price")
private double price;
// 비즈니스 로직을 포함함. 예를 들어, 할인 기능이나 가격 계산 메서드를 여기에 추가할 수 있음
// 생성자
public Product(String productName, double price) {
this.productName = productName;
this.price = price;
}
}
여담으로 Entity에 복잡한 비즈니스로직을 넣어 서비스로직이 간단하게 정의되는걸 도메인 주도 설계(Domain-Driven Design, DDD)라고 한다!
@Getter
@Setter
public class OrderDTO {
// 데이터 전송에만 사용됨.
private final Long orderId;
private final String product;
private final int quantity;
public OrderDTO(Long orderId, String product, int quantity) {
this.orderId = orderId;
this.product = product;
this.quantity = quantity;
}
}
값 그자체를 나타내는 객체다. 우선 코드부터보자
@Getter
public class Money {
//불변성을 가짐. 데이터 전송에 사용됨.
private final BigDecimal amount;
private final Currency currency;
// equals()와 hashCode() : 객체의 동등성 비교를 위해 구현됨.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
return Objects.equals(amount, money.amount) &&
Objects.equals(currency, money.currency);
}
@Override
public int hashCode() {
return Objects.hash(amount, currency);
}
}
예시로 돈(Money)클래스를 생성해볼 수 있다.
돈은 변경되지 않는다. 천원이 갑자기 990원이되고 그러진않으니까? 그래서 한번 생성된 돈 객체는 불변한다. 또한 금액은 String이나 integer같이 단순한 데이터타입이 아니다. 즉, 독립적인 개념으로 표현할 때 VO를 사용한다! 이게 왜 필요한지 아직 감이 안잡힌다면?
public class MoneyExample {
public static void main(String[] args) {
// Money 객체 생성
Money money1 = new Money(new BigDecimal("100.00"), Currency.getInstance("USD"));
Money money2 = new Money(new BigDecimal("50.00"), Currency.getInstance("USD"));
Money money3 = new Money(new BigDecimal("100.00"), Currency.getInstance("USD"));
// Money 객체 비교
boolean areEqual1and2 = money1.equals(money2);
boolean areEqual1and3 = money1.equals(money3);
System.out.println("money1 equals money2: " + areEqual1and2);
// 출력: money1 equals money2: false
System.out.println("money1 equals money3: " + areEqual1and3);
// 출력: money1 equals money3: true
}
}
Set<Money> moneySet = new HashSet<>();
moneySet.add(money1);
moneySet.add(money2);
moneySet.add(money3);
System.out.println("HashSet size: " + moneySet.size());
// 출력: HashSet size: 2
오버라이드한 equals
메소드와 HashCode
메소드를 사용한 예시이다. 통화(USD)는 같지만 금액이 다르거나 같을때와 중복된 경우를 구분할 수 있다.