반응형
스프링에서 Data를 전달하는 객체에는 VO, DTO가 있는데 이게 어떤것인지 알아보자
VO (Value Object)와 DTO (Data Transfer Object)는 모두 Java 및 Spring과 같은 객체 지향 프로그래밍 및 프레임워크에서 데이터를 표현하고 전달하는 데 사용되는 설계 패턴이다.
1. VO, DTO의 차이점 (불변성)
1-1. VO(Value Object)의 불변성
- VO는 '불변 객체'다. 한 번 생성되면, 그 상태를 바꿀 수 없다. 작고, 속성이 정해진 객체로, 생성 시에 모든 속성이 설정되고, 이후에는 바뀌지 않는다. 불변성은 VO의 중요한 특징이다. 이건 프로그램의 복잡성을 줄여주고 안정성을 높여준다.
1-2. DTO(Data Transfer Object)의 불변성
- DTO는 원칙적으로 불변해야 하지만, 실제로는 상황에 따라 변경될 수 있다. 주로 다른 계층이나 서비스 간의 데이터 전달을 위해 쓰인다. DTO가 불변할지, 변경 가능할지는 사용 방법에 따라 다른데, 데이터를 전달한 뒤에는 원래 상태가 유지되는 게 일반적이다. 이렇게 하면 데이터 일관성을 유지하고 버그 가능성을 줄일 수 있다. Java의 'record' 같은 도구는 불변 데이터를 표현하는 데 적합하다. 그래서 DTO 정의에 쓸 수 있다.
2. VO, DTO의 차이점 (동등성)
2-1. VO(Value Object)의 동등성
- VO는 모든 속성 값에 의해 동등성이 결정된다. 즉, 두 VO가 같다는 건 모든 속성 값이 같다는 뜻이다. VO는 값에 의해 정의되기 때문에 'Value Object'라고 불린다.
2-2. VO 동등성 예시
- 예를 들어, 아래와 같은 Money 클래스가 있다고 가정해보자.
public class Money {
private final int amount;
private final String currency;
public Money(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Money money = (Money) obj;
return amount == money.amount && currency.equals(money.currency);
}
// hashCode() 메서드도 재정의
}
- 이 코드의 경우, 두 Money 객체가 동일한 amount와 currency 값을 가지면 동등하다고 판단된다.
Money money1 = new Money(100, "USD");
Money money2 = new Money(100, "USD");
System.out.println(money1.equals(money2)); // true
2-3. DTO(Data Transfer Object)의 동등성
- DTO의 동등성은 일반적으로 그것의 식별자(ID)나 일부 핵심 속성에 의해 결정된다. 이는 DTO가 데이터베이스의 레코드를 표현하는 경우가 많고, 이런 레코드는 고유한 ID를 갖는 경우가 일반적이기 때문이다.
2-4. DTO 동등성 예시
- DTO는 일반적으로 식별자(ID)나 일부 핵심 속성에 의해 동등성이 결정된다. 예를 들어, 아래와 같은 UserDTO 클래스가 있다고 가정해보자.
public class UserDTO {
private Long id;
private String name;
private String email;
// getters and setters
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
UserDTO userDTO = (UserDTO) obj;
return id.equals(userDTO.id);
}
// hashCode() 메서드도 재정의
}
- 이 경우, 두 UserDTO 객체가 동일한 id 값을 가지면 동등하다고 판단된다.
UserDTO user1 = new UserDTO();
user1.setId(1L);
user1.setName("Alice");
user1.setEmail("alice@example.com");
UserDTO user2 = new UserDTO();
user2.setId(1L);
user2.setName("Bob");
user2.setEmail("bob@example.com");
System.out.println(user1.equals(user2)); // true
- 여기서 user1과 user2는 이름과 이메일이 다르지만, ID가 같으므로 동등하다고 판단된다.
2-5. vo와 dto의 동등성 요약
- VO: 모든 속성 값이 같아야 동등하다고 판단된다. 값 자체에 의해 정의되므로 "Value Object"라고 불린다.
- DTO: 식별자나 핵심 속성이 같으면 동등하다고 판단된다. 데이터베이스 레코드를 표현하는 경우가 많으므로, 고유한 ID를 기준으로 판단하는 경우가 일반적이다.
3. 각각의 사용 목적
3-1. VO(Value Object)
- VO는 비즈니스 로직을 수행하는 데 사용되는 정보를 캡슐화한다. 이는 도메인의 의미 있는 개념을 표현하며, 불변성을 갖는 경우가 많다.
3-2. 예시 (Money class)
- 목적: 금액과 통화를 나타내는 VO로, 비즈니스 로직에서 금액의 계산과 같은 작업을 수행하는 데 사용된다.
- 특징: 모든 속성 값에 의해 동등성이 결정되며, 불변 객체로 설계되는 경우가 많다.
public class Money {
private final int amount;
private final String currency;
public Money(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}
// getters, equals, hashCode
}
3-3. DTO(Data Transfer Object)
- DTO는 서로 다른 계층 또는 시스템 간에 데이터를 전송하는 데 사용됩니다. 이는 데이터의 컨테이너 역할을 하며, 로직을 포함하지 않는 순수한 데이터 구조다.
3-4. 예시 UserDTO 클래스
- 목적: 서버와 클라이언트 간의 통신이나 데이터베이스의 레코드를 가져오는 등의 작업에 사용된다. 예를 들어, REST API의 응답으로 사용자 정보를 전달하는 데 사용할 수 있다.
- 특징: 일반적으로 식별자나 핵심 속성에 의해 동등성이 결정되며, 다른 계층간의 데이터 전송을 위해 설계된다.
public class UserDTO {
private Long id;
private String name;
private String email;
// getters and setters
}
3-5. 사용 목적 요약
- VO: 의미 있는 비즈니스 개념을 표현하며, 불변성을 갖고, 비즈니스 로직에서 사용된다.
- DTO: 계층 간 또는 시스템 간의 데이터 전송을 위한 구조로, 순수한 데이터 컨테이너 역할을 한다.
4. 예시를 통해 VO, DTO를 쉽게 이해해보자
4-1. VO (Value Object)는 '화폐'와 같다.
- 1000원 짜리 지폐를 생각해보자. 이 지폐는 1000원의 가치를 나타내고, 그 가치는 변하지 않는다. 다른 1000원 짜리 지폐와 비교해도 가치는 동일하다. 지폐의 색깔이나 크기, 무게가 달라도 1000원의 가치는 변하지 않는 것처럼, VO는 그 값을 나타내는 속성이 같다면 동일한 객체로 취급된다.
- 예시: Money 클래스의 amount와 currency가 같다면, 두 객체는 동일하다고 볼 수 있다.
4-2. DTO (Data Transfer Object)는 '학생의 성적표'와 유사하다.
- 학생의 성적표에는 이름, 과목별 점수 등 여러 정보가 들어있다. 성적표는 학기가 끝날 때마다 업데이트 될 수 있다. 성적표의 내용이 바뀌더라도, 같은 학생의 성적표라면 동일한 성적표로 취급된다. 성적이 변하더라도, 그 성적표는 같은 학생의 것이기 때문이다.
- 예시: UserDTO 클래스의 id가 같다면, 두 객체는 동일한 것으로 간주된다. 이름이나 이메일이 달라도, id가 같으면 같은 사용자의 데이터를 나타낸다.
4-3. 결론
- VO는 값의 불변성에 중점을 둔다. 값이 같다면 동일한 객체로 취급된다.
- 반면, DTO는 데이터의 전달과 변경에 초점을 맞춘다. 식별자가 같다면 동일한 객체로 취급되며, 내부 데이터는 변할 수 있다.
- 이 둘은 각각의 역할과 책임에 맞게 사용되어야 하며, 이를 통해 코드의 명확성과 유지보수성을 높일 수 있다.
5. JPA에서는 VO, DTO, Entity 클래스를 어떻게 사용할까?
5-1. Entity 클래스
- Entity 클래스는 데이터베이스의 테이블을 대표한다. 예를 들어, 'User' Entity 클래스는 사용자 테이블을 나타내는 것이다. 데이터베이스의 각 행은 'User' 인스턴스로 매핑되고, 이 인스턴스들은 데이터베이스에서 가져온 데이터를 표현하니까 변경이 가능하다. 여기서 독특한 점은, Entity 클래스에는 비즈니스 로직을 포함시킬 수 있다는 것이다. 이건 DTO와는 확연히 다른 부분이다.
5-2. DTO (Data Transfer Object)
- DTO는 'Data Transfer Object'의 약자로, 주로 여러 계층이나 시스템 간의 데이터 전송에 사용된다. 중요한 점은 Entity 클래스의 인스턴스를 클라이언트에 그대로 전달하지 않는다는 것이다. 대신 필요한 데이터만 DTO에 담아서 전달하게 된다. 이렇게 하면 데이터베이스 구조의 세부 사항을 숨기면서 필요한 데이터만 제공할 수 있다.
5-3. VO (Value Object)
- JPA에서 VO는 엔티티의 일부분을 나타내는 데 쓰이며, 주로 불변성을 가지고 있다. 예를 들면 'Address' VO가 있을 수 있는데, '도시', '도', '우편번호' 같은 필드를 가지고 있어서 'User' Entity의 일부로 사용될 수 있다.
5-4. 결론
- JPA 환경에서 Entity 클래스는 데이터베이스와 직접적인 매핑을 통해 데이터를 표현하고, DTO는 데이터 전송을 담당한다. 반면에 VO는 특정 비즈니스 개념을 나타내는 데 쓰이며, 엔티티의 일부로 활용되곤 한다. 이 세 개념을 잘 이해하고 사용하면, 코드가 더 유연하고 유지보수하기 좋아질 것이다.
6. DTO와 VO의 사용: 언제 어떤 것을 사용할까?
개발을 하다 보면, 데이터를 다루는 방식에 따라 DTO와 VO 중 어떤 것을 사용할지 결정해야 하는 순간이 온다. 이 둘은 상황에 따라 굉장히 유용할 수 있어. 그럼 언제 어느 것을 사용하는 게 좋을까?
6-1. DTO
- DTO는 데이터를 전달하는 데 집중한다. 예를 들어, JPA나 MyBatis 같은 ORM이나 SQL 매퍼 프레임워크에서 데이터 전달과 변환에 중점을 두기 때문에 DTO가 많이 쓰인다. 그리고 단순한 CRUD 작업, 즉 데이터를 생성하고, 읽고, 업데이트하고, 삭제하는 작업을 할 때 DTO는 이상적인 선택이 될 수 있다. 클라이언트와 서버 간의 통신에서도 DTO는 필요한 정보만을 묶어서 전달하기 때문에 유용하게 사용될 수 있다.
6-2. VO의 세계
- VO는 조금 다른 맥락에서 사용되는데, 도메인 주도 설계(DDD)에서 특히 유용하다. VO는 도메인의 중요한 개념이나 불변해야 할 데이터를 모델링하는 데 사용된다. 예를 들어 '주소'나 '화폐' 같은 것들은 값이 바뀌면 새로운 객체로 취급되어야 하기 때문에 VO로 표현하기에 좋다. 또한, VO는 불변성을 가질 수 있기 때문에 데이터의 일관성을 유지하는 데도 도움이 된다.
6-3. 결론
- 결국, DTO와 VO는 각자의 목적과 역할에 따라 사용되어야 한다. DTO는 데이터의 전달과 변환에 중점을 두는 반면, VO는 도메인의 복잡성과 불변성에 중점을 둔다. 적절한 선택은 프로젝트의 특성, 도메인의 복잡성, 그리고 개발팀의 설계 전략에 따라 달라질 수 있다. 그러니 상황을 잘 판단해서 적합한 것을 선택하는 것이 중요하다.
스프링의 데이터 접근 방식이 궁금하다면?
반응형
'Spring 기초 > Spring 기초 지식' 카테고리의 다른 글
Spring에서의 인스턴스 생성 비교: new 키워드 대 DI (0) | 2023.08.13 |
---|---|
스프링에서의 데이터베이스 접근 방법: DAO, Mapper, 그리고 @Mapper 어노테이션 사용법 (0) | 2023.08.09 |
Spring Boot 폼 데이터 바인딩: @ModelAttribute 활용법 (0) | 2023.08.09 |
Spring Boot에서 REST 컨트롤러 활용하기: @RestController 어노테이션 이해하기 (0) | 2023.08.08 |
Spring Boot 심화: 커스텀 어노테이션 만들기 (2편) (0) | 2023.08.08 |