✅ Record와 DTO의 차이
🚀 1. DTO (Data Transfer Object)란?
DTO(Data Transfer Object)는 계층 간 데이터 전송을 위한 객체입니다.
보통 클래스 형태로 선언되며, private 필드와 getter/setter 또는 @Getter(Lombok)를 사용하여 값에 접근합니다.
📌 DTO 예제 (클래스 기반)
public class PaymentConfirmRequestDTO {
private String paymentKey;
private String orderId;
private int amount;
public PaymentConfirmRequestDTO(String paymentKey, String orderId, int amount) {
this.paymentKey = paymentKey;
this.orderId = orderId;
this.amount = amount;
}
public String getPaymentKey() {
return paymentKey;
}
public String getOrderId() {
return orderId;
}
public int getAmount() {
return amount;
}
}
✅ DTO의 특징
✔ 클래스로 구현되며 getter/setter를 사용하여 값에 접근
✔ 필요할 경우 생성자, 유효성 검사 메서드, 로직 추가 가능
✔ 가변 객체 가능 (setter를 사용하면 값 변경 가능)
✔ 필드 수가 많거나 객체에 메서드를 추가해야 하는 경우 DTO가 적합
❌ DTO의 단점
❌ 보일러플레이트 코드(코드량)가 많아짐 (getter, setter 필요) -> Lombok을 쓰면 해결이 가능하다. 또 setter를 사용하지말고 Builder를 사용하도록 하자!(시간나면 Builder 관련해서도 작성해봐야겠다.)
❌ 불변성을 보장하지 않음 (setter를 사용하면 값 변경 가능)
➡ 결론: DTO는 주로 계층 간 데이터를 전달하는 객체로 사용되며, 복잡한 데이터를 다룰 때 적합합니다.
🚀 2. Record란?
record는 Java 14에서 도입된 새로운 데이터 클래스 개념으로,
DTO와 비슷한 역할을 하지만, 불변성을 유지하고 보일러플레이트 코드 없이 간결한 데이터 구조를 제공합니다.
📌 Record 예제
public record PaymentConfirmRequest(String paymentKey, String orderId, int amount) {}
📌 클래스 대비 코드가 훨씬 짧고, getter 및 toString(), equals(), hashCode()가 자동 생성됨.
✅ Record의 특징
✔ 자동으로 getter, toString(), equals(), hashCode()가 생성됨 → 보일러플레이트 코드 제거
✔ 불변 객체 (Immutable) → 값 변경 불가능
✔ 생성자에서 기본적인 유효성 검사 가능
✔ 클래스보다 훨씬 가볍고 빠르게 동작
❌ Record의 단점
❌ 필드 값을 변경할 수 없음 (불변 객체이기 때문에 setter 없음)
❌ 메서드를 추가하여 로직을 구현할 수는 있지만, DTO만큼 자유롭지 않음
❌ Spring에서 일부 기능(예: Jackson 직렬화)과의 호환성을 고려해야 함
➡ 결론: Record는 데이터를 단순히 전달하는 용도로 적합하며, 데이터를 변경할 필요가 없는 경우 사용하면 좋음.
🚀 3. DTO vs Record 비교
비교 항목DTO (클래스)Record
| 코드 길이 | 길다 (getter, setter 필요) | 짧다 (getter, setter 자동 생성) |
| 불변성 | 변경 가능 (setter 존재) | 불변 (값 변경 불가) |
| 메서드 추가 | 가능 (비즈니스 로직 포함 가능) | 제한적 (기본적으로 데이터 보관용) |
| 객체 비교 | equals()와 hashCode()를 직접 오버라이딩해야 함 | 자동 생성됨 |
| 사용 용도 | 복잡한 데이터 구조, 상태 변경이 필요한 경우 | 단순 데이터 전달, 불변성이 필요한 경우 |
🚀 4. Record를 사용하면 좋은 경우
✅ 간단한 데이터 전송 (예: API 요청, 응답 DTO)
✅ 값이 변경될 필요 없는 객체 (Immutable Data)
✅ 객체 비교(equals(), hashCode())가 필요한 경우
✅ DTO에서 보일러플레이트 코드를 제거하고 싶을 때
🚀 5. Record를 사용하지 않고 DTO를 써야 하는 경우
❌ 필드 값 변경이 필요할 경우 (setter가 필요하면 DTO를 사용)
❌ 복잡한 데이터 구조를 다루고, 추가적인 로직(유효성 검사, 비즈니스 로직 등)이 필요한 경우
❌ Spring의 Jackson과 직렬화 문제를 고려해야 할 경우 (@JsonProperty 등 추가 설정 필요할 수도 있음)
➡ 결론:
• 불변성을 유지하면서 단순 데이터 전달 → Record 사용
• 객체의 상태를 변경해야 하거나, 추가적인 비즈니스 로직 필요 → DTO (클래스) 사용
🚀 6. Record 사용 예제 (Toss Payments 결제 승인 요청)
✅ Record 사용 예제
package com.tutti.server.core.payment.application.dto;
public record PaymentConfirmRequest(String paymentKey, String orderId, int amount) {}
📌 컨트롤러에서 Record를 활용하는 방법
@PostMapping("/confirm")
public ResponseEntity<Map<String, Object>> confirmPayment(@RequestBody PaymentConfirmRequest request) {
Map<String, Object> response = paymentService.confirmPayment(request);
return ResponseEntity.ok(response);
}
📌 서비스에서 Record를 활용하는 방법
@Override
public Map<string, object=""> confirmPayment(PaymentConfirmRequest request) {
Map<string, object=""> requestData = Map.of(
"paymentKey", request.paymentKey(),
"orderId", request.orderId(),
"amount", request.amount()
);
return restClient.post()
.uri("https://api.tosspayments.com/v1/payments/confirm")
.headers(headers -> headers.setBasicAuth(widgetSecretKey, ""))
.body(requestData)
.retrieve()
.body(new ParameterizedTypeReference<map<string, object="">>() {});
}</map<string,></string,></string,>
➡ 위 방식으로 DTO 클래스를 만들지 않고, 간결하게 record를 활용할 수 있음! 🚀
🚀 7. 최종 결론
사용 목적RecordDTO
| 간단한 데이터 전달 | ✅ 추천 | 가능 |
| 객체의 값 변경 필요 | ❌ 불가능 | ✅ 가능 (setter 사용 가능) |
| 비즈니스 로직 포함 | ❌ 제한적 | ✅ 가능 |
| 코드 길이 | ✅ 짧음 | ❌ 상대적으로 김 |
| 불변성(Immutable) 유지 | ✅ 가능 | ❌ 불가능 (setter로 변경 가능) |
| Spring에서 사용 | ✅ 가능 (주의할 점 있음) | ✅ 일반적으로 사용 |
📌 Record는 간결하고 불변성이 필요한 경우 유용하지만, 복잡한 로직이 필요한 경우 DTO(클래스)를 사용하는 것이 적절합니다.
➡ Spring Boot 프로젝트에서 불변성이 필요하고 간결한 구조가 중요한 경우 Record를 사용하면 코드 유지보수가 더 편리해진다.