Skip to content

jeremy0405/11st-assignment

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

11st-assignment

과제를 구현하며 고민한 사항

1. 일관된 예외를 설계하고 예외 응답을 내리도록 했습니다.

  • 클라이언트에서 예외 처리 로직을 일관되게 구현할 수 있도록 ErrorResponse를 설계했습니다.
  • @Valid 에서 예외 발생 시 fieldError에서 예외가 발생한 필드를 ErrorResponse에 담아 정확한 예외 메시지를 클라이언트에 전달하고자 했습니다.
  • 어플리케이션에서 발생한 예외는 CustomException을 정의한 후 ErrorCode Enum을 통해 예외를 관리했습니다.
  • @ControllerAdvice를 통해 Custom 예외와 @Valid에서 발생하는 예외를 핸들링 했습니다.

2. 조회 API의 경우 데이터의 양이 방대할 것으로 예상되므로 Page 처리와 캐시를 적용했습니다.

  • 조회의 경우 수많은 데이터가 있을 것으로 예상되어 페이징 처리를 하여 성능 향상을 도모했습니다.
  • 페이징 처리 시 fetch join을 사용하면 모든 데이터를 가져온 후 어플리케이션 레벨에서 페이징 하기 때문에 성능 저하가 발생하기 때문에 inner join 후 spring.jpa.default_batch_fetch_size 를 통해 해결했습니다.
  • 상품 조회 시 cache를 적용했습니다. 인자값으로 LocalDateTime에서 오늘 날짜와 Pageable의 페이지 번호를 결합한 키를 만들어 캐싱을 했습니다.
    • 데이터 정합성을 위해서 cacheConfig 시 1분마다 캐시를 삭제하도록 했습니다.

3. 비즈니스 로직을 엔티티에서 처리하도록 구현하여 객체지향적인 설계를 하고자 노력했습니다.

  • 비즈니스 로직을 엔티티에서 처리하도록 해서 객체지향적인 코드를 작성하고자 했습니다.
  • 서비스 레이어에서는 Transaction, Cache 적용과 같은 infra를 적용하는데 집중하도록 했습니다.

4. 대량 트래픽에서 이슈가 없도록 구현했습니다.

  • 상품 조회 시 캐시를 사용하여 대량 트래픽에서 효율적으로 응답을 반환하도록 했습니다.
  • 여러 사용자가 동시 주문 시 동시성 문제를 pessimistic lock으로 해결했습니다.
    • pessimistic lock의 작동을 확인하기 위해 테스트 코드를 작성했습니다.

설계

테이블 설계

image


API 설계

기능 API
상품 조회 GET /api/products
상품 주문 POST /api/orders
상품 주문 취소 POST /api/orders/{order_id}/cancel
주문 내역 조회 GET /api/orders

예외 설계

예외 코드 내용
A001 유효하지 않은 ProductId 요청 시 발생하는 예외
A002 유효하지 않은 OrderId 요청 시 발생하는 예외
B001 상품 주문 시 상품의 재고가 부족하면 발생하는 예외
B002 상품 주문 시 요청의 금액이 상품의 금액보다 작으면 발생하는 예외
B003 상품 주문 시 상품의 상태가 판매 중지된 상품이면 발생하는 예외
C001 상품 취소 시 주문이 이미 완료된 상태일 때 발생하는 예외
C002 상품 취소 시 주문이 이미 취소된 상태일 때 발생하는 예외
C003 상품 취소 시 취소 금액과 총 주문 금액이 일치하지 않을 때 발생하는 예외
D001 입력 파라미터가 잘못된 경우 발생하는 예외

API 요청 및 응답 예시

◀️ Click! - 상품 조회 요청 및 응답 예시

기능

  • display_date 기준으로 전시중인 상품을 페이징 처리하여 반환한다.

API

  • GET /api/products

Query Param

  • Required
    • None
  • Option
    • display_date
      • display_date=2022-08-21T00:00
      • 미입력시 default: 현재시간
    • page
      • page=1
      • 미입력시 default: page=0

