[Spring] 무신사 클론 1 - 상품 api spec

춤인형의 개발일지·2025년 2월 25일

Spring실습

목록 보기
35/40

내가 맡은 부분은 상품 부분이다.

➡️ 상품 등록

  • Method: POST

  • Path: /products

  • Example Endpoint: https://localhost:8080/products

  • Request Parameters:

    • RequestHeader:
      • adminToken (String) : 관리자 토큰
    • Body Parameters: (ProductCreateRequest)
      • productName (String) : 상품 이름
      • price (int) : 상품 가격
      • description (String) : 상품 설명
      • category (Category) : 카테고리
      • productCondition (Condition) : 상품 상태
      • productCount (int) : 상품 개수
      • options (List<optionGroup): 상품 옵션
        • optionGroupName (String) : 옵션 그룹명 (예: "색상", "사이즈")
        • optionValues (List<OptionValue): 옵션 값 목록
          • value (String): 옵션 값
          • stock (int) : 재고
      • createdAt (LocalDateTime) : 등록 시간
        ex)
        {
          "productName": "신발",
          "price": 50000,
          "description": "편안한 신발입니다.",
          "category": SHOSE,
          "productCondition": "NEW",
          "options": [
          {
          "optionGroupName": "색상",
          "optionValues": [
          { "value": "빨강", "stock": 5 },
          { "value": "검정", "stock": 10 }
          ] },
          {
          "optionGroupName": "사이즈",
          "optionValues": [
          { "value": "230", "stock": 3 },
          { "value": "240", "stock": 7 },
          { "value": "250", "stock": 5 }
          ]
          }
          ]
          }
  • Response: (ProductCreateResponse)

    • productId(Long) :상품 ID
    • productName (String) : 상품 이름
    • price (int) : 상품 가격
    • description (String) : 상품 설명
    • category (Category) : 카테고리
    • productCondition (Condition) : 상품 상태
    • productCount (int) : 상품 개수
    • options (List<optionGroup): 상품 옵션 그룹 목록
      • optionGroupName (String) : 옵션 그룹명 (예: "색상", "사이즈")
      • optionValues (List<OptionValue): 옵션 값 목록
        - value (String): 옵션 값
        - stock (int) : 재고
    • createAt (LocalDateTime) : 등록 시간

➡️ 상품 수정

  • Method: PUT
  • Path: /products/{productId}
  • Example Endpoint: https://localhost:8080/products/1
  • Request Parameters:
    • RequestHeader:
      • adminToken (String) : 관리자 토큰
    • Path Segment Parameter :
      • productId (Long) : 상품 ID
    • Body Parameters: (ProductUpdateRequest)
      • productName (String) : 상품 이름
      • price (int) : 상품 가격
      • description (String) : 상품 설명
      • category (Category) : 카테고리
      • productCondition (Condition) : 상품 상태
      • options (List<optionGroup): 상품 옵션 그룹 목록
        • optionGroupName (String) : 옵션 그룹명 (예: "색상", "사이즈")
        • optionValues (List<String): 옵션 값 목록 (예: ["빨강", "파랑", "녹색"])
          • value (String): 옵션 값
          • stock (int) : 재고
      • updatedAt (LocalDateTime) : 수정 시간
  • Response: (ProductUpdateResponse)
    • productId(Long) :상품 ID
    • price (int) : 상품 가격
    • description (String) : 상품 설명
    • category (Category) : 카테고리
    • productCondition (Condition) : 상품 상태
    • options (List<optionGroup): 상품 옵션 그룹 목록
      • optionGroupName (String) : 옵션 그룹명
      • optionValues (List<String): 옵션 값 목록
        • value (String): 옵션 값
        • stock (int) : 재고
    • updatedAt (LocalDateTime) : 수정 시간

➡️ 상품 삭제

  • Method: DELETE
  • Path: /products/{productId}
  • Example Endpoint: https://localhost:8080/products/1
  • Request Parameters:
    • RequestHeader:
      • adminToken (String) : 관리자 토큰
    • Path Segment Parameter :
      • productId (Long) : 상품 ID
  • Response: X

➡️ 상품 목록 조회

  • Method: GET
  • Path: /products
  • Example Endpoint: https://localhost:8080/products
  • Request Parameters:
    • Body Parameters:
  • Response: (ProductListResponse)
    • productId(Long) :상품 ID
    • productName (String) : 상품 이름
    • price (int) : 상품 가격
    • category (Category) : 카테고리
    • productCondition (Condition) : 상품 상태

➡️ 상품 상세 조회

  • Method: GET
  • Path: /products/{productId}
  • Example Endpoint: https://localhost:8080/products/1
  • Request Parameters:
    • Path Segment Parameters:
      • productId (Long) : 상품 ID
  • Response: (ProductResponse)
    • productId(Long) :상품 ID
    • productName (String) : 상품 이름
    • price (int) : 상품 가격
    • description (String) : 상품 설명
    • category (Category) : 카테고리
    • productCondition (Condition) : 상품 상태
    • options (List<optionGroup): 상품 옵션 그룹 목록
      • optionGroupName (String) : 옵션 그룹명 (예: "색상", "사이즈")
      • optionValues (List<String): 옵션 값 목록

여기서의 고민!

❓option을 어떻게 Entity로 구성할 것인가

일단 Entity로 option을 구성하는건 맞다. 상품 옵션을 저장할 때 상품이 무조건 있어야 한다. 그래서 내가 생각한 option의 Entity는

