From c0dcfd8aa69e2c7e316fd19b2c35527541e59906 Mon Sep 17 00:00:00 2001 From: "hanyoung.park" Date: Mon, 2 Feb 2026 01:26:59 +0900 Subject: [PATCH 1/3] remove: deprecated codeguide --- .codeguide/loopers-1-week.md | 45 ------------------------------------ 1 file changed, 45 deletions(-) delete mode 100644 .codeguide/loopers-1-week.md diff --git a/.codeguide/loopers-1-week.md b/.codeguide/loopers-1-week.md deleted file mode 100644 index a8ace53e..00000000 --- a/.codeguide/loopers-1-week.md +++ /dev/null @@ -1,45 +0,0 @@ -## ๐Ÿงช Implementation Quest - -> ์ง€์ •๋œ **๋‹จ์œ„ ํ…Œ์ŠคํŠธ / ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ / E2E ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค**๋ฅผ ํ•„์ˆ˜๋กœ ๊ตฌํ˜„ํ•˜๊ณ , ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผ์‹œํ‚ค๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•ฉ๋‹ˆ๋‹ค. - -### ํšŒ์› ๊ฐ€์ž… - -**๐Ÿงฑ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ** - -- [ ] ID ๊ฐ€ `์˜๋ฌธ ๋ฐ ์ˆซ์ž 10์ž ์ด๋‚ด` ํ˜•์‹์— ๋งž์ง€ ์•Š์œผ๋ฉด, User ๊ฐ์ฒด ์ƒ์„ฑ์— ์‹คํŒจํ•œ๋‹ค. -- [ ] ์ด๋ฉ”์ผ์ด `xx@yy.zz` ํ˜•์‹์— ๋งž์ง€ ์•Š์œผ๋ฉด, User ๊ฐ์ฒด ์ƒ์„ฑ์— ์‹คํŒจํ•œ๋‹ค. -- [ ] ์ƒ๋…„์›”์ผ์ด `yyyy-MM-dd` ํ˜•์‹์— ๋งž์ง€ ์•Š์œผ๋ฉด, User ๊ฐ์ฒด ์ƒ์„ฑ์— ์‹คํŒจํ•œ๋‹ค. - -**๐Ÿ”— ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ** - -- [ ] ํšŒ์› ๊ฐ€์ž…์‹œ User ์ €์žฅ์ด ์ˆ˜ํ–‰๋œ๋‹ค. ( spy ๊ฒ€์ฆ ) -- [ ] ์ด๋ฏธ ๊ฐ€์ž…๋œ ID ๋กœ ํšŒ์›๊ฐ€์ž… ์‹œ๋„ ์‹œ, ์‹คํŒจํ•œ๋‹ค. - -**๐ŸŒ E2E ํ…Œ์ŠคํŠธ** - -- [ ] ํšŒ์› ๊ฐ€์ž…์ด ์„ฑ๊ณตํ•  ๊ฒฝ์šฐ, ์ƒ์„ฑ๋œ ์œ ์ € ์ •๋ณด๋ฅผ ์‘๋‹ต์œผ๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค. -- [ ] ํšŒ์› ๊ฐ€์ž… ์‹œ์— ์„ฑ๋ณ„์ด ์—†์„ ๊ฒฝ์šฐ, `400 Bad Request` ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. - -### ๋‚ด ์ •๋ณด ์กฐํšŒ - -**๐Ÿ”— ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ** - -- [ ] ํ•ด๋‹น ID ์˜ ํšŒ์›์ด ์กด์žฌํ•  ๊ฒฝ์šฐ, ํšŒ์› ์ •๋ณด๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค. -- [ ] ํ•ด๋‹น ID ์˜ ํšŒ์›์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ, null ์ด ๋ฐ˜ํ™˜๋œ๋‹ค. - -**๐ŸŒ E2E ํ…Œ์ŠคํŠธ** - -- [ ] ๋‚ด ์ •๋ณด ์กฐํšŒ์— ์„ฑ๊ณตํ•  ๊ฒฝ์šฐ, ํ•ด๋‹นํ•˜๋Š” ์œ ์ € ์ •๋ณด๋ฅผ ์‘๋‹ต์œผ๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค. -- [ ] ์กด์žฌํ•˜์ง€ ์•Š๋Š” ID ๋กœ ์กฐํšŒํ•  ๊ฒฝ์šฐ, `404 Not Found` ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. - -### ํฌ์ธํŠธ ์กฐํšŒ - -**๐Ÿ”— ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ** - -- [ ] ํ•ด๋‹น ID ์˜ ํšŒ์›์ด ์กด์žฌํ•  ๊ฒฝ์šฐ, ๋ณด์œ  ํฌ์ธํŠธ๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค. -- [ ] ํ•ด๋‹น ID ์˜ ํšŒ์›์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ, null ์ด ๋ฐ˜ํ™˜๋œ๋‹ค. - -**๐ŸŒ E2E ํ…Œ์ŠคํŠธ** - -- [ ] ํฌ์ธํŠธ ์กฐํšŒ์— ์„ฑ๊ณตํ•  ๊ฒฝ์šฐ, ๋ณด์œ  ํฌ์ธํŠธ๋ฅผ ์‘๋‹ต์œผ๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค. -- [ ] `X-USER-ID` ํ—ค๋”๊ฐ€ ์—†์„ ๊ฒฝ์šฐ, `400 Bad Request` ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. From 3310a3d56c1b923f66c5b96b98232816adeaf8eb Mon Sep 17 00:00:00 2001 From: madirony Date: Wed, 4 Feb 2026 01:27:01 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix=20:=20=EC=98=88=EC=A0=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=EC=9D=84=20=EC=9C=84=ED=95=9C=20testcontaine?= =?UTF-8?q?rs=20=EB=B2=84=EC=A0=84=20=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + gradle.properties | 1 + 2 files changed, 2 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 9c8490b8..dc167f2e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -42,6 +42,7 @@ subprojects { dependencyManagement { imports { mavenBom("org.springframework.cloud:spring-cloud-dependencies:${project.properties["springCloudDependenciesVersion"]}") + mavenBom("org.testcontainers:testcontainers-bom:${project.properties["testcontainersVersion"]}") } } diff --git a/gradle.properties b/gradle.properties index 142d7120..5ae37ac9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,7 @@ springBootVersion=3.4.4 springDependencyManagementVersion=1.1.7 springCloudDependenciesVersion=2024.0.1 ### Library versions ### +testcontainersVersion=2.0.2 springDocOpenApiVersion=2.7.0 springMockkVersion=4.0.2 mockitoVersion=5.14.0 From d68086e0f7d119fbd8f63534ef45ab55dbcf02bb Mon Sep 17 00:00:00 2001 From: lkk6201 Date: Fri, 13 Feb 2026 17:06:06 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EC=A0=95=EC=9D=98=EC=84=9C,=20=EC=8B=9C=ED=80=80?= =?UTF-8?q?=EC=8A=A4=20=EB=8B=A4=EC=9D=B4=EC=96=B4=EA=B7=B8=EB=9E=A8,=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=8B=A4=EC=9D=B4=EC=96=B4?= =?UTF-8?q?=EA=B7=B8=EB=9E=A8,=20ERD,=20=ED=81=B4=EB=A1=9C=EB=93=9C=20SKIL?= =?UTF-8?q?L.md=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/skills/requirements-analysis/SKILL.md | 77 ++ docs/design/01-requirements.md | 218 ++++++ docs/design/02-sequence-diagrams.md | 667 ++++++++++++++++++ docs/design/03-class-diagram.md | 300 ++++++++ docs/design/04-erd.md | 133 ++++ 5 files changed, 1395 insertions(+) create mode 100644 .claude/skills/requirements-analysis/SKILL.md create mode 100644 docs/design/01-requirements.md create mode 100644 docs/design/02-sequence-diagrams.md create mode 100644 docs/design/03-class-diagram.md create mode 100644 docs/design/04-erd.md diff --git a/.claude/skills/requirements-analysis/SKILL.md b/.claude/skills/requirements-analysis/SKILL.md new file mode 100644 index 00000000..3485a8af --- /dev/null +++ b/.claude/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 diff --git a/docs/design/01-requirements.md b/docs/design/01-requirements.md new file mode 100644 index 00000000..2962cace --- /dev/null +++ b/docs/design/01-requirements.md @@ -0,0 +1,218 @@ +# ๋ธŒ๋žœ๋“œ & ์ƒํ’ˆ ์š”๊ตฌ์‚ฌํ•ญ ๋ถ„์„ + +## 1. ์š”๊ตฌ์‚ฌํ•ญ ์ •๋ฆฌ + +### 1-1. ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž (Brands / Products) + +| # | ๊ธฐ๋Šฅ | ์ƒ์„ธ | +|---|------|---------------------------------------| +| 1 | ๋ธŒ๋žœ๋“œ ๊ธฐ๋ณธ ์ •๋ณด ์กฐํšŒ | ๋ธŒ๋žœ๋“œ ์ด๋ฆ„, ์†Œ๊ฐœ๋ฌธ๊ตฌ, ํŒ๋งค์ค‘ ์ƒํ’ˆ ์ด ๊ฐœ์ˆ˜, ๊ณต์‹ ์›น์‚ฌ์ดํŠธ ๋งํฌ | +| 2 | ๋ธŒ๋žœ๋“œ ์ƒํ’ˆ ๋ชฉ๋ก ์กฐํšŒ | ์„ ํƒํ•œ ๋ธŒ๋žœ๋“œ์— ์†ํ•œ ์ƒํ’ˆ ๋ชฉ๋ก (ํŽ˜์ด์ง•) | +| 3 | ์ƒํ’ˆ ์ƒ์„ธ ์กฐํšŒ | ์ƒํ’ˆ๋ช…, ๊ฐ€๊ฒฉ, ์ข‹์•„์š” ์ˆ˜, ์ƒ์„ฑ์ผ์‹œ, ์„ค๋ช…, ์ด๋ฏธ์ง€, ์ƒํƒœ | +| 4 | ์ƒํ’ˆ ๋ชฉ๋ก ์ •๋ ฌ | ์ตœ์‹ ์ˆœ, ๋‚ฎ์€ ๊ฐ€๊ฒฉ์ˆœ, ๋†’์€ ๊ฐ€๊ฒฉ์ˆœ, ์ข‹์•„์š” ๋งŽ์€์ˆœ | +| 5 | ๋ธŒ๋žœ๋“œ ๊ฒ€์ƒ‰ | ๋ธŒ๋žœ๋“œ๋ช… ๊ธฐ๋ฐ˜ LIKE ๊ฒ€์ƒ‰ | +| 6 | ์ƒํ’ˆ ๊ฒ€์ƒ‰ | ์ƒํ’ˆ๋ช… ๊ธฐ๋ฐ˜ LIKE ๊ฒ€์ƒ‰ | + +### 1-2. ๊ด€๋ฆฌ์ž (Brands & Products ADMIN) + +| # | ๊ธฐ๋Šฅ | ์ƒ์„ธ | +|---|------|------| +| 1 | ๋ธŒ๋žœ๋“œ ๋ชฉ๋ก ์กฐํšŒ | ๋“ฑ๋ก๋œ ๋ธŒ๋žœ๋“œ ์ „์ฒด ๋ชฉ๋ก (ํŽ˜์ด์ง•) | +| 2 | ๋ธŒ๋žœ๋“œ ๋“ฑ๋ก | ๋ธŒ๋žœ๋“œ๋ช…, ์†Œ๊ฐœ๋ฌธ๊ตฌ, ์›น์‚ฌ์ดํŠธ URL | +| 3 | ๋ธŒ๋žœ๋“œ ์ˆ˜์ • | ๋ธŒ๋žœ๋“œ๋ช…, ์†Œ๊ฐœ๋ฌธ๊ตฌ, ์›น์‚ฌ์ดํŠธ URL ์ˆ˜์ • ๊ฐ€๋Šฅ | +| 4 | ๋ธŒ๋žœ๋“œ ์‚ญ์ œ | Soft Delete. ํ•ด๋‹น ๋ธŒ๋žœ๋“œ์˜ ์ƒํ’ˆ ์ผ๊ด„ soft delete (๋ฒŒํฌ UPDATE) | +| 5 | ๋ธŒ๋žœ๋“œ ์ƒํ’ˆ ๋ชฉ๋ก ์กฐํšŒ | ๋ธŒ๋žœ๋“œ์— ๋“ฑ๋ก๋œ ์ƒํ’ˆ ๋ชฉ๋ก (ํŽ˜์ด์ง•) | +| 6 | ๋ธŒ๋žœ๋“œ ์ƒํ’ˆ ์ƒ์„ธ ์กฐํšŒ | ์ƒํ’ˆ ์ „์ฒด ์ •๋ณด ์กฐํšŒ | +| 7 | ์ƒํ’ˆ ๋“ฑ๋ก | ๋ธŒ๋žœ๋“œ์— ์ƒํ’ˆ ๋“ฑ๋ก (๋ธŒ๋žœ๋“œ ์กด์žฌ ํ™•์ธ ํ•„์ˆ˜) | +| 8 | ์ƒํ’ˆ ์ˆ˜์ • | ์ƒํ’ˆ ์ •๋ณด ์ˆ˜์ • (์†Œ์† ๋ธŒ๋žœ๋“œ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€) | +| 9 | ์ƒํ’ˆ ์‚ญ์ œ | Soft Delete | + +## 2. ์„ค๊ณ„ ๊ฒฐ์ • ์‚ฌํ•ญ + +| ํ•ญ๋ชฉ | ๊ฒฐ์ • | ์ด์œ  | +|------|------|----------------------------------------------| +| ์‚ญ์ œ ์ „๋žต | Soft Delete | BaseEntity ํŒจํ„ด๊ณผ ์ผ๊ด€์„ฑ ์œ ์ง€ | +| API ๋ถ„๋ฆฌ | ๋Œ€๊ณ ๊ฐ `/api/v1`, ์–ด๋“œ๋ฏผ `/api-admin/v1` | ๋„๋ฉ”์ธ ์„œ๋น„์Šค ๊ณต์œ , ์ปจํŠธ๋กค๋Ÿฌ/DTO๋งŒ ๋ถ„๋ฆฌ. ํ—ค๋” ๊ธฐ๋ฐ˜ ์‹๋ณ„ | +| ์ธ์ฆ/์ธ๊ฐ€ | ๋ฏธ๊ตฌํ˜„ (ํ—ค๋” ๊ธฐ๋ฐ˜ ์‹๋ณ„๋งŒ) | ๋Œ€๊ณ ๊ฐ: X-Loopers-LoginId/LoginPw, ์–ด๋“œ๋ฏผ: X-Loopers-Ldap | +| ์ƒํ’ˆ ์ด๋ฏธ์ง€ | ๋‹จ์ผ ์ด๋ฏธ์ง€ URL ํ•„๋“œ | ์š”๊ตฌ์‚ฌํ•ญ ๋ฒ”์œ„ ๋‚ด ์ตœ์†Œ ๊ตฌํ˜„ | +| ์ƒํ’ˆ ์ƒํƒœ | `SELLING`, `SOLD_OUT` | ์ตœ์†Œ ๊ตฌ์„ฑ. ์ถ”ํ›„ ํ™•์žฅ ์˜ˆ์ • | +| ๊ฒ€์ƒ‰ ๋ฐฉ์‹ | SQL LIKE | ์ถ”ํ›„ ๋ฐ์ดํ„ฐ ์ฆ๊ฐ€ ์‹œ Full-text, ElasticSearch ๋“ฑ ๊ฒ€ํ†  ์˜ˆ์ • | +| ํŽ˜์ด์ง• | Offset ๊ธฐ๋ฐ˜ (Pageable) | Spring Data ํ‘œ์ค€ ํ™œ์šฉ | +| ์ข‹์•„์š” ์ˆ˜ | `likeCount` ๋น„์ •๊ทœํ™” ํ•„๋“œ | ์กฐํšŒ/์ •๋ ฌ ์„ฑ๋Šฅ. ์ข‹์•„์š” ๋„๋ฉ”์ธ ์—ฐ๋™ ์‹œ ๋™๊ธฐํ™” | +| Brand ์ฐธ์กฐ | `brandId(Long)` | ๋„๋ฉ”์ธ ๊ฐ„ ๊ฒฐํ•ฉ๋„ ์ตœ์†Œํ™” | +| ๋Œ€๋Ÿ‰ ์‚ญ์ œ | ๋ฒŒํฌ UPDATE ์ฟผ๋ฆฌ | ์„ฑ๋Šฅ ์šฐ์„ . JPQL ์ผ๊ด„ ์ฒ˜๋ฆฌ | + +## 3. ์ž ์žฌ ๋ฆฌ์Šคํฌ + +| ๋ฆฌ์Šคํฌ | ํ˜„์žฌ ๋Œ€์‘ | ์ถ”ํ›„ ๊ฒ€ํ†  | +|--------|----------|----------| +| LIKE ๊ฒ€์ƒ‰ ์„ฑ๋Šฅ | ๋ฐ์ดํ„ฐ ์†Œ๊ทœ๋ชจ ์‹œ ๋ฌธ์ œ์—†์Œ | Full-text index ๋˜๋Š” Elasticsearch | +| likeCount ๋™์‹œ์„ฑ | ์ข‹์•„์š” ๋„๋ฉ”์ธ ๋ฏธ๊ตฌํ˜„ | ๋‚™๊ด€์ /๋น„๊ด€์  ๋ฝ ๋˜๋Š” Redis ํ™œ์šฉ | +| ๋Œ€๋Ÿ‰ ์ƒํ’ˆ ์‚ญ์ œ | ๋ฒŒํฌ UPDATE | ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์‚ญ์ œ | +| soft delete ํ•„ํ„ฐ ๋ˆ„๋ฝ | ์กฐํšŒ ์ฟผ๋ฆฌ์— `deletedAt IS NULL` ํ•„์ˆ˜ | `@Where` ๋˜๋Š” QueryDSL ๊ธฐ๋ณธ ํ•„ํ„ฐ | + +--- + +# ์ข‹์•„์š” & ์ฃผ๋ฌธ ์š”๊ตฌ์‚ฌํ•ญ ๋ถ„์„ + +## 4. ์š”๊ตฌ์‚ฌํ•ญ ์ •๋ฆฌ + +### 4-1. ์ข‹์•„์š” (Likes) + +| # | ๊ธฐ๋Šฅ | ์ƒ์„ธ | +|---|------|------| +| 1 | ์ƒํ’ˆ ์ข‹์•„์š” ๋“ฑ๋ก | ์กฐํšŒ ์ค‘์ธ ์ƒํ’ˆ์— ์ข‹์•„์š”๋ฅผ ๋“ฑ๋ก. ์ด๋ฏธ ์ข‹์•„์š” ์ƒํƒœ๋ฉด ๊ทธ๋Œ€๋กœ ์œ ์ง€ (๋ฉฑ๋“ฑ) | +| 2 | ์ƒํ’ˆ ์ข‹์•„์š” ์ทจ์†Œ | ์ด๋ฏธ ์ข‹์•„์š”ํ•œ ์ƒํ’ˆ์˜ ์ข‹์•„์š”๋ฅผ ์ทจ์†Œ. ์ข‹์•„์š”๊ฐ€ ์—†๋Š” ์ƒํƒœ๋ฉด ๊ทธ๋Œ€๋กœ ์œ ์ง€ (๋ฉฑ๋“ฑ) | +| 3 | ์ข‹์•„์š” ๋ชฉ๋ก ์กฐํšŒ | ๋‚ด๊ฐ€ ์ข‹์•„์š” ํ•œ ์ƒํ’ˆ ๋ชฉ๋ก์„ ์ƒํ’ˆ ์ƒ์„ธ ์ •๋ณด ํฌํ•จํ•˜์—ฌ ์กฐํšŒ (ํŽ˜์ด์ง•) | + +- ๋ชจ๋“  ๊ธฐ๋Šฅ์€ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋งŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. +- ๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ: DB ๋ณตํ•ฉ ์œ ๋‹ˆํฌ ํ‚ค + ์„œ๋น„์Šค ๋ ˆ์ด์–ด ๋”๋ธ” ์ฒดํฌ. + +### 4-2. ์ฃผ๋ฌธ (Orders) + +| # | ๊ธฐ๋Šฅ | ์ƒ์„ธ | +|---|------|------| +| 1 | ์ฃผ๋ฌธ ์š”์ฒญ | ๋ณต์ˆ˜ ์ƒํ’ˆ ์ฃผ๋ฌธ. ์ƒํ’ˆ ์Šค๋ƒ…์ƒท ์ €์žฅ, ์žฌ๊ณ  ํ™•์ธ ๋ฐ ์ฐจ๊ฐ | +| 2 | ์ฃผ๋ฌธ ๋ชฉ๋ก ์กฐํšŒ | ๋ณธ์ธ์˜ ์ฃผ๋ฌธ ๋ชฉ๋ก์„ ๊ธฐ๊ฐ„ ํ•„ํ„ฐ(`startAt`, `endAt`)์™€ ํŽ˜์ด์ง•์œผ๋กœ ์กฐํšŒ | +| 3 | ์ฃผ๋ฌธ ์ƒ์„ธ ์กฐํšŒ | ๋‹จ์ผ ์ฃผ๋ฌธ์˜ ์ƒ์„ธ ์ •๋ณด(์ฃผ๋ฌธ ํ•ญ๋ชฉ ํฌํ•จ) ์กฐํšŒ | + +- ๋ชจ๋“  ๊ธฐ๋Šฅ์€ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋งŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. +- ๊ฒฐ์ œ๋Š” ์ด๋ฒˆ ์„ค๊ณ„์—์„œ ์ƒ๋žตํ•œ๋‹ค. + +### 4-3. ์ฃผ๋ฌธ ADMIN + +| # | ๊ธฐ๋Šฅ | ์ƒ์„ธ | +|---|------|------| +| 1 | ์ฃผ๋ฌธ ๋ชฉ๋ก ์กฐํšŒ | ์ „์ฒด ์‚ฌ์šฉ์ž์˜ ์ฃผ๋ฌธ ๋ชฉ๋ก์„ ํŽ˜์ด์ง•์œผ๋กœ ์กฐํšŒ | +| 2 | ์ฃผ๋ฌธ ์ƒ์„ธ ์กฐํšŒ | ๋‹จ์ผ ์ฃผ๋ฌธ์˜ ์ƒ์„ธ ์ •๋ณด(์ฃผ๋ฌธ ํ•ญ๋ชฉ ํฌํ•จ) ์กฐํšŒ | + +- ๋ชจ๋“  ๊ธฐ๋Šฅ์€ ๊ด€๋ฆฌ์ž๋งŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. + +## 5. ์„ค๊ณ„ ๊ฒฐ์ • ์‚ฌํ•ญ + +| ํ•ญ๋ชฉ | ๊ฒฐ์ • | ์ด์œ  | +|------|------|------| +| ์žฌ๊ณ  ๊ด€๋ฆฌ | ๋ณ„๋„ Stock ์—”ํ‹ฐํ‹ฐ ๋ถ„๋ฆฌ | Product์™€ ๋…๋ฆฝ์ ์ธ ์žฌ๊ณ  ๋„๋ฉ”์ธ. ๋น„๊ด€์  ๋ฝ ๋ฒ”์œ„ ์ตœ์†Œํ™”, ์ถ”ํ›„ ์ž…/์ถœ๊ณ  ์ด๋ ฅ ํ™•์žฅ ๊ฐ€๋Šฅ | +| ๋ถ€๋ถ„ ์žฌ๊ณ  ๋ถ€์กฑ | ์ „์ฒด ์ฃผ๋ฌธ ์‹คํŒจ (๋กค๋ฐฑ) | ํŠธ๋žœ์žญ์…˜ ๋‹จ์ˆœํ™”, ๊ฒฐ์ œ ์—ฐ๋™ ์‹œ์—๋„ ์•ˆ์ „. ๋ถ€๋ถ„ ์ฃผ๋ฌธ ํ—ˆ์šฉ ์‹œ ๊ธˆ์•ก ๋ถˆ์ผ์น˜ ๋ฆฌ์Šคํฌ | +| likeCount ๋™๊ธฐํ™” | ์„œ๋น„์Šค ๋ ˆ์ด์–ด ์ง์ ‘ ์ฆ๊ฐ | ๊ฐ™์€ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ๋น„๊ด€์  ๋ฝ์œผ๋กœ ์ •ํ•ฉ์„ฑ ๋ณด์žฅ. ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ๋น„๋™๊ธฐ ๋Œ€๋น„ ๊ตฌํ˜„ ๋‹จ์ˆœ | +| ์ฃผ๋ฌธ ์ƒํƒœ | ๋‹จ์ผ ORDERED, ์ทจ์†Œ ๋ฏธ๊ตฌํ˜„ | ๊ฒฐ์ œ ์ƒ๋žต์ด๋ฏ€๋กœ ์ฃผ๋ฌธ ์ฆ‰์‹œ ํ™•์ •. ์ทจ์†Œ/ํ™˜๋ถˆ์€ ๊ฒฐ์ œ ๋„์ž… ์‹œ ํ•จ๊ป˜ ๊ตฌํ˜„ | +| ์ข‹์•„์š” ๋ชฉ๋ก | ์ƒํ’ˆ ์ƒ์„ธ ํฌํ•จ | ํด๋ผ์ด์–ธํŠธ N+1 ํ˜ธ์ถœ ๋ฐฉ์ง€. Product ์กฐ์ธ์œผ๋กœ ํ•œ ๋ฒˆ์— ๋ฐ˜ํ™˜ | +| ์ฃผ๋ฌธ ์Šค๋ƒ…์ƒท | ์ด๋ฆ„ + ๊ฐ€๊ฒฉ + ์ด๋ฏธ์ง€ | ์ฃผ๋ฌธ ์ด๋ ฅ ํ™•์ธ์— ํ•„์š”ํ•œ ํ•ต์‹ฌ ์ •๋ณด. ์ƒํ’ˆ ์‚ญ์ œ/์ˆ˜์ • ํ›„์—๋„ ์›๋ž˜ ๊ฑฐ๋ž˜ ์กฐ๊ฑด ๋ณด์กด | +| ์žฌ๊ณ  0 ์ฒ˜๋ฆฌ | ์ž๋™ OUT_OF_STOCK ์ „ํ™˜ | SELLING โ†’ OUT_OF_STOCK ์ž๋™ ์ „ํ™˜, ์žฌ์ž…๊ณ  ์‹œ SELLING ์ž๋™ ๋ณต์›. SOLD_OUT์€ ์–ด๋“œ๋ฏผ ์ˆ˜๋™ | +| ์ฃผ๋ฌธ ๋ชฉ๋ก ํ•„ํ„ฐ | startAt/endAt ๊ธฐ๊ฐ„ ํ•„ํ„ฐ | ์‚ฌ์šฉ์ž ์ฃผ๋ฌธ ์กฐํšŒ ์‹œ ๊ธฐ๊ฐ„ ๋ฒ”์œ„ ์ง€์ •. ์–ด๋“œ๋ฏผ์€ ์ „์ฒด ๋ชฉ๋ก + ํŽ˜์ด์ง•๋งŒ | +| ์‚ญ์ œ ์ „๋žต | Soft Delete | BaseEntity ํŒจํ„ด๊ณผ ์ผ๊ด€์„ฑ ์œ ์ง€ | + +## 6. ์„ค๊ณ„ ๊ณ ๋ฏผ๊ณผ ๊ฒฐ์ • + +### 6-1. ์ข‹์•„์š” ๊ธฐ๋Šฅ์˜ ๋ฉฑ๋“ฑ์„ฑ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ์–ด๋–ป๊ฒŒ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ์„๊นŒ? + +**๊ณ ๋ฏผ ๋ฐฐ๊ฒฝ**: ์ข‹์•„์š” ๋“ฑ๋ก์„ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœํ•ด๋„ ๊ฒฐ๊ณผ๋Š” '์ข‹์•„์š”' ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•ด์•ผ ํ•˜๊ณ , ์ทจ์†Œ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœํ•ด๋„ ๊ฒฐ๊ณผ๋Š” '์ทจ์†Œ' ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•ด์•ผ ํ•œ๋‹ค. ๋„คํŠธ์›Œํฌ ์žฌ์‹œ๋„๋‚˜ ํด๋ผ์ด์–ธํŠธ ์ค‘๋ณต ์š”์ฒญ์—๋„ ๋ถ€์ž‘์šฉ์ด ์—†์–ด์•ผ ํ•œ๋‹ค. + +**๊ฒ€ํ† ํ•œ ์„ ํƒ์ง€**: + +| ์„ ํƒ์ง€ | ์žฅ์  | ๋‹จ์  | +|--------|------|------| +| A. DB ์œ ๋‹ˆํฌ ํ‚ค๋งŒ ์˜์กด | ๊ตฌํ˜„ ๋‹จ์ˆœ | ์œ ๋‹ˆํฌ ํ‚ค ์œ„๋ฐ˜ ์‹œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํ•„์š”, likeCount ์ •ํ•ฉ์„ฑ ๋ฆฌ์Šคํฌ | +| B. ์„œ๋น„์Šค ๋ ˆ์ด์–ด ์‚ฌ์ „ ๊ฒ€์ฆ๋งŒ | ์ฝ”๋“œ ๋ ˆ๋ฒจ์—์„œ ๋ช…ํ™• | ๋™์‹œ ์š”์ฒญ ์‹œ ๋ ˆ์ด์Šค ์ปจ๋””์…˜ ๊ฐ€๋Šฅ | +| **C. DB ์œ ๋‹ˆํฌ ํ‚ค + ์„œ๋น„์Šค ๋ ˆ์ด์–ด ๋”๋ธ” ์ฒดํฌ** | ์ •์ƒ ํ๋ฆ„์€ ์„œ๋น„์Šค์—์„œ ์ฒ˜๋ฆฌ, ๋™์‹œ์„ฑ ์—ฃ์ง€ ์ผ€์ด์Šค๋Š” DB๊ฐ€ ๋ฐฉ์–ด | ๋‘ ๊ณณ์—์„œ ์ค‘๋ณต ๋กœ์ง | + +**๊ฒฐ์ •: ์„ ํƒ์ง€ C (๋”๋ธ” ์ฒดํฌ)** + +- **์„œ๋น„์Šค ๋ ˆ์ด์–ด**: ์ข‹์•„์š” ๋“ฑ๋ก ์‹œ `findByUserIdAndProductId`๋กœ ๊ธฐ์กด ๋ ˆ์ฝ”๋“œ ํ™•์ธ. ์ด๋ฏธ ์กด์žฌํ•˜๋ฉด INSERT ์—†์ด ๊ธฐ์กด ๋ ˆ์ฝ”๋“œ ๋ฐ˜ํ™˜(๋ฉฑ๋“ฑ). ์ทจ์†Œ ์‹œ์—๋„ ๋ ˆ์ฝ”๋“œ ์—†์œผ๋ฉด ๋ฌด์‹œ(๋ฉฑ๋“ฑ). +- **DB ๋ ˆ๋ฒจ**: `LIKES` ํ…Œ์ด๋ธ”์— `(user_id, product_id)` ๋ณตํ•ฉ ์œ ๋‹ˆํฌ ํ‚ค ์„ค์ •. ์„œ๋น„์Šค ๋ ˆ์ด์–ด๋ฅผ ์šฐํšŒํ•˜๋Š” ๋™์‹œ ์š”์ฒญ์ด ๋“ค์–ด์™€๋„ ์ค‘๋ณต INSERT๊ฐ€ ๋ฌผ๋ฆฌ์ ์œผ๋กœ ๋ถˆ๊ฐ€๋Šฅ. +- **likeCount ์ •ํ•ฉ์„ฑ**: ์ข‹์•„์š” ๋“ฑ๋ก/์ทจ์†Œ ์‹œ ๊ฐ™์€ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ProductEntity.likeCount๋ฅผ ๋น„๊ด€์  ๋ฝ์œผ๋กœ ์ฆ๊ฐ. ์„œ๋น„์Šค ๋ ˆ์ด์–ด ์‚ฌ์ „ ๊ฒ€์ฆ์ด ๋ถˆํ•„์š”ํ•œ likeCount ๋ณ€๊ฒฝ์„ ๋ฐฉ์ง€ํ•˜๊ณ , ์œ ๋‹ˆํฌ ํ‚ค๊ฐ€ ์ตœ์ข… ๋ฐฉ์–ด์„  ์—ญํ• . + +### 6-2. ์ฃผ๋ฌธ ์ƒ์„ฑ ์‹œ ์žฌ๊ณ /ํฌ์ธํŠธ ์ฐจ๊ฐ์„ ์–ด๋–ป๊ฒŒ ์—ฐ๊ฒฐํ–ˆ๋Š”๊ฐ€? + +**๊ณ ๋ฏผ ๋ฐฐ๊ฒฝ**: ์ฃผ๋ฌธ์€ ๋ณต์ˆ˜ ์ƒํ’ˆ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•˜๋ฉฐ, ๊ฐ ์ƒํ’ˆ์˜ ์žฌ๊ณ  ํ™•์ธ๊ณผ ์ฐจ๊ฐ์ด ์›์ž์ ์œผ๋กœ ์ด๋ฃจ์–ด์ ธ์•ผ ํ•œ๋‹ค. ๋™์‹œ ์ฃผ๋ฌธ ์‹œ์—๋„ ์žฌ๊ณ ๊ฐ€ ์Œ์ˆ˜๊ฐ€ ๋˜์–ด์„  ์•ˆ ๋œ๋‹ค. (ํฌ์ธํŠธ/๊ฒฐ์ œ๋Š” ์ด๋ฒˆ ์Šค์ฝ”ํ”„์—์„œ ์ƒ๋žตํ•˜๋ฏ€๋กœ, ์žฌ๊ณ  ์ฐจ๊ฐ์— ์ง‘์ค‘ํ•œ๋‹ค.) + +**๊ฒ€ํ† ํ•œ ์„ ํƒ์ง€**: + +| ์„ ํƒ์ง€ | ์žฅ์  | ๋‹จ์  | +|--------|------|------| +| A. ๋‚™๊ด€์  ๋ฝ (๋ฒ„์ „ ํ•„๋“œ) | ๋ฝ ๊ฒฝํ•ฉ ๋‚ฎ์Œ, ์ฝ๊ธฐ ์„ฑ๋Šฅ ์ข‹์Œ | ์ถฉ๋Œ ์‹œ ์žฌ์‹œ๋„ ๋กœ์ง ํ•„์š”, ์ธ๊ธฐ ์ƒํ’ˆ์€ ์žฌ์‹œ๋„ ๋นˆ๋ฒˆ | +| **B. ๋น„๊ด€์  ๋ฝ (SELECT FOR UPDATE)** | ์ถฉ๋Œ ๋ฐฉ์ง€ ํ™•์‹ค, ์žฌ์‹œ๋„ ๋ถˆํ•„์š” | ๋ฝ ๋Œ€๊ธฐ ๋ฐœ์ƒ, ๋ฐ๋“œ๋ฝ ๊ฐ€๋Šฅ์„ฑ | +| C. Redis ๋ถ„์‚ฐ ๋ฝ | DB ๋ถ€ํ•˜ ๋ถ„์‚ฐ | ์ธํ”„๋ผ ๋ณต์žก๋„ ์ฆ๊ฐ€, ๋ฝ ํ•ด์ œ ์‹คํŒจ ์‹œ ์ฒ˜๋ฆฌ ํ•„์š” | + +**๊ฒฐ์ •: ์„ ํƒ์ง€ B (๋น„๊ด€์  ๋ฝ) + ๋ฐ๋“œ๋ฝ ๋ฐฉ์ง€ ์ „๋žต** + +- **Stock ์—”ํ‹ฐํ‹ฐ ๋ถ„๋ฆฌ**: Product์™€ ์žฌ๊ณ ๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ๋น„๊ด€์  ๋ฝ ๋ฒ”์œ„๋ฅผ ์ตœ์†Œํ™”. Product ์ „์ฒด๋ฅผ ์ž ๊ทธ์ง€ ์•Š๊ณ  Stock๋งŒ ์ž ๊ธˆ. +- **productId ์˜ค๋ฆ„์ฐจ์ˆœ ์ •๋ ฌ ํ›„ ๋ฝ ํš๋“**: ๋ณต์ˆ˜ ์ƒํ’ˆ ์ฃผ๋ฌธ ์‹œ ํ•ญ์ƒ ๊ฐ™์€ ์ˆœ์„œ๋กœ ๋ฝ์„ ํš๋“ํ•˜์—ฌ ๋ฐ๋“œ๋ฝ ๋ฐฉ์ง€. +- **์ „์ฒด ๋กค๋ฐฑ ์ •์ฑ…**: ํ•˜๋‚˜๋ผ๋„ ์žฌ๊ณ  ๋ถ€์กฑ์ด๋ฉด ์ „์ฒด ํŠธ๋žœ์žญ์…˜ ๋กค๋ฐฑ. ๋ถ€๋ถ„ ์ฃผ๋ฌธ์„ ํ—ˆ์šฉํ•˜๋ฉด ์ถ”ํ›„ ๊ฒฐ์ œ ์—ฐ๋™ ์‹œ ๊ธˆ์•ก ๋ถˆ์ผ์น˜ ๋ฆฌ์Šคํฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฏ€๋กœ all-or-nothing. +- **OUT_OF_STOCK ์ž๋™ ์ „ํ™˜**: ์žฌ๊ณ  ์ฐจ๊ฐ ํ›„ 0์ด ๋˜๋ฉด ProductEntity.status๋ฅผ ์ž๋™์œผ๋กœ OUT_OF_STOCK์œผ๋กœ ๋ณ€๊ฒฝ. ์žฌ์ž…๊ณ  ์‹œ(Stock ์ฆ๊ฐ€) ์ž๋™ SELLING ๋ณต์›. ์˜๊ตฌ ํŒ๋งค์ค‘์ง€(SOLD_OUT)๋Š” ์–ด๋“œ๋ฏผ์ด ์ˆ˜๋™์œผ๋กœ ๊ด€๋ฆฌ. +- **ํ™•์žฅ ํฌ์ธํŠธ**: ๊ฒฐ์ œ/ํฌ์ธํŠธ ์ฐจ๊ฐ์ด ์ถ”๊ฐ€๋  ๋•Œ, Facade์— ๊ฒฐ์ œ ์„œ๋น„์Šค ํ˜ธ์ถœ์„ ์ˆœ์„œ์— ๋งž๊ฒŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค. ๊ฒฐ์ œ ์‹คํŒจ ์‹œ ์žฌ๊ณ  ๋ณต์›๋„ ๊ฐ™์€ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์ฒ˜๋ฆฌ. + +### 6-3. ๋„๋ฉ”์ธ ๊ฐ์ฒด๋ฅผ ์–ด๋–ป๊ฒŒ ๋‚˜๋ˆ„๊ณ  ์ฑ…์ž„์„ ์œ„์ž„ํ–ˆ๋Š”๊ฐ€? + +**๊ณ ๋ฏผ ๋ฐฐ๊ฒฝ**: ๊ธฐ์กด User, Brand, Product๋Š” Domain โ†” Entity ๋ถ„๋ฆฌ ํŒจํ„ด์„ ๋”ฐ๋ฅด๊ณ  ์žˆ๋‹ค. ์ƒˆ๋กœ์šด ๋„๋ฉ”์ธ(Like, Order, Stock)๋„ ๊ฐ™์€ ํŒจํ„ด์„ ์ ์šฉํ•ด์•ผ ํ• ์ง€, ๋„๋ฉ”์ธ์˜ ํŠน์„ฑ์— ๋”ฐ๋ผ ๋‹ฌ๋ฆฌํ•  ๊ฒƒ์ธ์ง€๊ฐ€ ๋ฌธ์ œ. + +**๋„๋ฉ”์ธ๋ณ„ ๊ฒฐ์ •**: + +| ๋„๋ฉ”์ธ | Domain โ†” Entity ๋ถ„๋ฆฌ | ์ด์œ  | +|--------|:---:|------| +| Like | X (Entity๋งŒ) | userId + productId ์กฐํ•ฉ ์™ธ์— ๋ณ„๋„ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์—†์Œ. ์œ ํšจ์„ฑ ๊ฒ€์ฆ๋„ ์—†์œผ๋ฏ€๋กœ Entity๊ฐ€ ๊ณง ๋„๋ฉ”์ธ ๋ชจ๋ธ | +| Order | X (Entity๋งŒ) | ํ˜„์žฌ ์ƒํƒœ๋Š” ORDERED ํ•˜๋‚˜. ์ƒํƒœ ์ „์ด ๋กœ์ง์ด ์ƒ๊ธฐ๋ฉด(๊ฒฐ์ œ ๋„์ž… ์‹œ) ๊ทธ๋•Œ ๋ถ„๋ฆฌ ๊ฒ€ํ†  | +| OrderItem | X (Entity๋งŒ) | ์Šค๋ƒ…์ƒท ๋ฐ์ดํ„ฐ ์ €์žฅ์ด ํ•ต์‹ฌ ์ฑ…์ž„. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๋ณด๋‹ค ๋ฐ์ดํ„ฐ ๋ณด์กด ์—ญํ•  | +| Stock | O (Domain + Entity) | ์žฌ๊ณ  ์ฐจ๊ฐ/์ฆ๊ฐ€ ์‹œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ํ•„์š” (์Œ์ˆ˜ ๋ฐฉ์ง€, ์ฐจ๊ฐ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํ™•์ธ). ๋„๋ฉ”์ธ ๊ฐ์ฒด์—์„œ `deduct()`, `increase()` ๋ฉ”์„œ๋“œ๋กœ ๋น„์ฆˆ๋‹ˆ์Šค ๋ฃฐ ์บก์Аํ™” | + +**์ฑ…์ž„ ์œ„์ž„ ๊ตฌ์กฐ**: + +| ๋ ˆ์ด์–ด | ์—ญํ•  | +|--------|------| +| **Facade** | ๋„๋ฉ”์ธ ๊ฐ„ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜. LikeFacade๋Š” UserService + LikeService + ProductService๋ฅผ ์กฐํ•ฉ. OrderFacade๋Š” UserService + ProductService + StockService + OrderService๋ฅผ ์กฐํ•ฉ | +| **Service** | ๋‹จ์ผ ๋„๋ฉ”์ธ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง. LikeService๋Š” ์ข‹์•„์š” CRUD + ๋ฉฑ๋“ฑ์„ฑ ๊ฒ€์ฆ. OrderService๋Š” ์ฃผ๋ฌธ/์ฃผ๋ฌธํ•ญ๋ชฉ ์ƒ์„ฑ๊ณผ ์กฐํšŒ. StockService๋Š” ์žฌ๊ณ  ์ฐจ๊ฐ/์ฆ๊ฐ€ + OUT_OF_STOCK ์ž๋™ ์ „ํ™˜ | +| **Repository** | ์˜์†์„ฑ ์ธํ„ฐํŽ˜์ด์Šค. ๋„๋ฉ”์ธ ๋ ˆ์ด์–ด์— ์ธํ„ฐํŽ˜์ด์Šค, Infrastructure์— ๊ตฌํ˜„์ฒด | + +**StockService โ†’ ProductService ์ง์ ‘ ์˜์กด์— ๋Œ€ํ•œ ๊ณ ๋ฏผ**: + +StockService๊ฐ€ ์žฌ๊ณ  0์ผ ๋•Œ ProductService๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์€ Service ๊ฐ„ ์ง์ ‘ ์˜์กด์ด๋‹ค. ์ด๋ฅผ Facade์—์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜๋„ ์žˆ์—ˆ์ง€๋งŒ, "์žฌ๊ณ ๊ฐ€ 0์ด๋ฉด ํ’ˆ์ ˆ ์ƒํƒœ๋กœ ์ „ํ™˜"์€ ์žฌ๊ณ  ๋„๋ฉ”์ธ์˜ ํ•ต์‹ฌ ์ •์ฑ…์ด๋ฏ€๋กœ StockService์— ๋‘์—ˆ๋‹ค. ์ถ”ํ›„ ๊ฒฐํ•ฉ๋„๊ฐ€ ๋ฌธ์ œ๋˜๋ฉด ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋กœ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. + +### 6-4. ERD ์„ค๊ณ„ ์‹œ ์–ด๋–ค ๋ถ€๋ถ„์—์„œ ๊ณ ๋ฏผ์ด ์žˆ์—ˆ๋Š”๊ฐ€? + +**๊ณ ๋ฏผ 1: LIKES ํ…Œ์ด๋ธ”์˜ soft delete vs hard delete** + +์ข‹์•„์š” ์ทจ์†Œ ์‹œ ๋ ˆ์ฝ”๋“œ๋ฅผ soft delete ํ• ์ง€ hard delete ํ• ์ง€๊ฐ€ ๋ฌธ์ œ์˜€๋‹ค. + +| ์„ ํƒ์ง€ | ์žฅ์  | ๋‹จ์  | +|--------|------|------| +| A. Soft delete (deletedAt) | ์ด๋ ฅ ์ถ”์  ๊ฐ€๋Šฅ, BaseEntity ์ผ๊ด€์„ฑ | ์œ ๋‹ˆํฌ ํ‚ค์— deletedAt ํฌํ•จ ํ•„์š”, ๋ณต์žก๋„ ์ฆ๊ฐ€ | +| **B. Hard delete** | ์œ ๋‹ˆํฌ ํ‚ค ๋‹จ์ˆœ (user_id, product_id), ๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ ์ง๊ด€์  | ์ข‹์•„์š” ์ด๋ ฅ ์†Œ์‹ค | + +**๊ฒฐ์ •: Hard delete**. ์ข‹์•„์š”๋Š” ์ด๋ ฅ ๋ณด์กด ํ•„์š”์„ฑ์ด ๋‚ฎ๊ณ , soft delete ์‹œ ์œ ๋‹ˆํฌ ํ‚ค์— deletedAt์„ ํฌํ•จํ•ด์•ผ ํ•˜๋Š”๋ฐ ์ด๋Š” ๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ ๋กœ์ง์„ ๋ณต์žกํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค. ๊ฐ™์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ข‹์•„์š” โ†’ ์ทจ์†Œ โ†’ ์žฌ์ข‹์•„์š” ํ•  ๋•Œ soft delete ๋ ˆ์ฝ”๋“œ๊ฐ€ ์œ ๋‹ˆํฌ ํ‚ค ์ถฉ๋Œ์„ ์ผ์œผํ‚ค๋Š” ๋ฌธ์ œ๋ฅผ ์›์ฒœ ์ฐจ๋‹จํ•œ๋‹ค. + +**๊ณ ๋ฏผ 2: ORDER_ITEM.product_id๋ฅผ FK๋กœ ์„ค์ •ํ•  ๊ฒƒ์ธ๊ฐ€?** + +| ์„ ํƒ์ง€ | ์žฅ์  | ๋‹จ์  | +|--------|------|------| +| A. FK ์„ค์ • | ์ฐธ์กฐ ๋ฌด๊ฒฐ์„ฑ ๋ณด์žฅ | ์ƒํ’ˆ ์‚ญ์ œ ์‹œ ์ฃผ๋ฌธ ์ด๋ ฅ ์กฐํšŒ ๋ถˆ๊ฐ€๋Šฅ ๋˜๋Š” ์‚ญ์ œ ์ฐจ๋‹จ | +| **B. FK ๋ฏธ์„ค์ • (์ฐธ์กฐ์šฉ ID๋งŒ)** | ์ƒํ’ˆ soft delete ํ›„์—๋„ ์ฃผ๋ฌธ ์ด๋ ฅ ๋ณด์กด | DB ๋ ˆ๋ฒจ ๋ฌด๊ฒฐ์„ฑ ๋ณด์žฅ ์—†์Œ | + +**๊ฒฐ์ •: FK ๋ฏธ์„ค์ •**. ์ฃผ๋ฌธ ์Šค๋ƒ…์ƒท์€ ์ฃผ๋ฌธ ์‹œ์ ์˜ ์ƒํ’ˆ ์ •๋ณด๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๋ณด์กดํ•˜๋Š” ๊ฒƒ์ด ๋ชฉ์ ์ด๋‹ค. product_id๋Š” ์›๋ณธ ์ƒํ’ˆ ์ถ”์ ์šฉ ์ฐธ์กฐ ID์ผ ๋ฟ, ์ƒํ’ˆ์˜ ์กด์žฌ ์—ฌ๋ถ€์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ์ฃผ๋ฌธ ์ด๋ ฅ์ด ์œ ์ง€๋˜์–ด์•ผ ํ•œ๋‹ค. + +**๊ณ ๋ฏผ 3: Stock์„ Product ๋‚ด ํ•„๋“œ๋กœ ๋‘˜ ๊ฒƒ์ธ๊ฐ€, ๋ณ„๋„ ํ…Œ์ด๋ธ”๋กœ ๋ถ„๋ฆฌํ•  ๊ฒƒ์ธ๊ฐ€?** + +| ์„ ํƒ์ง€ | ์žฅ์  | ๋‹จ์  | +|--------|------|------| +| A. Product ๋‚ด stock ํ•„๋“œ | ์กฐ์ธ ๋ถˆํ•„์š”, ๋‹จ์ˆœ | ๋น„๊ด€์  ๋ฝ ์‹œ Product ์ „์ฒด๊ฐ€ ์ž ๊น€, ์ข‹์•„์š” ๋“ฑ ๋‹ค๋ฅธ ์—ฐ์‚ฐ๊ณผ ๋ฝ ๊ฒฝํ•ฉ | +| **B. ๋ณ„๋„ STOCK ํ…Œ์ด๋ธ”** | ๋ฝ ๋ฒ”์œ„ ์ตœ์†Œํ™”, ์žฌ๊ณ  ๋„๋ฉ”์ธ ๋…๋ฆฝ์„ฑ | ์กฐ์ธ ๋น„์šฉ, ํ…Œ์ด๋ธ” ๊ด€๋ฆฌ ํฌ์ธํŠธ ์ฆ๊ฐ€ | + +**๊ฒฐ์ •: ๋ณ„๋„ STOCK ํ…Œ์ด๋ธ”**. ์ฃผ๋ฌธ ์‹œ ๋น„๊ด€์  ๋ฝ์œผ๋กœ ์žฌ๊ณ ๋ฅผ ์ฐจ๊ฐํ•˜๋Š”๋ฐ, Product์— stock ํ•„๋“œ๋ฅผ ๋‘๋ฉด ์ข‹์•„์š”(likeCount ์ฆ๊ฐ)์™€ ์žฌ๊ณ  ์ฐจ๊ฐ์ด ๊ฐ™์€ ํ–‰์—์„œ ๋ฝ ๊ฒฝํ•ฉ์„ ์ผ์œผํ‚จ๋‹ค. Stock์„ ๋ถ„๋ฆฌํ•˜๋ฉด ์žฌ๊ณ  ๋ฝ๊ณผ ์ข‹์•„์š” ๋ฝ์ด ๋…๋ฆฝ์ ์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค. + +**๊ณ ๋ฏผ 4: ProductStatus์— OUT_OF_STOCK ์ถ”๊ฐ€** + +๊ธฐ์กด ์„ค๊ณ„๋Š” `SELLING`๊ณผ `SOLD_OUT` ๋‘ ๊ฐ€์ง€ ์ƒํƒœ๋งŒ ์กด์žฌํ–ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์žฌ๊ณ ๊ฐ€ 0์ด ๋œ ์ƒํ™ฉ(์ผ์‹œ์  ํ’ˆ์ ˆ)๊ณผ ์–ด๋“œ๋ฏผ์ด ์˜๋„์ ์œผ๋กœ ํŒ๋งค๋ฅผ ์ค‘์ง€ํ•œ ์ƒํ™ฉ(์˜๊ตฌ ํŒ๋งค ์ค‘์ง€)์€ ์„ฑ๊ฒฉ์ด ๋‹ค๋ฅด๋‹ค. + +**๊ฒฐ์ •: 3๋‹จ๊ณ„ ์ƒํƒœ** +- `SELLING` โ€” ํŒ๋งค ์ค‘ (์žฌ๊ณ  > 0) +- `OUT_OF_STOCK` โ€” ์ผ์‹œ์  ํ’ˆ์ ˆ (์žฌ๊ณ  = 0, ์žฌ์ž…๊ณ  ์‹œ ์ž๋™ SELLING ๋ณต์›) +- `SOLD_OUT` โ€” ์˜๊ตฌ ํŒ๋งค ์ค‘์ง€ (์–ด๋“œ๋ฏผ ์ˆ˜๋™ ์„ค์ •) + +## 7. ์ž ์žฌ ๋ฆฌ์Šคํฌ + +| ๋ฆฌ์Šคํฌ | ํ˜„์žฌ ๋Œ€์‘ | ์ถ”ํ›„ ๊ฒ€ํ†  | +|--------|----------|----------| +| ์ฃผ๋ฌธ ํŠธ๋žœ์žญ์…˜ ๋น„๋Œ€ํ™” | productId ์˜ค๋ฆ„์ฐจ์ˆœ ๋ฝ ํš๋“์œผ๋กœ ๋ฐ๋“œ๋ฝ ๋ฐฉ์ง€ | ๋ถ„์‚ฐ ๋ฝ(Redis) ๋˜๋Š” Saga ํŒจํ„ด | +| likeCount ๋ฝ ๊ฒฝํ•ฉ | ๋น„๊ด€์  ๋ฝ + Stock ํ…Œ์ด๋ธ” ๋ถ„๋ฆฌ๋กœ ๊ฒฝํ•ฉ ๋ถ„๋ฆฌ | Redis ์นด์šดํ„ฐ + ๋ฐฐ์น˜ ๋™๊ธฐํ™” | +| StockService โ†’ ProductService ๊ฒฐํ•ฉ | Service ๊ฐ„ ์ง์ ‘ ์˜์กด ํ—ˆ์šฉ (์žฌ๊ณ  ์ •์ฑ…) | ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋กœ ๋ถ„๋ฆฌ | +| ์Šค๋ƒ…์ƒท ๋ฐ์ดํ„ฐ ๋ˆ„์  | ํ˜„์žฌ ๊ทœ๋ชจ์—์„œ ๋ฌธ์ œ ์—†์Œ | ์•„์นด์ด๋น™ ์ •์ฑ… ๊ฒ€ํ†  | +| OUT_OF_STOCK ๋ณต์› ๋ˆ„๋ฝ | ์žฌ์ž…๊ณ (Stock ์ฆ๊ฐ€) ์‹œ ์ž๋™ SELLING ๋ณต์› | ์–ด๋“œ๋ฏผ ์žฌ๊ณ  ๊ด€๋ฆฌ API ํ•„์š” | \ No newline at end of file diff --git a/docs/design/02-sequence-diagrams.md b/docs/design/02-sequence-diagrams.md new file mode 100644 index 00000000..1627efc3 --- /dev/null +++ b/docs/design/02-sequence-diagrams.md @@ -0,0 +1,667 @@ +# ์‹œํ€€์Šค ๋‹ค์ด์–ด๊ทธ๋žจ โ€” ๋ธŒ๋žœ๋“œ & ์ƒํ’ˆ + +## API ๊ณตํ†ต ๊ทœ์น™ + +- ๋Œ€๊ณ ๊ฐ API: `/api/v1` prefix. ์œ ์ € ์‹๋ณ„ ์‹œ `X-Loopers-LoginId`, `X-Loopers-LoginPw` ํ—ค๋” ์‚ฌ์šฉ. +- ์–ด๋“œ๋ฏผ API: `/api-admin/v1` prefix. ์–ด๋“œ๋ฏผ ์‹๋ณ„ ์‹œ `X-Loopers-Ldap: loopers.admin` ํ—ค๋” ์‚ฌ์šฉ. +- ์ธ์ฆ/์ธ๊ฐ€๋Š” ๊ตฌํ˜„ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ํ—ค๋” ๊ธฐ๋ฐ˜ ์‹๋ณ„๋งŒ ์ˆ˜ํ–‰ํ•œ๋‹ค. + +--- + +## 1. ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž โ€” ๋ธŒ๋žœ๋“œ ์ƒ์„ธ ์กฐํšŒ + +Facade๊ฐ€ BrandService์™€ ProductService๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ๋ธŒ๋žœ๋“œ ๊ธฐ๋ณธ ์ •๋ณด + ํŒ๋งค์ค‘ ์ƒํ’ˆ ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. + +```mermaid +sequenceDiagram + actor User as ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž + participant Controller as BrandController + participant Facade as BrandFacade + participant BrandService as BrandService + participant ProductService as ProductService + participant BrandRepo as BrandRepository + participant ProductRepo as ProductRepository + + User->>Controller: GET /api/v1/brands/{brandId} + Controller->>Facade: getBrandDetail(brandId) + Facade->>BrandService: getBrand(brandId) + BrandService->>BrandRepo: findById(brandId) + BrandRepo-->>BrandService: BrandEntity + BrandService-->>Facade: BrandEntity + Facade->>ProductService: countSellingProducts(brandId) + ProductService->>ProductRepo: countByBrandIdAndStatus(brandId, SELLING) + ProductRepo-->>ProductService: count + ProductService-->>Facade: count + Facade-->>Controller: BrandInfo (์ด๋ฆ„, ์†Œ๊ฐœ, ์›น์‚ฌ์ดํŠธ, ํŒ๋งค์ค‘ ์ƒํ’ˆ์ˆ˜) + Controller-->>User: ApiResponse +``` + +- ํฌ์ธํŠธ: Facade๊ฐ€ BrandService์™€ ProductService๋ฅผ ๊ฐ๊ฐ ํ˜ธ์ถœํ•˜์—ฌ ์กฐํ•ฉํ•œ๋‹ค. ๋ธŒ๋žœ๋“œ ๊ธฐ๋ณธ ์ •๋ณด์™€ ํŒ๋งค์ค‘ ์ƒํ’ˆ ๊ฐœ์ˆ˜๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ์„œ๋น„์Šค์˜ ์ฑ…์ž„์ด๋ฏ€๋กœ, Facade์—์„œ ์กฐํ•ฉํ•˜๋Š” ๊ฒƒ์ด ์ ์ ˆํ•˜๋‹ค. + +## 2. ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž โ€” ์ƒํ’ˆ ๋ชฉ๋ก ์กฐํšŒ (์ •๋ ฌ/ํŽ˜์ด์ง•) + +์ •๋ ฌ ์กฐ๊ฑด์€ Controller์—์„œ enum์œผ๋กœ ๋ณ€ํ™˜, Repository์—์„œ ๋™์  ์ •๋ ฌ ์ฒ˜๋ฆฌ. + +```mermaid +sequenceDiagram + actor User as ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž + participant Controller as ProductController + participant Facade as ProductFacade + participant ProductService as ProductService + participant ProductRepo as ProductRepository + + User->>Controller: GET /api/v1/brands/{brandId}/products?sort=PRICE_ASC&page=0&size=20 + Controller->>Facade: getProducts(brandId, sort, pageable) + Facade->>ProductService: getProductsByBrand(brandId, sort, pageable) + ProductService->>ProductRepo: findByBrandId(brandId, sort, pageable) + ProductRepo-->>ProductService: Page + ProductService-->>Facade: Page + Facade-->>Controller: PageInfo + Controller-->>User: ApiResponse> +``` + +- ํฌ์ธํŠธ: ์ •๋ ฌ ์กฐ๊ฑด์€ Controller์—์„œ enum์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ „๋‹ฌํ•˜๊ณ , Repository์—์„œ ๋™์  ์ •๋ ฌ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค. Pageable์€ Spring Data์˜ ํ‘œ์ค€ ํŽ˜์ด์ง•์„ ํ™œ์šฉํ•œ๋‹ค. + +## 3. ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž โ€” ๋ธŒ๋žœ๋“œ ๊ฒ€์ƒ‰ + +```mermaid +sequenceDiagram + actor User as ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž + participant Controller as BrandController + participant Facade as BrandFacade + participant BrandService as BrandService + participant BrandRepo as BrandRepository + + User->>Controller: GET /api/v1/brands/search?keyword=๋‚˜์ดํ‚ค&page=0&size=20 + Controller->>Facade: searchBrands(keyword, pageable) + Facade->>BrandService: searchBrands(keyword, pageable) + BrandService->>BrandRepo: searchByName(keyword, pageable) + BrandRepo-->>BrandService: Page + BrandService-->>Facade: Page + Facade-->>Controller: PageInfo + Controller-->>User: ApiResponse> +``` + +## 4. ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž โ€” ์ƒํ’ˆ ๊ฒ€์ƒ‰ + +```mermaid +sequenceDiagram + actor User as ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž + participant Controller as ProductController + participant Facade as ProductFacade + participant ProductService as ProductService + participant ProductRepo as ProductRepository + + User->>Controller: GET /api/v1/products/search?keyword=์šด๋™ํ™”&page=0&size=20 + Controller->>Facade: searchProducts(keyword, pageable) + Facade->>ProductService: searchProducts(keyword, pageable) + ProductService->>ProductRepo: searchByName(keyword, pageable) + ProductRepo-->>ProductService: Page + ProductService-->>Facade: Page + Facade-->>Controller: PageInfo + Controller-->>User: ApiResponse> +``` + +## 5. Admin โ€” ๋ธŒ๋žœ๋“œ ๋ชฉ๋ก ์กฐํšŒ + +```mermaid +sequenceDiagram + actor Admin as ๊ด€๋ฆฌ์ž + participant Controller as AdminBrandController + participant Facade as BrandFacade + participant BrandService as BrandService + participant BrandRepo as BrandRepository + + Note over Admin, Controller: Header: X-Loopers-Ldap: loopers.admin + Admin->>Controller: GET /api-admin/v1/brands?page=0&size=20 + Controller->>Facade: getBrands(pageable) + Facade->>BrandService: getBrands(pageable) + BrandService->>BrandRepo: findAll(pageable) + BrandRepo-->>BrandService: Page + BrandService-->>Facade: Page + Facade-->>Controller: PageInfo + Controller-->>Admin: ApiResponse> +``` + +## 6. Admin โ€” ๋ธŒ๋žœ๋“œ ๋“ฑ๋ก + +```mermaid +sequenceDiagram + actor Admin as ๊ด€๋ฆฌ์ž + participant Controller as AdminBrandController + participant Facade as BrandFacade + participant BrandService as BrandService + participant BrandRepo as BrandRepository + + Note over Admin, Controller: Header: X-Loopers-Ldap: loopers.admin + Admin->>Controller: POST /api-admin/v1/brands + Controller->>Facade: registerBrand(command) + Facade->>BrandService: registerBrand(brand) + Note over BrandService: Brand ๋„๋ฉ”์ธ ๊ฐ์ฒด ์ƒ์„ฑ (์œ ํšจ์„ฑ ๊ฒ€์ฆ) + BrandService->>BrandRepo: save(brandEntity) + BrandRepo-->>BrandService: BrandEntity + BrandService-->>Facade: BrandEntity + Facade-->>Controller: BrandInfo + Controller-->>Admin: ApiResponse +``` + +## 7. Admin โ€” ๋ธŒ๋žœ๋“œ ์ˆ˜์ • + +```mermaid +sequenceDiagram + actor Admin as ๊ด€๋ฆฌ์ž + participant Controller as AdminBrandController + participant Facade as BrandFacade + participant BrandService as BrandService + participant BrandRepo as BrandRepository + + Note over Admin, Controller: Header: X-Loopers-Ldap: loopers.admin + Admin->>Controller: PUT /api-admin/v1/brands/{brandId} + Controller->>Facade: updateBrand(brandId, command) + Facade->>BrandService: updateBrand(brandId, brand) + BrandService->>BrandRepo: findById(brandId) + BrandRepo-->>BrandService: BrandEntity + Note over BrandService: Brand ๋„๋ฉ”์ธ ๊ฐ์ฒด๋กœ ์œ ํšจ์„ฑ ๊ฒ€์ฆ + Note over BrandService: brandEntity.update(name, introduction, websiteUrl) + BrandService->>BrandRepo: save(brandEntity) + BrandRepo-->>BrandService: BrandEntity + BrandService-->>Facade: BrandEntity + Facade-->>Controller: BrandInfo + Controller-->>Admin: ApiResponse +``` + +## 8. Admin โ€” ๋ธŒ๋žœ๋“œ ์‚ญ์ œ (์ƒํ’ˆ ์ผ๊ด„ ์‚ญ์ œ) + +ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ์ƒํ’ˆ ๋ฒŒํฌ soft delete -> ๋ธŒ๋žœ๋“œ soft delete ์ˆœ์„œ๋กœ ์ฒ˜๋ฆฌ. + +```mermaid +sequenceDiagram + actor Admin as ๊ด€๋ฆฌ์ž + participant Controller as AdminBrandController + participant Facade as BrandFacade + participant BrandService as BrandService + participant ProductService as ProductService + participant BrandRepo as BrandRepository + participant ProductRepo as ProductRepository + + Note over Admin, Controller: Header: X-Loopers-Ldap: loopers.admin + Admin->>Controller: DELETE /api-admin/v1/brands/{brandId} + Controller->>Facade: deleteBrand(brandId) + + rect rgb(255, 240, 240) + Note over Facade, ProductRepo: @Transactional ๊ฒฝ๊ณ„ + Facade->>ProductService: deleteAllByBrandId(brandId) + ProductService->>ProductRepo: bulkSoftDeleteByBrandId(brandId) + Note over ProductRepo: UPDATE product SET deleted_at = NOW() WHERE brand_id = ? + ProductRepo-->>ProductService: void + ProductService-->>Facade: void + Facade->>BrandService: deleteBrand(brandId) + BrandService->>BrandRepo: softDelete(brandId) + BrandRepo-->>BrandService: void + BrandService-->>Facade: void + end + + Facade-->>Controller: void + Controller-->>Admin: ApiResponse +``` + +- ํฌ์ธํŠธ: ๋ธŒ๋žœ๋“œ ์‚ญ์ œ์™€ ์ƒํ’ˆ ์ผ๊ด„ ์‚ญ์ œ๋Š” ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ์ฒ˜๋ฆฌ๋œ๋‹ค. ์ƒํ’ˆ์„ ๋จผ์ € ์‚ญ์ œํ•œ ํ›„ ๋ธŒ๋žœ๋“œ๋ฅผ ์‚ญ์ œํ•˜๋Š” ์ˆœ์„œ. Facade ๋ ˆ๋ฒจ์—์„œ @Transactional์„ ๊ฑธ์–ด ๋‘ ์„œ๋น„์Šค ํ˜ธ์ถœ์„ ๋ฌถ๋Š”๋‹ค. + +## 9. Admin โ€” ์ƒํ’ˆ ๋“ฑ๋ก + +์ƒํ’ˆ ๋“ฑ๋ก ์‹œ ๋ธŒ๋žœ๋“œ ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ ํ•„์ˆ˜. Product ๋„๋ฉ”์ธ ๊ฐ์ฒด์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์ˆ˜ํ–‰. + +```mermaid +sequenceDiagram + actor Admin as ๊ด€๋ฆฌ์ž + participant Controller as AdminProductController + participant Facade as ProductFacade + participant BrandService as BrandService + participant ProductService as ProductService + participant BrandRepo as BrandRepository + participant ProductRepo as ProductRepository + + Note over Admin, Controller: Header: X-Loopers-Ldap: loopers.admin + Admin->>Controller: POST /api-admin/v1/brands/{brandId}/products + Controller->>Facade: registerProduct(brandId, command) + Facade->>BrandService: getBrand(brandId) + BrandService->>BrandRepo: findById(brandId) + BrandRepo-->>BrandService: BrandEntity + BrandService-->>Facade: BrandEntity + Facade->>ProductService: registerProduct(brandId, command) + Note over ProductService: Product ๋„๋ฉ”์ธ ๊ฐ์ฒด ์ƒ์„ฑ (์œ ํšจ์„ฑ ๊ฒ€์ฆ) + ProductService->>ProductRepo: save(productEntity) + ProductRepo-->>ProductService: ProductEntity + ProductService-->>Facade: ProductEntity + Facade-->>Controller: ProductInfo + Controller-->>Admin: ApiResponse +``` + +- ํฌ์ธํŠธ: ์ƒํ’ˆ ๋“ฑ๋ก ์‹œ ๋ธŒ๋žœ๋“œ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ๋จผ์ € ํ™•์ธํ•œ๋‹ค. ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด CoreException(NOT_FOUND)์„ ๋˜์ง„๋‹ค. Product ๋„๋ฉ”์ธ ๊ฐ์ฒด์—์„œ ๋น„์ฆˆ๋‹ˆ์Šค ์œ ํšจ์„ฑ ๊ฒ€์ฆ(๊ฐ€๊ฒฉ > 0 ๋“ฑ)์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. + +## 10. Admin โ€” ์ƒํ’ˆ ์ˆ˜์ • + +์ƒํ’ˆ ์ˆ˜์ • ์‹œ ์†Œ์† ๋ธŒ๋žœ๋“œ๋Š” ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€. + +```mermaid +sequenceDiagram + actor Admin as ๊ด€๋ฆฌ์ž + participant Controller as AdminProductController + participant Facade as ProductFacade + participant ProductService as ProductService + participant ProductRepo as ProductRepository + + Note over Admin, Controller: Header: X-Loopers-Ldap: loopers.admin + Admin->>Controller: PUT /api-admin/v1/brands/{brandId}/products/{productId} + Controller->>Facade: updateProduct(productId, command) + Facade->>ProductService: updateProduct(productId, command) + ProductService->>ProductRepo: findById(productId) + ProductRepo-->>ProductService: ProductEntity + Note over ProductService: Product ๋„๋ฉ”์ธ ๊ฐ์ฒด๋กœ ์œ ํšจ์„ฑ ๊ฒ€์ฆ + Note over ProductService: productEntity.update(name, price, description, imageUrl, status) + ProductService->>ProductRepo: save(productEntity) + ProductRepo-->>ProductService: ProductEntity + ProductService-->>Facade: ProductEntity + Facade-->>Controller: ProductInfo + Controller-->>Admin: ApiResponse +``` + +## 11. Admin โ€” ์ƒํ’ˆ ์‚ญ์ œ + +```mermaid +sequenceDiagram + actor Admin as ๊ด€๋ฆฌ์ž + participant Controller as AdminProductController + participant Facade as ProductFacade + participant ProductService as ProductService + participant ProductRepo as ProductRepository + + Note over Admin, Controller: Header: X-Loopers-Ldap: loopers.admin + Admin->>Controller: DELETE /api-admin/v1/brands/{brandId}/products/{productId} + Controller->>Facade: deleteProduct(productId) + Facade->>ProductService: deleteProduct(productId) + ProductService->>ProductRepo: findById(productId) + ProductRepo-->>ProductService: ProductEntity + Note over ProductService: productEntity.delete() (soft delete) + ProductService->>ProductRepo: save(productEntity) + ProductRepo-->>ProductService: void + ProductService-->>Facade: void + Facade-->>Controller: void + Controller-->>Admin: ApiResponse +``` + +## 12. Admin โ€” ๋ธŒ๋žœ๋“œ ์ƒํ’ˆ ๋ชฉ๋ก ์กฐํšŒ + +```mermaid +sequenceDiagram + actor Admin as ๊ด€๋ฆฌ์ž + participant Controller as AdminProductController + participant Facade as ProductFacade + participant ProductService as ProductService + participant ProductRepo as ProductRepository + + Note over Admin, Controller: Header: X-Loopers-Ldap: loopers.admin + Admin->>Controller: GET /api-admin/v1/brands/{brandId}/products?page=0&size=20 + Controller->>Facade: getProductsByBrand(brandId, pageable) + Facade->>ProductService: getProductsByBrand(brandId, pageable) + ProductService->>ProductRepo: findByBrandId(brandId, pageable) + ProductRepo-->>ProductService: Page + ProductService-->>Facade: Page + Facade-->>Controller: PageInfo + Controller-->>Admin: ApiResponse> +``` + +## 13. Admin โ€” ๋ธŒ๋žœ๋“œ ์ƒํ’ˆ ์ƒ์„ธ ์กฐํšŒ + +```mermaid +sequenceDiagram + actor Admin as ๊ด€๋ฆฌ์ž + participant Controller as AdminProductController + participant Facade as ProductFacade + participant ProductService as ProductService + participant ProductRepo as ProductRepository + + Note over Admin, Controller: Header: X-Loopers-Ldap: loopers.admin + Admin->>Controller: GET /api-admin/v1/brands/{brandId}/products/{productId} + Controller->>Facade: getProduct(productId) + Facade->>ProductService: getProduct(productId) + ProductService->>ProductRepo: findById(productId) + ProductRepo-->>ProductService: ProductEntity + ProductService-->>Facade: ProductEntity + Facade-->>Controller: ProductInfo + Controller-->>Admin: ApiResponse +``` + +--- + +# ์‹œํ€€์Šค ๋‹ค์ด์–ด๊ทธ๋žจ โ€” ์ข‹์•„์š” & ์ฃผ๋ฌธ + +## 14. ์ข‹์•„์š” ๋“ฑ๋ก + +๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ์ด ํ•ต์‹ฌ. ์„œ๋น„์Šค ๋ ˆ์ด์–ด์—์„œ ๊ธฐ์กด ๋ ˆ์ฝ”๋“œ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ๋จผ์ € ํ™•์ธํ•˜๊ณ , ์ด๋ฏธ ์ข‹์•„์š” ์ƒํƒœ๋ฉด ์—๋Ÿฌ ์—†์ด ๊ธฐ์กด ๋ ˆ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. likeCount ์ฆ๊ฐ์€ ๊ฐ™์€ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ๋น„๊ด€์  ๋ฝ์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค. + +```mermaid +sequenceDiagram + actor User as ์‚ฌ์šฉ์ž + participant Controller as LikeController + participant Facade as LikeFacade + participant UserSvc as UserService + participant ProductSvc as ProductService + participant LikeSvc as LikeService + participant LikeRepo as LikeRepository + participant ProductRepo as ProductRepository + + User->>Controller: POST /api/v1/products/{productId}/likes + Note over User, Controller: X-Loopers-LoginId / X-Loopers-LoginPw ํ—ค๋”๋กœ ์‚ฌ์šฉ์ž ์‹๋ณ„ + Controller->>Facade: like(loginId, productId) + + Facade->>UserSvc: getUserByLoginId(loginId) + UserSvc-->>Facade: UserEntity + + Facade->>ProductSvc: getProduct(productId) + ProductSvc-->>Facade: ProductEntity (์กด์žฌ ํ™•์ธ) + + rect rgb(255, 240, 220) + Note over LikeSvc, ProductRepo: ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„ + Facade->>LikeSvc: like(userId, productId) + LikeSvc->>LikeRepo: findByUserIdAndProductId(userId, productId) + + alt ์ด๋ฏธ ์ข‹์•„์š” ์ƒํƒœ + LikeRepo-->>LikeSvc: Optional(LikeEntity) + LikeSvc-->>Facade: ๊ธฐ์กด ๋ ˆ์ฝ”๋“œ ๋ฐ˜ํ™˜ (๋ฉฑ๋“ฑ) + else ์ข‹์•„์š” ์—†์Œ + LikeRepo-->>LikeSvc: Optional.empty() + LikeSvc->>LikeRepo: save(new LikeEntity) + LikeSvc->>ProductSvc: increaseLikeCount(productId) + Note right of ProductSvc: ๋น„๊ด€์  ๋ฝ์œผ๋กœ likeCount + 1 + ProductSvc->>ProductRepo: SELECT FOR UPDATE โ†’ UPDATE likeCount + LikeSvc-->>Facade: ์ƒˆ LikeEntity ๋ฐ˜ํ™˜ + end + end + + Facade-->>Controller: LikedProductInfo + Controller-->>User: ApiResponse (SUCCESS) +``` + +- ํฌ์ธํŠธ: ์„œ๋น„์Šค ๋ ˆ์ด์–ด ์‚ฌ์ „ ๊ฒ€์ฆ์ด ๋ถˆํ•„์š”ํ•œ likeCount ๋ณ€๊ฒฝ์„ ๋ฐฉ์ง€ํ•œ๋‹ค. DB ๋ณตํ•ฉ ์œ ๋‹ˆํฌ ํ‚ค `(user_id, product_id)`๋Š” ๋™์‹œ ์š”์ฒญ ์‹œ ์ตœ์ข… ๋ฐฉ์–ด์„  ์—ญํ• . + +--- + +## 15. ์ข‹์•„์š” ์ทจ์†Œ + +๋“ฑ๋ก๊ณผ ๋Œ€์นญ์ ์œผ๋กœ ๋ฉฑ๋“ฑ์„ฑ์„ ๋ณด์žฅํ•œ๋‹ค. ์ข‹์•„์š” ๋ ˆ์ฝ”๋“œ๊ฐ€ ์—†์œผ๋ฉด ์—๋Ÿฌ ์—†์ด ๋ฌด์‹œํ•œ๋‹ค. + +```mermaid +sequenceDiagram + actor User as ์‚ฌ์šฉ์ž + participant Controller as LikeController + participant Facade as LikeFacade + participant UserSvc as UserService + participant LikeSvc as LikeService + participant ProductSvc as ProductService + participant LikeRepo as LikeRepository + participant ProductRepo as ProductRepository + + User->>Controller: DELETE /api/v1/products/{productId}/likes + Note over User, Controller: X-Loopers-LoginId / X-Loopers-LoginPw ํ—ค๋”๋กœ ์‚ฌ์šฉ์ž ์‹๋ณ„ + Controller->>Facade: unlike(loginId, productId) + + Facade->>UserSvc: getUserByLoginId(loginId) + UserSvc-->>Facade: UserEntity + + rect rgb(255, 240, 220) + Note over LikeSvc, ProductRepo: ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„ + Facade->>LikeSvc: unlike(userId, productId) + LikeSvc->>LikeRepo: findByUserIdAndProductId(userId, productId) + + alt ์ข‹์•„์š” ๋ ˆ์ฝ”๋“œ ์—†์Œ + LikeRepo-->>LikeSvc: Optional.empty() + LikeSvc-->>Facade: ๋ฌด์‹œ (๋ฉฑ๋“ฑ) + else ์ข‹์•„์š” ์กด์žฌ + LikeRepo-->>LikeSvc: Optional(LikeEntity) + LikeSvc->>LikeRepo: delete(likeEntity) + Note right of LikeRepo: Hard delete (soft delete ์•„๋‹˜) + LikeSvc->>ProductSvc: decreaseLikeCount(productId) + Note right of ProductSvc: ๋น„๊ด€์  ๋ฝ์œผ๋กœ likeCount - 1 + ProductSvc->>ProductRepo: SELECT FOR UPDATE โ†’ UPDATE likeCount + LikeSvc-->>Facade: ์™„๋ฃŒ + end + end + + Facade-->>Controller: void + Controller-->>User: ApiResponse (SUCCESS) +``` + +- ํฌ์ธํŠธ: ์ข‹์•„์š”๋Š” hard delete๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. soft delete ์‹œ ์œ ๋‹ˆํฌ ํ‚ค์— deletedAt์„ ํฌํ•จํ•ด์•ผ ํ•˜๋ฉฐ, ์žฌ์ข‹์•„์š” ์‹œ ๋ณต์žก๋„๊ฐ€ ์ฆ๊ฐ€ํ•˜๊ธฐ ๋•Œ๋ฌธ. + +--- + +## 16. ์ข‹์•„์š” ๋ชฉ๋ก ์กฐํšŒ + +์‚ฌ์šฉ์ž๊ฐ€ ์ข‹์•„์š” ํ•œ ์ƒํ’ˆ ๋ชฉ๋ก์„ ์ƒํ’ˆ ์ƒ์„ธ ํฌํ•จํ•˜์—ฌ ์กฐํšŒํ•œ๋‹ค. + +```mermaid +sequenceDiagram + actor User as ์‚ฌ์šฉ์ž + participant Controller as LikeController + participant Facade as LikeFacade + participant UserSvc as UserService + participant LikeSvc as LikeService + participant LikeRepo as LikeRepository + + User->>Controller: GET /api/v1/likes?page=0&size=20 + Note over User, Controller: X-Loopers-LoginId / X-Loopers-LoginPw ํ—ค๋”๋กœ ์‚ฌ์šฉ์ž ์‹๋ณ„ + Controller->>Facade: getLikedProducts(loginId, pageable) + + Facade->>UserSvc: getUserByLoginId(loginId) + UserSvc-->>Facade: UserEntity + + Facade->>LikeSvc: getLikedProducts(userId, pageable) + LikeSvc->>LikeRepo: findByUserIdWithProduct(userId, pageable) + Note right of LikeRepo: Like JOIN Product ์กฐํšŒ + LikeRepo-->>LikeSvc: Page + LikeSvc-->>Facade: Page + + Facade-->>Controller: Page + Controller-->>User: ApiResponse (Page) +``` + +- ํฌ์ธํŠธ: ํ—ค๋”์˜ loginId๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์‹๋ณ„ํ•˜๋ฏ€๋กœ ๋ณ„๋„์˜ pathVariable ์—†์ด ๋ณธ์ธ์˜ ์ข‹์•„์š” ๋ชฉ๋ก๋งŒ ์กฐํšŒ ๊ฐ€๋Šฅํ•˜๋‹ค. + +--- + +## 17. ์ฃผ๋ฌธ ์ƒ์„ฑ + +ํ•ต์‹ฌ ํ๋ฆ„: ์ƒํ’ˆ ์กด์žฌ/ํŒ๋งค ์ƒํƒœ ํ™•์ธ โ†’ ์žฌ๊ณ  ๋น„๊ด€์  ๋ฝ ์ฐจ๊ฐ โ†’ ์Šค๋ƒ…์ƒท๊ณผ ํ•จ๊ป˜ ์ฃผ๋ฌธ ์ƒ์„ฑ. ํ•˜๋‚˜๋ผ๋„ ์‹คํŒจํ•˜๋ฉด ์ „์ฒด ๋กค๋ฐฑ. + +```mermaid +sequenceDiagram + actor User as ์‚ฌ์šฉ์ž + participant Controller as OrderController + participant Facade as OrderFacade + participant UserSvc as UserService + participant ProductSvc as ProductService + participant StockSvc as StockService + participant OrderSvc as OrderService + participant StockRepo as StockRepository + participant OrderRepo as OrderRepository + + User->>Controller: POST /api/v1/orders { items: [{productId, quantity}, ...] } + Note over User, Controller: X-Loopers-LoginId / X-Loopers-LoginPw ํ—ค๋”๋กœ ์‚ฌ์šฉ์ž ์‹๋ณ„ + Controller->>Facade: createOrder(loginId, orderCommand) + + Facade->>UserSvc: getUserByLoginId(loginId) + UserSvc-->>Facade: UserEntity + + rect rgb(255, 240, 220) + Note over Facade, OrderRepo: ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„ + Note over Facade: productId ์˜ค๋ฆ„์ฐจ์ˆœ ์ •๋ ฌ (๋ฐ๋“œ๋ฝ ๋ฐฉ์ง€) + + loop ๊ฐ ์ฃผ๋ฌธ ํ•ญ๋ชฉ (productId ์˜ค๋ฆ„์ฐจ์ˆœ) + Facade->>ProductSvc: getProduct(productId) + ProductSvc-->>Facade: ProductEntity (์กด์žฌ/ํŒ๋งค์ค‘ ํ™•์ธ) + + Facade->>StockSvc: deductStock(productId, quantity) + StockSvc->>StockRepo: findByProductIdWithLock(productId) + Note right of StockRepo: SELECT FOR UPDATE + StockRepo-->>StockSvc: StockEntity + + alt ์žฌ๊ณ  ๋ถ€์กฑ (quantity > stock) + StockSvc-->>Facade: CoreException (์žฌ๊ณ  ๋ถ€์กฑ) + Note over Facade, OrderRepo: ์ „์ฒด ๋กค๋ฐฑ + else ์žฌ๊ณ  ์ถฉ๋ถ„ + StockSvc->>StockRepo: save (quantity ์ฐจ๊ฐ) + alt ์ฐจ๊ฐ ํ›„ ์žฌ๊ณ  = 0 + StockSvc->>ProductSvc: changeStatus(productId, OUT_OF_STOCK) + end + StockSvc-->>Facade: StockEntity + end + end + + Note over Facade: ์ƒํ’ˆ ์ •๋ณด ์Šค๋ƒ…์ƒท ์ƒ์„ฑ (name, price, imageUrl) + Facade->>OrderSvc: createOrder(userId, orderItemSnapshots) + OrderSvc->>OrderRepo: save(OrderEntity + OrderItemEntities) + OrderSvc-->>Facade: OrderEntity + end + + Facade-->>Controller: OrderInfo + Controller-->>User: ApiResponse (SUCCESS) +``` + +- ํฌ์ธํŠธ: productId ์˜ค๋ฆ„์ฐจ์ˆœ ์ •๋ ฌ ํ›„ ์ˆœ์ฐจ์ ์œผ๋กœ ๋ฝ์„ ํš๋“ํ•˜์—ฌ ๋ฐ๋“œ๋ฝ์„ ๋ฐฉ์ง€ํ•œ๋‹ค. ์Šค๋ƒ…์ƒท ์ƒ์„ฑ์€ ์žฌ๊ณ  ์ฐจ๊ฐ์ด ๋ชจ๋‘ ์„ฑ๊ณตํ•œ ํ›„, ์ฃผ๋ฌธ ์ €์žฅ ์ง์ „์— ์ˆ˜ํ–‰. + +--- + +## 18. ์ฃผ๋ฌธ ๋ชฉ๋ก ์กฐํšŒ (์‚ฌ์šฉ์ž) + +๊ธฐ๊ฐ„ ํ•„ํ„ฐ(startAt, endAt)์™€ ํŽ˜์ด์ง•์„ ์ง€์›ํ•œ๋‹ค. + +```mermaid +sequenceDiagram + actor User as ์‚ฌ์šฉ์ž + participant Controller as OrderController + participant Facade as OrderFacade + participant UserSvc as UserService + participant OrderSvc as OrderService + participant OrderRepo as OrderRepository + + User->>Controller: GET /api/v1/orders?startAt=2026-01-31&endAt=2026-02-10&page=0&size=20 + Note over User, Controller: X-Loopers-LoginId / X-Loopers-LoginPw ํ—ค๋”๋กœ ์‚ฌ์šฉ์ž ์‹๋ณ„ + Controller->>Facade: getOrders(loginId, startAt, endAt, pageable) + + Facade->>UserSvc: getUserByLoginId(loginId) + UserSvc-->>Facade: UserEntity + + Facade->>OrderSvc: getOrdersByUserId(userId, startAt, endAt, pageable) + OrderSvc->>OrderRepo: findByUserIdAndCreatedAtBetween(userId, startAt, endAt, pageable) + OrderRepo-->>OrderSvc: Page + OrderSvc-->>Facade: Page + + Facade-->>Controller: Page + Controller-->>User: ApiResponse (Page) +``` + +- ํฌ์ธํŠธ: ๊ธฐ๊ฐ„ ํ•„ํ„ฐ๋Š” `createdAt` ๊ธฐ์ค€. startAt, endAt์€ ์„ ํƒ์  ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ, ๋ฏธ์ œ๊ณต ์‹œ ์ „์ฒด ์กฐํšŒ. + +--- + +## 19. ์ฃผ๋ฌธ ์ƒ์„ธ ์กฐํšŒ (์‚ฌ์šฉ์ž) + +์ฃผ๋ฌธ ๊ธฐ๋ณธ ์ •๋ณด + ์ฃผ๋ฌธ ํ•ญ๋ชฉ(์Šค๋ƒ…์ƒท ํฌํ•จ)์„ ์กฐํšŒํ•œ๋‹ค. + +```mermaid +sequenceDiagram + actor User as ์‚ฌ์šฉ์ž + participant Controller as OrderController + participant Facade as OrderFacade + participant UserSvc as UserService + participant OrderSvc as OrderService + participant OrderRepo as OrderRepository + participant OrderItemRepo as OrderItemRepository + + User->>Controller: GET /api/v1/orders/{orderId} + Note over User, Controller: X-Loopers-LoginId / X-Loopers-LoginPw ํ—ค๋”๋กœ ์‚ฌ์šฉ์ž ์‹๋ณ„ + Controller->>Facade: getOrderDetail(loginId, orderId) + + Facade->>UserSvc: getUserByLoginId(loginId) + UserSvc-->>Facade: UserEntity + + Facade->>OrderSvc: getOrder(orderId) + OrderSvc->>OrderRepo: findById(orderId) + OrderRepo-->>OrderSvc: OrderEntity + OrderSvc-->>Facade: OrderEntity + + Note over Facade: ์ฃผ๋ฌธ์˜ userId์™€ ์š”์ฒญ์ž userId ์ผ์น˜ ํ™•์ธ + + Facade->>OrderSvc: getOrderItems(orderId) + OrderSvc->>OrderItemRepo: findByOrderId(orderId) + OrderItemRepo-->>OrderSvc: List + OrderSvc-->>Facade: List + + Facade-->>Controller: OrderDetailInfo (์ฃผ๋ฌธ + ํ•ญ๋ชฉ ์Šค๋ƒ…์ƒท) + Controller-->>User: ApiResponse (OrderDetail) +``` + +- ํฌ์ธํŠธ: Facade์—์„œ ์ฃผ๋ฌธ ์†Œ์œ ์ž ํ™•์ธ. ํƒ€ ์œ ์ €์˜ ์ฃผ๋ฌธ ์ƒ์„ธ ์กฐํšŒ๋ฅผ ์ฐจ๋‹จ. + +--- + +## 20. ์ฃผ๋ฌธ ๋ชฉ๋ก ์กฐํšŒ (์–ด๋“œ๋ฏผ) + +์ „์ฒด ์ฃผ๋ฌธ์„ ํŽ˜์ด์ง•์œผ๋กœ ์กฐํšŒํ•œ๋‹ค. ํ•„ํ„ฐ ์—†์Œ. + +```mermaid +sequenceDiagram + actor Admin as ๊ด€๋ฆฌ์ž + participant Controller as AdminOrderController + participant Facade as OrderFacade + participant OrderSvc as OrderService + participant OrderRepo as OrderRepository + + Admin->>Controller: GET /api-admin/v1/orders?page=0&size=20 + Note over Admin, Controller: X-Loopers-Ldap: loopers.admin ํ—ค๋” ํ™•์ธ + Controller->>Facade: getAllOrders(pageable) + + Facade->>OrderSvc: getAllOrders(pageable) + OrderSvc->>OrderRepo: findAll(pageable) + OrderRepo-->>OrderSvc: Page + OrderSvc-->>Facade: Page + + Facade-->>Controller: Page + Controller-->>Admin: ApiResponse (Page) +``` + +--- + +## 21. ์ฃผ๋ฌธ ์ƒ์„ธ ์กฐํšŒ (์–ด๋“œ๋ฏผ) + +์–ด๋“œ๋ฏผ์€ ์†Œ์œ ์ž ํ™•์ธ ์—†์ด ๋ชจ๋“  ์ฃผ๋ฌธ์˜ ์ƒ์„ธ๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค. + +```mermaid +sequenceDiagram + actor Admin as ๊ด€๋ฆฌ์ž + participant Controller as AdminOrderController + participant Facade as OrderFacade + participant OrderSvc as OrderService + participant OrderRepo as OrderRepository + participant OrderItemRepo as OrderItemRepository + + Admin->>Controller: GET /api-admin/v1/orders/{orderId} + Note over Admin, Controller: X-Loopers-Ldap: loopers.admin ํ—ค๋” ํ™•์ธ + Controller->>Facade: getOrderDetailForAdmin(orderId) + + Facade->>OrderSvc: getOrder(orderId) + OrderSvc->>OrderRepo: findById(orderId) + OrderRepo-->>OrderSvc: OrderEntity + OrderSvc-->>Facade: OrderEntity + + Facade->>OrderSvc: getOrderItems(orderId) + OrderSvc->>OrderItemRepo: findByOrderId(orderId) + OrderItemRepo-->>OrderSvc: List + OrderSvc-->>Facade: List + + Facade-->>Controller: OrderDetailInfo (์ฃผ๋ฌธ + ํ•ญ๋ชฉ ์Šค๋ƒ…์ƒท) + Controller-->>Admin: ApiResponse (OrderDetail) +``` + +- ํฌ์ธํŠธ: ์‚ฌ์šฉ์ž ์ƒ์„ธ ์กฐํšŒ(19๋ฒˆ)์™€ ๋™์ผํ•œ ํ๋ฆ„์ด์ง€๋งŒ ์†Œ์œ ์ž ํ™•์ธ์„ ์ƒ๋žตํ•œ๋‹ค. Facade์— ๋ณ„๋„ ๋ฉ”์„œ๋“œ(`getOrderDetailForAdmin`)๋กœ ๋ถ„๋ฆฌ. diff --git a/docs/design/03-class-diagram.md b/docs/design/03-class-diagram.md new file mode 100644 index 00000000..948de2b7 --- /dev/null +++ b/docs/design/03-class-diagram.md @@ -0,0 +1,300 @@ +# ํด๋ž˜์Šค ๋‹ค์ด์–ด๊ทธ๋žจ + +๋„๋ฉ”์ธ ๊ฐ์ฒด์˜ ์ฑ…์ž„, ์˜์กด ๋ฐฉํ–ฅ, ์‘์ง‘๋„๋ฅผ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•ด ์ž‘์„ฑํ•œ๋‹ค. +๋ ˆ์ด์–ด ๊ตฌ์กฐ(Controller, Facade, Repository ๋“ฑ)๋Š” ์‹œํ€€์Šค ๋‹ค์ด์–ด๊ทธ๋žจ์—์„œ ํ™•์ธํ•˜๋ฉฐ, ์—ฌ๊ธฐ์„œ๋Š” ๋„๋ฉ”์ธ ๋ชจ๋ธ์— ์ง‘์ค‘ํ•œ๋‹ค. + +--- + +## 1. Brand & Product ๋„๋ฉ”์ธ + +Brand์™€ Product์˜ ๋„๋ฉ”์ธ ๊ฐ์ฒด, ์—”ํ‹ฐํ‹ฐ, ๊ทธ๋ฆฌ๊ณ  ๋‘ ๋„๋ฉ”์ธ ๊ฐ„ ๊ด€๊ณ„๋ฅผ ํ‘œํ˜„ํ•œ๋‹ค. + +```mermaid +classDiagram + direction TB + + class Brand { + -String name + -String introduction + -String websiteUrl + +Brand(name, introduction, websiteUrl) + -validateName(name) + -validateIntroduction(introduction) + } + + class BrandEntity { + -String name + -String introduction + -String websiteUrl + +from(Brand)$ BrandEntity + +update(name, introduction, websiteUrl) + } + + class Product { + -String name + -Long price + -String description + -String imageUrl + -ProductStatus status + +Product(name, price, description, imageUrl, status) + -validateName(name) + -validatePrice(price) + } + + class ProductStatus { + <> + SELLING + OUT_OF_STOCK + SOLD_OUT + } + + class ProductEntity { + -Long brandId + -String name + -Long price + -String description + -String imageUrl + -ProductStatus status + -Long likeCount + +from(Product, Long brandId)$ ProductEntity + +update(name, price, description, imageUrl, status) + +increaseLikeCount() + +decreaseLikeCount() + } + + class BaseEntity { + <> + #Long id + #ZonedDateTime createdAt + #ZonedDateTime updatedAt + #ZonedDateTime deletedAt + +delete() + +restore() + } + + BrandEntity --|> BaseEntity + ProductEntity --|> BaseEntity + ProductEntity --> ProductStatus + ProductEntity ..> BrandEntity : brandId๋กœ ์ฐธ์กฐ + Brand <.. BrandEntity : from()์œผ๋กœ ๋ณ€ํ™˜ + Product <.. ProductEntity : from()์œผ๋กœ ๋ณ€ํ™˜ +``` + +### ํฌ์ธํŠธ +- **Domain โ†” Entity ๋ถ„๋ฆฌ**: Brand, Product๋Š” ์ˆœ์ˆ˜ ๋„๋ฉ”์ธ ๊ฐ์ฒด(์œ ํšจ์„ฑ ๊ฒ€์ฆ), BrandEntity, ProductEntity๋Š” JPA ์˜์† ๊ฐ์ฒด. +- **ProductEntity.brandId**: `@ManyToOne` ๋Œ€์‹  Long ID ์ฐธ์กฐ๋กœ ๋„๋ฉ”์ธ ๊ฐ„ ๊ฒฐํ•ฉ๋„ ์ตœ์†Œํ™”. +- **ProductEntity.likeCount**: ๋น„์ •๊ทœํ™” ํ•„๋“œ. Like ๋„๋ฉ”์ธ๊ณผ ๊ฐ™์€ ํŠธ๋žœ์žญ์…˜์—์„œ ๋น„๊ด€์  ๋ฝ์œผ๋กœ ๋™๊ธฐํ™”. +- **ProductStatus 3๋‹จ๊ณ„**: `SELLING` โ†’ `OUT_OF_STOCK`(์žฌ๊ณ  0 ์ž๋™) โ†’ `SOLD_OUT`(์–ด๋“œ๋ฏผ ์ˆ˜๋™). + +--- + +## 2. Like ๋„๋ฉ”์ธ + +Like๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๋‹จ์ˆœํ•˜์—ฌ Domain โ†” Entity ๋ถ„๋ฆฌ ์—†์ด Entity๊ฐ€ ๋„๋ฉ”์ธ ๋ชจ๋ธ์„ ๊ฒธํ•œ๋‹ค. + +```mermaid +classDiagram + direction TB + + class LikeEntity { + -Long userId + -Long productId + +LikeEntity(userId, productId) + } + + class BaseEntity { + <> + #Long id + #ZonedDateTime createdAt + #ZonedDateTime updatedAt + #ZonedDateTime deletedAt + } + + LikeEntity --|> BaseEntity + LikeEntity ..> UserEntity : userId๋กœ ์ฐธ์กฐ + LikeEntity ..> ProductEntity : productId๋กœ ์ฐธ์กฐ +``` + +### ํฌ์ธํŠธ +- **Domain โ†” Entity ๋ถ„๋ฆฌ ์•ˆ ํ•จ**: userId + productId ์กฐํ•ฉ ์™ธ์— ์œ ํšจ์„ฑ ๊ฒ€์ฆ์ด๋‚˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์—†์Œ. +- **Hard delete**: soft delete ์‹œ ์œ ๋‹ˆํฌ ํ‚ค ์ถฉ๋Œ ๋ฌธ์ œ๋ฅผ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ๋ฌผ๋ฆฌ ์‚ญ์ œ. BaseEntity๋ฅผ ์ƒ์†ํ•˜์ง€๋งŒ `deletedAt`์€ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ. +- **๋ณตํ•ฉ ์œ ๋‹ˆํฌ ํ‚ค**: `(userId, productId)` โ€” ๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ์˜ DB ๋ ˆ๋ฒจ ๋ฐฉ์–ด์„ . + +--- + +## 3. Order & OrderItem ๋„๋ฉ”์ธ + +Order๋Š” ํ˜„์žฌ ์ƒํƒœ๊ฐ€ ORDERED ํ•˜๋‚˜์ด๋ฏ€๋กœ Domain โ†” Entity ๋ถ„๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š๋Š”๋‹ค. OrderItem์€ ์ฃผ๋ฌธ ์‹œ์ ์˜ ์ƒํ’ˆ ์Šค๋ƒ…์ƒท์„ ๋ณด์กดํ•˜๋Š” ์—ญํ• . + +```mermaid +classDiagram + direction TB + + class OrderEntity { + -Long userId + -OrderStatus status + +OrderEntity(userId, status) + } + + class OrderStatus { + <> + ORDERED + } + + class OrderItemEntity { + -Long orderId + -Long productId + -Long quantity + -String productName + -Long productPrice + -String productImageUrl + +OrderItemEntity(orderId, productId, quantity, productName, productPrice, productImageUrl) + } + + class BaseEntity { + <> + #Long id + #ZonedDateTime createdAt + #ZonedDateTime updatedAt + #ZonedDateTime deletedAt + } + + OrderEntity --|> BaseEntity + OrderEntity --> OrderStatus + OrderItemEntity --|> BaseEntity + OrderEntity "1" --> "*" OrderItemEntity : contains + OrderEntity ..> UserEntity : userId๋กœ ์ฐธ์กฐ + OrderItemEntity ..> ProductEntity : productId๋กœ ์ฐธ์กฐ (FK ์•„๋‹˜) +``` + +### ํฌ์ธํŠธ +- **Domain โ†” Entity ๋ถ„๋ฆฌ ์•ˆ ํ•จ**: ์ƒํƒœ ์ „์ด ๋กœ์ง์ด ์—†์œผ๋ฏ€๋กœ Entity๊ฐ€ ๋„๋ฉ”์ธ ๋ชจ๋ธ ๊ฒธ์ž„. ๊ฒฐ์ œ ๋„์ž… ์‹œ ์ƒํƒœ๊ฐ€ ๋ณต์žกํ•ด์ง€๋ฉด ๊ทธ๋•Œ ๋ถ„๋ฆฌ ๊ฒ€ํ† . +- **OrderItemEntity = ์Šค๋ƒ…์ƒท**: ์ฃผ๋ฌธ ์‹œ์ ์˜ ์ƒํ’ˆ ์ •๋ณด(name, price, imageUrl)๋ฅผ ์ง์ ‘ ์ €์žฅ. ์ƒํ’ˆ ์ˆ˜์ •/์‚ญ์ œ ํ›„์—๋„ ์›๋ž˜ ๊ฑฐ๋ž˜ ์กฐ๊ฑด ๋ณด์กด. +- **productId๋Š” FK ์•„๋‹˜**: ์ฐธ์กฐ ๋ฌด๊ฒฐ์„ฑ๋ณด๋‹ค ์ด๋ ฅ ๋ณด์กด์„ ์šฐ์„ . + +--- + +## 4. Stock ๋„๋ฉ”์ธ + +Stock์€ ์žฌ๊ณ  ์ฐจ๊ฐ/์ฆ๊ฐ€ ์‹œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ(์Œ์ˆ˜ ๋ฐฉ์ง€)์ด ํ•„์š”ํ•˜๋ฏ€๋กœ Domain โ†” Entity ๋ถ„๋ฆฌ๋ฅผ ์ ์šฉํ•œ๋‹ค. + +```mermaid +classDiagram + direction TB + + class Stock { + -Long quantity + +Stock(quantity) + +deduct(Long amount) + +increase(Long amount) + -validateQuantity(quantity) + } + + class StockEntity { + -Long productId + -Long quantity + +from(Stock, Long productId)$ StockEntity + +toDomain() Stock + +update(Long quantity) + } + + class BaseEntity { + <> + #Long id + #ZonedDateTime createdAt + #ZonedDateTime updatedAt + #ZonedDateTime deletedAt + } + + StockEntity --|> BaseEntity + Stock <.. StockEntity : toDomain + StockEntity ..> ProductEntity : productId 1๋Œ€1 ์ฐธ์กฐ +``` + +### ํฌ์ธํŠธ +- **Domain โ†” Entity ๋ถ„๋ฆฌ ์ ์šฉ**: `Stock.deduct()`, `Stock.increase()`์—์„œ ์Œ์ˆ˜ ๋ฐฉ์ง€, ์ฐจ๊ฐ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ๊ฒ€์ฆ. ๋น„์ฆˆ๋‹ˆ์Šค ๋ฃฐ์„ ๋„๋ฉ”์ธ ๊ฐ์ฒด์— ์บก์Аํ™”. +- **๋น„๊ด€์  ๋ฝ ๋Œ€์ƒ**: `StockEntity`๋Š” `SELECT FOR UPDATE`๋กœ ๋™์‹œ์„ฑ ์ œ์–ด. Product์™€ ๋ถ„๋ฆฌํ•˜์—ฌ ์ข‹์•„์š”(likeCount ๋ฝ)์™€ ๊ฒฝํ•ฉํ•˜์ง€ ์•Š์Œ. +- **1:1 ๊ด€๊ณ„**: `productId` UNIQUE ์ œ์•ฝ์œผ๋กœ ์ƒํ’ˆ๋‹น ํ•˜๋‚˜์˜ ์žฌ๊ณ  ๋ ˆ์ฝ”๋“œ ๋ณด์žฅ. + +--- + +## 5. ๋„๋ฉ”์ธ ๊ฐ„ ์˜์กด ๊ด€๊ณ„ + +์ „์ฒด ๋„๋ฉ”์ธ์˜ ์˜์กด ๋ฐฉํ–ฅ์„ ํ‘œํ˜„ํ•œ๋‹ค. ํ™”์‚ดํ‘œ๋Š” "์˜์กดํ•œ๋‹ค" ๋ฐฉํ–ฅ. + +```mermaid +classDiagram + direction LR + + class User { + loginId + password + name + email + birthDate + } + + class Brand { + name + introduction + websiteUrl + } + + class Product { + brandId + name + price + status + likeCount + } + + class Like { + userId + productId + } + + class Stock { + productId + quantity + } + + class Order { + userId + status + } + + class OrderItem { + orderId + productId + quantity + productName_์Šค๋ƒ…์ƒท + productPrice_์Šค๋ƒ…์ƒท + productImageUrl_์Šค๋ƒ…์ƒท + } + + Product ..> Brand : brandId ์ฐธ์กฐ + Like ..> User : userId ์ฐธ์กฐ + Like ..> Product : productId ์ฐธ์กฐ likeCount ์ฆ๊ฐ + Stock ..> Product : productId 1๋Œ€1 ์ฐธ์กฐ + Order ..> User : userId ์ฐธ์กฐ + Order "1" --> "*" OrderItem : contains + OrderItem ..> Product : productId ์ฐธ์กฐ ์Šค๋ƒ…์ƒท +``` + +### ํฌ์ธํŠธ +- **Product๊ฐ€ ์ค‘์‹ฌ ๋„๋ฉ”์ธ**: Brand, Like, Stock, OrderItem ๋ชจ๋‘ Product์— ์˜์กด. likeCount ์ฆ๊ฐ, ์ƒํƒœ ๋ณ€๊ฒฝ, ์Šค๋ƒ…์ƒท ์กฐํšŒ ๋“ฑ ๋‹ค์–‘ํ•œ ์—ญํ• . +- **๋„๋ฉ”์ธ ๊ฐ„ ์ฐธ์กฐ๋Š” ID**: `@ManyToOne` ๊ฐ™์€ JPA ์—ฐ๊ด€๊ด€๊ณ„ ์—†์ด Long ID๋กœ ์ฐธ์กฐ. ๊ฒฐํ•ฉ๋„ ์ตœ์†Œํ™”. +- **Stock โ†’ Product ์ƒํƒœ ๋ณ€๊ฒฝ**: ์žฌ๊ณ  0์ด๋ฉด OUT_OF_STOCK ์ž๋™ ์ „ํ™˜, ์žฌ์ž…๊ณ  ์‹œ SELLING ๋ณต์›. ์œ ์ผํ•œ Service ๊ฐ„ ์ง์ ‘ ์˜์กด. + +--- + +## Domain โ†” Entity ๋ถ„๋ฆฌ ๊ธฐ์ค€ ์š”์•ฝ + +| ๋„๋ฉ”์ธ | ๋ถ„๋ฆฌ ์—ฌ๋ถ€ | ์ด์œ  | +|--------|:---:|------| +| Brand | O | ์ด๋ฆ„, ์†Œ๊ฐœ๋ฌธ๊ตฌ ์œ ํšจ์„ฑ ๊ฒ€์ฆ | +| Product | O | ์ด๋ฆ„, ๊ฐ€๊ฒฉ ์œ ํšจ์„ฑ ๊ฒ€์ฆ | +| Like | X | ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์—†์Œ (userId + productId ์กฐํ•ฉ๋งŒ) | +| Order | X | ํ˜„์žฌ ์ƒํƒœ ํ•˜๋‚˜ (ORDERED). ๊ฒฐ์ œ ๋„์ž… ์‹œ ๋ถ„๋ฆฌ ๊ฒ€ํ†  | +| OrderItem | X | ์Šค๋ƒ…์ƒท ๋ฐ์ดํ„ฐ ๋ณด์กด ์—ญํ• . ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์—†์Œ | +| Stock | O | ์ฐจ๊ฐ/์ฆ๊ฐ€ ์‹œ ์Œ์ˆ˜ ๋ฐฉ์ง€ ๋“ฑ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ํ•„์š” | diff --git a/docs/design/04-erd.md b/docs/design/04-erd.md new file mode 100644 index 00000000..bd25bbf3 --- /dev/null +++ b/docs/design/04-erd.md @@ -0,0 +1,133 @@ +## ERD + +์˜์†์„ฑ ๊ตฌ์กฐ์™€ ๊ด€๊ณ„, ์ธ๋ฑ์Šค ์ „๋žต์„ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•ด ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ ์ƒํ’ˆ ๋ชฉ๋ก์˜ ๋‹ค์ค‘ ์ •๋ ฌ ์š”๊ตฌ์‚ฌํ•ญ๊ณผ ๊ฒ€์ƒ‰ ์š”๊ตฌ์‚ฌํ•ญ์— ๋Œ€ํ•œ ์ธ๋ฑ์Šค ์„ค๊ณ„, ์ข‹์•„์š”์˜ ๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ์„ ์œ„ํ•œ ๋ณตํ•ฉ ์œ ๋‹ˆํฌ ํ‚ค, ์ฃผ๋ฌธ ์Šค๋ƒ…์ƒท์˜ ๋…๋ฆฝ์„ฑ, Stock ๋ถ„๋ฆฌ์— ๋”ฐ๋ฅธ ๋ฝ ๋ฒ”์œ„ ์ตœ์†Œํ™”๊ฐ€ ํ•ต์‹ฌ ํฌ์ธํŠธ์ž…๋‹ˆ๋‹ค. + +```mermaid +erDiagram + USER { + bigint id PK "AUTO_INCREMENT (idx)" + varchar(50) login_id UK "NOT NULL (๋กœ๊ทธ์ธ ID)" + varchar(200) password "NOT NULL (๋น„๋ฐ€๋ฒˆํ˜ธ)" + varchar(50) name "NOT NULL (์ด๋ฆ„)" + varchar(10) birth_date "NOT NULL (์ƒ๋…„์›”์ผ)" + varchar(100) email "NOT NULL (์ด๋ฉ”์ผ)" + datetime created_at "NOT NULL (์ƒ์„ฑ์ผ์ž)" + datetime updated_at "NOT NULL (์ˆ˜์ •์ผ์ž)" + datetime deleted_at "NULL (soft delete) (์‚ญ์ œ์ผ์ž)" + } + + BRAND { + bigint id PK "AUTO_INCREMENT (idx)" + varchar(100) name "NOT NULL (๋ธŒ๋žœ๋“œ๋ช…)" + varchar(500) introduction "NOT NULL (์†Œ๊ฐœ๊ธ€)" + varchar(500) website_url "NULL (์›น์‚ฌ์ดํŠธ URL)" + datetime created_at "NOT NULL (์ƒ์„ฑ์ผ์ž)" + datetime updated_at "NOT NULL (์ˆ˜์ •์ผ์ž)" + datetime deleted_at "NULL (soft delete) (์‚ญ์ œ์ผ์ž)" + } + + PRODUCT { + bigint id PK "AUTO_INCREMENT (idx)" + bigint brand_id FK "NOT NULL (๋ธŒ๋žœ๋“œ idx)" + varchar(200) name "NOT NULL (์ƒํ’ˆ๋ช…)" + bigint price "NOT NULL (์ƒํ’ˆ ๊ฐ€๊ฒฉ)" + varchar(2000) description "NULL (์„ค๋ช…)" + varchar(500) image_url "NULL (์ƒํ’ˆ ์ด๋ฏธ์ง€ URL)" + varchar(20) status "NOT NULL (SELLING/OUT_OF_STOCK/SOLD_OUT) (์ƒํ’ˆ ์ƒํƒœ)" + bigint like_count "NOT NULL DEFAULT 0 (์ข‹์•„์š” ์ˆ˜)" + datetime created_at "NOT NULL (์ƒ์„ฑ์ผ์ž)" + datetime updated_at "NOT NULL (์ˆ˜์ •์ผ์ž)" + datetime deleted_at "NULL (soft delete) (์‚ญ์ œ์ผ์ž)" + } + + LIKES { + bigint id PK "AUTO_INCREMENT (idx)" + bigint user_id "NOT NULL (์‚ฌ์šฉ์ž idx)" + bigint product_id "NOT NULL (์ƒํ’ˆ idx)" + datetime created_at "NOT NULL (์ƒ์„ฑ์ผ์ž)" + datetime updated_at "NOT NULL (์ˆ˜์ •์ผ์ž)" + } + + STOCK { + bigint id PK "AUTO_INCREMENT (idx)" + bigint product_id UK "NOT NULL (์ƒํ’ˆ idx, 1:1)" + bigint quantity "NOT NULL DEFAULT 0 (์žฌ๊ณ  ์ˆ˜๋Ÿ‰)" + datetime created_at "NOT NULL (์ƒ์„ฑ์ผ์ž)" + datetime updated_at "NOT NULL (์ˆ˜์ •์ผ์ž)" + datetime deleted_at "NULL (soft delete) (์‚ญ์ œ์ผ์ž)" + } + + ORDERS { + bigint id PK "AUTO_INCREMENT (idx)" + bigint user_id "NOT NULL (์‚ฌ์šฉ์ž idx)" + varchar(20) status "NOT NULL (ORDERED) (์ฃผ๋ฌธ ์ƒํƒœ)" + datetime created_at "NOT NULL (์ƒ์„ฑ์ผ์ž)" + datetime updated_at "NOT NULL (์ˆ˜์ •์ผ์ž)" + datetime deleted_at "NULL (soft delete) (์‚ญ์ œ์ผ์ž)" + } + + ORDER_ITEM { + bigint id PK "AUTO_INCREMENT (idx)" + bigint order_id FK "NOT NULL (์ฃผ๋ฌธ idx)" + bigint product_id "NOT NULL (์ƒํ’ˆ idx, ์ฐธ์กฐ์šฉ)" + bigint quantity "NOT NULL (์ฃผ๋ฌธ ์ˆ˜๋Ÿ‰)" + varchar(200) product_name "NOT NULL (์Šค๋ƒ…์ƒท: ์ƒํ’ˆ๋ช…)" + bigint product_price "NOT NULL (์Šค๋ƒ…์ƒท: ์ƒํ’ˆ ๊ฐ€๊ฒฉ)" + varchar(500) product_image_url "NULL (์Šค๋ƒ…์ƒท: ์ƒํ’ˆ ์ด๋ฏธ์ง€ URL)" + datetime created_at "NOT NULL (์ƒ์„ฑ์ผ์ž)" + datetime updated_at "NOT NULL (์ˆ˜์ •์ผ์ž)" + datetime deleted_at "NULL (soft delete) (์‚ญ์ œ์ผ์ž)" + } + + BRAND ||--o{ PRODUCT : "has" + USER ||--o{ LIKES : "likes" + PRODUCT ||--o{ LIKES : "liked by" + PRODUCT ||--|| STOCK : "has stock" + USER ||--o{ ORDERS : "places" + ORDERS ||--o{ ORDER_ITEM : "contains" +``` + +## ์ธ๋ฑ์Šค ์ „๋žต (์ข‹์•„์š” & ์ฃผ๋ฌธ) + +| ํ…Œ์ด๋ธ” | ์ธ๋ฑ์Šค | ์œ ํ˜• | ์ปฌ๋Ÿผ | ์šฉ๋„ | +|--------|--------|------|------|------| +| LIKES | `uk_likes_user_product` | UNIQUE | `(user_id, product_id)` | ๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ. ๋™์ผ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ™์€ ์ƒํ’ˆ์— ์ค‘๋ณต ์ข‹์•„์š” ๋ฐฉ์ง€ | +| LIKES | `idx_likes_user_id` | INDEX | `(user_id)` | ์ข‹์•„์š” ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๋Šฅ (์œ ๋‹ˆํฌ ํ‚ค์˜ ์„ ํ–‰ ์ปฌ๋Ÿผ์ด๋ฏ€๋กœ ๋ณ„๋„ ์ธ๋ฑ์Šค ๋ถˆํ•„์š”ํ•  ์ˆ˜ ์žˆ์Œ) | +| STOCK | `uk_stock_product_id` | UNIQUE | `(product_id)` | ์ƒํ’ˆ 1:1 ๊ด€๊ณ„ ๋ณด์žฅ | +| ORDERS | `idx_orders_user_id` | INDEX | `(user_id)` | ์‚ฌ์šฉ์ž๋ณ„ ์ฃผ๋ฌธ ๋ชฉ๋ก ์กฐํšŒ | +| ORDERS | `idx_orders_user_created` | INDEX | `(user_id, created_at)` | ๊ธฐ๊ฐ„ ํ•„ํ„ฐ + ์‚ฌ์šฉ์ž๋ณ„ ์กฐํšŒ | +| ORDER_ITEM | `idx_order_item_order_id` | INDEX | `(order_id)` | ์ฃผ๋ฌธ๋ณ„ ํ•ญ๋ชฉ ์กฐํšŒ | + +## ์„ค๊ณ„ ํฌ์ธํŠธ + +### 1. LIKES ํ…Œ์ด๋ธ” โ€” hard delete ์ฑ„ํƒ + +LIKES๋Š” BaseEntity์˜ `deletedAt` ํŒจํ„ด์„ ๋”ฐ๋ฅด์ง€ ์•Š๋Š”๋‹ค. ์ข‹์•„์š” ์ทจ์†Œ ์‹œ ๋ ˆ์ฝ”๋“œ๋ฅผ ๋ฌผ๋ฆฌ์ ์œผ๋กœ ์‚ญ์ œ(hard delete)ํ•œ๋‹ค. + +**์ด์œ **: soft delete ์ ์šฉ ์‹œ ๋ณตํ•ฉ ์œ ๋‹ˆํฌ ํ‚ค์— `deletedAt`์„ ํฌํ•จํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ด๋Š” ์ข‹์•„์š” โ†’ ์ทจ์†Œ โ†’ ์žฌ์ข‹์•„์š” ํ๋ฆ„์—์„œ ์œ ๋‹ˆํฌ ํ‚ค ์ถฉ๋Œ ๋ฌธ์ œ๋ฅผ ์•ผ๊ธฐํ•œ๋‹ค. hard delete๋กœ ๋‹จ์ˆœํ™”ํ•˜๋ฉด `(user_id, product_id)` ์œ ๋‹ˆํฌ ํ‚ค๋งŒ์œผ๋กœ ๋ฉฑ๋“ฑ์„ฑ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋‹ค. + +### 2. ORDER_ITEM.product_id โ€” FK ๋ฏธ์„ค์ • + +`product_id`๋Š” ์›๋ณธ ์ƒํ’ˆ ์ถ”์ ์šฉ ์ฐธ์กฐ ID์ผ ๋ฟ, FK ์ œ์•ฝ์„ ์„ค์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค. + +**์ด์œ **: ์ฃผ๋ฌธ ์Šค๋ƒ…์ƒท์€ ์ฃผ๋ฌธ ์‹œ์ ์˜ ์ƒํ’ˆ ์ •๋ณด๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๋ณด์กดํ•˜๋Š” ๊ฒƒ์ด ๋ชฉ์ . ์ƒํ’ˆ์ด soft delete๋˜์–ด๋„ ์ฃผ๋ฌธ ์ด๋ ฅ์€ ์˜ํ–ฅ๋ฐ›์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค. ์Šค๋ƒ…์ƒท ํ•„๋“œ(product_name, product_price, product_image_url)๊ฐ€ ์›๋ณธ ์ƒํ’ˆ๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ ๋ณด์กด๋œ๋‹ค. + +### 3. STOCK ๋ถ„๋ฆฌ โ€” ๋ฝ ๋ฒ”์œ„ ์ตœ์†Œํ™” + +Stock์„ Product ๋‚ด ํ•„๋“œ๊ฐ€ ์•„๋‹Œ ๋ณ„๋„ ํ…Œ์ด๋ธ”๋กœ ๋ถ„๋ฆฌํ•œ๋‹ค. + +**์ด์œ **: ์ฃผ๋ฌธ ์‹œ ์žฌ๊ณ  ์ฐจ๊ฐ์€ ๋น„๊ด€์  ๋ฝ(`SELECT FOR UPDATE`)์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, Product์— stock ํ•„๋“œ๋ฅผ ๋‘๋ฉด ์ข‹์•„์š”(likeCount ๋น„๊ด€์  ๋ฝ)์™€ ์ฃผ๋ฌธ(stock ๋น„๊ด€์  ๋ฝ)์ด ๊ฐ™์€ ํ–‰์—์„œ ๊ฒฝํ•ฉํ•œ๋‹ค. ๋ถ„๋ฆฌํ•˜๋ฉด ๋‘ ๋ฝ์ด ๋…๋ฆฝ์ ์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค. + +### 4. ORDERS ๊ธฐ๊ฐ„ ์ธ๋ฑ์Šค + +์‚ฌ์šฉ์ž ์ฃผ๋ฌธ ๋ชฉ๋ก์€ `startAt`, `endAt` ๊ธฐ๊ฐ„ ํ•„ํ„ฐ๋ฅผ ์ง€์›ํ•˜๋ฏ€๋กœ `(user_id, created_at)` ๋ณตํ•ฉ ์ธ๋ฑ์Šค๋ฅผ ์„ค์ •ํ•œ๋‹ค. + +### 5. ProductStatus ํ™•์žฅ + +๊ธฐ์กด `SELLING`, `SOLD_OUT`์— `OUT_OF_STOCK`์ด ์ถ”๊ฐ€๋œ๋‹ค. + +| ์ƒํƒœ | ์˜๋ฏธ | ์ „ํ™˜ ์ฃผ์ฒด | +|------|------|----------| +| SELLING | ํŒ๋งค ์ค‘ | ๊ธฐ๋ณธ ์ƒํƒœ, ์žฌ์ž…๊ณ  ์‹œ ์ž๋™ ๋ณต์› | +| OUT_OF_STOCK | ์ผ์‹œ์  ํ’ˆ์ ˆ | ์žฌ๊ณ  0 ์‹œ ์ž๋™ ์ „ํ™˜ (StockService) | +| SOLD_OUT | ์˜๊ตฌ ํŒ๋งค ์ค‘์ง€ | ์–ด๋“œ๋ฏผ ์ˆ˜๋™ ์„ค์ • | \ No newline at end of file