Error Response

  • display_date의 ISO Date Time Format이 잘못되었을 때

  • GET http://localhost:8080/api/products?display_date=2022-08-21T00&page=0

  • Http Status: 400

{
  "errorInfo": {
    "code": "D001",
    "message": "요청이 올바르지 않습니다"
  },
  "fieldErrors": [
    {
      "field": "display_date",
      "value": "2022-08-21T00",
      "reason": "typeMismatch"
    }
  ]
}

Success Response

  • GET http://localhost:8080/api/products?display_date=2022-08-21T00:00&page=1
  • Http Status: 200
{
  "content": [
    {
      "id": 6,
      "name": "(아마존)Corsai 벤전스LPX DDR4 데스크톱 메모리 키트 16GB (2x8GB) 블랙(CMK16GX4M2B3200C16)",
      "price": 84270,
      "quantity": 30,
      "sellerId": 3,
      "sellerName": "하이닉스",
      "status": "SALE"
    },
    {
      "id": 7,
      "name": "갤럭시S22",
      "price": 1200000,
      "quantity": 30,
      "sellerId": 4,
      "sellerName": "삼성",
      "status": "SALE"
    },
    {
      "id": 8,
      "name": "갤럭시 워치 4",
      "price": 220000,
      "quantity": 60,
      "sellerId": 4,
      "sellerName": "삼성",
      "status": "SALE"
    },
    {
      "id": 9,
      "name": "갤럭시S10",
      "price": 1000000,
      "quantity": 100,
      "sellerId": 4,
      "sellerName": "삼성",
      "status": "SUSPENDED"
    },
    {
      "id": 10,
      "name": "갤럭시 버즈 프로",
      "price": 330000,
      "quantity": 100,
      "sellerId": 4,
      "sellerName": "삼성",
      "status": "SALE"
    }
  ],
  "pageable": {
    "sort": {
      "sorted": false,
      "unsorted": true,
      "empty": true
    },
    "pageNumber": 1,
    "pageSize": 5,
    "offset": 5,
    "paged": true,
    "unpaged": false
  },
  "totalPages": 2,
  "totalElements": 10,
  "last": true,
  "numberOfElements": 5,
  "sort": {
    "sorted": false,
    "unsorted": true,
    "empty": true
  },
  "size": 5,
  "number": 1,
  "first": false,
  "empty": false
}

특이 사항

  • 캐시 적용
  • spring.jpa.default_batch_fetch_size를 통한 N+1 쿼리 해결

◀️ Click! - 상품 주문 요청 및 응답 예시

기능

  • 사용자가 상품을 주문하면 주문 수량만큼 상품의 재고가 감소하고 상품이 주문된다.
  • 여러 가지 상품을 한 번의 주문에 주문할 수 있다.

API

  • POST /api/orders

Header

  • x-user-id:greatpeople

Request Body

{
  "orders": [
    {
      "productId": 2,
      "price": 800000,
      "quantity": 1
    },
    {
      "productId": 4,
      "price": 110000,
      "quantity": 1
    }
  ],
  "address": {
    "city": "서울시 송파구",
    "street": "송파대로 567",
    "zipCode": "05503"
  }
}

