[PROJECT] 정기구독 웹서비스 7 - 장바구니 Controller

zirryo·2023년 1월 22일
0

💊 Pillivery

목록 보기
7/7

22.11.17 THU (DAY 8)


💿 REST API

  • 어플리케이션 간의 데이터 통신을 위한 Application Programming Interface
  • Representational State Transfer, 데이터의 이름으로 상태를 구분하여 전송하는 방식
  • RESTful : REST API 제공하는 웹서비스 시스템을 지칭함. ex) "OO 서비스는 RESTful 하다"



사용하는 이유

  • HTTP 프로토콜 인프라를 그대로 사용하기 때문에 HTTP를 따르는 모든 플랫폼에서 사용이 가능함.
  • 요청의 의도를 파악하기 쉽고, 서버와 클라이언트의 역할을 명확히 나눌 수 있음.

6 원칙

  1. Uniform Interface

    🔗 velog.io/@zirryo/series
    🪄 velog, zirryo의 series 를 나타내는 것을 바로 알 수 있음.

    • 데이터를 식별 가능하게 해야한다는 원칙
    • URL만 보고도 어느 데이터를 어떤 상태로 전송해야 하는지 구별할 수 있어야 함.
    • 하나의 URL, 하나의 데이터
  2. Client Server

    • 클라이언트와 서버는 반드시 분리되어야 함.
    • 클라이언트는 데이터를 서버에 요청하고 서버는 클라이언트의 요청에 따른 데이터를 응답해야 한다.
  3. Stateless

    • HTTP 프로토콜을 따르기 때문에 HTTP 특징과 같이 상태를 저장하지 않음.
    • 각각의 요청은 독립적이며, 하나의 요청에 모든 정보륻 담아 전송함.
  4. Cacheable

    • 요청을 통해 보내는 자료는 저장(캐싱)되어야 함.
    • 저장된 자료를 주고 받을 때는 속도가 향상됨.
  5. Layered System

    • 요청된 정보를 검색하는데 계층 구조로 분리되어 있어야 함 (여러 레이어를 거쳐 요청을 처리)
    • 중간 서버 등을 둬 서버 확장성을 보장.
  6. Code On Demand

    • 서버는 XML이나 JSON으로 응답하지만, 필요한 경우 코드 자체를 데이터로 클라이언트에 전달할 수 있음.



URL 네이밍 규칙

  1. 명사 사용

    ⭕️ : velog.io/@zirryo/reviews
    ❌ : velog.io/@zirryo/reviewed

  2. 소문자 사용

    ⭕️ : velog.io/@zirryo/series
    ❌ : velog.io/@zirryo/Series

  3. 복수형 사용

    ⭕️ : velog.io/@zirryo/users
    ❌ : velog.io/@zirryo/user

  4. - 구분자 사용

    ⭕️ : velog.io/@zirryo/best-sellers
    ❌ : velog.io/@zirryo/bestSellers

  5. url 을 / 로 끝내지 않음

    ⭕️ : velog.io/@zirryo/carts
    ❌ : velog.io/@zirryo/cart/

  6. 파일 확장자를 포함하지 않음

    ⭕️ : velog.io/@zirryo/item
    ❌ : velog.io/@zirryo/item.jpg




💿 Cart Controller

