|
| 1 | +# 01-requirements.md - 요구사항 명세 |
| 2 | + |
| 3 | +## 📑 목차 |
| 4 | + |
| 5 | +- [0. 전제 조건 및 제약사항](#0-전제-조건-및-제약사항) |
| 6 | +- [1. 전체 유저 시나리오](#1-전체-유저-시나리오) |
| 7 | +- [2. 도메인별 상세 요구사항](#2-도메인별-상세-요구사항) |
| 8 | + - [2.1 상품](#21-상품) |
| 9 | + - [2.1.1 상품 목록 조회](#211-상품-목록-조회) |
| 10 | + - [2.1.2 상품 상세 조회](#212-상품-상세-조회) |
| 11 | + - [2.1.3 상품 좋아요](#213-상품-좋아요) |
| 12 | + - [2.2 브랜드](#22-브랜드) |
| 13 | + - [2.3 주문](#23-주문) |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## 0. 전제 조건 및 제약사항 |
| 18 | + |
| 19 | +### 0.1 설계 범위 |
| 20 | +- **포함**: 상품 조회, 브랜드 조회, 좋아요, 주문 생성 및 결제 (재고/포인트 차감) |
| 21 | +- **제외**: 회원가입, 포인트 충전 (1주차 구현 완료) |
| 22 | + |
| 23 | +### 0.2 인증/인가 |
| 24 | +- 모든 API는 별도의 인증없이 X-USER-ID 헤더로 사용자 식별 |
| 25 | +- 토큰이 없으면 비회원 사용자 |
| 26 | +- 회원가입 API는 1주차 완료 (설계 범위 제외) |
| 27 | + |
| 28 | +### 0.3 포인트 시스템 |
| 29 | +- 포인트 충전 API는 1주차 완료 (설계 범위 제외) |
| 30 | +- 포인트 조회 및 차감/사용은 주문 과정에서 처리 (설계 필요) |
| 31 | + |
| 32 | +### 0.4 데이터 사전 등록 |
| 33 | +- 상품 데이터는 사전 등록되어 있다고 가정 |
| 34 | +- 브랜드 데이터는 사전 등록되어 있다고 가정 |
| 35 | + |
| 36 | +--- |
| 37 | + |
| 38 | +## 1. 전체 유저 시나리오 |
| 39 | + |
| 40 | +사용자의 서비스 이용 여정: |
| 41 | + |
| 42 | +1. **브랜드 탐색**: 관심있는 브랜드를 조회하여 브랜드 정보를 확인한다. |
| 43 | + |
| 44 | +2. **상품 탐색**: 브랜드별 필터링, 다양한 정렬 조건(최신순/가격순/좋아요순)으로 상품 목록을 탐색하고, 관심 상품의 상세 정보를 확인한다. |
| 45 | + |
| 46 | +3. **관심 표현**: 마음에 드는 상품에 좋아요를 등록하거나 취소하며, 나중에 좋아요한 상품 목록을 다시 확인할 수 있다. |
| 47 | + |
| 48 | +4. **구매 및 확인**: 여러 상품을 한 번에 주문하고, 보유한 포인트로 결제한다. 재고와 포인트가 차감되며, 주문 정보는 외부 시스템으로 전송된다. 나중에 주문 이력을 조회하여 과거 주문 내역을 확인할 수 있다. |
| 49 | + |
| 50 | +--- |
| 51 | + |
| 52 | +## 2. 도메인별 상세 요구사항 |
| 53 | + |
| 54 | +### 2.1 상품 |
| 55 | + |
| 56 | +#### 2.1.1 상품 목록 조회 |
| 57 | + |
| 58 | +##### 유저 시나리오 |
| 59 | + |
| 60 | +사용자 김철수는 새로운 상품을 찾아 쇼핑을 시작한다. |
| 61 | + |
| 62 | +1. 김철수는 상품 목록을 열어 최신순으로 정렬된 상품들을 확인한다. |
| 63 | +2. 특정 브랜드의 상품만 보고 싶어 브랜드를 선택하여 필터링한다. |
| 64 | +3. 가격이 저렴한 순서로 보고 싶어 가격 낮은 순으로 정렬한다. |
| 65 | +4. 한 페이지에 20개씩 상품을 확인하며, 다음 페이지로 이동하여 더 많은 상품을 탐색한다. |
| 66 | + |
| 67 | +##### 기능 요구사항 |
| 68 | + |
| 69 | +- **엔드포인트**: `GET /api/v1/products` |
| 70 | +- **쿼리 파라미터**: |
| 71 | + - `brandId`: 특정 브랜드의 상품만 필터링 (선택) |
| 72 | + - `sort`: 정렬 기준 (기본값: `latest`, 선택: `price_asc`, `likes_desc`) |
| 73 | + - `page`: 페이지 번호 (기본값: 0) |
| 74 | + - `size`: 페이지당 상품 수 (기본값: 20) |
| 75 | +- **응답 데이터**: |
| 76 | + - 상품 ID, 상품명, 가격 |
| 77 | + - 브랜드 정보: ID, 이름 |
| 78 | + - 총 좋아요 수 |
| 79 | + - 현재 사용자의 좋아요 여부 |
| 80 | + - 페이지네이션 정보: 총 개수, 현재 페이지, 전체 페이지 수, 페이지당 개수, 정렬 조건 |
| 81 | + |
| 82 | +##### 제약사항 |
| 83 | + |
| 84 | +- 정렬 기능: 최신순(`latest`), 가격 오름차순(`price_asc`), 좋아요순(`likes_desc`) 구현 |
| 85 | +- 페이지네이션은 반드시 지원해야 함 |
| 86 | + |
| 87 | +--- |
| 88 | + |
| 89 | +#### 2.1.2 상품 상세 조회 |
| 90 | + |
| 91 | +##### 유저 시나리오 |
| 92 | + |
| 93 | +김철수는 상품 목록에서 관심있는 상품을 발견하고 상세 정보를 확인한다. |
| 94 | + |
| 95 | +1. 김철수는 상품을 클릭하여 상세 페이지로 이동한다. |
| 96 | +2. 상품의 이름, 가격, 설명, 브랜드 정보, 재고 수량, 총 좋아요 수를 확인한다. |
| 97 | +3. 해당 상품이 마음에 들어 구매를 결정한다. |
| 98 | + |
| 99 | +##### 기능 요구사항 |
| 100 | + |
| 101 | +- **엔드포인트**: `GET /api/v1/products/{productId}` |
| 102 | +- **경로 파라미터**: |
| 103 | + - `productId`: 조회할 상품의 ID |
| 104 | +- **응답 데이터**: |
| 105 | + - 상품 ID, 상품명, 가격, 상세 설명 |
| 106 | + - 브랜드 정보: ID, 이름 |
| 107 | + - 재고 수량 |
| 108 | + - 총 좋아요 수 |
| 109 | + - 현재 사용자의 좋아요 여부 |
| 110 | + |
| 111 | +##### 제약사항 |
| 112 | + |
| 113 | +- 존재하지 않는 상품 ID 조회 시 적절한 에러 응답 필요 |
| 114 | +- 재고 수량은 정확히 반영되어야 함 |
| 115 | + |
| 116 | +--- |
| 117 | + |
| 118 | +#### 2.1.3 상품 좋아요 |
| 119 | + |
| 120 | +##### 유저 시나리오 |
| 121 | + |
| 122 | +김철수는 마음에 드는 상품에 좋아요를 누르고 관리한다. |
| 123 | + |
| 124 | +1. 김철수는 상품 상세 페이지에서 좋아요 버튼을 클릭한다. |
| 125 | +2. 같은 상품에 실수로 다시 좋아요를 눌렀지만, 중복 등록되지 않는다. |
| 126 | +3. 나중에 생각이 바뀌어 좋아요 취소 버튼을 클릭한다. |
| 127 | +4. 이미 취소된 상태에서 다시 취소 버튼을 눌러도 에러 없이 정상 처리된다. |
| 128 | +5. 관심 상품을 다시 보기 위해 좋아요 목록 페이지로 이동한다. |
| 129 | + |
| 130 | +##### ✅ 정상 흐름 |
| 131 | + |
| 132 | +**좋아요 등록**: |
| 133 | +1. 로그인한 사용자가 상품 상세 페이지에서 좋아요 버튼 클릭 |
| 134 | +2. 시스템이 상품 존재 확인 |
| 135 | +3. 시스템이 좋아요 저장 |
| 136 | +4. 상품의 좋아요 수 증가 |
| 137 | +5. 200 OK 응답 |
| 138 | + |
| 139 | +**좋아요 취소**: |
| 140 | +1. 로그인한 사용자가 좋아요한 상품에서 취소 버튼 클릭 |
| 141 | +2. 시스템이 좋아요 삭제 |
| 142 | +3. 상품의 좋아요 수 감소 |
| 143 | +4. 200 OK 응답 |
| 144 | + |
| 145 | +##### 🔄 경계 케이스 |
| 146 | + |
| 147 | +- **이미 좋아요한 상품에 재등록**: 중복 저장하지 않고 200 OK (멱등성) |
| 148 | +- **이미 취소한 좋아요 재취소**: 에러 없이 200 OK (멱등성) |
| 149 | +- **네트워크 재시도**: 첫 요청 처리 후 재시도 시 멱등하게 동작 |
| 150 | + |
| 151 | +##### ❌ 에러 케이스 |
| 152 | + |
| 153 | +- **상품 없음**: 404 Not Found |
| 154 | +- **미인증 사용자**: 401 Unauthorized |
| 155 | +- **시스템 오류**: 500 Internal Server Error |
| 156 | + |
| 157 | +##### 기능 요구사항 |
| 158 | + |
| 159 | +- **좋아요 등록**: `POST /api/v1/like/products/{productId}` |
| 160 | + - 사용자는 각 상품에 한 번만 좋아요 가능 |
| 161 | + - 중복 등록 시도는 무시 |
| 162 | + - **응답 데이터**: 200 OK |
| 163 | + |
| 164 | +- **좋아요 취소**: `DELETE /api/v1/like/products/{productId}` |
| 165 | + - 이미 취소된 상태에서 재시도 시 정상 응답 |
| 166 | + - **응답 데이터**: 200 OK |
| 167 | + |
| 168 | +- **좋아요 목록 조회**: `GET /api/v1/like/products` |
| 169 | + - **쿼리 파라미터**: |
| 170 | + - `sort`: 정렬 기준 (기본값: `latest` - 최근 좋아요 순, 선택: `product_name`, `price_asc`, `price_desc`) |
| 171 | + - `page`: 페이지 번호 (기본값: 0) |
| 172 | + - `size`: 페이지당 상품 수 (기본값: 20) |
| 173 | + - **응답 데이터**: |
| 174 | + - 현재 사용자가 좋아요한 상품 목록 |
| 175 | + - 상품 기본 정보: ID, 상품명, 가격, 총 좋아요 수 |
| 176 | + - 브랜드 정보: ID, 이름 |
| 177 | + - 페이지네이션 정보: 총 개수, 현재 페이지, 전체 페이지 수, 페이지당 개수, 정렬 조건 |
| 178 | + |
| 179 | +##### 제약사항 |
| 180 | + |
| 181 | +- **멱등성 보장**: 동일한 요청을 여러 번 호출해도 결과는 동일해야 함 |
| 182 | + - 좋아요 등록: 이미 등록된 경우 중복 등록하지 않음 |
| 183 | + - 좋아요 취소: 이미 취소된 경우 에러 없이 정상 응답 |
| 184 | + - **구현 방식**: |
| 185 | + - DB 제약: `UNIQUE(ref_user_id, ref_product_id)` 제약으로 중복 방지 |
| 186 | + - 예외 처리: DB 제약 위반 시 애플리케이션에서 200 OK로 변환 |
| 187 | + - 동시성 보장: 동시 요청에도 DB 제약이 데이터 일관성 보장 |
| 188 | +- 좋아요 수는 상품 목록/상세 조회 시 정확히 반영 |
| 189 | +- X-USER-ID 헤더로 사용자 식별 |
| 190 | +- **대규모 트래픽 고려**: 충분히 많은 사용자가 좋아요를 사용하는 상황을 가정하여 조회 성능 최적화 필요 |
| 191 | + |
| 192 | +--- |
| 193 | + |
| 194 | +### 2.2 브랜드 |
| 195 | + |
| 196 | +#### 2.2.1 브랜드 조회 |
| 197 | + |
| 198 | +##### 유저 시나리오 |
| 199 | + |
| 200 | +김철수는 특정 브랜드에 관심이 생겨 브랜드 정보를 확인한다. |
| 201 | + |
| 202 | +1. 김철수는 상품 목록에서 본 브랜드 이름을 클릭하여 브랜드 페이지로 이동한다. |
| 203 | +2. 브랜드의 이름과 설명을 확인한다. |
| 204 | +3. 해당 브랜드가 마음에 들어 해당 브랜드의 상품만 필터링하여 탐색한다. |
| 205 | + |
| 206 | +##### 기능 요구사항 |
| 207 | + |
| 208 | +- **엔드포인트**: `GET /api/v1/brands/{brandId}` |
| 209 | +- **경로 파라미터**: |
| 210 | + - `brandId`: 조회할 브랜드의 ID |
| 211 | +- **응답 데이터**: |
| 212 | + - 브랜드 ID, 브랜드명 |
| 213 | + - 브랜드 설명 |
| 214 | + |
| 215 | +##### 제약사항 |
| 216 | + |
| 217 | +- 존재하지 않는 브랜드 ID 조회 시 적절한 에러 응답 필요 |
| 218 | + |
| 219 | +--- |
| 220 | + |
| 221 | +### 2.3 주문 |
| 222 | + |
| 223 | +#### 2.3.1 주문 생성 및 결제 |
| 224 | + |
| 225 | +##### 유저 시나리오 |
| 226 | + |
| 227 | +김철수는 여러 상품을 선택하여 주문한다. |
| 228 | + |
| 229 | +1. 김철수는 구매하려는 상품들을 선택하고 주문하기 버튼을 클릭한다. |
| 230 | + - 상품 A: 2개 |
| 231 | + - 상품 B: 1개 |
| 232 | + |
| 233 | +2. 시스템은 재고를 확인하고, 포인트를 차감하여 주문을 완료한다. |
| 234 | + |
| 235 | +3. 주문 완료 페이지에서 주문 번호와 주문 내역을 확인한다. |
| 236 | + |
| 237 | +4. 김철수는 나중에 자신의 주문 목록을 조회한다. |
| 238 | + |
| 239 | +5. 특정 주문을 선택하여 상세 내역을 다시 확인할 수 있다. |
| 240 | + |
| 241 | +##### ✅ 정상 흐름 |
| 242 | + |
| 243 | +1. 로그인한 사용자가 주문할 상품과 수량 선택 |
| 244 | +2. 시스템이 각 상품 재고 확인 |
| 245 | +3. 시스템이 총 결제 금액 계산 |
| 246 | +4. 시스템이 사용자 포인트 잔액 확인 |
| 247 | +5. 시스템이 재고 차감 |
| 248 | +6. 시스템이 포인트 차감 |
| 249 | +7. 시스템이 주문 정보 저장 |
| 250 | +8. 시스템이 주문 정보를 외부 시스템에 전송 |
| 251 | +9. 201 Created 응답 |
| 252 | + |
| 253 | +##### 🔄 경계 케이스 |
| 254 | + |
| 255 | +- **결제 처리 실패**: 재시도 또는 상태 마킹 후 수동 처리 |
| 256 | +- **주문 수량 0 또는 음수**: 400 Bad Request |
| 257 | + |
| 258 | +##### ❌ 에러 케이스 |
| 259 | + |
| 260 | +- **재고 부족**: 400 Bad Request |
| 261 | +- **포인트 부족**: 400 Bad Request |
| 262 | +- **상품 없음**: 404 Not Found |
| 263 | +- **미인증 사용자**: 401 Unauthorized |
| 264 | +- **시스템 오류**: 500 Internal Server Error, 트랜잭션 롤백 |
| 265 | +- **결제 처리 실패**: |
| 266 | + - 주문은 이미 DB에 저장 완료 (재고/포인트 차감 완료, 트랜잭션 커밋됨) |
| 267 | + - 자동 재시도 3회 (1초, 2초, 4초 간격, 지수 백오프) |
| 268 | + - 재시도 실패 시 주문 상태를 '결제 대기'로 마킹 |
| 269 | + - 500 Internal Server Error 응답 |
| 270 | + |
| 271 | +##### 기능 요구사항 |
| 272 | + |
| 273 | +- **주문 생성**: `POST /api/v1/orders` |
| 274 | + - **요청 바디**: |
| 275 | + ```json |
| 276 | + { |
| 277 | + "items": [ |
| 278 | + { "productId": 1, "quantity": 2 }, |
| 279 | + { "productId": 3, "quantity": 1 } |
| 280 | + ] |
| 281 | + } |
| 282 | + ``` |
| 283 | + - **처리 흐름**: |
| 284 | + 1. 각 상품의 재고 확인 |
| 285 | + 2. 총 결제 금액 계산 |
| 286 | + 3. 사용자 포인트 확인 |
| 287 | + 4. 재고 차감 |
| 288 | + 5. 포인트 차감 |
| 289 | + 6. 주문 정보 저장 |
| 290 | + 7. 외부 시스템 전송 (Mock 가능) |
| 291 | + - **응답 데이터**: |
| 292 | + - 주문 ID, 주문 일시 |
| 293 | + - 주문 상품 목록: 상품 ID, 상품명, 수량, 가격 |
| 294 | + - 총 결제 금액 |
| 295 | + |
| 296 | +- **주문 목록 조회**: `GET /api/v1/orders` |
| 297 | + - **쿼리 파라미터**: |
| 298 | + - `page`: 페이지 번호 (기본값: 0) |
| 299 | + - `size`: 페이지당 주문 수 (기본값: 20) |
| 300 | + - **응답 데이터**: |
| 301 | + - 주문 ID, 주문 일시, 총 금액 |
| 302 | + - 주문 상품 수 |
| 303 | + - 페이지네이션 정보: 총 개수, 현재 페이지, 전체 페이지 수, 페이지당 개수 |
| 304 | + |
| 305 | +- **주문 상세 조회**: `GET /api/v1/orders/{orderId}` |
| 306 | + - **응답 데이터**: |
| 307 | + - 주문 ID, 주문 일시 |
| 308 | + - 주문 상품 목록: 상품 ID, 상품명, 수량, 가격 |
| 309 | + - 총 결제 금액 |
| 310 | + |
| 311 | +##### 제약사항 |
| 312 | + |
| 313 | +- **트랜잭션 보장**: 재고 차감과 포인트 차감은 원자적으로 처리되어야 함 |
| 314 | + - 둘 중 하나라도 실패하면 전체 롤백 |
| 315 | + |
| 316 | +- **재고 부족**: 주문 수량이 재고보다 많을 경우 주문 실패 |
| 317 | + |
| 318 | +- **포인트 부족**: 사용자의 보유 포인트가 총 결제 금액보다 적을 경우 주문 실패 |
| 319 | + |
| 320 | +- **외부 시스템 연동**: 주문 정보 전송은 Mock으로 처리 가능 |
0 commit comments