본문 바로가기
📕 Spring Framework/Spring Project

2022.05.22 「Shopping Cart API [Ver.2]」

by GroovyArea 2022. 5. 22.
어제부터 장바구니 API 구현에 대해 고민을 정말 많이 했다.
당연히 DB를 이용하다 쿠키로 구현해보려는 시도가 처음이라 더 개념이 낯설었다.
추가 및 조회는 끝났는데, 오늘 아침 다시 보니 추가에서 이상한 로직이 있었고, 중복되는 코드가 있어서 수정을 하고 
장바구니 수정 및 삭제 API를 작성해보았다. 
API를 설계하면서 문제점들과 확실히 알게 된 점을 정리해보겠다~

장바구니 조회

장바구니를 조회한다는 것은 곧 장바구니에 들어있는 상품 목록의 데이터를 얻겠다는 것. 

그 상품 목록의 객체를 설계했다. 

public class CartItemDTO {

    private final Integer productNo;
    private Integer productStock;
    private final String productPrice;

    public CartItemDTO(Integer productNo, Integer productStock, String productPrice) {
        this.productNo = productNo;
        this.productStock = productStock;
        this.productPrice = productPrice;
    }

    public Integer getProductNo() {
        return productNo;
    }

    public Integer getProductStock() {
        return productStock;
    }

    public String getProductPrice() {
        return productPrice;
    }

    public void setProductStock(Integer productStock) {
        this.productStock = productStock;
    }

}

 

=> 최대한 불변 객체로 만드는 것이 좋다.

 

/**
 * 장바구니 목록 조회 메서드
 *
 * @param request servlet Request
 * @return Message 장바구니 목록
 */
@Auth(role = Auth.Role.BASIC_USER)
@GetMapping
public Message getCartList(HttpServletRequest request) throws UnsupportedEncodingException {
    Cookie[] requestCookies = request.getCookies();
    List<CartItemDTO> cartList;
    Cookie responseCookie;

    /* 쿠키가 존재할 때 카트 쿠키 반환 */
    if (requestCookies != null) {
        responseCookie = CookieUtil.getCartCookie(requestCookies);

        /* 장바구니 정보가 존재할 경우 */
        if (responseCookie != null) {
            String cookieValue = responseCookie.getValue();
            Map<Integer, CartItemDTO> cartDTOMap = JsonUtil.stringToMap(URLDecoder.decode(cookieValue, ENC_TYPE), Integer.class, CartItemDTO.class);

            if (cartDTOMap != null && !cartDTOMap.isEmpty()) {
                cartList = new ArrayList<>(cartDTOMap.values());
                return new Message
                        .Builder(cartList)
                        .httpStatus(HttpStatus.OK)
                        .mediaType(MediaType.APPLICATION_JSON)
                        .build();
            }
        }
    }
    return new Message
            .Builder(CART_EMPTY)
            .httpStatus(HttpStatus.NO_CONTENT)
            .build();
}

대략적인 흐름

  • 서블릿 객체를 통해 쿠키를 받아와 장바구니 쿠키를 반환한다.
  • objectMapper로 json 형태의 문자열을 디코드 해 Map 객체로 변환하여 List에 담근다.
  • 해당 리스트를 반환한다. 

그런데 여기서 문제가 있었다..

원래 JsonUtil 이란 클래스는 없다. 내가 따로 만든 것인데..

따로 만든 utils

결론부터 말하자면 일반 유틸 클래스는 인스턴스화에 굳이 Heap 영역에 저장할 필요가 없으니 Bean 등록을 하지 않는 것이다. 대신 private 생성자를 추가하자. 

추가함

두 번째 문제는 중복된 코드다. 

중복된 코드를 분리해야 유지보수에 효율적인 코드를 작성할 수 있으므로 따로 Utils를 만들어 사용했다.

 

public class CookieUtil {

    private static final String COOKIE_KEY = "Chicken";
    private static final String ENC_TYPE = "utf-8";

    /* 카트 쿠키 반환 메서드 */
    public static Cookie getCartCookie(Cookie[] requestCookies) {
        for (Cookie cookie : requestCookies) {
            if (COOKIE_KEY.equals(cookie.getName())) {
                return cookie;
            }
        }
        return null;
    }

    /* 카크 쿠키 값 디코딩 후 map 객체 반환 메서드 */
    public static Map<Integer, CartItemDTO> getCartItemDTOMap (Cookie responseCookie) throws UnsupportedEncodingException {
        String cookieValue = responseCookie.getValue();
        return JsonUtil.stringToMap(URLDecoder.decode(cookieValue, ENC_TYPE), Integer.class, CartItemDTO.class);
    }
}​

 