Error Response

  • Request Body의 productId에 해당하는 상품이 없을 때
  • Http Status: 400
{
  "errorInfo": {
    "code": "A001",
    "message": "해당 상품이 존재하지 않습니다"
  },
  "fieldErrors": []
}
  • Request Body의 수량보다 상품의 재고가 적을 때
  • Http Status: 400
{
  "errorInfo": {
    "code": "B001",
    "message": "재고가 부족합니다"
  },
  "fieldErrors": []
}
  • Request Body의 금액보다 상품의 가격이 클 때
  • Http Status: 400
{
  "errorInfo": {
    "code": "B002",
    "message": "입금된 금액이 충분하지 않습니다"
  },
  "fieldErrors": []
}
  • Request Body의 productId에 해당하는 상품이 판매 중지일 때
  • Http Status: 400
{
  "errorInfo": {
    "code": "B003",
    "message": "판매 중지된 상품입니다"
  },
  "fieldErrors": []
}
  • Request Body의 값이 음수이거나 비어 있어 Valid에서 검증되는 경우
  • Http Status: 400
{
  "errorInfo": {
    "code": "D001",
    "message": "요청이 올바르지 않습니다"
  },
  "fieldErrors": [
    {
      "field": "orders[1].productId",
      "value": "-9",
      "reason": "0보다 커야 합니다"
    },
    {
      "field": "address.city",
      "value": "",
      "reason": "비어 있을 수 없습니다"
    },
    {
      "field": "orders[1].price",
      "value": "-11000000",
      "reason": "0보다 커야 합니다"
    },
    {
      "field": "orders[1].quantity",
      "value": "-1",
      "reason": "0보다 커야 합니다"
    }
  ]
}

Success Response

  • 생성된 주문의 식별값을 반환
  • Http Status: 201
{
  "orderId": 8
}

특이 사항

  • 비관적 락을 통해 상품의 수량을 감소시켜 동시성 문제 해결
  • Entity에 비즈니스 로직을 넣어 Entity가 직접 자신의 정보를 수정하도록 함

◀️ Click! - 상품 주문 취소 요청 및 응답 예시

기능

  • 사용자가 상품 주문 취소하면 상품의 재고가 원래대로 돌아가고 주문이 취소된다.

API

  • POST /api/orders/{order_id}/cancel

Request Body

{
  "cancelPrice": 4040000
}

Error Response

  • PathVariable의 orderId에 해당하는 주문이 없을 때
  • Http Status: 400
{
  "errorInfo": {
    "code": "A002",
    "message": "해당 주문이 존재하지 않습니다"
  },
  "fieldErrors": []
}
  • PathVariable의 orderId에 해당하는 주문이 완료된 상태일 때
  • Http Status: 400
{
  "errorInfo": {
    "code": "C001",
    "message": "이미 완료된 주문은 취소가 불가능합니다"
  },
  "fieldErrors": []
}
  • PathVariable의 orderId에 해당하는 주문이 이미 취소 상태일 때
  • Http Status: 400
{
  "errorInfo": {
    "code": "C002",
    "message": "이미 취소된 주문은 취소가 불가능합니다"
  },
  "fieldErrors": []
}
  • Request Body의 취소 금액과 총 주문 금액이 일치하지 않을 때
  • Http Status: 400
{
  "errorInfo": {
    "code": "C003",
    "message": "취소 금액과 총 주문 금액이 일치하지 않습니다."
  },
  "fieldErrors": []
}
  • Request Body의 값이 음수이거나 비어 있어 Valid에서 검증되는 경우
  • Http Status: 400
{
  "errorInfo": {
    "code": "D001",
    "message": "요청이 올바르지 않습니다"
  },
  "fieldErrors": [
    {
      "field": "cancelPrice",
      "value": "-4040000",
      "reason": "0 이상이어야 합니다"
    }
  ]
}

Success Response

  • 취소된 주문의 식별값을 반환
  • Http Status: 200
{
  "orderId": 1
}

특이 사항

  • Entity에 비즈니스 로직을 넣어 Entity가 직접 자신의 정보를 수정하도록 함

◀️ Click! - 주문 내역 조회 요청 및 응답 예시

기능

  • start_date, end_date 사이에 해당하는 회원의 주문 내역을 페이징 처리하여 반환한다.

API

  • GET /api/products

Header

  • x-user-id:greatpeople

Query Param

  • Required
    • start_date
    • end_date

