어제부터 장바구니 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 이란 클래스는 없다. 내가 따로 만든 것인데..
결론부터 말하자면 일반 유틸 클래스는 인스턴스화에 굳이 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
반응형
'📕 Spring Framework > Spring Project' 카테고리의 다른 글
2022.05.25 「Email 인증」 (0) | 2022.05.25 |
---|---|
2022.05.24 「코드 리팩토링 Ver.2」 (0) | 2022.05.24 |
2022.05.21 「 Shopping Cart API 만들기 」 (0) | 2022.05.21 |
2022.05.20 《스프링 부트 권한 처리》 (0) | 2022.05.20 |
2022.05.18 「Redis DB 적용」 (0) | 2022.05.18 |