option Entity

  • id (Long): 옵션 ID
  • productId (Product) : 상품 ID
  • optionTitle (String) : 옵션 이름
  • optionValues (List<String) : 옵션 값

이렇게 생각했다. 왜냐하면 나는 이렇게 했을 때의 장점이

  • 옵션 값이 리스트로 저장되어 조회가 쉬움
    예를 들어 "색상" 옵션에는 ["빨강", "파랑", "검정"] 같은 값을 한 줄(row)에 저장
    "사이즈" 옵션에는 ["230", "240", "250"]을 저장 가능

이렇게 되기 때문에 더 관리하기 편하다고 생각했다. 하지만 이 Entity의 문제는

  • optionValues가 List<String>로 JSON 형태로 저장되므로, SQL에서 직접 검색하거나 조인하기 어려움
    예를 들어 "빨강" 옵션에 대한 재고(stock) 를 관리해야 한다면 어려움

그렇다,, 그래서 나는 Product / ProductOptionGroup / ProductOption 이렇게 관리하는게 좋을 것 같다.

이런식으로 관계를 매핑해준 다음에 엔티티를 구성했다.

Product Entity

  • id (Long) : 상품 ID
  • name (String) : 상품 이름
  • price (int) : 상품 가격
  • description (String) : 상품 설명
  • category (Category) : 카테고리
  • productCondition (Condition) : 상품 상태
  • isDeleted (boolean) : 삭제 여부
  • createdAt (LocalDateTime) : 상품 등록 일시
  • updatedAt (LocalDateTime) : 상품 정보 수정 일시
  • reviewCount (int) : 리뷰 수
  • isOnSale (boolean) : 판매 중 여부
  • isPrivate (boolean) : 비공개
  • productOptions (List<ProductOption) : 상품 옵션

ProductOption Entity

  • id (Long) : 옵션 ID
  • product (Product) : 상품
  • optionGroupName (String) : 상품 옵션 이름
  • optionDetails (List<ProductOptionDetail>) : 상품 옵션 이름의 값

ProductOptionDetail Entity

  • id (Long) : 옵션 디테일 ID
  • optionGroupName (ProductOption) : 상품 옵션 이름
  • optionValue (String) : 상품 옵션 이름의 값
  • stock (int) : 재고

이렇게 Entity들을 설계했다.

❓또 고민
1:N관계가 계속해서 중첩이 된다. 이게 맞나....?

option과 optionDetail이 한번에 저장되고 싶다
예를 들어, 색상이 빨간색인 신발의 사이즈가 230인 상품의 재고가 3개 남은걸 저장하고 싶다.
그럼, @ElementCollection으로 아예 처음부터 종속적으로 저장해도 될까?

일단, ElementCollection은 완전 종속적인 관계일 때만 사용한다.
예를 들어 인스타 게시물을 올릴 때 무조건 사진을 올려야되니까 @ElementCollection으로 할 수 있다.

근데 이 경우는 230의 재고가 만약 0이 된다면 품절처리로 바꿔야하고, 중간에 새로운 색상이 추가가 된다면 수정하기도 어렵다. 따라서 oneToMany로 사용해서 부모자식의 관계를 맺어줘야 하는 것 같다.

❓또또 고민
그럼 HasMap()을 사용해서 key는 optionName, value는 옵션 값을 저장해놓으면 되지 않을까?

지피티의 도움을 받아서 함 알아봤다.

HashMap 사용의 단점

  • 관계형 데이터베이스와의 일관성:
    👍 : HashMap 객체 내에서 빠르게 키-값 쌍을 다룰 수 있음
    👎 : 관계형 데이터베이스에서는 Product, ProductOption, ProductOptionDetail 사이의 관계를 명확하게 정의해야함.
    예를 들어, ProductOptionDetail이 ProductOption에 속하고, ProductOption이 Product에 속하는 구조에서 이 관계를 HashMap으로만 처리하면, 데이터베이스에서 이 관계를 명확히 정의하거나 관리하기 어려움.
  • 검색 및 조회 성능:
    👍 : HashMap은 메모리 내에서 데이터를 빠르게 조회할 수 있음
    👎 : 최적화 어려움 / 데이터베이스에서는 대규모 데이터를 효율적으로 조회하는 데 있어 인덱스나 조인 같은 데이터베이스 최적화 기법을 사용하는 것이 중요함

❓왜 관계형 DB에서 HashMap보다는 객체 관계를 사용하는 것이 더 나은가?

관계형 데이터베이스에서는 데이터의 정규화가 중요하기에, Product, ProductOption, ProductOptionDetail 사이의 관계를 테이블 간 외래키로 연결하는 방식이 더 적합하다. 이렇게 하면 각 데이터의 일관성을 유지하고, 데이터베이스에서 제공하는 조인이나 인덱싱 등의 최적화 기능을 활용할 수 있다.

결국엔 Entity로 1:N관계로 맺어서 사용하는게 맞는 것 같다.
근데 나중에 되면 1:N의 관계도 끊어버린다고 한다. 너무 불필요한 1:N의 관계 때문에 데이터가 한번 접근하기 위해서는 여러가지를 찾고 찾고 찾아서 찾을 수 있기 때문이다. 일단 1:N의 관계로 맺어보고, 수정하는걸로 해보자

0개의 댓글