1주차 프로젝트를 하면서 현재, 주문 api 개발을 하고 있는데요!
DTO를 설계하면서 @Builder에 대해 궁금해졌어요.
DTO와 @Builder에 관해서 궁금했던 점과 그에 관련해서 정리해봅니다.
들어가기에 앞서 DTO가 무엇일까 ?
DTO란?
- Data Transfer Obejct의 줄임말. 즉, 데이터를 전달하기 위한 객체이다.
- 계층 간 데이터 전달용 (Controller ↔ Service ↔ View)
왜 사용할까?
- Entity 그대로 노출하면 위험하거나 불필요한 정보까지 외부로 나갈 수 있기 때문에
- 요청이나 응답시 필요한 데이터만 따로 담아서 주고 받기 위해 사용된다.
DTO을 RequestDto 와 ResponseDto로 구분짓기도 하는데
- API가 어떤 입력을 받을지 어떤 출력을 줄지 정의하면서 DTO 구조가 자연스럽게 나온다.
- 즉, DTO는 “DB(Entity)” 기준이 아니라 “클라이언트(API 소비자)” 기준으로 설계해야한다.
- 클라이언트로부터 입력을 받으면 그 입력데이터 구조를 RequestDto로 설계하고,
- 서버에서 처리하고 클라이언트에게 출력을 줄거면 출력데이터 구조를 ResponseDto로 설계한다.
예를 들어 주문 API라면:
- Request: 주문 요청을 보낼 때 필요한 정보 (어떤 메뉴 몇 개, 어디로 배달할지)
- Response: 주문이 완료된 후 클라이언트가 알아야 하는 정보 (주문 ID, 상태, 결제 상태 등)
Client
↓ (JSON)
Controller
↓ (RequestDto)
Service
↓ (Entity)
Repository
↑ (Entity)
Service
↑ (ResponseDto)
Controller
↑ (JSON)
Client
이런 구조이다.
requestDto
- 클라이언트 → 서버 방향
- 클라이언트가 API를 호출할 때 전달하는 데이터를 담는 객체
- 예: 회원가입 요청 시, username, password, email 같은 값들을 담음
- 보통 @RequestBody, @ModelAttribute, @RequestParam 같은 방식으로 컨트롤러에서 받아옴
- 목적: 클라이언트가 전달한 데이터를 안전하게 캡슐화해서 서비스 계층으로 넘김
responseDto
- 서버 → 클라이언트 방향
- API 요청을 처리한 후 클라이언트에게 응답할 데이터를 담는 객체
- 예: 주문 응답 시, orderId, status, totalPrice, placedAt 같은 값들을 전달
- 목적: Entity 그대로 노출하지 않고, 필요한 정보만 가공해서 전달 (보안 + API 명세 준수)
@Builder란?
@Builder (빌더 패턴)
- 롬복이 생성자(혹은 클래스)에 대해 빌더 메서드를 자동으로 생성해준다.
- 예를 들어 OrderItemResponseDto.builder().itemId(...).menuName(...).build() 처럼 체이닝 방식으로 객체를 만들 수 있다.
- 장점은 생성자의 파라미터 순서를 외울 필요 없고, 선택적으로 값들을 세팅할 수 있다.
→ 여기서 @Builder를 붙인 이유는, 생성자의 파라미터가 많고 타입도 헷갈릴 수 있으니까 빌더로 안전하게 객체를 생성하려는 것이다.
장점
- 가독성 : 어떤 필드에 어떤 값을 넣는지 명확히 보인다.
- 선택적 필드 초기화 : 생성자처럼 모든 필드를 다 넣을 필요가 없다.
- 불변성 유지 : final 필드 + 빌더 조합으로 setter 없이 객체 생성한다.
- 안정성 : 매개변수 순서 실수할 일이 없다.
## @Builder 사용
@Getter
@Builder
public class OrderItemResponseDto {
private final UUID itemId;
private final UUID menuItemId;
private final String menuName;
private final BigInteger unitPrice;
private final int quantity;
}
@Builder 를 붙이면, Lombok이 내부적으로 다음과 같은 빌더 클래스를 자동으로 생성해준다.
OrderItemResponseDto orderItem = OrderItemResponseDto.builder()
.itemId(item.getId())
.menuItemId(menuItem.getId())
.menuName(menuItem.getName())
.unitPrice(menuItem.getPrice())
.quantity(menuItem.getQuantity())
.build();
즉, 사용자를 직접 호출하지 않고도, 필드 이름으로 명시적으로 값을 넣을 수 있어 가독성이 높고 실수도 줄일 수 있는 장점이 있다!
그러면 다른 역할에서 이 dto 빌더 클래스를 어떻게 사용하는가 ?
// Service
public OrderItemResponseDto getOrderItem(MenuItem menuItem) {
return OrderItemResponseDto.builder()
.itemId(UUID.randomUUID())
.menuItemId(menuItem.getId())
.menuName(menuItem.getName())
.unitPrice(menuItem.getPrice())
.quantity(1)
.build();
}
// Controller
@GetMapping("/order/item/{menuId}")
public ResponseEntity<OrderItemResponseDto> getOrderItem(@PathVariable UUID menuId) {
MenuItem menuItem = menuService.findById(menuId);
OrderItemResponseDto dto = orderService.getOrderItem(menuItem);
return ResponseEntity.ok(dto);
}
이렇게 Service에서 빌더로 DTO를 만들고, Controller에서는 그 DTO를 응답으로 반환하는 구조가 된다.
'Spring' 카테고리의 다른 글
| 자주 사용하는 Lombok 생성자 어노테이션 (0) | 2025.10.23 |
|---|---|
| @Transactional(readOnly =true) 하는 이유 (0) | 2025.10.18 |
| @ModelAttribute 언제 사용할까? (0) | 2025.03.24 |
| HTTP 상태코드에 대해 설명해주세요. (0) | 2024.08.29 |
| [Spring] 테스트 케이스 작성 / Intellj 편한 단축키 (0) | 2024.08.10 |