장바구니 수정

장바구니 수정은 추가와 마찬가지로 쿠키에 있는 값을 뽑아 디코드 후 Map 객체로 변환하여 각각의 객체로 만들어 받아온 DTO를 이용하여 수정 후 다시 인코딩 후 쿠키에 넣어 반환하는 형식이다.

 

/**
 * 장바구니 상품 변경 메서드
 *
 * @param modifyCartDTO 변경할 카트 객체
 * @param request       Servlet Request 객체
 * @param response      Servlet Response 객체
 * @return 변경 메시지
 * @throws UnsupportedEncodingException 인코딩 문제 시 예외 발생
 * @throws NoCartException              쿠키 없을 때 예외 발생
 */
@Auth(role = Auth.Role.BASIC_USER)
@PutMapping
public ResponseEntity<String> modifyCart(@RequestBody(required = true) CartItemDTO modifyCartDTO, HttpServletRequest request,
                                         HttpServletResponse response) throws UnsupportedEncodingException, NoCartException {
    Integer productNo = modifyCartDTO.getProductNo();
    Cookie[] requestCookies = request.getCookies();
    Cookie responseCookie = null;

    /* 쿠키가 존재할 때 카트 쿠키 반환 */
    if (requestCookies != null) {
        responseCookie = CookieUtil.getCartCookie(requestCookies);
    }

    if (responseCookie == null) {
        throw new NoCartException(NULL_MODIFY_COOKIE);
    }

    Map<Integer, CartItemDTO> cartDTOMap = CookieUtil.getCartItemDTOMap(responseCookie);

    /* 기존 상품 객체 삭제 */
    cartDTOMap.remove(productNo);

    /* 넘어온 상품 객체를 추가 */
    cartDTOMap.put(productNo, modifyCartDTO);

    responseCookie.setValue(URLEncoder.encode(JsonUtil.objectToString(cartDTOMap), ENC_TYPE));
    response.addCookie(responseCookie);

    return ResponseEntity.ok().body(ResponseMessage.MODIFY_MESSAGE.getValue());
}

=> 추가 핸들러와 중복되는 코드가 있다.. 저걸 어떻게 분리할까..

코드 설계가 잘못되었나도 싶다.

 

장바구니 삭제

장바구니 삭제는 단일 상품의 삭제이므로 변경과 거의 비슷하다.

 

/**
 * 장바구니 상품 삭제 메서드
 *
 * @param deleteCartDTO 삭제할 카트 객체
 * @param request       Servlet Request 객체
 * @param response      Servlet Response 객체
 * @return 삭제 메시지
 * @throws UnsupportedEncodingException 인코딩 문제 시 예외 발생
 * @throws NoCartException              쿠키 없을 때 예외 발생
 */
@Auth(role = Auth.Role.BASIC_USER)
@DeleteMapping
public ResponseEntity<String> deleteCart(@RequestBody CartItemDTO deleteCartDTO, HttpServletRequest request,
                                         HttpServletResponse response) throws NoCartException, UnsupportedEncodingException {
    Integer productNo = deleteCartDTO.getProductNo();
    Cookie[] requestCookies = request.getCookies();
    Cookie responseCookie = null;

    if (requestCookies != null) {
        responseCookie = CookieUtil.getCartCookie(requestCookies);
    }

    if (responseCookie == null) {
        throw new NoCartException(NULL_REMOVE_COOKIE);
    }

    Map<Integer, CartItemDTO> cartDTOMap = CookieUtil.getCartItemDTOMap(responseCookie);

    /* 해당 상품 맵에서 삭제 */
    cartDTOMap.remove(productNo);

    responseCookie.setValue(URLEncoder.encode(JsonUtil.objectToString(cartDTOMap), ENC_TYPE));
    response.addCookie(responseCookie);

    return ResponseEntity.ok().body(ResponseMessage.DELETE_MESSAGE.getValue());
}

 

중복된 코드의 리팩터링을 거쳐야 할 것 같은 느낌이 든다.

 

참조 : https://passionha.tistory.com/403?category=457136 

 

AJAX를 통해 json object 배열데이터를 controller의 List<Map>파라미터로 보내기

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 //JSP 내 JS부분 //f_arrAnlys 미리 생성해서 데이터 넣어둔 전역배열 function fn_exec(){     var arrPObj =..

passionha.tistory.com

 

반응형