Spring/Spring 개발 상식

DTO 생성 방법

mint* 2023. 7. 10. 19:05
728x90

DTO(Data Transfer Object) - 데이터 전송 객체

비지니스 계층과 프레젠테이션 계층간 데이터 교환을 하기 위해 사용하는 객체이다.

  • 비지니스 계층 : 데이터베이스나 외부 시스템과 상호 작용하여 데이터를 검색하거나 수정하는 작업 수행
  • 프레젠테이션 계층 : 웹 클라이언트의 요청 및 응답 처리

DTO는 Domain model의 복사본으로, 다양한 표현 계층의 Logic(표현 방식)을 추가하여 사용된다.

 

더 자세한 내용은 ⬇️

https://shout-to-my-mae.tistory.com/303

 

DAO vs DTO vs VO

DAO(Data Access Object) 데이터베이스의 data에 접근하여 CRUD 기능을 수행하는 객체 DB와 연결할 Connection까지 설정되어 제공되는 경우(ex) MyBatis) 는 필요 없다. 서비스와 DB를 연결하는 Repostitory package가 D

shout-to-my-mae.tistory.com

 

DB 데이터 -> Java -> Front

DB Entity - Java Object

1 => 변수 (스칼라, 단일 데이터)

[1, 2, 3] => 벡터

{id=1, name="user", password="1234", email="asdkkwe@nate.com}

=> 클래스(차원이 다른 데이터 모음)로 표현 : 모델(Model)

 

모델링

데이터베이스의 테이블을 보고 모델을 만드는 과정

프론트엔드 개발자에게 모델이 아닌 DTO로 전달

 

✅ 모델의 데이터를 DTO로 가공해서 넘겨줄 수 있다.

ex) average_price=price/12

 

DTO 생성 시나리오

DTO에 넣을 데이터도 시나리오를 고려해서 순서대로 만드는 것이 좋다.

✅ 관리자 데이터 만든 후 사용자 데이터 만들기

✅ DTO 객체 생성 순서 : Product -> Option -> User -> Cart -> OrderItem

 

 

DTO 생성 방법

단일 파일 생성

CartItemDTO.java

@Getter @Setter
public class CartRespFindAllDTO {
    private List<ProductDTO> products;
    private int totalPrice;

    @Builder
    public CartRespFindAllDTO(List<ProductDTO> products, int totalPrice) {
        this.products = products;
        this.totalPrice = totalPrice;
    }
}

✳️ 파일 개수가 너무 많아질 수 있으므로 패키지를 만들어 도메인 별로 관리하는 것이 좋다.

 

static inner 클래스로 생성

1. DTO를 사용하는 클래스 안에 생성

CartRestController.java

package com.example.kakaoshop.cart;

import com.example.kakaoshop._core.utils.ApiUtils;
import com.example.kakaoshop.cart.response.CartItemDTO;
import com.example.kakaoshop.cart.response.CartRespFindAllDTO;
import com.example.kakaoshop.cart.response.ProductOptionDTO;
import com.example.kakaoshop.cart.response.ProductDTO;
import lombok.Builder;
import lombok.Data;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/carts")
public class CartRestController {
    //장바구니 수정
    @PostMapping("/update") //  /carts/update
    public ResponseEntity<?> updateCart(@RequestBody List<CartUpdateRequestDTO> request) {
        // 카트 Info 리스트 만들기
        List<CartInfoDTO> cartInfoDTOList = new ArrayList<>();

        // 카트 Info 리스트에 담기
        CartInfoDTO cartInfoDTO1 = CartInfoDTO.builder()
                .cartId(4)
                .optionId(1)
                .optionName("01. 슬라이딩 지퍼백 크리스마스에디션 4종")
                .quantity(10)
                .price(100000)
                .build();
        cartInfoDTOList.add(cartInfoDTO1);

        CartInfoDTO cartInfoDTO2 = CartInfoDTO.builder()
                .cartId(5)
                .optionId(2)
                .optionName("02. 슬라이딩 지퍼백 플라워에디션 5종")
                .quantity(10)
                .price(109000)
                .build();
        cartInfoDTOList.add(cartInfoDTO2);

        //responseDTO 만들기
        CartUpdateResponseDTO responseDTO = CartUpdateResponseDTO.builder()
                .carts(cartInfoDTOList)
                .totalPrice(209000)
                .build();

        return ResponseEntity.ok(ApiUtils.success(responseDTO));
    }
    @Data
    static class CartUpdateRequestDTO{
        private int cartId;
        private int quantity;

