From 591ab05d75c47375ce9fd01f0e0677e6521fc514 Mon Sep 17 00:00:00 2001 From: Avocado Date: Fri, 13 Feb 2026 00:17:08 +0900 Subject: [PATCH 1/7] =?UTF-8?q?docs:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=AA=85=EC=84=B8=EC=84=9C=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(=EB=B8=8C=EB=9E=9C=EB=93=9C,=20=EC=83=81=ED=92=88,=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94,=20=EC=9E=A5=EB=B0=94=EA=B5=AC=EB=8B=88,=20?= =?UTF-8?q?=EC=A3=BC=EB=AC=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .docs/design/01-requirements.md | 260 ++++++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 .docs/design/01-requirements.md diff --git a/.docs/design/01-requirements.md b/.docs/design/01-requirements.md new file mode 100644 index 00000000..e7a9fd95 --- /dev/null +++ b/.docs/design/01-requirements.md @@ -0,0 +1,260 @@ +# 요구사항 정리 + +> 본 문서는 감성 이커머스 서비스의 브랜드, 상품, 좋아요, 장바구니, 주문 도메인에 대한 요구사항을 유저 시나리오와 비즈니스 규칙 중심으로 정리한 결과물이다. + +--- + +## 1. 목적 및 범위 + +### 목적 + +고객이 브랜드별 상품을 탐색하고, 마음에 드는 상품에 좋아요를 표시하고, 장바구니에 담아 주문할 수 있는 이커머스 서비스를 구축한다. 어드민은 브랜드와 상품을 관리하고, 주문 현황을 파악한다. + +### 범위 + +본 문서가 다루는 범위는 다음 도메인으로 **한정**한다. + +- **브랜드** — 상품을 묶는 그룹 단위. 어드민이 관리하고, 고객이 조회한다. +- **상품** — 고객이 탐색하고 주문하는 대상. 가격과 재고를 가진다. +- **좋아요** — 고객이 상품에 대한 관심을 표현하는 행위. +- **장바구니** — 고객이 구매 전 상품을 담아두는 행위. +- **주문** — 고객이 상품을 구매하는 행위. 주문 시점의 상품 정보가 보존된다. + +### 액터 + +- **고객(User)** — 상품을 탐색하고, 좋아요를 누르고, 장바구니에 담고, 주문한다. 로그인이 필요한 행위와 필요 없는 행위가 구분된다. +- **어드민(Admin)** — 브랜드와 상품을 등록·수정·삭제하고, 전체 주문 현황을 조회한다. + + +--- + +## 2. 유저 시나리오 + +### 2.1 고객 — 상품 탐색 + +> 고객은 브랜드와 상품 정보를 자유롭게 탐색하여 구매 의사결정을 내린다. + +**시나리오** + +1. 고객이 특정 브랜드의 정보를 조회한다. +2. 고객이 상품 목록을 조회한다. 브랜드별로 필터링하거나, 다양한 기준으로 정렬할 수 있다. +3. 고객이 특정 상품의 상세 정보를 조회한다. + +**드러나는 행위** + +- 상품 탐색은 **로그인 없이** 가능하다 (비회원도 조회 가능). +- 상품 목록은 **페이지 단위**로 제공된다. +- 정렬 기준: 최신순(기본), 가격 낮은 순, 가격 높은 순, 인기순(좋아요 많은 순). + +--- + +### 2.2 고객 — 좋아요 + +> 고객은 마음에 드는 상품에 좋아요를 표시하고, 이를 취소할 수 있다. 자신이 좋아요한 상품 목록을 확인할 수 있다. + +**시나리오** + +1. 고객이 마음에 드는 상품에 좋아요를 누른다. +2. 고객이 이미 좋아요한 상품의 좋아요를 취소한다. +3. 고객이 자신이 좋아요한 상품 목록을 조회한다. + +**드러나는 행위** + +- 좋아요는 **로그인한 고객만** 가능하다. +- 한 고객은 하나의 상품에 **한 번만** 좋아요할 수 있다. +- 좋아요 목록은 **본인의 것만** 조회할 수 있다. + +--- + +### 2.3 고객 — 장바구니 + +> 고객은 구매할 상품을 장바구니에 담고, 수량·옵션을 수정하거나 품목을 삭제할 수 있다. + +**시나리오** + +1. 고객이 상품과 옵션·수량을 선택하여 장바구니에 담는다. 이미 담긴 동일 상품·옵션이 있으면 수량이 합산된다. +2. 고객이 장바구니에 담긴 상품 목록을 조회한다. +3. 고객이 장바구니 품목의 수량·옵션을 수정한다. +4. 고객이 장바구니 품목을 개별 또는 선택 삭제한다. + +**드러나는 행위** + +- 장바구니는 **로그인한 고객만** 사용 가능하다. +- 동일 상품·동일 옵션 추가 시 **수량 합산**한다. +- 조회 시점의 최신 상품 정보(가격, 품절 여부, 재고량 등)를 반영한다. + +--- + +### 2.4 고객 — 주문 + +> 고객은 여러 상품을 한 번에 주문한다. 주문 후 자신의 주문 내역을 조회할 수 있으며, 취소할 수 있다. + +**시나리오** + +1. 고객이 원하는 상품들과 수량을 선택하여 주문한다. +2. 고객이 특정 기간 내 자신의 주문 목록을 조회한다. **시작일과 종료일을 반드시 지정**해야 한다. +3. 고객이 특정 주문의 상세 내역(어떤 상품을, 얼마에, 몇 개 샀는지)을 조회한다. +4. 고객이 자신의 주문을 취소한다. + +**드러나는 행위** + +- 주문은 **로그인한 고객만** 가능하다. +- 하나의 주문에 **여러 상품**을 담을 수 있다 (상품 + 수량의 목록). +- 주문 시점의 상품 정보(이름, 가격 등)가 **스냅샷으로 보존**된다. +- 주문 목록 조회 시 **시작일과 종료일을 반드시 지정**해야 한다. +- 고객은 **본인의 주문만** 조회·취소할 수 있다. +- 결제 미완료: 즉시 취소. 결제 완료: 취소 시 재고 복구. 배송 시작 후: 취소 불가. + +--- + +### 2.5 어드민 — 브랜드 관리 + +> 어드민은 이커머스에서 판매할 브랜드를 등록하고, 수정하고, 삭제하고, 조회한다. + +**시나리오** + +1. 어드민이 새로운 브랜드를 등록한다. +2. 어드민이 브랜드 목록을 페이지 단위로 조회한다. +3. 어드민이 특정 브랜드의 상세 정보를 조회한다. +4. 어드민이 브랜드 정보를 수정한다. +5. 어드민이 브랜드를 삭제한다. 이때 해당 브랜드의 **모든 상품도 함께 삭제(연쇄 삭제)**된다. + +--- + +### 2.6 어드민 — 상품 관리 + +> 어드민은 브랜드에 속한 상품을 등록하고, 수정하고, 삭제하고, 조회한다. + +**시나리오** + +1. 어드민이 특정 브랜드에 속하는 상품을 등록한다. +2. 어드민이 상품 목록을 페이지 단위로 조회한다. 브랜드별 필터링이 가능하다. +3. 어드민이 특정 상품의 상세 정보를 조회한다. +4. 어드민이 상품 정보를 수정한다. 단, **상품의 소속 브랜드는 변경할 수 없다**. +5. 어드민이 상품을 삭제한다. + +--- + +### 2.7 어드민 — 주문 조회 + +> 어드민은 전체 주문 현황을 파악하고, 개별 주문의 상세 내역을 확인한다. + +**시나리오** + +1. 어드민이 전체 주문 목록을 페이지 단위로 조회한다. +2. 어드민이 특정 주문의 상세 내역(주문자 정보 + 주문 상품 스냅샷)을 조회한다. + +**드러나는 행위** + +- 어드민은 **모든 고객의 주문**을 조회할 수 있다. + +--- + +## 3. 비즈니스 규칙 + +### 3.1 재고 + +| 규칙 | 설명 | +|------|------| +| 재고 충분성 | 주문하려는 모든 상품의 재고가 주문 수량 이상이어야 한다 | +| All or Nothing | 주문 내 하나의 상품이라도 재고가 부족하면 전체 주문이 실패한다 | +| 재고 차감 시점 | 주문이 성공하면 즉시 재고가 차감된다 | +| 동시성 보장 | 동시에 같은 상품을 주문하더라도 재고가 음수가 되어서는 안 된다. 비관적 락으로 보장한다 | + +### 3.2 주문 + +| 규칙 | 설명 | +|------|------| +| 주문 항목 필수 | 주문에는 최소 하나 이상의 상품이 포함되어야 한다 | +| 삭제된 상품 주문 불가 | 삭제된 상품은 주문할 수 없다 | +| 스냅샷 보존 | 주문 시점의 상품 정보(이름, 가격 등)가 주문 항목에 스냅샷으로 저장된다 | +| 주문 초기 상태 | 주문 생성 시 초기 상태는 ORDERED. 결제·취소 시 확장 | +| 주문 조회 기간 | 고객의 주문 목록 조회 시 **시작일과 종료일을 반드시 지정**해야 한다 | +| 주문 취소 | 결제 미완료 즉시 취소, 결제 완료 시 재고 복구, 배송 시작 후 취소 불가 | + +### 3.3 좋아요 + +| 규칙 | 설명 | +|------|------| +| 1인 1좋아요 | 한 고객은 하나의 상품에 한 번만 좋아요할 수 있다. 중복 시 실패한다 | +| 삭제된 상품 좋아요 불가 | 삭제된 상품에는 좋아요를 할 수 없다 | +| 좋아요 취소 전제 | 좋아요가 존재해야만 취소할 수 있다 | + +### 3.4 장바구니 + +| 규칙 | 설명 | +|------|------| +| 동일 품목 합산 | 동일 상품·동일 옵션이 있으면 수량을 합산한다 | +| 상품 유효성 | 추가·수정 시 상품의 판매 상태 및 재고를 확인한다 | +| 본인 소유 | 고객은 본인의 장바구니만 조회·수정·삭제할 수 있다 | + +### 3.5 브랜드-상품 관계 + +| 규칙 | 설명 | +|------|------| +| 브랜드 필수 소속 | 상품은 반드시 하나의 브랜드에 속해야 한다 | +| 유효한 브랜드만 | 상품 등록 시 지정하는 브랜드는 이미 등록되어 있고 삭제되지 않은 브랜드여야 한다 | +| 브랜드 변경 불가 | 상품이 등록된 후에는 소속 브랜드를 변경할 수 없다 | +| 브랜드 삭제 연쇄 | 브랜드를 삭제하면 해당 브랜드의 **모든 상품도 함께 삭제(연쇄 삭제)**된다 | + +### 3.6 접근 권한 + +| 규칙 | 설명 | +|------|------| +| 고객 주문 접근 | 고객은 본인의 주문만 조회·취소할 수 있다. 타인 접근 시 "존재하지 않음"으로 응답한다 | +| 어드민 주문 접근 | 어드민은 모든 고객의 주문을 조회할 수 있다 | +| 좋아요 목록 접근 | 고객은 본인의 좋아요 목록만 조회할 수 있다 | +| 상품 탐색 접근 | 상품과 브랜드 조회는 로그인 없이 누구나 가능하다 | + +### 3.7 정렬 + +| 규칙 | 설명 | +|------|------| +| 상품 정렬 기준 | 최신순, 가격 낮은 순, 가격 높은 순, 인기순(좋아요 많은 순)을 지원한다 | +| 기본 정렬 | 정렬 기준을 지정하지 않으면 최신순으로 정렬된다 | + +--- + +## 4. 예외 및 정책 + +### 4.1 삭제 정책 + +| 도메인 | 방식 | 설명 | +|--------|------|------| +| **상품(Product)** | Soft delete | 주문·영수증·CS에서 상품 이력 필요. 물리 삭제 금지 | +| **브랜드(Brand)** | Soft delete | 삭제 시 해당 상품 연쇄 삭제(soft) | +| **주문(Order)** | 물리 삭제 금지 | 취소는 상태값(ORDER_STATUS=CANCELLED)으로 처리 | +| **좋아요(Like)** | Hard delete | 현재 좋아요 수/여부만 필요. 레코드 DELETE | +| **장바구니(CartItem)** | Hard delete | 주문 스냅샷으로 내용 보존. 레코드 DELETE | + +### 4.2 인증 정책 + +- 고객: `X-Loopers-LoginId` 헤더로 식별. +- 어드민: `X-Loopers-Ldap` 헤더로 식별. +- 인증 필요 API에서 헤더가 없거나 유효하지 않으면 **401 Unauthorized**로 응답한다. + +### 4.3 존재하지 않는 자원 접근 + +- 존재하지 않거나 삭제된 자원에 대한 조회/수정/삭제 요청은 **404 NOT_FOUND**로 응답한다. +- 타인의 자원에 접근하는 경우에도 동일하게 **404 NOT_FOUND**로 응답한다 (자원 존재 여부 노출 방지). + +### 4.4 페이징 정책 + +- 목록 조회는 페이지 단위로 제공한다. +- 기본값: 페이지 번호 0, 페이지당 20건. + +### 4.5 주문 상태 정책 + +- 주문 생성 시 ORDERED 상태. +- 결제·배송 도메인 추가 시 PAID, SHIPPING, DELIVERED, CANCELLED 등으로 확장. +- 취소 시: ORDER_STATUS = CANCELLED, 필요 시 재고 복구. + +--- + +## 5. 범위 제외 사항 + +| 제외 항목 | 사유 | +|-----------|------| +| 결제(Payment) | 향후 별도 단계에서 추가 개발 예정 | +| 쿠폰(Coupon) | 향후 별도 단계에서 추가 개발 예정 | +| 포인트(Point) | 향후 별도 단계에서 추가 개발 예정 | \ No newline at end of file From 10010b1954c77b6356831d443d1c7fc764c2bcf8 Mon Sep 17 00:00:00 2001 From: Avocado Date: Fri, 13 Feb 2026 02:06:20 +0900 Subject: [PATCH 2/7] =?UTF-8?q?docs:=20=EC=9C=A0=EB=B9=84=EC=BF=BC?= =?UTF-8?q?=ED=84=B0=EC=8A=A4=20=EC=96=B8=EC=96=B4=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AGENTS.md 참조 반영 - 01-requirements.md 참조 반영 --- .docs/design/00-ubiquitous-language.md | 55 ++++++++++++++++++++++++++ .docs/design/01-requirements.md | 3 +- AGENTS.md | 1 + 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 .docs/design/00-ubiquitous-language.md diff --git a/.docs/design/00-ubiquitous-language.md b/.docs/design/00-ubiquitous-language.md new file mode 100644 index 00000000..a9391ed0 --- /dev/null +++ b/.docs/design/00-ubiquitous-language.md @@ -0,0 +1,55 @@ +# 유비쿼터스 언어 (Ubiquitous Language) + +> 모든 협업자가 **동일한 언어로 도메인을 이해하고 소통**하기 위한 전략적 용어 체계이다. +> 요구사항(01), 시퀀스(02), 클래스 다이어그램(03), ERD(04), 코드, API 명세는 **이 문서의 용어를 기준**으로 작성한다. + +--- + +## 1. 목적 + +- 용어 불일치로 인한 설계 오류를 줄인다 (예: '상품' vs '아이템' 혼용 방지). +- 기획자, 디자이너, 개발자, QA가 같은 단어로 같은 개념을 이해한다. +- 코드와 문서 간 용어 정합성을 유지한다 (코드 = 모델, 문서 = 명세). + +--- + +## 2. 도메인 용어 표 + +| 한글 | 영문 (코드·API·ERD) | 비고 | +|------|---------------------|------| +| 상품 | **Product** | 도메인·패키지·클래스·API 경로 | +| 브랜드 | **Brand** | 동일 | +| 좋아요 | **Like** | 동일 | +| 주문 | **Order** | 동일 | +| 장바구니 | **Cart** | 동일 (예외: 복수형 없음) | +| 고객/회원 | **User** | 액터·인증 맥락 | +| 어드민 | **Admin** | 액터·권한 맥락 | + +### 도메인·패키지·API 네이밍 규칙 + +- **도메인(복수)**: Cart 제외하고 **도메인 + s** 형식으로 통일한다. + - Products, Brands, Likes, Orders + - Cart는 단수만 사용 (Carts 사용하지 않음) +- **클래스/엔티티**: 단수형 (Product, Brand, Like, Order, Cart, User). + +--- + +## 3. 적용 방법 + +1. **기능/요구사항 문서**, **ERD**, **API 명세서**, **클래스/패키지명**에 위 표의 영문을 그대로 사용한다. +2. **새 용어 도입 전**에는 반드시 이 문서에 정의와 맥락을 추가하고 팀에 공유한다. +3. 한글 문서(요구사항 등)에서 개념을 설명할 때 괄호로 영문을 붙일 경우, 이 표와 동일한 단어를 쓴다. + +--- + +## 4. 자주 겪는 실수 + +- 도메인마다 표현을 다르게 쓰는 것 (예: 한 곳은 '상품', 다른 곳은 '아이템'). +- 코드에서는 영문/축약어, 기획서에서는 한글/다른 단어를 혼용하는 것. +- enum·상태 값에 의미 없는 이름 사용 (예: 상태1, 상태2) → 도메인에서 쓰는 상태명을 그대로 사용할 것. + +--- + +## 5. 변경 이력 + +- 용어 추가·수정 시 이 섹션에 날짜와 변경 내용을 남긴다. diff --git a/.docs/design/01-requirements.md b/.docs/design/01-requirements.md index e7a9fd95..0bde943f 100644 --- a/.docs/design/01-requirements.md +++ b/.docs/design/01-requirements.md @@ -1,6 +1,7 @@ # 요구사항 정리 -> 본 문서는 감성 이커머스 서비스의 브랜드, 상품, 좋아요, 장바구니, 주문 도메인에 대한 요구사항을 유저 시나리오와 비즈니스 규칙 중심으로 정리한 결과물이다. +> 본 문서는 감성 이커머스 서비스의 브랜드, 상품, 좋아요, 장바구니, 주문 도메인에 대한 요구사항을 유저 시나리오와 비즈니스 규칙 중심으로 정리한 결과물이다. +> **용어 정의**: 한글↔영문 도메인 용어는 [00-ubiquitous-language.md](./00-ubiquitous-language.md)를 따른다. --- diff --git a/AGENTS.md b/AGENTS.md index 3f42db42..9007f566 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -211,6 +211,7 @@ Example: signUp_withDuplicateId_shouldFail() 3. **Document-Driven Changes**: - For structural changes (new module, layer, or pattern), update relevant docs FIRST - Ensure `.codeguide/`, `README.md`, and this `AGENTS.md` stay synchronized + - **유비쿼터스 언어**: 도메인 용어는 `.docs/design/00-ubiquitous-language.md`를 기준으로 하며, 코드·API·문서에 동일한 단어를 사용한다. ### Branch & PR Strategy From 92058e693c4e490251956dd825f47661f3ff1441 Mon Sep 17 00:00:00 2001 From: Avocado Date: Fri, 13 Feb 2026 02:09:03 +0900 Subject: [PATCH 3/7] =?UTF-8?q?docs:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B6=84=EC=84=9D=20=EC=8A=A4=ED=82=AC(SKILL.md)?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/requirements-analysis/SKILL.md | 77 +++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 skills/requirements-analysis/SKILL.md diff --git a/skills/requirements-analysis/SKILL.md b/skills/requirements-analysis/SKILL.md new file mode 100644 index 00000000..a2f14602 --- /dev/null +++ b/skills/requirements-analysis/SKILL.md @@ -0,0 +1,77 @@ +--- +name: requirements-analysis +description: + 제공된 요구사항을 분석하고, 개발자와의 질문/대답을 통해 애매한 요구사항을 명확히 하여 정리합니다. + 모든 정리가 끝나면, 시퀀스 다이어그램, 클래스 다이어그램, ERD 등을 Mermaid 문법으로 작성한다. + 요구사항이 제공되었을 때, 코드를 작성하기 전 이를 명확히 하는 데에 사용합니다. +--- +요구사항을 분석할 때 반드시 다음 흐름을 따른다. +### 1️⃣ 요구사항을 그대로 믿지 말고, 문제 상황으로 다시 설명한다. +- 요구사항 문장을 정리하는 데서 끝내지 않는다. +- "무엇을 만들까?"가 아니라 "지금 어떤 문제가 있고, 그걸 왜 해결하려는가?" 로 재해석한다. +- 다음 관점을 분리해서 정리한다: + - 사용자 관점 + - 비즈니스 관점 + - 시스템 관점 +> 예시 +> "주문 실패 시 결제를 취소한다" → "결제 성공/실패와 주문 상태가 어긋나지 않도록 일관성을 유지하려는 문제" + +### 2️⃣ 애매한 요구사항을 숨기지 말고 드러낸다 +- 추측하거나 알아서 결정하지 않는다. +- 요구사항에서 결정되지 않은 부분을 명시적으로 나열한다. +**다음 유형의 질문을 반드시 포함한다:** +- 정책 질문: 기준 시점, 성공/실패 조건, 예외 처리 규칙 +- 경계 질문: 어디까지가 한 책임인가, 어디서 분리되는가 +- 확장 질문: 나중에 바뀔 가능성이 있는가 + +### 3️⃣ 요구사항 명확화를 위한 질문을 개발자 답변이 쉬운 형태로 제시한다 +- 질문은 우선순위를 가진다 (중요한 것부터). +- 선택지가 있는 경우, 옵션 + 영향도를 함께 제시한다. +> 형식 예시: +- 선택지 A: 하나의 트랜잭션으로 처리 → 구현 단순, 확장성 낮음 +- 선택지 B: 단계별 분리 → 구조 복잡, 확장/보상 처리 유리 + +### 4️⃣ 합의된 내용을 바탕으로 개념 모델부터 잡는다 +- 바로 코드나 기술 얘기로 들어가지 않는다. +- 먼저 다음을 정의한다: + - 액터 (사용자, 외부 시스템) + - 핵심 도메인 + - 보조/외부 시스템 +- 이 단계는 “구현”이 아니라 설계 사고 정렬이 목적이다. + +### 5️⃣ 다이어그램은 항상 이유 → 다이어그램 → 해석 순서로 제시한다 +**다이어그램을 그리기 전에 반드시 설명한다** +- 왜 이 다이어그램이 필요한지 +- 이 다이어그램으로 무엇을 검증하려는지 + +**다이어그램은 Mermaid 문법으로 작성한다** +사용 기준: +- **시퀀스 다이어그램** + - 책임 분리 + - 호출 순서 + - 트랜잭션 경계 확인 +- **클래스 다이어그램** + - 도메인 책임 + - 의존 방향 + - 응집도 확인 +- **ERD** + - 영속성 구조 + - 관계의 주인 + - 정규화 여부 + +### 6️⃣ 다이어그램을 던지고 끝내지 말고 읽는 법을 짚어준다 +- "이 구조에서 특히 봐야 할 포인트"를 2~3줄로 설명한다. +- 설계 의도가 드러나도록 해석을 붙인다. + +### 7️⃣ 설계의 잠재 리스크를 반드시 언급한다 +- 현재 설계가 가질 수 있는 위험을 숨기지 않는다. + - 트랜잭션 비대화 + - 도메인 간 결합도 증가 + - 정책 변경 시 영향 범위 확대 +- 해결책은 정답처럼 말하지 않고 선택지로 제시한다. + +### 톤 & 스타일 가이드 +- 강의처럼 설명하지 말고 설계 리뷰 톤을 유지한다 +- 정답이라고 제시하기보다, 다른 선택지가 있다면 이를 제공하도록 한다. +- 코드보다 의도, 책임, 경계를 더 중요하게 다룬다 +- 구현 전에 생각해야 할 것을 끌어내는 데 집중한다 \ No newline at end of file From 652417eac8b983b33c16dbeae837a8a33da8d693 Mon Sep 17 00:00:00 2001 From: Avocado Date: Fri, 13 Feb 2026 03:12:19 +0900 Subject: [PATCH 4/7] =?UTF-8?q?docs:=20=EC=8B=9C=ED=80=80=EC=8A=A4=20?= =?UTF-8?q?=EB=8B=A4=EC=9D=B4=EC=96=B4=EA=B7=B8=EB=9E=A8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Brands · Products — 상품 등록 (Product ↔ Brand) - Products — 상품 수정 (관리자 권한, 브랜드 검증) - Likes — 좋아요 등록 (Product ↔ Likes) - Cart — 장바구니 옵션 수정 (Cart ↔ Product) - Orders — 주문 생성 (Order ↔ Product, 재고 확인/스냅샷) - Orders — 주문 취소 (Order ↔ Product, 상태 검증) --- .docs/design/02-sequence-diagrams.md | 328 +++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 .docs/design/02-sequence-diagrams.md diff --git a/.docs/design/02-sequence-diagrams.md b/.docs/design/02-sequence-diagrams.md new file mode 100644 index 00000000..aff93a10 --- /dev/null +++ b/.docs/design/02-sequence-diagrams.md @@ -0,0 +1,328 @@ +# 시퀀스 다이어그램 (도메인: Brands, Products, Likes, Cart, Orders) + +> 본 문서는 **책임 분리**, **호출 순서**, **트랜잭션 경계** 확인을 위해 시퀀스 다이어그램을 사용한다. +> 각 다이어그램은 **이유 → 다이어그램 → 해석 → 잠재 리스크** 순서로 제시한다. +> 참조: [00-ubiquitous-language.md](./00-ubiquitous-language.md) + +--- + +## 1. Brands · Products — 상품 등록 (Product ↔ Brand) + +- 상품 등록 시 Brands 도메인(브랜드)과의 경계, Controller → Facade → Service 호출 순서, 트랜잭션 경계가 한 요청 안에서 어떻게 유지되는지 검증하기 위함. +- 브랜드 유효성(존재·미삭제) 검사 후 상품 생성이 단일 트랜잭션으로 이루어지는지, 실패 시 롤백이 기대대로 동작하는지 검증하기 위함. + +### 다이어그램 + +```mermaid +sequenceDiagram + participant Admin + participant ProductController + participant ProductFacade + participant ProductService + participant BrandRepository + participant ProductRepository + + Admin->>ProductController: 상품 등록 요청 + ProductController->>ProductFacade: register(request) + Note over ProductFacade: @Transactional + ProductFacade->>ProductService: register(request) + + ProductService->>BrandRepository: findById(brandId) + BrandRepository-->>ProductService: Brand or empty + alt 브랜드 미존재 또는 삭제됨(soft delete) + ProductService-->>ProductFacade: 예외(BAD_REQUEST / NOT_FOUND) + end + + ProductService->>ProductService: Product 생성 + ProductService->>ProductRepository: save(product) + ProductRepository-->>ProductService: saved Product + ProductService-->>ProductFacade: ProductInfo + ProductFacade-->>ProductController: ProductInfo + ProductController-->>Admin: 201 Created +``` + +### 해석 + +- **봐야 할 포인트**: 트랜잭션은 Facade에서 시작되므로, 브랜드 조회·상품 생성·저장이 한 단위로 커밋/롤백된다. 브랜드가 없거나 삭제된 경우 예외로 빠져 나가며 DB 변경 없이 끝난다. +- **설계 의도**: 상품은 반드시 유효한 브랜드에만 속한다는 도메인 규칙을 Service에서 한 번에 검증·생성하도록 했다. + +### 잠재 리스크 + +- **리스크**: BrandRepository가 soft delete를 구분하지 않으면, 삭제된 브랜드에 상품이 등록될 수 있다. +- **선택지**: (A) Repository에 `findByIdAndNotDeleted` 등 삭제 제외 조회를 두고 사용. (B) Service에서 조회된 Brand 엔티티의 삭제 플래그를 검사 후 진행. + +--- + +## 2. Products — 상품 수정 (관리자 권한, 브랜드 검증) + +- 어드민 상품 수정 시 권한 검증이 support에서 선행되고, 그 다음 도메인(Products)만 다루는지 호출 순서를 명확히 하기 위함. +- 인증/권한이 Controller 이전(support)에서 처리되는지, 브랜드 변경 불가·삭제된 상품 수정 불가가 Service에서 일관되게 적용되는지 검증하기 위함. + +### 다이어그램 + +```mermaid +sequenceDiagram + participant Admin + participant Support as support (권한) + participant ProductController + participant ProductFacade + participant ProductService + participant ProductRepository + + Admin->>Support: 요청 (X-Loopers-Ldap) + Support->>Support: @AdminOnly / 인터셉터 검증 + Support->>ProductController: 인증 통과 + ProductController->>ProductFacade: update(productId, request) + Note over ProductFacade: @Transactional + ProductFacade->>ProductService: update(productId, request) + + ProductService->>ProductRepository: findById(productId) + ProductRepository-->>ProductService: Product or empty + alt 상품 미존재 또는 삭제됨 + ProductService-->>ProductFacade: 예외(NOT_FOUND) + end + + ProductService->>ProductService: 브랜드 변경 포함 여부 확인 + alt 브랜드 변경 시도 + ProductService-->>ProductFacade: 예외(BAD_REQUEST) + end + + ProductService->>ProductService: 입력 형식 검증 및 상품 정보 갱신 + ProductService->>ProductRepository: save(product) + ProductService-->>ProductFacade: ProductInfo + ProductFacade-->>ProductController: ProductInfo + ProductController-->>Admin: 200 OK +``` + +### 해석 + +- **봐야 할 포인트**: 권한 실패 시 Support 단에서 차단되므로 ProductController·Facade·Service는 호출되지 않는다. 상품 수정 비즈니스 규칙(브랜드 변경 불가, 삭제된 상품 404)은 모두 Service에만 있다. +- **설계 의도**: 권한은 support, 리소스는 각 도메인에 맞게, Products 도메인은 “수정 가능 여부”만 판단하고 “누가 요청했는지”는 보지 않는다. + +### 잠재 리스크 + +- **리스크**: Support와 도메인 레이어의 테스트가 분리되어 있어, 권한 실패 + 상품 수정 실패 조합 시나리오를 E2E에서만 검증하게 될 수 있다. +- **선택지**: (A) 어드민 E2E에서 권한 없음 → 401, 권한 있음 + 잘못된 상품 → 404를 각각 한 번씩 검증. (B) Support 권한 필터/인터셉터 단위 테스트로 401 케이스를 고정하고, 도메인 E2E는 유효한 어드민 헤더만 사용. + +--- + +## 3. Likes — 좋아요 등록 (Product ↔ Likes) + +- Likes가 Products에 의존하는 경계(상품 존재·미삭제, 1인 1좋아요)와 호출 순서·트랜잭션 경계를 보이기 위함. +- 삭제된 상품에는 좋아요가 불가한지, 중복 좋아요 시 CONFLICT로 실패하는지, 한 트랜잭션 안에서 검증·생성·저장이 이루어지는지 검증하기 위함. + +### 다이어그램 + +```mermaid +sequenceDiagram + participant User + participant LikeController + participant LikeFacade + participant LikeService + participant ProductService + participant LikeRepository + + User->>LikeController: 좋아요 등록 (productId) + LikeController->>LikeFacade: addLike(userId, productId) + Note over LikeFacade: @Transactional + LikeFacade->>LikeService: addLike(userId, productId) + + LikeService->>ProductService: findByIdAndNotDeleted(productId) + ProductService-->>LikeService: Product or empty + alt 상품 미존재 또는 삭제됨 + LikeService-->>LikeFacade: 예외(NOT_FOUND) + end + + LikeService->>LikeRepository: existsByUserIdAndProductId(userId, productId) + LikeRepository-->>LikeService: boolean + alt 이미 좋아요 존재 + LikeService-->>LikeFacade: 예외(CONFLICT) + end + + LikeService->>LikeService: Like 생성 + LikeService->>LikeRepository: save(like) + LikeRepository-->>LikeService: saved Like + LikeService-->>LikeFacade: LikeInfo + LikeFacade-->>LikeController: LikeInfo + LikeController-->>User: 201 Created +``` + +### 해석 + +- **봐야 할 포인트**: 상품 검증 → 중복 검사 → Like 생성·저장이 한 트랜잭션으로 묶여 있어, 동시에 같은 사용자가 같은 상품에 좋아요를 두 번 요청해도 한 건만 성공하고 나머지는 CONFLICT로 처리할 수 있다(DB unique 제약과 함께). +- **설계 의도**: “삭제된 상품 좋아요 불가”, “1인 1좋아요”를 Service에서 순서대로 검증하고, Facade는 트랜잭션 경계만 담당한다. + +### 잠재 리스크 + +- **리스크**: Likes가 ProductService에 직접 의존하므로, Products의 조회 스펙(findByIdAndNotDeleted)이 바뀌면 Likes 쪽이 영향을 받는다. +- **선택지**: (A) 현재처럼 ProductService 메서드로 조회(단순, 결합 명시적). (B) 이벤트/캐시로 상품 상태를 공유해 결합 완화(복잡도·일관성 부담 증가). + +--- + +## 4. Cart — 장바구니 옵션 수정 (Cart ↔ Product) + +- 장바구니 수정 시 Cart 도메인과 Product 도메인(판매 상태·재고·옵션)이 어떻게 협력하는지, 검증 순서와 트랜잭션 경계를 확인하기 위함. +- 판매 중지·삭제된 상품 → 재고/옵션 순으로 검증하는지, 실패 시 롤백이 되는지 검증하기 위함. + +### 다이어그램 + +```mermaid +sequenceDiagram + participant User + participant CartController + participant CartFacade + participant CartService + participant ProductService + participant CartRepository + + User->>CartController: 장바구니(Cart) 옵션 수정 (cartItemId, 수량/옵션) + CartController->>CartFacade: updateItem(userId, cartItemId, request) + Note over CartFacade: @Transactional + CartFacade->>CartService: updateItem(userId, cartItemId, request) + + CartService->>CartRepository: findByUserIdAndCartItemId(userId, cartItemId) + CartRepository-->>CartService: CartItem or empty + alt 장바구니 항목(Cart item) 미존재 + CartService-->>CartFacade: 예외(NOT_FOUND) + end + + CartService->>ProductService: validateProductAvailability(productId, quantity, optionId) + Note over ProductService: 내부: 판매 상태 검사 → 재고/옵션 검사 순 + ProductService-->>CartService: 검증 결과 + alt 판매 불가/삭제/재고·옵션 불일치 + CartService-->>CartFacade: 예외(BAD_REQUEST / NOT_FOUND) + end + + CartService->>CartService: 장바구니 항목(CartItem) 수량/옵션 갱신 + CartService->>CartRepository: save(cartItem) + CartService-->>CartFacade: CartInfo + CartFacade-->>CartController: CartInfo + CartController-->>User: 200 OK +``` + +### 해석 + +- **봐야 할 포인트**: 장바구니 항목(CartItem) 소유 확인 후, ProductService의 `validateProductAvailability` 한 번 호출로 판매 상태·재고·옵션을 원자적으로 검증한다. 검증 순서(상태 → 재고/옵션)는 ProductService 내부에서 유지되며, 호출 1회로 트랜잭션 길이와 레이스 조건 가능성을 줄인다. 모든 변경은 Facade 트랜잭션 안에서만 커밋된다. +- **설계 의도**: 요구사항(추가·수정 시 판매 상태 및 재고 확인)을 단일 검증 메서드로 묶어 정합성과 성능을 함께 확보했다. + +### 잠재 리스크 + +- **리스크**: ProductService의 validateProductAvailability 내부 스펙(검증 순서, 에러 타입 구분)이 바뀌면 Cart 도메인이 영향을 받는다. +- **선택지**: (A) ProductService에서 실패 원인별 구체적인 예외/에러 코드를 반환해 클라이언트 메시지 구분 가능하게 유지한다. (B) 단순히 BAD_REQUEST/NOT_FOUND만 반환하고 메시지는 공통 문구로 처리한다. + +--- + +## 5. Orders — 주문 생성 (Order ↔ Product, 재고 확인/스냅샷) + +- 주문 생성 시 여러 상품에 대한 검증·스냅샷·저장의 호출 순서와, 재고 차감은 결제 완료 시점이라는 정책이 시퀀스에 드러나도록 하기 위함. +- 삭제된 상품 주문 불가, 재고 확인 후 주문만 생성·재고는 나중에 차감하는 흐름, 트랜잭션 경계가 주문 저장까지임을 확인하기 위함. + +### 다이어그램 + +```mermaid +sequenceDiagram + participant User + participant OrderController + participant OrderFacade + participant OrderService + participant ProductService + participant OrderRepository + + User->>OrderController: 주문 생성 (상품 목록, 수량) + OrderController->>OrderFacade: create(userId, request) + Note over OrderFacade: @Transactional + OrderFacade->>OrderService: create(userId, request) + + Note over OrderService: 주문 항목 전체 리스트 준비 + OrderService->>ProductService: validateProducts(orderItemRequestList) + ProductService->>ProductService: 일괄 검증 (존재·미삭제·재고) + ProductService-->>OrderService: 검증 결과 + alt 하나라도 미존재/삭제/재고 부족 + OrderService-->>OrderFacade: 예외(NOT_FOUND / BAD_REQUEST) + end + + OrderService->>OrderService: Product 스냅샷 생성 + OrderService->>OrderService: Order + OrderItem 생성 + OrderService->>OrderRepository: save(order) + OrderRepository-->>OrderService: saved Order + Note over OrderService: 재고 차감은 결제 완료 시 처리. 결제 시 재고 부족 시 주문 실패/취소 처리 + OrderService-->>OrderFacade: OrderInfo + OrderFacade-->>OrderController: OrderInfo + OrderController-->>User: 201 Created +``` + +### 해석 + +- **봐야 할 포인트**: 주문 항목 전체를 `validateProducts(orderItemRequestList)` 한 번으로 검증하여 N번 조회를 피하고, 트랜잭션 길이를 줄인다. 주문 생성 트랜잭션에는 “재고 확인”만 들어가고 “재고 차감”은 없으며, Note대로 결제 완료 시점에 처리한다. 그 시점에 재고 부족이면 주문 실패/취소로 정합성을 맞추는 설계이다. +- **설계 의도**: 주문 생성과 결제를 분리해 두었고, 삭제된 상품·재고 부족은 Bulk 검증 단계에서 NOT_FOUND/BAD_REQUEST로 처리한다. + +### 잠재 리스크 + +- **리스크**: 주문 생성 후 결제 전에 다른 주문으로 재고가 소진되면, 결제 완료 시 재고 부족으로 실패할 수 있다(결제·주문 보상 처리 필요). +- **선택지**: (A) 주문 생성 시 재고 예약(선점) 후 결제 완료 시 예약→차감, 미결제 타임아웃 시 예약 해제. (B) 현재처럼 주문은 재고 확인만 하고, 결제 완료 시 차감 실패하면 주문 실패/취소로 처리. + +--- + +## 6. Orders — 주문 취소 (Order ↔ Product, 상태 검증) + +- 주문 취소 시 본인/타인 구분(404 통일), 상태 검증, 결제 완료 건의 재고 복구가 한 트랜잭션으로 처리되는지 확인하기 위함. +- 타인 주문 접근 시 NOT_FOUND로 응답하는지, 재고 복구 실패 시 취소 전체가 롤백되는지 검증하기 위함. + +### 다이어그램 + +```mermaid +sequenceDiagram + participant User + participant OrderController + participant OrderFacade + participant OrderService + participant ProductService + participant OrderRepository + + User->>OrderController: 주문 취소 (orderId) + OrderController->>OrderFacade: cancel(userId, orderId) + Note over OrderFacade: @Transactional + OrderFacade->>OrderService: cancel(userId, orderId) + + OrderService->>OrderRepository: findById(orderId) + OrderRepository-->>OrderService: Order or empty + alt 주문 미존재 + OrderService-->>OrderFacade: 예외(NOT_FOUND) + end + + OrderService->>OrderService: 본인 주문 확인 + alt 타인 주문 (요구사항 4.3: 존재 여부 노출 방지) + OrderService-->>OrderFacade: 예외(NOT_FOUND) + end + + OrderService->>OrderService: 주문 상태 확인 + alt 배송 시작됨 등 취소 불가 상태 + OrderService-->>OrderFacade: 예외(BAD_REQUEST) + end + + alt 결제 완료된 주문 + OrderService->>ProductService: restoreStock(orderItems) + ProductService->>ProductService: 재고 복구 + alt 재고 복구 실패 (예: 상품 삭제) + ProductService-->>OrderService: 예외 + Note over OrderFacade: 트랜잭션 롤백 + Note over OrderService, ProductService: ProductService는 삭제/미존재 등 복구 불가 케이스를 방어 로직으로 최소화 + end + end + + OrderService->>OrderService: 주문 상태 취소로 변경 + OrderService->>OrderRepository: save(order) + OrderService-->>OrderFacade: OrderInfo + OrderFacade-->>OrderController: OrderInfo + OrderController-->>User: 200 OK +``` + +### 해석 + +- **봐야 할 포인트**: 주문 조회/취소/수정 시, “주문 없음”과 “타인 주문”은 서비스 레이어에서 구분하지 않고 동일하게 NOT_FOUND로 반환하여 요구사항 4.3(존재 여부 비노출)을 준수한다. 결제 완료 주문은 재고 복구 후 상태를 취소로 바꾸며, 재고 복구 실패 시 예외로 트랜잭션이 롤백되어 취소가 반영되지 않는다. 구현 시 ProductService에서 상품/옵션 삭제 등으로 복구가 불가한 경우를 최소화하는 방어 로직(예: Soft Delete만 사용, 삭제된 상품은 복구 스킵 등)을 두고, 그래도 실패하는 예외 케이스는 로그 및 운영 대응 정책으로 정의한다. +- **설계 의도**: 본인/상태/재고 복구를 Service에서 순서대로 검증하고, Facade는 트랜잭션 경계만 담당한다. 재고 복구 실패 시 전체 롤백으로 주문·재고 정합성을 유지한다. + +### 잠재 리스크 + +- **리스크**: 재고 복구 중 상품이 삭제되었거나 옵션이 없어지면 복구 실패가 나고, 사용자 입장에서는 “취소가 안 된다”는 인지가 필요하다(에러 메시지 설계 필요). +- **선택지**: (A) 재고 복구 실패 시에도 주문 상태만 CANCELLED로 두고, 재고는 수동/배치로 보정. (B) 현재처럼 재고 복구 실패 시 전체 롤백해 “취소 실패”로 응답하고, ProductService에 재고 복구 실패 케이스(완전 삭제 등)를 최소화하는 방어 로직을 두어 롤백이 발생하는 상황을 줄인다. From 2e1eca6ada42592da6d1ded701f65bbc3d07af64 Mon Sep 17 00:00:00 2001 From: Avocado Date: Fri, 13 Feb 2026 04:27:32 +0900 Subject: [PATCH 5/7] =?UTF-8?q?docs:=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EB=8B=A4=EC=9D=B4=EC=96=B4=EA=B7=B8=EB=9E=A8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(=EB=B8=8C=EB=9E=9C=EB=93=9C,=20=EC=83=81=ED=92=88,?= =?UTF-8?q?=20=EC=A2=8B=EC=95=84=EC=9A=94,=20=EC=9E=A5=EB=B0=94=EA=B5=AC?= =?UTF-8?q?=EB=8B=88,=20=EC=A3=BC=EB=AC=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .docs/design/03-class-diagram.md | 485 +++++++++++++++++++++++++++++++ 1 file changed, 485 insertions(+) create mode 100644 .docs/design/03-class-diagram.md diff --git a/.docs/design/03-class-diagram.md b/.docs/design/03-class-diagram.md new file mode 100644 index 00000000..bee7cde0 --- /dev/null +++ b/.docs/design/03-class-diagram.md @@ -0,0 +1,485 @@ +# 클래스 다이어그램 (도메인: Brands, Products, Likes, Cart, Orders) + +> 본 문서는 **도메인 책임**, **의존 방향**, **응집도** 확인을 위해 클래스 다이어그램을 사용한다. +> 각 다이어그램은 **이유 → 다이어그램 → 해석 → 잠재 리스크** 순서로 제시한다. +> 참조: [00-ubiquitous-language.md](./00-ubiquitous-language.md), [01-requirements.md](./01-requirements.md), [02-sequence-diagrams.md](./02-sequence-diagrams.md) + +--- + +## 0. 클래스 다이어그램 & 도메인 모델 + +클래스 다이어그램은 **시스템 구성 객체** 간 구조와 책임을 시각화하는 설계 도구다. + +### 왜 중요한가 + +- 도메인 개념 간 **책임과 관계**를 시각화한다. +- 설계 → 코드 전환 시 **패키지 구조·의존성 설계**의 기준이 된다. + +### 접근 원칙 + +| 원칙 | 내용 | +| ------------------ | ------------------------------------------------------------------------------------------------------------------ | +| **엔티티/VO 분리** | ID 존재 여부·생명 주기로 구분. ID 있고 영속되는 것은 Entity, 값 검증·불변 표현은 VO(Transient). **VO는 별도 테이블 없이 Entity 필드에 값만 저장**한다. | +| **연관 관계** | **단방향**을 기본으로 하고, 양방향은 필요한 경우만 최소화한다. | +| **비즈니스 책임** | 검증·계산·상태 변경 등 **비즈니스 규칙은 도메인 객체(Entity/VO)**에 두고, Service는 조율·트랜잭션 경계에 집중한다. | +| **설계 후 점검** | "한 객체에 책임이 몰리지 않았는가?"를 반드시 점검한다. | + +- 본 프로젝트에서는 **다른 애그리거트 참조는 ID만 보유**(Product → Brand는 brandId, Like → Product는 productId)하고, 연관 객체 직접 참조는 도메인 모델에서 최소화한다(인프라·N+1 이슈 방지). + +### 자주 겪는 실수 + +- **모든 필드를 객체로 표현** → 지나친 복잡도. 가격·금액 등 값 하나면 VO(Price) 또는 원시 타입(BigDecimal)으로 충분한지 판단. +- **도메인 책임 없이 Service에 모든 로직 집중** → Entity/VO는 getter/setter만, 검증·계산은 전부 Service에 두는 패턴. 비즈니스 규칙은 도메인 객체에 두고 Service는 조율만 하도록 분리. +- **VO를 테이블처럼 다루기** → 예: Price를 별도 DB 테이블로 설계. VO는 값 객체로 엔티티 필드에 포함되어 저장된다. + +--- + +## 1. 레이어별 클래스 구조 (전체 의존 방향) + +- 설계 검증 목적: **interfaces → application → domain → infrastructure** 의존이 한 방향으로만 유지되는지, Controller가 Facade만 바라보고 Facade가 Service만 바라보는지 확인하기 위함. +- 도메인 간 의존(Likes/Cart/Orders가 Products에 의존)이 domain 레이어 안에서만 발생하는지 확인하기 위함. + +### 다이어그램 + +```mermaid +classDiagram + direction TB + + subgraph interfaces + ProductController + BrandController + LikeController + CartController + OrderController + ProductV1Dto + BrandV1Dto + LikeV1Dto + CartV1Dto + OrderV1Dto + end + + subgraph application + ProductFacade + BrandFacade + LikeFacade + CartFacade + OrderFacade + ProductInfo + BrandInfo + LikeInfo + CartInfo + OrderInfo + end + + subgraph domain + ProductService + BrandService + LikeService + CartService + OrderService + ProductRepository + BrandRepository + LikeRepository + CartRepository + OrderRepository + end + + subgraph domain_models + ProductModel + BrandModel + LikeModel + CartItemModel + OrderModel + OrderItemModel + end + + subgraph infrastructure + ProductRepositoryImpl + BrandRepositoryImpl + LikeRepositoryImpl + CartRepositoryImpl + OrderRepositoryImpl + ProductJpaRepository + BrandJpaRepository + LikeJpaRepository + CartItemJpaRepository + OrderJpaRepository + end + + ProductController --> ProductFacade + BrandController --> BrandFacade + LikeController --> LikeFacade + CartController --> CartFacade + OrderController --> OrderFacade + + ProductFacade --> ProductService + ProductFacade --> ProductInfo + BrandFacade --> BrandService + BrandFacade --> BrandInfo + LikeFacade --> LikeService + LikeFacade --> LikeInfo + CartFacade --> CartService + CartFacade --> CartInfo + OrderFacade --> OrderService + OrderFacade --> OrderInfo + + ProductService --> ProductRepository + ProductService --> BrandRepository + ProductService --> ProductModel + BrandService --> BrandRepository + BrandService --> BrandModel + LikeService --> LikeRepository + LikeService --> ProductService + LikeService --> LikeModel + CartService --> CartRepository + CartService --> ProductService + CartService --> CartItemModel + OrderService --> OrderRepository + OrderService --> ProductService + OrderService --> OrderModel + OrderService --> OrderItemModel + + ProductRepositoryImpl ..|> ProductRepository + BrandRepositoryImpl ..|> BrandRepository + LikeRepositoryImpl ..|> LikeRepository + CartRepositoryImpl ..|> CartRepository + OrderRepositoryImpl ..|> OrderRepository + ProductRepositoryImpl --> ProductJpaRepository + BrandRepositoryImpl --> BrandJpaRepository + LikeRepositoryImpl --> LikeJpaRepository + CartRepositoryImpl --> CartItemJpaRepository + OrderRepositoryImpl --> OrderJpaRepository +``` + +### 해석 + +- **봐야 할 포인트**: interfaces는 application만 의존하고, application은 domain만 의존한다. domain의 Repository는 인터페이스만 두고 구현은 infrastructure가 담당하므로, domain은 JPA/Spring에 무관하게 유지된다. +- **도메인 간 의존**: LikeService, CartService, OrderService가 ProductService를 참조한다. 상품 존재·미삭제·재고·스냅샷 검증을 Product 도메인에 맡기기 위한 설계이며, 시퀀스 다이어그램(02)의 호출 순서와 일치한다. +- **설계 의도**: AGENTS.md의 레이어 규칙(Controller → Facade → Service → Repository)과 일치하도록, 책임이 계층별로 나뉘어 있음을 다이어그램으로 고정한다. + +### 잠재 리스크 + +- **리스크**: ProductService에 대한 의존이 Likes/Cart/Orders 세 도메인에 퍼져 있어, ProductService 시그니처나 정책 변경 시 영향 범위가 넓다. +- **선택지**: (A) ProductService의 공개 메서드(findByIdAndNotDeleted, validateProductAvailability, validateProducts, restoreStock 등)를 최소한으로 유지하고 변경 시 하위 호환을 유지. (B) 공통 검증을 별도 도메인 서비스(예: ProductValidationService)로 분리해 Product 도메인과 결합 완화(클래스 수 증가). + +--- + +## 2. Brand 도메인 + +- 브랜드 단일 도메인의 모델·서비스·저장소 책임과 soft delete 반영을 확인하기 위함. +- 어드민의 브랜드 CRUD(등록·조회·수정·삭제)가 클래스 책임으로 드러나는지 검증하기 위함. + +### 다이어그램 + +```mermaid +classDiagram + direction TB + + class BrandModel { + <> + +String name + +boolean deleted + +isDeleted() + } + + class BrandService { + <> + +findById(Long) + +findByIdAndNotDeleted(Long) + +register(RegisterRequest) + +update(Long, UpdateRequest) + +delete(Long) + } + + class BrandRepository { + <> + +findById(Long) + +findByIdAndNotDeleted(Long) + +save(BrandModel) + } + + BrandService --> BrandRepository : 조회/저장 + BrandService --> BrandModel : 생성/갱신 +``` + +### 해석 + +- **봐야 할 포인트**: BrandModel은 이름·삭제 여부 등 브랜드 상태만 보유한다. Soft delete이므로 deleted 플래그와 isDeleted() 등 도메인 행위는 모델에 둔다. 조회 시 "미삭제만" 쓰는 경우 findByIdAndNotDeleted는 Repository 또는 Service에서 제공한다. +- **설계 의도**: 브랜드 삭제 시 해당 브랜드의 모든 상품 연쇄 삭제(01 요구사항 4.1)는 서비스에서 일괄 처리하거나 DB CASCADE로 처리한다. Product 도메인은 brandId로만 참조하므로 Brand는 독립 애그리거트로 유지된다. + +### 잠재 리스크 + +- **리스크**: BrandRepository에 findByIdAndNotDeleted를 두지 않으면, ProductService 등에서 삭제된 브랜드를 "유효한 브랜드"로 조회할 수 있다. +- **선택지**: (A) BrandRepository에 findByIdAndNotDeleted를 두고 외부 도메인(ProductService)에서 해당 메서드만 사용. (B) Service에서 조회된 Brand의 deleted 플래그를 명시적으로 검사. + +--- + +## 3. Product 도메인 (Brand와의 관계) + +- 상품이 반드시 하나의 브랜드에 속한다는 관계(01 요구사항 3.5)가 모델·서비스 책임에 어떻게 반영되는지 확인하기 위함. +- 상품 등록 시 브랜드 유효성 검사, 수정 시 브랜드 변경 불가, 주문/재고 검증·스냅샷·재고 복구가 ProductService에 어떻게 모이는지 검증하기 위함. + +### 다이어그램 + +```mermaid +classDiagram + direction TB + + class ProductModel { + <> + +Long brandId + +String name + +BigDecimal price + +int stockQuantity + +boolean deleted + +hasStock(quantity) + +snapshotForOrder() + } + + class ProductService { + <> + +register(RegisterRequest) + +update(Long, UpdateRequest) + +findById(Long) + +findByIdAndNotDeleted(Long) + +validateProductAvailability(productId, quantity, optionId) + +validateProducts(List~OrderItemRequest~) + +restoreStock(List~OrderItem~) + } + + class ProductRepository { + <> + +findById(Long) + +save(ProductModel) + } + + class BrandRepository { + <> + +findByIdAndNotDeleted(Long) + } + + ProductModel --> ProductService : 생성/갱신 + ProductService --> ProductRepository : 조회/저장 + ProductService --> BrandRepository : 브랜드 유효성 검사 +``` + +### 해석 + +- **봐야 할 포인트**: ProductModel은 brandId만 보유하고 BrandModel을 직접 참조하지 않는다(다른 애그리거트 루트 참조 최소화). **재고 충족 여부**는 ProductModel.hasStock(quantity) 등 모델 책임으로 두어 Service에만 로직이 몰리지 않도록 한다. 가격 등은 VO(Price)로 검증 후 Entity 필드(price)에 값만 저장할 수 있다(§0 VO 구분). 브랜드 유효성(존재·미삭제)은 ProductService가 BrandRepository를 통해 조율한다. +- **설계 의도**: Likes/Cart/Orders가 ProductService의 findByIdAndNotDeleted, validateProductAvailability, validateProducts, restoreStock에 의존하므로, 이 메서드 시그니처와 정책이 변경되면 영향 범위가 넓다. 스냅샷·재고는 주문 도메인과의 협력 경계를 나타낸다. + +### 잠재 리스크 + +- **리스크**: BrandRepository에서 findByIdAndNotDeleted를 제공하지 않으면 삭제된 브랜드에 상품이 등록될 수 있다(02 시퀀스 잠재 리스크와 동일). +- **선택지**: (A) BrandRepository에 findByIdAndNotDeleted를 두고 ProductService에서 해당 메서드만 사용. (B) Service에서 조회된 Brand의 deleted 플래그를 명시적으로 검사. + +--- + +## 4. Like 도메인 (User · Product와의 관계) + +- 좋아요가 "한 고객이 한 상품에 한 번만"이라는 비즈니스 규칙(01 요구사항 3.3)을 모델·서비스·저장소에서 어떻게 나누어 가지는지 확인하기 위함. +- LikeService가 ProductService에 의존하는 경계가 명확한지 검증하기 위함. + +### 다이어그램 + +```mermaid +classDiagram + direction TB + + class LikeModel { + <> + +Long userId + +Long productId + +createdAt + } + + class LikeService { + <> + +addLike(userId, productId) + +removeLike(userId, productId) + +findLikesByUserId(userId, pageable) + } + + class LikeRepository { + <> + +existsByUserIdAndProductId(userId, productId) + +save(LikeModel) + +delete(LikeModel) + +findByUserId(userId, pageable) + } + + class ProductService { + <> + +findByIdAndNotDeleted(productId) + } + + LikeModel --> LikeService : 생성/삭제 주체 + LikeService --> LikeRepository : 중복 검사, 저장, 삭제 + LikeService --> ProductService : 상품 존재·미삭제 검증 +``` + +### 해석 + +- **봐야 할 포인트**: "1인 1좋아요"는 LikeRepository.existsByUserIdAndProductId + DB unique 제약(userId, productId)으로 보장된다. LikeService는 상품 검증 → 중복 검사 → Like 생성 순서를 유지한다(02 시퀀스와 동일). +- **설계 의도**: 삭제된 상품 좋아요 불가는 ProductService.findByIdAndNotDeleted에 위임하고, Like 도메인은 "좋아요 존재 여부·생성·취소"에만 집중한다. + +### 잠재 리스크 + +- **리스크**: ProductService 조회 스펙 변경 시 Like 도메인이 영향을 받는다(02 잠재 리스크와 동일). +- **선택지**: (A) ProductService의 findByIdAndNotDeleted 시그니처를 안정적으로 유지. (B) 상품 상태 조회를 이벤트/캐시로 공유해 결합 완화(복잡도 증가). + +--- + +## 5. Cart 도메인 (CartItem · Product와의 관계) + +- 장바구니 항목(CartItem)이 고객·상품·옵션·수량을 어떻게 보유하고, CartService가 ProductService의 검증(판매 상태·재고·옵션)을 어떻게 사용하는지 확인하기 위함. +- 요구사항 3.4(동일 품목 합산, 상품 유효성 검사)가 서비스 책임으로 드러나는지 검증하기 위함. + +### 다이어그램 + +```mermaid +classDiagram + direction TB + + class CartItemModel { + <> + +Long userId + +Long productId + +Long optionId + +int quantity + +updatedAt + +isSameProduct(productId, optionId) + } + + class CartService { + <> + +addItem(userId, request) + +getItems(userId) + +updateItem(userId, cartItemId, request) + +removeItems(userId, cartItemIds) + } + + class CartRepository { + <> + +findByUserId(userId) + +findByUserIdAndCartItemId(userId, cartItemId) + +save(CartItemModel) + +delete(CartItemModel) + } + + class ProductService { + <> + +validateProductAvailability(productId, quantity, optionId) + } + + CartItemModel --> CartService : 추가/수정/삭제 주체 + CartService --> CartRepository : 조회/저장/삭제 + CartService --> ProductService : 판매 상태·재고·옵션 검증 +``` + +### 해석 + +- **봐야 할 포인트**: **동일 품목 여부**는 CartItemModel.isSameProduct(productId, optionId)로 모델 책임을 두고, CartService는 조회·합산·저장을 조율한다. "동일 상품·동일 옵션 수량 합산"은 기존 항목 조회 후 quantity 갱신으로 처리. 추가·수정 시 상품 유효성은 ProductService.validateProductAvailability 한 번 호출로 검증한다(02 시퀀스 해석과 일치). +- **설계 의도**: Cart 도메인은 "어떤 상품을 얼마나 담았는지"와 "본인 소유 검증"만 담당하고, "판매 가능·재고·옵션 유효"는 Product 도메인에 위임한다. + +### 잠재 리스크 + +- **리스크**: validateProductAvailability 내부 스펙(검증 순서, 에러 타입) 변경 시 Cart 도메인이 영향을 받는다(02 잠재 리스크와 동일). +- **선택지**: (A) ProductService에서 실패 원인별 구체 예외/에러 코드 반환. (B) BAD_REQUEST/NOT_FOUND만 반환하고 메시지는 공통 처리. + +--- + +## 6. Order 도메인 (Order · OrderItem · Product 스냅샷) + +- 주문이 여러 상품(OrderItem)을 포함하고, 주문 시점 상품 정보를 스냅샷으로 보존하는 구조가 모델에 어떻게 반영되는지 확인하기 위함. +- OrderService가 ProductService(validateProducts, restoreStock)에 의존하는 경계가 명확한지 검증하기 위함. + +### 다이어그램 + +```mermaid +classDiagram + direction TB + + class OrderModel { + <> + +Long userId + +OrderStatus status + +LocalDateTime orderedAt + +getOrderItems() + } + + class OrderItemModel { + <> + +Long productId + +String productNameSnapshot + +BigDecimal priceSnapshot + +int quantity + +Long optionId + +of(product, quantity) + } + + class OrderService { + <> + +create(userId, request) + +findOrders(userId, startDate, endDate, pageable) + +findById(userId, orderId) + +cancel(userId, orderId) + } + + class OrderRepository { + <> + +findById(orderId) + +save(OrderModel) + +findByUserIdAndOrderedAtBetween(userId, start, end, pageable) + } + + class ProductService { + <> + +validateProducts(List~OrderItemRequest~) + +restoreStock(List~OrderItem~) + } + + OrderModel "1" *--> "N" OrderItemModel : orderItems + OrderService --> OrderRepository : 조회/저장 + OrderService --> OrderModel + OrderService --> OrderItemModel : 스냅샷 생성 + OrderService --> ProductService : 주문 전 검증, 취소 시 재고 복구 +``` + +### 해석 + +- **봐야 할 포인트**: OrderItemModel은 주문 시점의 상품명·가격·수량·옵션을 스냅샷으로 보유한다. **스냅샷 생성**은 OrderItemModel.of(product, quantity) 등 모델/팩토리 책임으로 두어, 이후 Product가 바뀌어도 주문 내역이 변하지 않도록 한다(01 요구사항 3.2 스냅샷 보존). 재고 차감은 주문 생성 트랜잭션에 포함하지 않고, 결제 완료 시점에 처리한다(02 시퀀스 해석과 일치). +- **설계 의도**: 주문 생성 시 validateProducts로 일괄 검증 후 Order + OrderItem 생성·저장만 담당하고, 재고 복구는 취소 시 OrderService → ProductService.restoreStock으로 처리한다. + +### 잠재 리스크 + +- **리스크**: 주문 생성 후 결제 전 다른 주문으로 재고 소진 시, 결제 완료 시점 재고 차감 실패 가능(02 잠재 리스크와 동일). 재고 복구 시 상품/옵션 삭제로 실패하면 취소 전체 롤백된다. +- **선택지**: (A) 주문 생성 시 재고 예약(선점) 후 결제 완료 시 예약→차감. (B) 현재처럼 주문은 재고 확인만 하고, 결제 완료 시 차감 실패 시 주문 실패/취소 처리. (B') 재고 복구 실패 시 주문 상태만 CANCELLED로 두고 재고는 수동/배치 보정. + +--- + +## 7. 요약 표 (클래스 책임 · 레이어) + +| 레이어 | 도메인 | 주요 클래스 | 책임 | +| ------------------ | ------- | --------------------------------------------------------- | ------------------------------------------------ | +| **domain** | Brand | BrandModel, BrandService, BrandRepository | 브랜드 CRUD, soft delete | +| **domain** | Product | ProductModel, ProductService, ProductRepository | 상품 CRUD, 재고/옵션 검증, 스냅샷·재고 복구 지원 | +| **domain** | Like | LikeModel, LikeService, LikeRepository | 좋아요 추가/취소, 1인 1좋아요 검증 | +| **domain** | Cart | CartItemModel, CartService, CartRepository | 장바구니 추가/수정/삭제, 동일 품목 합산 | +| **domain** | Order | OrderModel, OrderItemModel, OrderService, OrderRepository | 주문 생성/조회/취소, 스냅샷 보존, 본인 검증 | +| **application** | 공통 | *Facade, *Info | 트랜잭션 경계, 도메인 결과 → Info 변환 | +| **interfaces** | 공통 | *Controller, *V1Dto | HTTP 요청/응답, DTO 변환 | +| **infrastructure** | 공통 | *JpaRepository, *RepositoryImpl | JPA 영속성, Repository 인터페이스 구현 | + +--- + +## 8. 설계 후 점검 + +다이어그램 반영 후 아래를 점검한다. + +- [ ] **한 객체에 책임이 몰리지 않았는가?** — Service에만 비즈니스 규칙이 몰려 있지 않은지, Entity/VO에 검증·계산이 들어갈 부분은 없는지 확인. +- [ ] **엔티티/VO 구분이 생명 주기·ID 기준으로 명확한가?** — 영속되는 것(Entity)과 값/검증만 쓰는 것(VO, Transient) 구분. +- [ ] **연관 관계가 단방향 위주인가?** — 양방향 참조가 꼭 필요한 경우만 두었는지 확인. +- [ ] **VO를 테이블로 분리하려는 유혹은 없는가?** — Price, Address 등은 엔티티 필드로 포함 저장하는지 확인. + +--- From 7afbe2e4d4b9d098e7600537c7c073ac18365de8 Mon Sep 17 00:00:00 2001 From: Avocado Date: Fri, 13 Feb 2026 05:16:55 +0900 Subject: [PATCH 6/7] =?UTF-8?q?docs:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=C2=B7=EC=8B=9C=ED=80=80=EC=8A=A4=C2=B7=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=EB=8B=A4=EC=9D=B4=EC=96=B4=EA=B7=B8=EB=9E=A8?= =?UTF-8?q?=20=EB=B3=B4=EC=99=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 01-requirements: 재고 차감 시점을 시퀀스 다이어그램에 맞게 "결제 완료 시점"으로 정리. 동시성 보장 문구에 비관적 락 명시. 주문 상태 정책(4.5)에 취소 가능 조건 및 04-erd 참조 추가. - 02-sequence: 주문 생성(§5) Note에 결제 시 재고 차감·비관적 락(PESSIMISTIC_WRITE) 적용 명시. 주문 취소(§6) 시 재고 복구 시 비관적 락 적용 Note 추가. - 03-class: ProductRepository에 findByIdAndNotDeleted(Long) 추가. 해석에 findById vs findByIdAndNotDeleted 용도 설명 보강. --- .docs/design/01-requirements.md | 10 +++++----- .docs/design/02-sequence-diagrams.md | 3 ++- .docs/design/03-class-diagram.md | 3 ++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.docs/design/01-requirements.md b/.docs/design/01-requirements.md index 0bde943f..2c3e2514 100644 --- a/.docs/design/01-requirements.md +++ b/.docs/design/01-requirements.md @@ -159,8 +159,8 @@ |------|------| | 재고 충분성 | 주문하려는 모든 상품의 재고가 주문 수량 이상이어야 한다 | | All or Nothing | 주문 내 하나의 상품이라도 재고가 부족하면 전체 주문이 실패한다 | -| 재고 차감 시점 | 주문이 성공하면 즉시 재고가 차감된다 | -| 동시성 보장 | 동시에 같은 상품을 주문하더라도 재고가 음수가 되어서는 안 된다. 비관적 락으로 보장한다 | +| 재고 차감 시점 | **결제 완료 시점**에 재고가 차감된다. 주문 생성 시점에는 재고 확인만 수행한다(시퀀스 다이어그램 02 §5 기준). | +| 동시성 보장 | 동시에 같은 상품을 주문(결제)하더라도 재고가 음수가 되어서는 안 된다. **재고 차감 시 비관적 락**으로 보장한다. | ### 3.2 주문 @@ -246,9 +246,9 @@ ### 4.5 주문 상태 정책 -- 주문 생성 시 ORDERED 상태. -- 결제·배송 도메인 추가 시 PAID, SHIPPING, DELIVERED, CANCELLED 등으로 확장. -- 취소 시: ORDER_STATUS = CANCELLED, 필요 시 재고 복구. +- 주문 생성 시 초기 상태는 **ORDERED**. 결제·배송 도메인 추가 시 PAID, SHIPPING, DELIVERED, CANCELLED로 확장. +- **취소 가능**: ORDERED(즉시), PAID(재고 복구 후 CANCELLED). SHIPPING, DELIVERED는 취소 불가. +- 취소 시: ORDER_STATUS = CANCELLED, 결제 완료 건은 재고 복구 후 상태 변경. 상세 전이 규칙은 [04-erd.md](./04-erd.md) §2 주문 상태 전이 규칙 표 참고. --- diff --git a/.docs/design/02-sequence-diagrams.md b/.docs/design/02-sequence-diagrams.md index aff93a10..9e9723ef 100644 --- a/.docs/design/02-sequence-diagrams.md +++ b/.docs/design/02-sequence-diagrams.md @@ -245,7 +245,7 @@ sequenceDiagram OrderService->>OrderService: Order + OrderItem 생성 OrderService->>OrderRepository: save(order) OrderRepository-->>OrderService: saved Order - Note over OrderService: 재고 차감은 결제 완료 시 처리. 결제 시 재고 부족 시 주문 실패/취소 처리 + Note over OrderService: 재고 차감은 결제 완료 시점에 처리. 결제 시 재고 차감 시 비관적 락(PESSIMISTIC_WRITE) 적용. 재고 부족 시 주문 실패/취소 처리 OrderService-->>OrderFacade: OrderInfo OrderFacade-->>OrderController: OrderInfo OrderController-->>User: 201 Created @@ -302,6 +302,7 @@ sequenceDiagram alt 결제 완료된 주문 OrderService->>ProductService: restoreStock(orderItems) + Note over ProductService: 재고 복구 시 비관적 락 적용(동시성 보장) ProductService->>ProductService: 재고 복구 alt 재고 복구 실패 (예: 상품 삭제) ProductService-->>OrderService: 예외 diff --git a/.docs/design/03-class-diagram.md b/.docs/design/03-class-diagram.md index bee7cde0..2649c4e9 100644 --- a/.docs/design/03-class-diagram.md +++ b/.docs/design/03-class-diagram.md @@ -250,6 +250,7 @@ classDiagram class ProductRepository { <> +findById(Long) + +findByIdAndNotDeleted(Long) +save(ProductModel) } @@ -265,7 +266,7 @@ classDiagram ### 해석 -- **봐야 할 포인트**: ProductModel은 brandId만 보유하고 BrandModel을 직접 참조하지 않는다(다른 애그리거트 루트 참조 최소화). **재고 충족 여부**는 ProductModel.hasStock(quantity) 등 모델 책임으로 두어 Service에만 로직이 몰리지 않도록 한다. 가격 등은 VO(Price)로 검증 후 Entity 필드(price)에 값만 저장할 수 있다(§0 VO 구분). 브랜드 유효성(존재·미삭제)은 ProductService가 BrandRepository를 통해 조율한다. +- **봐야 할 포인트**: ProductModel은 brandId만 보유하고 BrandModel을 직접 참조하지 않는다(다른 애그리거트 루트 참조 최소화). **재고 충족 여부**는 ProductModel.hasStock(quantity) 등 모델 책임으로 두어 Service에만 로직이 몰리지 않도록 한다. **ProductRepository**는 `findById`(관리/내부용), **findByIdAndNotDeleted**(Like/Cart/Order 등에서 "판매 중인 상품" 조회용)를 제공한다. 가격 등은 VO(Price)로 검증 후 Entity 필드(price)에 값만 저장할 수 있다(§0 VO 구분). 브랜드 유효성(존재·미삭제)은 ProductService가 BrandRepository를 통해 조율한다. - **설계 의도**: Likes/Cart/Orders가 ProductService의 findByIdAndNotDeleted, validateProductAvailability, validateProducts, restoreStock에 의존하므로, 이 메서드 시그니처와 정책이 변경되면 영향 범위가 넓다. 스냅샷·재고는 주문 도메인과의 협력 경계를 나타낸다. ### 잠재 리스크 From 6a60d1079359cea7112004edad710ef9be228381 Mon Sep 17 00:00:00 2001 From: Avocado Date: Fri, 13 Feb 2026 05:21:18 +0900 Subject: [PATCH 7/7] =?UTF-8?q?docs:=20ERD=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(04-erd)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .docs/design/04-erd.md | 198 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 .docs/design/04-erd.md diff --git a/.docs/design/04-erd.md b/.docs/design/04-erd.md new file mode 100644 index 00000000..5af4a7a6 --- /dev/null +++ b/.docs/design/04-erd.md @@ -0,0 +1,198 @@ +# ERD (Brand, Product, Like, Cart, Order) + +> 본 문서는 **영속성 구조**, **관계의 주인**, **정규화 여부** 확인을 위해 ERD를 사용한다. +> **관계 설계 원칙**: FK **제약**은 사용하지 않고, **참조용 컬럼**만 두어 조인·조회는 동일하게 사용한다. 참조 정합성은 서비스 레이어에서 검증한다. +> 참조: [00-ubiquitous-language.md](./00-ubiquitous-language.md), [01-requirements.md](./01-requirements.md), [02-sequence-diagrams.md](./02-sequence-diagrams.md), [03-class-diagram.md](./03-class-diagram.md) + +--- + +## 0. ERD가 중요한 이유 + +- **도메인 모델의 물리적 구현 기반**: 클래스 다이어그램(03)의 엔티티가 어떤 테이블·컬럼으로 매핑되는지 정의한다. +- **성능 이슈와 직결**: 조회 쿼리, 인덱스 전략이 ERD 구조에 의존한다. +- **API·도메인·DB 간 구조 일관성**: 용어(00)와 요구사항(01)이 테이블·컬럼명으로 일치하도록 유지한다. + +### 접근 원칙 + +| 원칙 | 적용 | +|------|------| +| **1:N 관계** | N 쪽 테이블에 참조 컬럼(`xxx_id`)만 둠. **FK 제약은 DDL에 넣지 않음.** | +| **N:M** | 조인 테이블(예: like는 user–product 다대다를 user_id, product_id로 표현). 복합 UNIQUE로 비즈니스 제약. | +| **enum** | VARCHAR 또는 코드 값 저장(예: order.status, user.gender). | +| **soft delete** | `deleted_at` 컬럼 사용. NULL이면 미삭제. | +| **상태 관리** | `status` 컬럼으로 명확한 상태 전이 표현(예: order.status = ORDERED, PAID, CANCELLED). | +| **재고 동시성** | 재고 차감·복구 시 **비관적 락**(예: JPA `@Lock(LockModeType.PESSIMISTIC_WRITE)`, SELECT FOR UPDATE)을 사용하여 음수 재고를 방지한다. | + +--- + +## 1. 전체 ERD + +- DB 테이블·컬럼·**논리적 관계**(참조 컬럼)가 요구사항(01)의 삭제 정책·브랜드-상품 소속·주문 스냅샷·1인 1좋아요에 맞게 설계되었는지 검증하기 위함. +- **FK 제약은 사용하지 않음**. 관계는 참조 컬럼으로만 표현하고, 조인·조회는 동일하게 사용. + +### 다이어그램 + +```mermaid +erDiagram + user { + bigint id PK + string email + string encrypted_password + string birth_date + string gender + bigint points + timestamptz created_at + timestamptz updated_at + } + + brand { + bigint id PK + string name + timestamptz created_at + timestamptz updated_at + timestamptz deleted_at + } + + product { + bigint id PK + bigint brand_id + string name + decimal price + int stock_quantity + timestamptz created_at + timestamptz updated_at + timestamptz deleted_at + } + + like { + bigint id PK + bigint user_id + bigint product_id + timestamptz created_at + } + + cart { + bigint id PK + bigint user_id + bigint product_id + bigint option_id + int quantity + timestamptz created_at + timestamptz updated_at + } + + order { + bigint id PK + bigint user_id + string status + timestamptz ordered_at + timestamptz created_at + timestamptz updated_at + } + + order_item { + bigint id PK + bigint order_id + bigint product_id + string product_name_snapshot + decimal price_snapshot + int quantity + bigint option_id + } + + user ||--o{ like : user_id + user ||--o{ cart : user_id + user ||--o{ order : user_id + brand ||--o{ product : brand_id + product ||--o{ like : product_id + product ||--o{ cart : product_id + order ||--|{ order_item : order_id +``` + +- 위 관계선은 **논리적 관계**(어떤 컬럼이 어떤 테이블의 id를 참조하는지)를 나타낸다. **DDL에는 FOREIGN KEY 제약을 생성하지 않는다.** + +### 해석 + +- **봐야 할 포인트** + - **관계의 주인(참조 컬럼 보유)**: product.brand_id, like.user_id/product_id, cart.user_id/product_id, order.user_id, order_item.order_id. 조회 시 `ON b.id = p.brand_id` 등으로 조인. FK 제약은 없으므로 참조 정합성은 서비스에서 저장 전 검증. + - **삭제 정책**: brand·product는 `deleted_at`(soft delete). like·cart는 물리 DELETE. order는 물리 삭제 금지, 취소는 `status` 변경. + - **1인 1좋아요**: `like` 테이블에 **UNIQUE(user_id, product_id)** 제약으로 보장(구현 시 DDL에 포함). + - **상태**: order.status로 ORDERED, PAID, CANCELLED 등 상태 전이 표현. 전이 규칙은 §2 아래 "주문 상태 전이 규칙" 표 참고. + - **재고 락**: product.stock_quantity 갱신(차감·복구) 시 비관적 락을 적용한다. 구현 시 Repository/Service에서 조회·차감을 한 트랜잭션 내에서 수행. +- **정규화**: order_item의 상품명·가격 중복은 의도적 비정규화(주문 이력 보존). 그 외는 3NF 수준. + +### 잠재 리스크 및 보완 + +- **참조 무결성**: DB가 존재하지 않는 id 저장을 막지 않음. → 저장/수정 경로를 서비스로 일원화하고, 저장 전 참조 대상 존재·유효 여부 검증 + 실패 케이스 테스트로 보완. +- **연쇄 삭제**: Brand 삭제 시 Product soft delete를 서비스에서 빼먹을 수 있음. → 연쇄 로직을 한 서비스 메서드에 모으고, "Brand 삭제 후 해당 product.deleted_at 설정"을 통합 테스트로 검증. + +--- + +## 2. 테이블·컬럼 요약 + +| 테이블 | PK | 주요 컬럼 | 삭제 방식 | 비고 | +|--------|-----|------------|-----------|------| +| **user** | id | email, encrypted_password, birth_date, gender, points | - | Like/Cart/Order의 user_id 참조(참조 컬럼만, FK 제약 없음) | +| **brand** | id | name, deleted_at | Soft delete | deleted_at NULL이면 미삭제 | +| **product** | id | brand_id, name, price, stock_quantity, deleted_at | Soft delete | brand_id는 brand.id 참조(참조 컬럼만) | +| **like** | id | user_id, product_id, created_at | Hard delete | UNIQUE(user_id, product_id) 로 1인 1좋아요 | +| **cart** | id | user_id, product_id, option_id, quantity | Hard delete | 동일 상품·옵션 시 수량 합산 | +| **order** | id | user_id, status, ordered_at | 물리 삭제 금지 | status로 취소 등 상태 전이. 구현 시 테이블명 `orders` 사용 가능 | +| **order_item** | id | order_id, product_id, product_name_snapshot, price_snapshot, quantity, option_id | Order와 동일 | 스냅샷: 주문 시점 값 보존 | + +### 주문 상태 전이 규칙 (order.status) + +| 상태 | 설명 | 취소 가능 | +|------|------|-----------| +| ORDERED | 주문 생성 직후(결제 전) | 가능(즉시) | +| PAID | 결제 완료 | 가능(재고 복구 후 CANCELLED) | +| SHIPPING | 배송 중 | 불가 | +| DELIVERED | 배송 완료 | 불가 | +| CANCELLED | 취소됨 | - | + +- **규칙**: 주문 생성 시 초기값은 ORDERED. 결제·배송 도메인 추가 시 PAID, SHIPPING, DELIVERED로 확장. 취소 시 ORDERED/PAID → CANCELLED만 허용. + +--- + +## 3. 관계·참조 요약 + +| 관계 | 참조 컬럼(주인) | 카디널리티 | 비고 | +|------|-----------------|------------|------| +| Brand → Product | product.brand_id | N : 1 | 상품은 하나의 브랜드에만 속함 | +| User → Like | like.user_id | 1 : N | UNIQUE(user_id, product_id)로 1인 1상품 1좋아요 | +| Product → Like | like.product_id | 1 : N | | +| User → Cart | cart.user_id | 1 : N | | +| Product → Cart | cart.product_id | 1 : N | | +| User → Order | order.user_id | 1 : N | | +| Order → Order_item | order_item.order_id | 1 : N | 주문 항목은 주문에 종속 | + +- 위 참조 컬럼에 대한 **FOREIGN KEY 제약은 DDL에 정의하지 않는다.** 조인은 `테이블.id = 상대테이블.xxx_id` 로 수행. + +--- + +## 4. 인덱스 권장 (성능) + +| 테이블 | 인덱스 | 목적 | +|--------|--------|------| +| product | idx_product_brand_id (brand_id) | 브랜드별 상품 조회, 조인 | +| like | idx_like_user_id (user_id), idx_like_product_id (product_id) | 사용자별 좋아요 목록, 상품별 좋아요 수 | +| like | uk_like_user_product (user_id, product_id) UNIQUE | 1인 1좋아요 보장 | +| cart | idx_cart_user_id (user_id) | 사용자별 장바구니 조회 | +| order | idx_order_user_id (user_id), idx_order_ordered_at (ordered_at) | 사용자별·기간별 주문 조회 | +| order_item | idx_order_item_order_id (order_id) | 주문별 항목 조회 | + +### 인기순(좋아요 많은 순) 정렬 성능 + +- 상품 목록 "인기순" 정렬은 `like` 테이블의 `product_id`별 COUNT로 구현 가능하다. `idx_like_product_id`로 집계 쿼리 성능을 확보한다. +- **보완**: 상품 수·트래픽이 커지면 상품별 좋아요 수를 **캐시(Redis)** 또는 **집계 컬럼/테이블**로 유지하고, 정렬 시 해당 값을 사용하는 방안을 검토한다. + +--- + +## 5. 구현 시 유지할 것 + +- **참조 컬럼**: brand_id, user_id, product_id, order_id는 그대로 두고, 조인·조회는 현재 ERD와 동일하게 사용. +- **UNIQUE(user_id, product_id)** on like: 1인 1좋아요 보장을 위해 DDL에 포함. +- **JPA**: 다른 애그리거트 참조는 `Long brandId`, `Long userId` 등 ID만 두고, `@ManyToOne` 사용하지 않음. DDL은 Flyway/Liquibase로 관리 시 REFERENCES 절 포함하지 않음. +- **참조 정합성**: 저장/수정 전 서비스에서 참조 대상 존재·미삭제 여부 검증. + +---