[쇼핑몰 만들기 프로젝트] - 장바구니(1)

yeom yaloo·2023년 8월 3일
0

쇼핑몰

목록 보기
14/19
post-thumbnail

[들어가기에 앞서서]

장바구니를 구현하고자 할 때 어떤 방식으로 구현할까 고민을하던 도중 세션, 쿠키 아니면 DB를 사용해서 저장할 때 데이터를 저장하고 조회할 때 해당 데이터베이스를 조회해서 정보를 가져오는 방식을 고안했다.

고민을 진행하던 도중 나는 RedisCookie 방식을 사용해서 구현했다.

[쿠키 세션 그리고 레디스]

부제: 쿠키와 세션만으로도 충분히 구현 가능한 장바구니 기능에 레디스라는 Nosql을 사용한 이유
쿠키와 세션만으로도 장바구니 기능은 충분히 구현이 가능하다 그렇다면 나는 왜 레디스까지 도입을 하여 작업을 진행했을까?

1. 확장성

  • 레디스는 메모리 내 데이터 저장소로 많은 양의 데이터와 높은 수준의 읽기 및 쓰기 작업을 효율적으로 처리할 수 있게 했다.
  • 따라서 많은 사용자의 장바구니 데이터를 동시 처리하는데 적합하다고 판단하여 이를 사용하였다.

2. 성능

  • 레디스는 데이터를 메모리에 저장하기 때문에 빠른 검색과 조작이 가능하다.
  • 이 특징은 사용자가 장바구니에 상품을 추가하거나 수정하고 삭제할 때 빠르게 기능을 수행할 수 있다고 판단하여 이를 사용하였다.

3. 지속성

  • 쿠키의 경우엔 주로 클라이언트측 스토리지에 사용되지만 레디스의 경우엔 데이터를 디스크에 저장합니다.
  • 이 특징은 서버가 다시 시작되어도 사용자가 추가해둔 장바구니의 데이터를 잃지 않기 때문에 이를 사용하였다.

4. 만료

  • 레디스에는 데이터 만료 시간을 설정할 수 있다.
  • 이 특징은 장바구니에 담아둔 상품이 회원, 비회원에 따라서 일정 시간이 지나면 자동으로 삭제될 수 있게 하는 기능을 도입하기 위해서 이를 사용하였다.

5. 데이터 구조 지원

  • 레디스 자체적으로 여러 자료구조를 지원하는데 이를 사용하면 효율적인 쿼리 및 조작을 가능하게 하여 이를 사용하였다.

6. 분산 아키텍처

  • 분산 환경에서 레디스를 사용하면 여러 장점이 있다.
  • 확장성, 고가용성, 데이터 복제, 성능, 트랜잭션 지원, 캐싱 기능... 여러 장점이 존재하기 때문에 해당 분산 구조에서는 레디스를 사용한 장바구니 구현이 적합하다고 판단하였다.

7. 백엔드 서비스와의 통합

  • 레디스는 마이크로 서비스 아키텍처와 쉽게 통합할 수 있다는 장점이 있다.
  • 이 특성으로 인해서 애플리케이션의 다양한 구성 요소간의 데이터 동기화와 공유가 원활하게 이루어질 수 있다고 생각 되어 이를 사용하였다.

8. 실시간 업데이트

  • Redis는 발행/구독 (pub/sub) 메시징을 지원하여 실시간 업데이트를 가능하게 합니다.
  • 이는 상품의 가용성이나 가격 변경과 같이 사용자에게 카트 변경 사항을 알리는 데 유용하기 때문에 이를 사용하였습니다.

9. 쿠키를 사용한 이유

  • 쿠키는 조작이 쉽다는 단점이 있어 민감한 정보를 이용할 땐 쿠키에 저장하는 것을 지양한다. 그러나 장바구니 정보 자체가 민감한 정보는 아니라고 판단하여 해당 장바구니 정보를 쿠키에 저장하고자 했다
  • 쿠키를 사용하여 세션 데이터를 클라이언트에 저장하면 서버 부하 감소, 서버 확장성 향상, 클라이언트 측 데이터 활용 등의 이점을 얻을 수 있습니다.
  • 또한 외부 저장소 사용을 고려하여 세션 데이터의 안정성과 확장성을 더욱 향상시킬 수 있습니다.