Controller
여러 형태의 클라이언트로부터 요청을 받아 이를 비즈니스 로직으로 전달하고,
비즈니스 로직의 결과를 클라이언트에게 응답해주는 역할을 하는 클래스


  • Structure

    // CartController.java
    @Slf4j
    @RestController
    @RequiredArgsConstructor
    @RequestMapping("/carts")
    public class CartController {
    
        private final CartService cartService;
        private final ItemCartService itemCartService;
        private final CartMapper cartMapper;
        private final ItemMapper itemMapper;
        private final ItemCartMapper itemCartMapper;
        ...
        ...
    
    }
    • @Slf4j : Simple Logging Facade for Java, 로깅 프레임워크 라이브러리
    • @RestController : 컨트롤러 클래스를 Spring Bean으로 등록함.
    • @RequiredArgsConstructor : final, @NotNull 이 붙은 필드의 생성자를 만드는 애너테이션 (순환 참조 방지, 객체 변이 방지)
    • @RequestMapping : 요청에 대한 Methods, URL 매핑.



  • getCart

    // CartController.java
    @GetMapping
    public ResponseEntity getCart(@RequestParam(value="subscription", defaultValue="false") boolean subscription) {
    
        Cart cart = cartService.findMyCart();
    
        return new ResponseEntity<>(new SingleResponseDto<>(cartMapper.cartToCartResponseDto(
                            cart, cartService, subscription, itemCartService, itemMapper, itemCartMapper)), HttpStatus.OK);
    }
    // 서버의 응답 예시
    {
      "data": {
        "cartId": 1, // 카트 고유 식별자
        "subscription": false, // 일반 혹은 정기 카트 구분 (false == 일반 카트)
        "itemCarts": { // 현재 카트에 포함된 목록
          "data": [ // 각각의 항목이 data 리스트에 들어있음
            {
              "itemCartId": 1, // 아이템 카트 고유 식별자
              "quantity": 3, // 해당 아이템을 담은 수량
              "period": 0, // 정기 구독 일경우 구독 주기 정보 표기
              "buyNow": true, // 선택된 항목일경우 true, 선택되지 않았다면 false
              "subscription": false, // 일반 혹은 정기 항목 구분
              "item": { // 해당 아이템의 간략한 정보
                "itemId": 1, // 아이템 고유 식별자
                "brand": "BRAND1", // 제약회사명
                "thumbnail": "썸네일_이미지의_주소.jpg", // 썸네일 이미지를 불러올 주소
                "title": "메가타민 비타민B", // 아이템의 이름
                "capacity": 30, // 영양제 한 통에 포함된 수량(30 -> 30정)
                "price": 12000, // 정가
                "discountRate": 20, // 현재 할인율
                "disCountPrice": 9600 // 실제 구매 금액 (할인가 적용)
              },
              "createdAt": "2022-11-30T13:05:41.934396+09:00", // 처음 카트에 해당 아이템을 담은 시간
              "updatedAt": "2022-11-30T13:05:41.93441+09:00"
            }
          ],
          "pageInfo": null // 카트 목록은 무한 스크롤 기능을 적용하므로 페이지 정보 필요없음
        },
        "totalItems": 1, // 현재 카트에 포함된 아이템 중 buyNow==true 인 항목의 수
        "totalPrice": 36000, // 현재 카트에 포함된 아이템 중 buyNow==true 인 정가 총합
        "totalDiscountPrice": 7200, // 현재 카트에 포함된 아이템 중 buyNow==true 인 할인 금액 총합
        "expectPrice": 28800 // 현재 카트에 담긴 물건을 주문할 경우 결제 예상 금액
      }
    }

    • 일반 / 정기 여부를 @RequestParam 으로 받아, 조건에 맞는 카트의 정보를 리턴하는 장바구니 조회 컨트롤러
    • ex1) URL : zirryo-project.com/carts?subscription=false 현재 브라우저에 로그인 된 유저의 일반 장바구니 데이터를 불러옴.
    • ex2) URL : zirryo-project.com/carts?subscription=true 현재 브라우저에 로그인 된 유저의 정기 장바구니 데이터를 불러옴.

🔗 전체 코드



