일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 에라토스테네스의체
- BOJ
- 딥러닝
- SpringBoot
- 알고리즘
- WebMvcTest
- DP
- python3
- 백준
- PS
- Spring
- 책리뷰
- REST API
- web
- 코딩
- 로지스틱회귀
- C++
- 스택
- 그리디
- 신경망기초
- responsebody
- 정렬
- 쉬운딥러닝
- FNN
- Spring Data JPA
- withmockuser
- Backend
- testing
- RequestBody
- Andrew Ng
- Today
- Total
꾸준히하자아자
카카오 쇼핑하기 클론프로젝트 #3 본문
매주 프로젝트 진행상황을 블로그에 업데이트 하려고 했지만...
4주차 까지는 스프링부트에 적응하면서 과제 하느라 바빴고 쏟아지는 새로운 개념들을 익히기 바빴다..ㅎ ㅎ
저번 주에 드디어 6주간의 프로젝트가 끝나고 뭘 해야 좋을 지 고민하다가...
새로운 프로젝트를 하는 것 보단 기존 프로젝트의 부족한 기능을 추가하거나 코드를 리팩토링 하는 시간을 갖는 게 좋을것 같다고 생각했다.
기존 프로젝트엔 "장바구니 조회" , "장바구니 추가" , "장바구니 수량 수정 기능"만 구현했다.
따라서 "장바구니 옵션 삭제" 기능을 추가해봤다.
제대로 잘 구현했는지 확신할 수 없지만ㅜ..ㅜ 일단 스스로 구현한 내용을 바탕으로 적어보겠다.
사진은 프론트 UI인데 각 옵션마다 삭제할 수 있는 x 버튼을 추가했다고 가정하자.
x 버튼을 눌렀을 때, /carts/update 경로로 DELETE 요청이 들어오고
Request Header에는 Bearer Token이 들어가고
Request Body (예시)
{
"cartId" : 3
}
Response Body (예시)
{
"success": true,
"response": {
"carts": [
{
"cartId": 4,
"optionId": 1,
"optionName": "01. 슬라이딩 지퍼백 크리스마스에디션 4종",
"quantity": 10,
"price": 100000
},
{
"cartId": 5,
"optionId": 2,
"optionName": "02. 슬라이딩 지퍼백 플라워에디션 5종",
"quantity": 10,
"price": 109000
}
],
"totalPrice": 209000
},
라고 가정하자.
예시 응답 Body에는 cartId가 3인 옵션이 삭제된 후의 장바구니를 나타내었다.
장바구니 옵션 삭제 로직을 구현할 때 고민했던 점
1. 요청Body에 (장바구니 수량 수정 로직과 같이) 리스트 형태로 넘겨줄지 말지 고민했다.
{
"cartId": 1
}
{
"cartId": 2
} //or 하나만?
- 장바구니 수정 로직, 장바구니 추가 로직을 구현할 때도 리스트 형태로 넘겨줬었다..,,
- 화면 설계서를 보면 동시에 두 옵션의 수량을 수정하는 것이 아닌 하나하나 수량을 수정하는데 굳이 리스트로 넘겨줘야하나? 라는 생각이 들었다.
- 프론트가 요청Body를 백에게 넘겨주는 작동원리를 몰라서 이런 의문이 드는 것 같다.
- 마찬가지로 삭제 요청도 사용자가 하나하나 처리하므로 굳이 리스트 형태로 넘겨주지 않아도 될 것 같다고 생각했다.
- 물론 리스트 형태로 넘기는 게 정답일 수도 있지만! 그냥 단일 객체로 넘기는 형태로 작성하기로 했다.
2. 화면 UI내에서 발생할 수 없는 요청Body에 대한 예외처리도 하는 게 좋다. (추후에 공부)
- 처음엔 클라이언트는 보통 화면 UI 내에서 조작하기 때문에 들어올 수 없는 예외처리는 필요 없다고 생각했다.
- 하지만 잘못된 경로로 이상한 요청이 들어올 수 있기 때문에 최대한 모든 예외를 잡아야 한다.
- 명백히 잘못 들어온 경우라면 다양한 사이드 이팩트 방지를 위해 예외처리해 주는게 맞다고 한다!
- 보안문제, 데이터 무결성 유지, 서버 자원 보호 등의 이유도 있다.
3. 마지막에 남은 하나의 레코드를 삭제하려고 할 때 삭제가 되지 않는 에러 발생
처음에 작성한 서비스 계층 로직
@Transactional
public CartResponse.DeleteDTO delete(CartRequest.DeleteDTO requestDTO, User sessionUser) {
List<Cart> cartList = cartJPARepository.findAllByUserId(sessionUser.getId());
if(cartList.isEmpty()){
throw new Exception404("장바구니가 비어있습니다.");
}
HashSet<Integer> set = new HashSet<>();
for(Cart cart : cartList){
set.add(cart.getId());
}
if(!set.contains(requestDTO.getCartId())){
throw new Exception400("장바구니에 없는 상품은 삭제할 수 없습니다.");
}
for(Cart cart : cartList){
if(requestDTO.getCartId() == cart.getId()){
cartJPARepository.deleteById(requestDTO.getCartId());
cartList.remove(cart);
}
}
return new CartResponse.DeleteDTO(cartList);
}
- user의 장바구니 정보를 가져오고, 장바구니가 비었다면 예외처리를 한다.
- 만약에 요청Body로 존재하지 않는 cartId 가 들어온다면 예외처리를 한다.
- for 루프를 돌면서 요청Body로 들어온 cartId와 DB에 존재하는 cartId가 같다면 delete하고 cartList에도 삭제한다.
- But 문제 발생….!!
- 삭제를 하나하나 하다가 장바구니에 마지막 하나의 상품이 남고 삭제 시도를 했을 때 500 에러가 발생했다.
- cartJPARepository.deleteById(requestDTO.getCartId())를 호출한 후에 cartList 에서 해당 요소를 제거하는 것은 데이터베이스와의 동기화 문제를 야기할 수 있다고 한다.
- 삭제 작업은 데이터베이스에서 먼저 수행되도록 보장해야 한다.
- 따라서 삭제가 완료된 후 리스트에서 제거하는 로직으로 변경해야 한다.
변경 후
for (Cart cart : cartList) {
if (requestDTO.getCartId() == cart.getId()) {
cartJPARepository.deleteById(requestDTO.getCartId());
break; // 삭제 후 반복문 종료
}
}
cartList.removeIf(cart -> cart.getId() == requestDTO.getCartId());
테이블 확인
만약에 user_id가 2인 사용자로 로그인 한 상태라고 가정했을 때 모든 경우의 응답을 유도해보겠다.
장바구니에 없는 상품을 삭제하려고 시도했을 때
cartId가 4인 상품은 본인 장바구니에 있는 상품이 아니므로 삭제 불가능
cartId가 1인 상품 삭제
cartId가 3인 상품 삭제
cartId가 2인 상품 삭제 -> 장바구니에 담겨있던 상품들을 모두 삭제해서 장바구니가 비어있다.
장바구니가 비어있는 상태에서 상품을 삭제하려고 시도했을 경우
삭제 시 실행된 쿼리문
@Query("select c from Cart c join fetch c.option o join fetch o.product where c.user.id =:userId")
List<Cart> findAllByUserId(int userId);
불필요한 select쿼리를 방지하기 위해 product 테이블과 option 테이블을 fetch join 하여 한번에 값을 가져왔다.
Hibernate:
select
cart0_.id as id1_0_0_,
option1_.id as id1_4_1_,
product2_.id as id1_6_2_,
cart0_.option_id as option_i4_0_0_,
cart0_.price as price2_0_0_,
cart0_.quantity as quantity3_0_0_,
cart0_.user_id as user_id5_0_0_,
option1_.option_name as option_n2_4_1_,
option1_.price as price3_4_1_,
option1_.product_id as product_4_4_1_,
product2_.description as descript2_6_2_,
product2_.image as image3_6_2_,
product2_.price as price4_6_2_,
product2_.product_name as product_5_6_2_
from
cart_tb cart0_
inner join
option_tb option1_
on cart0_.option_id=option1_.id
inner join
product_tb product2_
on option1_.product_id=product2_.id
where
cart0_.user_id=?
Hibernate:
delete
from
cart_tb
where
id=?
끗
'프로젝트 > 카카오 쇼핑하기 web' 카테고리의 다른 글
카카오 쇼핑하기 클론프로젝트 #4 (0) | 2023.08.22 |
---|---|
카카오 쇼핑하기 클론프로젝트 #2 (0) | 2023.07.07 |
카카오 쇼핑하기 클론프로젝트 #1 (0) | 2023.07.02 |