[전체 상품 페이지에서 추가]

  1. 상품 목록 출력을 위해서 모든 상품 조회 API를 FE Server가 API Server에서 호출하여 상품 정보를 사용자가 확인할 수 있는 뷰페이지로 출력해준다.
  2. 출력한 상품 목록 리스트를 사용(API 호출해서 응답받은 데이터)해서 장바구니에 담아줄때 request에 정보를 넘겨준다. (request - productId, quantity, isEbook)
  3. RestController를 통해서 해당 요청에 따른 응답들을 돌려준다.
  • 1개 이하의 수량을 담을 경우 응답
  • 비회원이 이북 구매를 하고자 할 때의 응답
  • 회원이 이북을 구매하고자 할 때 1개 이상의 수량을 장바구니에 담으려고 할때의 응답
  • 제대로 된 요청을 할 경우의 응답
  1. 정상적으로 장바구니에 상품이 담겼다면 아래와 같이 confirm 창을 띄워 장바구니로 갈지 말지를 사용자가 선택할 수 있게 한다.
  2. 정상적으로 상품이 장바구니에 담겼다면 CART_NO이란 이름의 쿠키를 생성해주고 해당 쿠키에 담긴 UUID를 키값으로 레디스에도 저장해준다.
  3. 마지막으로 회원, 비회원 여부를(쿠키의 이름으로 구분) 확인해서 만료 시간을 설정해준다. (회원의 경우 30일 비회원의 경우 3일)

[4. 정상 요청에 응답]

[5-1. 정상적으로 상품이 장바구니에 담겼다면 CART_NO이란 이름의 쿠키를 생성]

[5-2. 해당 쿠키에 담긴 UUID를 키값으로 레디스에도 저장]

[Javascript로 원하는 데이터만 넘기기]

부제: th:each를 통해 출력하고 있는 데이터를 선택한 데이터 정보만 가져와보자

전체 상품 목록을 list 형태로 넘겨 받아서 타임리프 문법을 통해 해당 리스트를 출력해주고 있을 때 선택한 상품의 특정 정보를 얻어내고 싶으면 어떻게 진행해야 할까?


<div th:each="product, productStat:${products}" class="align-content-center mb-0 mt-3 border-bottom">

 <button th:if="${product.isSelled}"
	th:attr="product-id=${product.productId}"
	class="btn btn-yellow border-0 col-lg-6 col-sm-2 pb-2 mb-3 text-center text-light">
   장바구니
</button>
  • th:attr="product-id=${product.productId}": 타임리프 문법으로 데이터를 지정할 때 사용한다.
<script>
    document.querySelectorAll("button[product-id]").forEach(button => {
        button.addEventListener("click", function() {
            const productId = this.getAttribute("product-id");
            addToCart_singleSelect(productId);
            console.log("Clicked button for Product ID: " + productId);
        });
    });

    function addToCart_singleSelect(productId){
        const url = "/cart";
        const request = {
            productId: productId,
            quantity: 1,
            isEbook : false
        }

        fetch(url, {
            method:'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(request)
        }).then((response)=> {
            return response.json();
        }).then(data => {
            console.log(data.status)
            if (data.status === 200){
                confirmCartViewPage()
            } else {
                alert(data.data)
            }
        })

    }

    function confirmCartViewPage(){
        let result;
        result = confirm("해당 상품이 장바구니에 추가 되었습니다. 장바구니로 이동하시겠습니까?")

        if (result){
            window.location.href = "/cart";
        } else{
            console.log("계속 해당 페이지에 남아있기!~")
        }
    }
</script>
  • document.querySelectorAll("button[product-id]").forEach(button => { button.addEventListener("click", function(){}: list로 이루어진 products에서 해당 버튼을 클릭했을 때 상품의 pk값을 가져오는 코드이다.

  • addToCart_singleSelect(): productId를 넘겨받아 단일 상품 장바구니 추가 로직을 위한 fetch API를 사용합니다.

    • 이때 요청 데이터로 productId, quantity, isEbook이 있으며 단일 상품을 1개씩 추가하는 작업이기 때문에 quantity는 1로 고정합니다.
    • isEbook의 경우 API Server의 로직 수정으로 해당 작업은 변경될 수 있습니다.(고정되었지만 넘겨받은 정보로 해당 정보를 수정할 예정)
    • 해당 작업이 올바르지 못한 요청으로 판명되면 경고창이 뜨게 됩니다.
  • confirmCartViewPage(): 장바구니에 상품이 성공적으로 담겼을 때 장바구니에 담긴 상품 목록을 출력해주는 페이지로 넘어가거나 그대로 페이지에 남아있을 수 있는 선택을 위한 창을 띄우는 기능을 합니다.

profile
즐겁고 괴로운 개발😎

2개의 댓글

comment-user-thumbnail
2023년 8월 3일

잘 봤습니다. 좋은 글 감사합니다.

1개의 답글