💿 ItemCart Controller

  • Structure

    // ItemCartController.java
    @Slf4j
    @RestController
    @RequiredArgsConstructor
    @RequestMapping("/carts")
    public class ItemCartController {
    
        private final ItemCartService itemCartService;
        private final ItemCartMapper itemCartMapper;
        private final ItemMapper itemMapper;
        private final ItemService itemService;
        private final CartService cartService;
        private final UserService userService;
        ...
        ...
    
    }

    카트의 개별 항목인 🔗 ItemCart를 다루는 컨트롤러 클래스

    • @Slf4j : Simple Logging Facade for Java, 로깅 프레임워크 라이브러리
    • @RestController : 컨트롤러 클래스를 Spring Bean으로 등록함.
    • @RequiredArgsConstructor : final, @NotNull 이 붙은 필드의 생성자를 만드는 애너테이션 (순환 참조 방지, 객체 변이 방지)
    • @RequestMapping : 요청에 대한 Methods, URL 매핑.


  • postItemCart

    // ItemCartController.java
    @PostMapping("/{item-id}")
    public ResponseEntity postItemCart(@Valid @RequestBody ItemCartDto.Post itemCartPostDto,
        @PathVariable("item-id") @Positive long itemId) {
    
        ItemCart itemCart = itemCartService.addItemCart(itemCartMapper.
                itemCartPostDtoToItemCart(itemId, userService, itemService, itemCartPostDto));
        cartService.refreshCart(itemCart.getCart().getCartId(), itemCart.isSubscription());
    
        return new ResponseEntity<>(
                new SingleResponseDto<>(itemCartMapper.itemCartToItemCartResponseDto(
                        itemMapper, itemCart)), HttpStatus.CREATED);
    }
    // ItemCartDto.java
    @Getter
    public static class Post {
        @Min(value = 1, message = "수량은 1개 이상 선택해주세요.")
        private Integer quantity; // 아이템의 수량
        private Integer period; // (정기 구독 항목일 경우) 구독 주기
        private boolean subscription; // 구독 여부
    }
    • 장바구니에 특정 아이템을 담는 요청을 처리하는 컨트롤러
    • @PostMapping: HTTP(S)의 POST request를 처리하는 애너테이션
    • @PathVariable : itemId 를 이용해서 장바구니에 담을 아이템의 정보를 얻음.
    • @RequestBody : dto 클래스를 이용해 필요한 정보를 입력 받음.
    • refreshCart : 카트의 정보를 갱신하는 메서드


  • upDownItemCart

    // ItemCartController.java
    @PatchMapping("/itemcarts/{itemcart-id}")
    public ResponseEntity upDownItemCart(@PathVariable("itemcart-id") @Positive long itemCartId,
                                              @RequestParam(value="upDown") int upDown) {
    
        ItemCart upDownItemCart = itemCartService.updownItemCart(itemCartId, upDown);
        cartService.refreshCart(upDownItemCart.getCart().getCartId(), upDownItemCart.isSubscription());
    
        return new ResponseEntity<>(new SingleResponseDto<>(
                itemCartMapper.itemCartToItemCartResponseDto(itemMapper, upDownItemCart)), HttpStatus.OK);
    }
    • 장바구니에서 특정 아이템의 수량을 증가 혹은 감소시키는 컨트롤러
    • @PatchMapping: HTTP(S)의 PATCH request를 처리하는 애너테이션
    • @PathVariable : itemcartId 를 이용해서 수량 변경할 대상을 찾기 위함.
    • @RequestParam : updown == 1 수량 증가, -1 수량 감소
    • refreshCart : 카트의 정보를 갱신하는 메서드


  • periodItemCart

    // ItemCartController.java
    @PatchMapping("/itemcarts/period/{itemcart-id}")
    public ResponseEntity periodItemCart(@PathVariable("itemcart-id") @Positive long itemCartId,
                                         @RequestParam(value="period") int period) {
    
        ItemCart itemCart = itemCartService.periodItemCart(itemCartId, period);
    
        return new ResponseEntity<>(new SingleResponseDto<>(
                itemCartMapper.itemCartToItemCartResponseDto(itemMapper, itemCart)), HttpStatus.OK);
    }
    • 장바구니에서 정기 구독 아이템의 구독 주기를 변경하는 컨트롤러
    • @PatchMapping: HTTP(S)의 PATCH request를 처리하는 애너테이션
    • @PathVariable : itemcartId 를 이용해서 구독 주기를 변경할 대상을 찾기 위함.
    • @RequestParam : period 값에 구독 주기를 정수로 입력함. (30, 60, 90, 120)
    • refreshCart : 카트의 정보를 갱신하는 메서드


  • excludeItemCart

    // ItemCartController.java
    @PatchMapping("/itemcarts/exclude/{itemcart-id}") 
    public ResponseEntity excludeItemCart(@PathVariable("itemcart-id") @Positive long itemCartId,
                                        @RequestParam(value="buyNow", defaultValue = "false") boolean buyNow) {
        ItemCart itemCart = itemCartService.excludeItemCart(itemCartId, buyNow);
        cartService.refreshCart(itemCart.getCart().getCartId(), itemCart.isSubscription());
    
        return new ResponseEntity(HttpStatus.OK);
    }
    • 장바구니에서 특정 아이템을 선택 혹은 선택해제 하는 컨트롤러
    • @PatchMapping: HTTP(S)의 PATCH request를 처리하는 애너테이션
    • @PathVariable : itemcartId 를 이용해서 선택 여부를 변경할 대상를 찾기 위함.
    • @RequestParam : buyNow == true 선택, false 선택 해제
    • refreshCart : 카트의 정보를 갱신하는 메서드


  • deleteItemCart

    // ItemCartController.java
    @DeleteMapping("/itemcarts/{itemcart-id}") 
    public ResponseEntity deleteItemCart(@PathVariable("itemcart-id") @Positive long itemCartId,
                                         @RequestParam(value = "subscription") boolean subscription) {
        long cartId = itemCartService.deleteItemCart(itemCartId);
        cartService.refreshCart(cartId, subscription);
    
        return new ResponseEntity(HttpStatus.NO_CONTENT);
    }
    • 장바구니에서 특정 아이템을 삭제하는 컨트롤러
    • @DeleteMapping: HTTP(S)의 DELETE request를 처리하는 애너테이션
    • @PathVariable : itemcartId 를 이용해서 삭제하고자 하는 대상을 찾기 위함.
    • @RequestParam : subscription == true 정기 구독 항목, false 일반 항목
    • refreshCart : 카트의 정보를 갱신하는 메서드

🔗 전체 코드



0개의 댓글