        @Builder
        public CartUpdateRequestDTO(int cartId, int quantity) {
            this.cartId = cartId;
            this.quantity = quantity;
        }
    }

    @Data
    static class CartUpdateResponseDTO{
        private List<CartInfoDTO> carts;
        private int totalPrice;

        @Builder
        public CartUpdateResponseDTO(List<CartInfoDTO> carts, int totalPrice) {
            this.carts = carts;
            this.totalPrice = totalPrice;
        }
    }
    @Data
    static class CartInfoDTO{
        private int cartId;
        private int optionId;
        private String optionName;
        private int quantity;
        private int price;

        @Builder
        public CartInfoDTO(int cartId, int optionId, String optionName, int quantity, int price) {
            this.cartId = cartId;
            this.optionId = optionId;
            this.optionName = optionName;
            this.quantity = quantity;
            this.price = price;
        }
    }

}

장점 : 사용하는 곳에서 바로 DTO를 가져와 쓸 수 있다.

단점 : 생성해야할 DTO가 많아지면 로직과 DTO의 코드가 섞여 코드가 매우 길어진다.

2. DTO를 패키지로 묶어 생성 (추천) ⭐️

import lombok.Getter;
import lombok.Setter;

import java.util.List;
import java.util.stream.Collectors;

public class ProductResponse {

    @Getter @Setter
    public static class FindAllDTO {

        private int id;
        private String productName;
        private String description;
        private String image;
        private int price;

        public FindAllDTO(Product product) {
            this.id = product.getId();
            this.productName = product.getProductName();
            this.description = product.getDescription();
            this.image = product.getImage();
            this.price = product.getPrice();
        }
    }


    @Getter @Setter
    public static class FindByIdDTO {

        private int id;
        private String productName;
        private String description;
        private String image;
        private int price;
        private List<OptionDTO> options;

        public FindByIdDTO(Product product, List<Option> optionList) {
            this.id = product.getId();
            this.productName = product.getProductName();
            this.description = product.getDescription();
            this.image = product.getImage();
            this.price = product.getPrice();
            this.options = optionList.stream().map(OptionDTO::new).collect(Collectors.toList());
        }

        @Getter @Setter
        public class OptionDTO {
            private int id;
            private String optionName;
            private int price;

            public OptionDTO(Option option) {
                this.id = option.getId();
                this.optionName = option.getOptionName();
                this.price = option.getPrice();
            }
        }
    }
}

✳️ DTO를 inner class로 묶어 작성하면

1. 필요한 DTO를 빠르게 찾을 수 있다.

2. 코드의 응집도가 높아진다.

3. 하나의 DTO를 여러 곳에서 사용할 수 있다.

4. 파일의 개수도 적어 관리하기 쉽다.

 

✳️ 파라미터를 객체로 받으면

- 필드마다 하나하나 작성하지 않고 객체만 넘겨주어도 되므로 편하다.

DTO의 중복 사용

DTO는 도메인 모델을 기반으로 생성되므로 View가 요구하는 표현 방식도 중복될 수 있다.

이때 기존 DTO를 재사용해야할지, 새로운 DTO를 생성해야할지 고민할 수 있다.

 

결론적으로는 새로운 DTO를 생성하는 것을 권장한다.

 

변경 가능한 표현 방식(View)

DTO는 사용자의 요청, 응답을 받는 바구니와도 같다.

DTO의 표현 방식은 요구에 맞게 언제든지 바뀔 수 있다.

DTO 사용 목적을 생각해보면, 표현 방식에 따라 모델이 수정되는 것을 막기 위함이다.

 

당장 같은 표현 방식이더라도 후에 언제든 바뀔 수 있으므로, DTO를 공유해서 사용하게 되면

표현 방식이 서로 달라진 후, 그에 따라 DTO를 새로 생성해주어야한다. 😞

 

✅ 도메인 분리 X

각각 다른 도메인에서 사용되는 DTO를 하나로 공유해서 사용하면 코드를 유연하게 변경하기 어려우며 확장이 어렵다. 
또한 한 도메인이 변경될 경우 다른 도메인에 영향을 미치며, 유지보수가 어렵다.

 

 

추상 클래스를 이용해 DTO의 중복을 제거하는 경우도 있다.

 

 

 

 

728x90