Error Response

  • start_date 또는 end_date의 ISO Date Time Format이 잘못되었을 때
  • GET http://localhost:8080/api/orders?start_date=2022-06-20&end_date=2022-08-21T00:00
  • Http Status: 400
{
  "errorInfo": {
    "code": "D001",
    "message": "요청이 올바르지 않습니다"
  },
  "fieldErrors": [
    {
      "field": "start_date",
      "value": "2022-06-20",
      "reason": "typeMismatch"
    }
  ]
}
  • start_date가 end_date보다 이후의 Date Time 일 때
  • GET http://localhost:8080/api/orders?start_date=9999-12-20&end_date=2022-08-21T00:00
  • Http Status: 400
{
  "errorInfo": {
    "code": "D001",
    "message": "요청이 올바르지 않습니다"
  },
  "fieldErrors": []
}

Success Response

  • GET http://localhost:8080/api/orders?start_date=2022-06-20T00:00&end_date=2022-08-21T00:00
  • Http Status: 200
{
  "content": [
    {
      "orderId": 2,
      "orderHistories": [
        {
          "productName": "문화상품권",
          "productPrice": 50000,
          "orderPrice": 500000,
          "orderQuantity": 10
        }
      ],
      "address": {
        "city": "서울시 송파구",
        "street": "송파대로 567",
        "zipCode": "05503"
      }
    },
    {
      "orderId": 3,
      "orderHistories": [
        {
          "productName": "(아마존)Corsai 벤전스LPX DDR4 데스크톱 메모리 키트 16GB (2x8GB) 블랙(CMK16GX4M2B3200C16)",
          "productPrice": 84270,
          "orderPrice": 84270,
          "orderQuantity": 1
        }
      ],
      "address": {
        "city": "서울시 송파구",
        "street": "송파대로 567",
        "zipCode": "05503"
      }
    },
    {
      "orderId": 4,
      "orderHistories": [
        {
          "productName": "아이패드",
          "productPrice": 800000,
          "orderPrice": 800000,
          "orderQuantity": 1
        },
        {
          "productName": "애플팬슬",
          "productPrice": 110000,
          "orderPrice": 110000,
          "orderQuantity": 1
        }
      ],
      "address": {
        "city": "서울시 송파구",
        "street": "송파대로 567",
        "zipCode": "05503"
      }
    },
    {
      "orderId": 5,
      "orderHistories": [
        {
          "productName": "갤럭시 버즈 프로",
          "productPrice": 330000,
          "orderPrice": 330000,
          "orderQuantity": 1
        }
      ],
      "address": {
        "city": "서울시 송파구",
        "street": "송파대로 567",
        "zipCode": "05503"
      }
    },
    {
      "orderId": 6,
      "orderHistories": [
        {
          "productName": "맥북프로",
          "productPrice": 3400000,
          "orderPrice": 3400000,
          "orderQuantity": 1
        }
      ],
      "address": {
        "city": "서울시 송파구",
        "street": "송파대로 567",
        "zipCode": "05503"
      }
    }
  ],
  "pageable": {
    "sort": {
      "unsorted": true,
      "sorted": false,
      "empty": true
    },
    "pageNumber": 0,
    "pageSize": 5,
    "offset": 0,
    "paged": true,
    "unpaged": false
  },
  "totalPages": 1,
  "totalElements": 5,
  "last": true,
  "numberOfElements": 5,
  "number": 0,
  "first": true,
  "size": 5,
  "sort": {
    "unsorted": true,
    "sorted": false,
    "empty": true
  },
  "empty": false
}

특이 사항

  • spring.jpa.default_batch_fetch_size를 통한 N+1 쿼리 해결

빌드 및 실행 방법

git clone https://github.com/jeremy0405/11st-assignment.git

cd 11st-assignment

chmod +x gradlew
./gradlew build

java -jar build/libs/elevenstreet-0.0.1-SNAPSHOT.jar

h2 console 실행

http://localhost:8080/h2-console

image

위와 같이 설정 후 Connect 하여 DB 확인 가능


Hits

About

11 Super Talent 상품 판매 API

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages