API 를 엔티티에서 DTO 로 변환해서 만들어보죠.
@RestController
@RequiredArgsConstructor
public class OrderApiController {
private final OrderRepository orderRepository;
@GetMapping("/api/v2/orders")
public List<OrderDto> ordersV2() {
List<Order> orders = orderRepository.findAllByString(new OrderSearch());
return orders.stream()
.map(OrderDto::new)
.collect(Collectors.toList());
}
@Data
static class OrderDto {
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
private List<OrderItem> orderItems;
public OrderDto(Order order) {
orderId = order.getId();
name = order.getMember().getName();
orderDate = order.getOrderDate();
orderStatus = order.getStatus();
address = order.getDelivery().getAddress();
//...1
order.getOrderItems().stream().forEach(o -> o.getItem().getName());
orderItems = order.getOrderItems();
}
}
}
스트림으로 돌려서 강제 초기화를 시켜줘야합니다.
DTO 내에 만들어준 orderItems 가 엔티티여서 그렇습니다. Hibernate5 모듈 설정 때문에 초기화 되지 않은 프록시 객체는 노출을 안할거니 말이죠.
실행을 하면 데이터가 잘 나오는 것을 볼 수 있습니다.
그런데 데이터가 잘 나오니까 API 가 잘 만들어졌다고 볼 수 있을까요?
결론부터 말하자면 아닙니다. 계속 강조해서 DTO 로 반환하라는 말의 의미는 DTO 안에도 엔티티가 있으면 안된다는 말입니다. 즉, 랩핑하는 것도 안됩니다.
응답 결과를 보면 OrderItem 엔티티의 스펙이 외부에 노출되고 있습니다. 엔티티가 외부에 노출되면 안된다는 뜻은 단순하게 DTO 감싸서 보내라는 뜻이 아니라 완전히 엔티티에 대한 의존을 끊어야 한다는 뜻입니다. 그래서 OrderItem 엔티티를 반환하는게 아니라 역시 DTO 로 바뀌어야 합니다.
추가적으로 변환하면서 orderItem 에서 노출하고 싶은 데이터에 대해서 생각을 해봅시다. 위의 응답을 보면 orderItem 안에 item 이란 depth 가 들어가고 있습니다. 만약 클라이언트 입장에서 상품명, 주문한 가격, 수량에 대한 정보만 필요하다고 한다면 depth 를 줄일 수 있겠죠?
이 요구사항에 맞춰서 DTO를 다시 만들어봅시다.
@RestController
@RequiredArgsConstructor
public class OrderApiController {
private final OrderRepository orderRepository;
@GetMapping("/api/v2/orders")
public List<OrderDto> ordersV2() {
List<Order> orders = orderRepository.findAllByString(new OrderSearch());
return orders.stream()
.map(OrderDto::new)
.collect(Collectors.toList());
}
@Data
static class OrderDto {
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
private List<OrderItemDto> orderItems;
public OrderDto(Order order) {
orderId = order.getId();
name = order.getMember().getName();
orderDate = order.getOrderDate();
orderStatus = order.getStatus();
address = order.getDelivery().getAddress();
orderItems = order.getOrderItems().stream()
.map(OrderItemDto::new)
.collect(Collectors.toList());
}
}
@Data
static class OrderItemDto {
private String itemName;
private int orderPrice;
private int count;
public OrderItemDto(OrderItem orderItem) {
itemName = orderItem.getItem().getName();
orderPrice = orderItem.getOrderPrice();
count = orderItem.getCount();
}
}
}
실행 결과를 보면 원하는 대로 데이터가 나오는 것을 볼 수 있습니다.