๋„๋ฉ”์ธ ์ฃผ๋„ ๊ฐœ๋ฐœ ์‹œ์ž‘ํ•˜๊ธฐ: DDD ํ•ต์‹ฌ ๊ฐœ๋… ์ •๋ฆฌ๋ถ€ํ„ฐ ๊ตฌํ˜„๊นŒ์ง€ ์ฑ…์„ ์ฝ๊ณ  ๋‚ด์šฉ์„ ์•„์ฃผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ •๋ฆฌํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค. ์ฑ…์—๋Š” ์ž์„ธํ•œ ์„ค๋ช…๊ณผ ์˜ˆ์ œ๊ฐ€ ๋งŽ์œผ๋‹ˆ ๊ผญ ๊ตฌ์ž…ํ•ด์„œ ์ฝ๋Š”๊ฒƒ์„ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค~๐Ÿ‘


1. ๋„๋ฉ”์ธ ๋ชจ๋ธ ์‹œ์ž‘ํ•˜๊ธฐ

1.1 ๋„๋ฉ”์ธ์ด๋ž€?

์†Œํ”„ํŠธ์›จ์–ด๋กœ ํ•ด๊ฒฐํ•˜๊ณ ์ž ํ•˜๋Š” ๋ฌธ์ œ ์˜์—ญ์„ ๋„๋ฉ”์ธ์ด๋ผ ํ•œ๋‹ค.

1.2 ๋„๋ฉ”์ธ ์ „๋ฌธ๊ฐ€์™€ ๊ฐœ๋ฐœ์ž ๊ฐ„ ์ง€์‹๊ณต์œ 

์ „๋ฌธ๊ฐ€๋‚˜ ๊ด€๋ จ์ž๊ฐ€ ์š”๊ตฌํ•œ ๋‚ด์šฉ์ด ํ•ญ์ƒ ์˜ฌ๋ฐ”๋ฅธ ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๊ฐœ๋ฐœ์ž๋Š” ์š”๊ตฌ์‚ฌํ•ญ์„ ์ดํ•ดํ•  ๋•Œ ์™œ ์ด๋Ÿฐ ๊ธฐ๋Šฅ์„ ์š”๊ตฌํ•˜๋Š”์ง€ ๋˜๋Š” ์‹ค์ œ๋กœ ์›ํ•˜๋Š”๊ฒŒ ๋ฌด์—‡์ธ์ง€ ์ƒ๊ฐํ•˜๊ณ  ์ „๋ฌธ๊ฐ€์™€ ๋Œ€ํ™”๋ฅผ ํ†ตํ•ด ์ง„์งœ๋กœ ์›ํ•˜๋Š” ๊ฒƒ์„ ์ฐพ์•„์•ผ ํ•œ๋‹ค.

1.3 ๋„๋ฉ”์ธ ๋ชจ๋ธ

๋„๋ฉ”์ธ ๋ชจ๋ธ์€ ํŠน์ • ๋„๋ฉ”์ธ์„ ๊ฐœ๋…์ ์œผ๋กœ ํ‘œํ˜„ํ•œ ๊ฒƒ์ด๋‹ค. ๋„๋ฉ”์ธ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋ฉด ์—ฌ๋Ÿฌ ๊ด€๊ณ„์ž๋“ค์ด ๋™์ผํ•œ ๋ชจ์Šต์œผ๋กœ ๋„๋ฉ”์ธ์„ ์ดํ•ดํ•˜๊ณ  ๋„๋ฉ”์ธ ์ง€์‹์„ ๊ณต์œ ํ•˜๋Š”๋ฐ ๋„์›€์ด ๋œ๋‹ค. ๋„๋ฉ”์ธ ๋ชจ๋ธ์„ ์ดํ•ดํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋œ๋‹ค๋ฉด ๊ฐ์ฒด๊ธฐ๋ฐ˜ ํ‘œํ˜„์ด๋“ , ํ•จ์ˆ˜ ํ‘œํ˜„์ด๋“  ํ‘œํ˜„ ๋ฐฉ์‹์ด ๋ฌด์—‡์ธ์ง€๋Š” ์ค‘์š”ํ•˜์ง€ ์•Š๋‹ค.

๊ฐ์ฒด๊ธฐ๋ฐ˜๋„๋ฉ”์ธ๋ชจ๋ธ
์ƒํƒœ๋‹ค์ด์–ด๊ทธ๋žจ๋„๋ฉ”์ธ๋ชจ๋ธ

1.4 ๋„๋ฉ”์ธ ๋ชจ๋ธ ํŒจํ„ด

์•„ํ‚คํ…์ฒ˜๊ตฌ์„ฑ
์˜์—ญ ์„ค๋ช…
์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค(UI) ๋˜๋Š” ํ‘œํ˜„(Presentation) ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‚ฌ์šฉ์ž์—๊ฒŒ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ค€๋‹ค. ์—ฌ๊ธฐ์„œ ์‚ฌ์šฉ์ž๋Š” ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ๋žŒ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์™ธ๋ถ€ ์‹œ์Šคํ…œ์ผ ์ˆ˜๋„ ์žˆ๋‹ค.
์‘์šฉ(Application) ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญํ•œ ๊ธฐ๋Šฅ์„ ์‹คํ–‰ํ•œ๋‹ค. ์—…๋ฌด ๋กœ์ง์„ ์ง์ ‘ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์œผ๋ฉฐ ๋„๋ฉ”์ธ ๊ณ„์ธต์„ ์กฐํ•ฉํ•ด์„œ ๊ธฐ๋Šฅ์„ ์‹คํ–‰ํ•œ๋‹ค.
๋„๋ฉ”์ธ(Domain) ์‹œ์Šคํ…œ์ด ์ œ๊ณตํ•  ๋„๋ฉ”์ธ ๊ทœ์น™์„ ๊ตฌํ˜„ํ•œ๋‹ค.
์ธํ”„๋ผ์ŠคํŠธ๋Ÿญ์ฒ˜(Infrastructure) ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋‚˜ ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ๊ณผ ๊ฐ™์€ ์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ์˜ ์—ฐ๋™์„ ์ฒ˜๋ฆฌํ•œ๋‹ค.


๊ฐœ๋… ๋ชจ๋ธ๊ณผ ๊ตฌํ˜„ ๋ชจ๋ธ
๊ฐœ๋… ๋ชจ๋ธ์€ ์ˆœ์ˆ˜ํ•˜๊ฒŒ ๋ฌธ์ œ๋ฅผ ๋ถ„์„ํ•œ ๊ฒฐ๊ณผ๋ฌผ์ด๋‹ค. ๊ฐœ๋… ๋ชจ๋ธ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ, ์„ฑ๋Šฅ, ๊ตฌํ˜„ ๊ธฐ์ˆ ๊ณผ ๊ฐ™์€ ๊ฒƒ์„ ๊ณ ๋ คํ•˜๊ณ  ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ๊ฐœ๋… ๋ชจ๋ธ์„ ์žˆ๋Š” ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค. ๊ทธ๋ž˜์„œ ๊ฐœ๋… ๋ชจ๋ธ์„ ๊ตฌํ˜„ ๊ฐ€๋Šฅํ•œ ํ˜•ํƒœ์˜ ๋ชจ๋ธ๋กœ ์ „ํ™˜ํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์น˜๊ฒŒ ๋œ๋‹ค.

์ฒ˜์Œ๋ถ€ํ„ฐ ์™„๋ฒฝํ•œ ๊ฐœ๋… ๋ชจ๋ธ์„ ๋งŒ๋“ค๊ธฐ๋ณด๋‹ค๋Š” ์ „๋ฐ˜์ ์ธ ๊ฐœ์š”๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋Š” ์ˆ˜์ค€์œผ๋กœ ๊ฐœ๋… ๋ชจ๋ธ์„ ์ž‘์„ฑํ•ด์•ผํ•œ๋‹ค. ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐ์—๋Š” ๊ฐœ์š” ์ˆ˜์ค€์˜ ๊ฐœ๋… ๋ชจ๋ธ๋กœ ๋„๋ฉ”์ธ์— ๋Œ€ํ•œ ์ „์ฒด ์œค๊ณฝ์„ ์ดํ•ดํ•˜๋Š” ๋ฐ ์ง‘์ค‘ํ•˜๊ณ , ๊ตฌํ˜„ํ•˜๋Š” ๊ณผ์ •์—์„œ ๊ฐœ๋… ๋ชจ๋ธ์„ ๊ตฌํ˜„ ๋ชจ๋ธ๋กœ ์ ์ง„์ ์œผ๋กœ ๋ฐœ์ „์‹œ์ผœ ๋‚˜๊ฐ€์•ผ ํ•œ๋‹ค.

1.5 ๋„๋ฉ”์ธ ๋ชจ๋ธ ๋„์ถœ

๋„๋ฉ”์ธ์„ ๋ชจ๋ธ๋งํ•  ๋•Œ ๊ธฐ๋ณธ์ด ๋˜๋Š” ์ž‘์—…์€ ๋ชจ๋ธ์„ ๊ตฌ์„ฑํ•˜๋Š” ํ•ต์‹ฌ ๊ตฌ์„ฑ์š”์†Œ, ๊ทœ์น™, ๊ธฐ๋Šฅ์„ ์ฐพ๋Š” ๊ฒƒ์ด๋‹ค. ์ด ๊ณผ์ •์€ ์š”๊ตฌ์‚ฌํ•ญ์—์„œ ์ถœ๋ฐœํ•œ๋‹ค. ์š”๊ตฌ์‚ฌํ•ญ์„ ํ†ตํ•ด ๋„๋ฉ”์ธ ๋ชจ๋ธ์„ ์ ์ง„์ ์œผ๋กœ ๋งŒ๋“ค์–ด ๋‚˜๊ฐ„๋‹ค. ์ด๋ ‡๊ฒŒ ๋งŒ๋“  ๋ชจ๋ธ์€ ์š”๊ตฌ์‚ฌํ•ญ ์ •๋ จ์„ ์œ„ํ•ด ๋„๋ฉ”์ธ ์ „๋ฌธ๊ฐ€๋‚˜ ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž์™€ ๋…ผ์˜ํ•˜๋Š” ๊ณผ์ •์—์„œ ๊ณต์œ ๋˜๊ธฐ๋„ ํ•œ๋‹ค. ๋ชจ๋ธ์„ ๊ณต์œ ํ•  ๋•Œ๋Š” ํ™”์ดํŠธ๋ณด๋“œ๋‚˜ ์œ„ํ‚ค ๊ฐ™์€ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ˆ„๊ตฌ๋‚˜ ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋ฉด ์ข‹๋‹ค.

1.6 ์—”ํ‹ฐํ‹ฐ์™€ ๋ฒจ๋ฅ˜

1.6.1 ์—”ํ‹ฐํ‹ฐ

  • ์‹๋ณ„์ž๋ฅผ ๊ฐ€์ง„๋‹ค.
  • ์‹๋ณ„์ž๋Š” ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด๋งˆ๋‹ค ๊ณ ์œ ํ•˜๋‹ค.

1.6.2 ์—”ํ‹ฐํ‹ฐ์˜ ์‹๋ณ„์ž ์ƒ์„ฑ

  • ํŠน์ • ๊ทœ์น™์— ๋”ฐ๋ผ ์ƒ์„ฑ.
  • UUID๋‚˜ Nano ID์™€ ๊ฐ™์€ ๊ณ ์œ  ์‹๋ณ„์ž ์ƒ์„ฑ๊ธฐ ์‚ฌ์šฉ.
  • ๊ฐ’์„ ์ง์ ‘ ์ž…๋ ฅ.
  • ์ผ๋ จ๋ฒˆํ˜ธ ์‚ฌ์šฉ(์‹œํ€€์Šค๋‚˜ DB์˜ ์ž๋™ ์ฆ๊ฐ€ ์ปฌ๋Ÿผ ์‚ฌ์šฉ).

1.6.3 ๋ฐธ๋ฅ˜ ํƒ€์ž…

  • ๋ฐธ๋ฅ˜ ํƒ€์ž…์€ ๊ฐœ๋…์ ์œผ๋กœ ์™„์ „ํ•œ ํ•˜๋‚˜๋ฅผ ํ‘œํ˜„ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
  • ์˜๋ฏธ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๊ธฐ๋„ ํ•œ๋‹ค.
  • ๋ฐธ๋ฅ˜ ํƒ€์ž…์„ ์œ„ํ•œ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ถˆ๋ณ€์œผ๋กœ ๊ตฌํ˜„ํ•œ๋‹ค.

1.6.4 ์—”ํ‹ฐํ‹ฐ ์‹๋ณ„์ž์™€ ๋ฐธ๋ฅ˜ ํƒ€์ž…

  • ์‹๋ณ„์ž๋ฅผ ์œ„ํ•œ ๋ฐธ๋ฅ˜ ํƒ€์ž…์„ ์‚ฌ์šฉํ•ด์„œ ์˜๋ฏธ๊ฐ€ ์ž˜ ๋“œ๋Ÿฌ๋‚˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

1.6.5 ๋„๋ฉ”์ธ ๋ชจ๋ธ์— set ๋„ฃ์ง€ ์•Š๊ธฐ

  • ์ƒํƒœ ๋ณ€๊ฒฝ์„ ์œ„ํ•œ set ์‚ฌ์šฉ์‹œ ๋„๋ฉ”์ธ ์ง€์‹์ด ์ฝ”๋“œ์—์„œ ์‚ฌ๋ผ์ง„๋‹ค.
  • ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์˜จ์ „ํ•˜์ง€ ์•Š์€ ์ƒํƒœ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค.

DTO๋„ ์ตœ๋Œ€ํ•œ ๋ถˆ๋ณ€ ๊ฐ์ฒด๋กœ ์‚ฌ์šฉํ•˜๋„๋ก ํ•˜์ž

1.7 ๋„๋ฉ”์ธ ์šฉ์–ด์™€ ์œ ๋น„์ฟผํ„ฐ์Šค ์–ธ์–ด

๋„๋ฉ”์ธ ์šฉ์–ด
STEP1, STEP2 ๊ฐ™์€๊ฒƒ์ด ์•„๋‹Œ PAYMENT_WAITING, PREPARING ๊ฐ™์€ ๋„๋ฉ”์ธ ์šฉ์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

์œ ๋น„์ฟผํ„ฐ์Šค ์–ธ์–ด
์ „๋ฌธ๊ฐ€, ๊ด€๊ณ„์ž ๊ฐœ๋ฐœ์ž๊ฐ€ ๋„๋ฉ”์ธ๊ณผ ๊ด€๋ จ๋œ ๊ณตํ†ต์˜ ์–ธ์–ด๋ฅผ ๋งŒ๋“ค๊ณ  ์ด๋ฅผ ๋Œ€ํ™”, ๋ฌธ์„œ, ๋„๋ฉ”์ธ ๋ชจ๋ธ, ์ฝ”๋“œ ํ…Œ์ŠคํŠธ ๋“ฑ ๋ชจ๋“  ๊ณณ์—์„œ ๊ฐ™์€ ์šฉ์–ด๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์†Œํ†ต ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์šฉ์–ด์˜ ๋ชจํ˜ธํ•จ์„ ์ค„์ผ ์ˆ˜ ์žˆ๊ณ  ๊ฐœ๋ฐœ์ž๋Š” ๋„๋ฉ”์ธ๊ณผ ์ฝ”๋“œ ์‚ฌ์ด์—์„œ ๋ถˆํ•„์š”ํ•œ ํ•ด์„ ๊ณผ์ •์„ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค.




2. ์•„ํ‚คํƒ์ฒ˜ ๊ฐœ์š”

2.1 ๋„ค ๊ฐœ์˜ ์˜์—ญ

ํ‘œํ˜„ ์˜์—ญ
์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ๋ฐ›์•„ ์‘์šฉ ์˜์—ญ์— ์ „๋‹ฌํ•˜๊ณ  ์‘์šฉ ์˜์—ญ์˜ ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๋‹ค์‹œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ฃผ๋Š” ์—ญํ• 

ํ‘œํ˜„์˜์—ญ


์‘์šฉ ์˜์—ญ
ํ‘œํ˜„ ์˜์—ญ์„ ํ†ตํ•ด ์š”์ฒญ์„ ์ „๋‹ฌ๋ฐ›์•„ ์‹œ์Šคํ…œ์ด ์‚ฌ์šฉ์ž์—๊ฒŒ ์ œ๊ณตํ•ด์•ผ ํ•  ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋Š” ์—ญํ• . ๋กœ์ง์„ ์ง์ ‘ ์ˆ˜ํ–‰ํ•˜๊ธฐ๋ณด๋‹ค๋Š” ๋„๋ฉ”์ธ ๋ชจ๋ธ์— ๋กœ์ง ์ˆ˜ํ–‰์„ ์œ„์ž„ํ•œ๋‹ค.

์‘์šฉ์˜์—ญ


๋„๋ฉ”์ธ ์˜์—ญ
๋„๋ฉ”์ธ ๋ชจ๋ธ์„ ๊ตฌํ˜„. ๋„๋ฉ”์ธ ๋ชจ๋ธ์€ ๋„๋ฉ”์ธ์˜ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•œ๋‹ค.


์ธํ”„๋ผ์ŠคํŠธ๋Ÿญ์ฒ˜ ์˜์—ญ
๊ตฌํ˜„ ๊ธฐ์ˆ ์— ๋Œ€ํ•œ ๊ฒƒ์„ ๋‹ค๋ฃฌ๋‹ค. ์ด ์˜์—ญ์€ ๋…ผ๋ฆฌ์ ์ธ ๊ฐœ๋…์„ ํ‘œํ˜„ํ•˜๊ธฐ๋ณด๋‹ค๋Š” ์‹ค์ œ ๊ตฌํ˜„์„ ๋‹ค๋ฃฌ๋‹ค.

์ธํ”„๋ผ์ŠคํŠธ๋Ÿญ์ฒ˜์˜์—ญ

2.2 ๊ณ„์ธต ๊ตฌ์กฐ ์•„ํ‚คํ…์ฒ˜

๊ณ„์ธต๊ตฌ์กฐ
๊ณ„์ธต๊ตฌ์กฐ์ƒ์˜์กด๊ด€๊ณ„
  • ์ƒ์œ„ ๊ณ„์ธต์—์„œ ํ•˜์œ„ ๊ณ„์ธต์œผ๋กœ ์˜์กด๋งŒ ์กด์žฌํ•˜๊ณ  ํ•˜์œ„ ๊ณ„์ธต์—์„œ ์ƒ์œ„ ๊ณ„์ธต์— ์˜์กดํ•˜์ง€ ์•Š๋Š”๋‹ค.

2.3 DIP

DIP(Dependency Inversion Principle) ์˜์กด์„ฑ ์—ญ์ „ ์›์น™

๊ณ ์ˆ˜์ค€์ €์ˆ˜์ค€
๊ณ ์ˆ˜์ค€์ €์ˆ˜์ค€2
๊ณ ์ˆ˜์ค€์ €์ˆ˜์ค€3
DIP

2.4 ๋„๋ฉ”์ธ ์˜์—ญ์˜ ์ฃผ์š” ๊ตฌ์„ฑ์š”์†Œ

์š”์†Œ ์„ค๋ช…
์—”ํ‹ฐํ‹ฐ ENTITY ๊ณ ์œ ์˜ ์‹๋ณ„์ž๋ฅผ ๊ฐ–๋Š” ๊ฐ์ฒด๋กœ ์ž์‹ ์˜ ๋ผ์ดํ”„ ์‚ฌ์ดํด์„ ๊ฐ–๋Š”๋‹ค. ์ฃผ๋ฌธ, ํšŒ์›, ์ƒํ’ˆ๊ณผ ๊ฐ™์ด ๋„๋ฉ”์ธ์˜ ๊ณ ์œ ํ•œ ๊ฐœ๋…์„ ํ‘œํ˜„ํ•œ๋‹ค. ๋„๋ฉ”์ธ ๋ชจ๋ธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•˜๋ฉฐ ํ•ด๋‹น ๋ฐ์ดํ„ฐ์™€ ๊ด€๋ จ๋œ ๊ธฐ๋Šฅ์„ ํ•จ๊ป˜ ์ œ๊ณตํ•œ๋‹ค.
๋ฐธ๋ฅ˜ VALUE ๊ณ ์œ ์˜ ์‹๋ณ„์ž๋ฅผ ๊ฐ–์ง€ ์•Š๋Š” ๊ฐ์ฒด๋กœ ์ฃผ๋กœ ๊ฐœ๋…์ ์œผ๋กœ ํ•˜๋‚˜์ธ ๊ฐ’์„ ํ‘œํ˜„ํ•œ๋‹ค. ์—”ํ‹ฐํ‹ฐ์˜ ์†์„ฑ์œผ๋กœ ์‚ฌ์šฉํ•  ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋‹ค๋ฅธ ๋ฐธ๋ฅ˜ ํƒ€์ž…์˜ ์†์„ฑ์œผ๋กœ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
์• ๊ทธ๋ฆฌ๊ฑฐํŠธ AGGREGATE ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ์™€ ๋ฐธ๋ฅ˜ ๊ฐ์ฒด๋ฅผ ๊ฐœ๋…์ ์œผ๋กœ ํ•˜๋‚˜๋กœ ๋ฌถ์€ ๊ฒƒ์ด๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ฃผ๋ฌธ๊ณผ ๊ด€๋ จ๋œ Order ์—”ํ‹ฐํ‹ฐ, OrderLine ๋ฐธ๋ฅ˜, Orderer ๋ฐธ๋ฅ˜ ๊ฐ์ฒด๋ฅผ โ€˜์ฃผ๋ฌธโ€™ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋กœ ๋ฌถ์„ ์ˆ˜ ์žˆ๋‹ค.
๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ REPOSITORY ๋„๋ฉ”์ธ ๋ชจ๋ธ์˜ ์˜์†์„ฑ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด DBMS ํ…Œ์ด๋ธ”์—์„œ ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด๋ฅผ ๋กœ๋”ฉํ•˜๊ฑฐ๋‚˜ ์ €์žฅํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.
๋„๋ฉ”์ธ ์„œ๋น„์Šค DOMAIN SERVICE ํŠน์ • ์—”ํ‹ฐํ‹ฐ์— ์†ํ•˜์ง€ ์•Š์€ ๋„๋ฉ”์ธ ๋กœ์ง์„ ์ œ๊ณตํ•œ๋‹ค. ๋„๋ฉ”์ธ ๋กœ์ง์ด ์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ์™€ ๋ฐธ๋ฅ˜๋ฅผ ํ•„์š”๋กœ ํ•˜๋ฉด ๋„๋ฉ”์ธ ์„œ๋น„์Šค์—์„œ ๋กœ์ง์„ ๊ตฌํ˜„ํ•œ๋‹ค.

2.5 ์š”์ฒญ ์ฒ˜๋ฆฌ ํ๋ฆ„

์š”์ฒญ์ฒ˜๋ฆฌํ๋ฆ„

2.6 ์ธํ”„๋ผ์ŠคํŠธ๋Ÿญ์ฒ˜ ๊ฐœ์š”

  • ๋„๋ฉ”์ธ ๊ฐ์ฒด์˜ ์˜์†์„ฑ ์ฒ˜๋ฆฌ, ํŠธ๋žœ์ ์…˜, REST ํด๋ผ์ด์–ธํŠธ ๋“ฑ ๋‹ค๋ฅธ์˜์—ญ์—์„œ ํ•„์š”๋กœ ํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ, ๊ตฌํ˜„ ๊ธฐ์ˆ , ๋ณด์กฐ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•œ๋‹ค.
  • ๋ณดํ†ต ์˜์กด์„ฑ์—ญ์ „ ์‚ฌ์šฉ
  • @Transactional ๊ฐ™์€ DIP๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์˜ˆ์™ธ๋„ ํŽธ์˜๋ฅผ ์œ„ํ•ด ํ—ˆ์šฉ

2.7 ๋ชจ๋“ˆ ๊ตฌ์„ฑ

๋ชจ๋“ˆ๊ตฌ์„ฑ1
๋ชจ๋“ˆ๊ตฌ์„ฑ2
๋ชจ๋“ˆ๊ตฌ์„ฑ3




3. ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ

3.1 ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ

๋ณต์žกํ•œ ๋„๋ฉ”์ธ์„ ์ดํ•ดํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๊ธฐ ์‰ฌ์šด ๋‹จ์œ„๋กœ ๋งŒ๋“œ๋ ค๋ฉด ์ƒ์œ„ ์ˆ˜์ค€์—์„œ ๋ชจ๋ธ์„ ์กฐ๋งํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ํ•„์š”ํ•œ๋ฐ, ๊ทธ ๋ฐฉ๋ฒ•์ด ๋ฐ”๋กœ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋‹ค. ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋Š” ๊ด€๋ จ๋œ ๊ฐ์ฒด๋ฅผ ํ•˜๋‚˜์˜ ๊ตฐ์œผ๋กœ ๋ฌถ์–ด ์ค€๋‹ค.

์• ๊ฑฐ๋ฆฌ๊ฑฐํŠธ๋ชจ๋ธ

์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋Š” ๊ฒฝ๊ณ„๋ฅผ ๊ฐ–๋Š”๋‹ค. ํ•œ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ์†ํ•œ ๊ฐ์ฒด๋Š” ๋‹ค๋ฅธ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ์†ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋Š” ๋…๋ฆฝ๋œ ๊ฐ์ฒด ๊ตฐ์ด๋ฉฐ ๊ฐ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋Š” ์ž๊ธฐ ์ž์‹ ์„ ๊ด€๋ฆฌํ•  ๋ฟ ๋‹ค๋ฅธ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ฒฝ๊ณ„๋ฅผ ์„ค์ •ํ•  ๋•Œ ๊ธฐ๋ณธ์ด ๋˜๋Š” ๊ฒƒ์€ ๋„๋ฉ”์ธ ๊ทœ์น™๊ณผ ์š”๊ตฌ์‚ฌํ•ญ์ด๋‹ค. ๋„๋ฉ”์ธ ๊ทœ์น™์— ๋”ฐ๋ผ ํ•จ๊ป˜ ์ƒ์„ฑ๋˜๋Š” ๊ตฌ์„ฑ์š”์†Œ๋Š” ํ•œ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ์†ํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๋‹ค.

3.2 ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ

์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ์†ํ•œ ๋ชจ๋“  ๊ฐ์ฒด๊ฐ€ ์ผ๊ด€๋œ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๋ ค๋ฉด ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ์ „์ฒด๋ฅผ ๊ด€๋ฆฌํ•  ์ฃผ์ฒด๊ฐ€ ํ•„์š”ํ•œ๋ฐ, ์ด ์ฑ…์ž„์„ ์ง€๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ ์—”ํ‹ฐํ‹ฐ์ด๋‹ค. ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ์†ํ•œ ๊ฐ์ฒด๋Š” ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ ์—”ํ‹ฐํ‹ฐ์— ์ง์ ‘ ๋˜๋Š” ๊ฐ„์ ‘์ ์œผ๋กœ ์†ํ•˜๊ฒŒ ๋œ๋‹ค.

์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋ฃจํŠธ

3.2.1 ๋„๋ฉ”์ธ ๊ทœ์น™๊ณผ ์ผ๊ด€์„ฑ

  • ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ์˜ ํ•ต์‹ฌ ์—ญํ• ์€ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์˜ ์ผ๊ด€์„ฑ์ด ๊นจ์ง€์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ๋Š” ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๊ฐ€ ์ œ๊ณตํ•ด์•ผ ํ•  ๋„๋ฉ”์ธ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ๋‹ค.
  • ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ์™ธ๋ถ€์—์„œ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ์†ํ•œ ๊ฐ์ฒด๋ฅผ ์ง์ ‘ ๋ณ€๊ฒฝํ•˜๋ฉด ์•ˆ๋œ๋‹ค. ์ด๊ฒƒ์€ ๋ชจ๋ธ์˜ ์ผ๊ด€์„ฑ์„ ๊นจ๋Š” ์›์ธ์ด ๋œ๋‹ค.
  • ๋‹จ์ˆœํžˆ ํ•„๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” set ๋ฉ”์„œ๋“œ๋ฅผ ๊ณต๊ฐœ(public) ๋ฒ”์œ„๋กœ ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค.
  • ๋ฐธ๋ฅ˜ ํƒ€์ž…์€ ๋ถˆ๋ณ€์œผ๋กœ ๊ตฌํ˜„ํ•œ๋‹ค.

3.2.2 ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ์˜ ๊ธฐ๋Šฅ ๊ตฌํ˜„

  • ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ๋Š” ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋‚ด๋ถ€์˜ ๋‹ค๋ฅธ ๊ฐ์ฒด๋ฅผ ์กฐํ•ฉํ•ด ๊ธฐ๋Šฅ์„ ์™„์„ฑํ•œ๋‹ค.
  • ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ๊ฐ€ ๊ตฌ์„ฑ์š”์†Œ์˜ ์ƒํƒœ๋งŒ ์ฐธ์กฐํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. ๊ธฐ๋Šฅ ์‹คํ–‰์„ ์œ„์ž„ํ•˜๊ธฐ๋„ ํ•œ๋‹ค.

3.2.3 ํŠธ๋žœ์ ์…˜ ๋ฒ”์œ„

  • ์ž‘์„์ˆ˜๋ก ์ข‹๋‹ค
  • ํ•œ ํŠธ๋žœ์ ์…˜์—์„œ๋Š” ํ•œ ๊ฐœ์˜ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋งŒ ์ˆ˜์ •ํ•œ๋‹ค.
  • ๋ถ€๋“์ดํ•˜๊ฒŒ ํ•œ ํŠธ๋žœ์ ์…˜์œผ๋กœ ๋‘ ๊ฐœ ์ด์ƒ์˜ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•œ๋‹ค๋ฉด ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์—์„œ ๋‹ค๋ฅธ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ๋ง๊ณ , ์‘์šฉ ์„œ๋น„์Šค์—์„œ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋ฅผ ์ˆ˜์ •ํ•˜๋„๋ก ๊ตฌํ˜„ํ•œ๋‹ค.
  • ๋‘ ๊ฐœ ์ด์ƒ์˜ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

3.3 ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ์™€ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ

  • ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ: ๊ฐœ๋…์ƒ ์™„์ „ํ•œ ํ•˜๋‚˜์˜ ๋„๋ฉ”์ธ ๋ชจ๋ธ
  • ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ: ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ์˜์†์„ฑ์„ ์ฒ˜๋ฆฌ
    • save: ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ์ €์žฅ
    • findById: ID๋กœ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋ฅผ ๊ตฌํ•จ

3.4 ID๋ฅผ ์ด์šฉํ•œ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ์ฐธ์กฐ

  • ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋„ ๋‹ค๋ฅธ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋ฅผ ์ฐธ์กฐํ•œ๋‹ค.
  • ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ์ฐธ์กฐ๋Š” ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ๊ฐ€ ๋‹ค๋ฅธ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ๋ฅผ ์ฐธ์กฐํ•œ๋‹ค๋Š” ๋œป์ด๋‹ค.


ํ•„๋“œ ์ฐธ์กฐ

ํ•„๋“œ์ฐธ์กฐ

ํ•„๋“œ ์ฐธ์กฐ ๋ฌธ์ œ์ 

  • ํŽธํ•œ ํƒ์ƒ‰ ์˜ค์šฉ
  • ๊ฒฐํ•ฉ๋„ ์ฆ๊ฐ€
  • ์„ฑ๋Šฅ์— ๋Œ€ํ•œ ๊ณ ๋ฏผ
  • ํ™•์žฅ ์–ด๋ ค์›€


ID๋ฅผ ์ด์šฉํ•œ ์ฐธ์กฐ

ID์ฐธ์กฐ

ID ์ฐธ์กฐ ์žฅ์ 

  • ๋ชจ๋ธ์˜ ๋ณต์žก๋„๋ฅผ ๋‚ฎ์ถ˜๋‹ค
  • ์‘์ง‘๋„ ์ฆ๊ฐ€
  • ๊ตฌํ˜„ ๋‚œ์ด๋„ ๊ฐ์†Œ
  • ํ™•์žฅ ์šฉ์ด

3.4.1 ID๋ฅผ ์ด์šฉํ•œ ์ฐธ์กฐ์™€ ์กฐํšŒ ์„ฑ๋Šฅ

ID๋ฅผ ์ด์šฉํ•œ ์กฐํšŒ๋Š” N + 1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
ํ•ด๊ฒฐ๋ฐฉ๋ฒ•์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • ์กฐํšŒ ์ „์šฉ ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•œ๋‹ค.
  • ์ฟผ๋ฆฌ๊ฐ€ ๋ณต์žกํ•˜๊ฑฐ๋‚˜ ํŠนํ™”๋œ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋ฉด ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค์™€ ๊ฐ™์€ ๊ธฐ์ˆ ๋กœ ๊ตฌํ˜„ํ•˜๋Š”๊ฑธ ๊ณ ๋ คํ•œ๋‹ค.

3.5 ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๊ฐ„ ์ง‘ํ•ฉ ์—ฐ๊ด€

1-N, M-N ์—ฐ๊ด€์— ๋Œ€ํ•ด ์‚ดํŽด๋ณธ๋‹ค.

1-N

public class Category {
    private Set<Product> products; //๋‹ค๋ฅธ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ๋Œ€ํ•œ 1-N ์—ฐ๊ด€
}

์กฐํšŒ์‹œ ๊ฐœ๋…์ ์œผ๋กœ ์• ๊ฑฐ๋ฆฌ๊ฑฐํŠธ ๊ฐ„์— 1-N ์—ฐ๊ด€์ด ์žˆ๋”๋ผ๋„ ์„ฑ๋Šฅ ๋ฌธ์ œ ๋•Œ๋ฌธ์— ์‹ค์ œ ๊ตฌํ˜„์— ๋ฐ˜์˜ํ•˜์ง€ ์•Š๋Š”๋‹ค. N-1๋กœ ์—ฐ๊ด€์ง€์–ด ๊ตฌํ˜„ํ•œ๋‹ค.

public class Product {
    private CategoryId categoryId;
}
public class ProductListService {
    public Page<Product> getProductOfCategory(Long categoryId, int page, int size) {
        Category category = categoryRepository.findById(categoryId);
        checkCategory(category);
        List<Product> products = productRepository.findByCategoryId(category.getId(), page, size);
        int totalCount = productRepository.countByCategoryId(category.getId());
        return new Page(page, size, totalCount, products);
    }
}


M-N
M-N ์—ฐ๊ด€์€ ๊ฐœ๋…์ ์œผ๋กœ ์–‘์ชฝ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ์ปฌ๋ ‰์…˜์œผ๋กœ ์—ฐ๊ด€์„ ๋งŒ๋“ ๋‹ค. ํ•˜์ง€๋งŒ ๊ตฌํ˜„์€ ์š”๊ตฌ์‚ฌํ•ญ์„ ๊ณ ๋ คํ•ด์„œ ๊ฒฐ์ •ํ•œ๋‹ค. ๊ฐœ๋…์ ์œผ๋กœ๋Š” ์ƒํ’ˆ๊ณผ ์นดํ…Œ๊ณ ๋ฆฌ์˜ ์–‘๋ฐฉํ–ฅ M-N ์—ฐ๊ด€์ด ์กด์žฌํ•˜์ง€๋งŒ ์‹ค์ œ ๊ตฌํ˜„์—์„œ๋Š” ์ƒํ’ˆ์—์„œ ์นดํ…Œ๊ณ ๋ฆฌ๋กœ์˜ ๋‹จ๋ฐฉํ–ฅ M-N ์—ฐ๊ด€๋งŒ ์ ์šฉํ•˜๋ฉด ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

public class Product {
    private Set<CategoryId> categoryIds;
}


RDBMS์—์„œ M-N ์—ฐ๊ด€ ๊ตฌํ˜„

RDBMS_M-N์—ฐ๊ด€


JPA๋ฅผ ์ด์šฉํ•˜์—ฌ ID ์ฐธ์กฐ๋ฅผ ์ด์šฉํ•œ M-N ๋‹จ๋ฐฉํ–ฅ ์—ฐ๊ด€ ๊ตฌํ˜„

@Entity
@Table(name = "product")
public class Product {
    @EmbeddedId
    private ProductId id;
    
    @ElementCollection
    @CollectionTable(name = "product_category",
        joinColumns = @JoinColumn(name = "product_id"))
    private Set<CategoryId> categoryIds;
    ...
}

3.6 ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋ฅผ ํŒฉํ† ๋ฆฌ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ

  • ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๊ฐ€ ๊ฐ–๊ณ  ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ด์šฉํ•ด์„œ ๋‹ค๋ฅธ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋ฅผ ์ƒ์„ฑํ•ด์•ผ ํ•œ๋‹ค๋ฉด ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•œ๋‹ค.
  • ๋ณ„๋„์˜ ๋„๋ฉ”์ธ ์„œ๋น„์Šค๋‚˜, ํŒฉํ† ๋ฆฌ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ๋‹ค.
public class Store {

    public Product createProduct (ProductId newProductId, ...์ƒ๋žต) {
        if (isBlocked()) {
            throw new StoreBlockedException();
        }
        return new Product(newProductId, getId(), ...์ƒ๋žต);
    }

}




4. ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ์™€ ๋ชจ๋ธ ๊ตฌํ˜„

์ž์„ธํ•œ ์„ค๋ช…์€ ์•„๋ž˜์˜ ๋ฌธ์„œ์™€ ์ฑ…์„ ์ฝ๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค. ๐Ÿ‘

4.1 JPA๋ฅผ ์ด์šฉํ•œ ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ ๊ตฌํ˜„

4.1.1 ๋ชจ๋“ˆ ์œ„์น˜

๋ชจ๋“ˆ์œ„์น˜

4.1.2 ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„

public interface OrderRepository {
    Order findById(OrderNo no);
    void save(Order order);
}
public class JpaOrderRepository implements OrderRepository {
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public Order findById(OrderNo id) {
        return entityManager.find(Order.class, id);
    }

    @Override
    public void save(Order order) {
        entityManager.persist(order);
    }
}

4.2 ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA๋ฅผ ์ด์šฉํ•œ ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ ๊ตฌํ˜„

@Entity
@Table(name = "purchase_order")
@Access(AccessType.FIELD)
public class Order {
    @EmbeddedId
    private OrderNo number;

    @Version
    private long version;

    @Embedded
    private Orderer orderer;

    ...์ƒ๋žต...
}
public interface OrderRepository extends Repository<Order, OrderNo> {
    Optional<Order> findById(OrderNo id);

    void save(Order order);

    default OrderNo nextOrderNo() {
        int randomNo = ThreadLocalRandom.current().nextInt(900000) + 100000;
        String number = String.format("%tY%<tm%<td%<tH-%d", new Date(), randomNo);
        return new OrderNo(number);
    }
}
@Service
public class CancelOrderService {
    private OrderRepository orderRepository;
    private CancelPolicy cancelPolicy;

    public CancelOrderService(OrderRepository orderRepository, CancelPolicy cancelPolicy) {
        this.orderRepository = orderRepository;
        this.cancelPolicy = cancelPolicy;
    }

    @Transactional
    public void cancel(OrderNo orderNo, Canceller canceller) {
        Order order = orderRepository.findById(orderNo)
                .orElseThrow(() -> new NoOrderException());
        if (!cancelPolicy.hasCancellationPermission(order, canceller)) {
            throw new NoCancellablePermission();
        }
        order.cancel();
    }
}

4.3 ๋งคํ•‘ ๊ตฌํ˜„

4.3.1 ์—”ํ‹ฐํ‹ฐ์™€ ๋ฐธ๋ฅ˜ ๊ธฐ๋ณธ ๋งคํ•‘ ๊ตฌํ˜„

์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์™€ JPA ๋งคํ•‘์„ ์œ„ํ•œ ๊ธฐ๋ณธ ๊ทœ์น™

  • ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ๋Š” ์—”ํ‹ฐํ‹ฐ์ด๋ฏ€๋กœ @Entity๋กœ ๋งคํ•‘ ์„ค์ •ํ•œ๋‹ค.

ํ•œ ํ…Œ์ด๋ธ”์— ์—”ํ‹ฐํ‹ฐ์™€ ๋ฐธ๋ฅ˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐ™์ด ์žˆ๋‹ค๋ฉด

  • ๋ฐธ๋ฅ˜๋Š” @Embeddable๋กœ ๋งคํ•‘ ์„ค์ •ํ•œ๋‹ค.
  • ๋ฐธ๋ฅ˜ ํƒ€์ž… ํ”„๋กœํผํ‹ฐ๋Š” @Embedded๋กœ ๋งคํ•‘ ์„ค์ •ํ•œ๋‹ค.
  • ๋งคํ•‘ํ•  ์ปฌ๋Ÿผ๋ช… ๋ณ€๊ฒฝ์€ @AttributeOverrides ๋ฅผ ์ด์šฉํ•œ๋‹ค
๋งคํ•‘๊ตฌํ˜„
@Embeddable
public class ShippingInfo {
    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "zipCode", column = @Column(name = "shipping_zip_code")),
            @AttributeOverride(name = "address1", column = @Column(name = "shipping_addr1")),
            @AttributeOverride(name = "address2", column = @Column(name = "shipping_addr2"))
    })
    private Address address;
    @Column(name = "shipping_message")
    private String message;
    @Embedded
    private Receiver receiver;
@Entity
@Table(name = "purchase_order")
@Access(AccessType.FIELD)
public class Order {
    ...
    @Embedded
    private Orderer orderer;

    @Embedded
    private ShippingInfo shippingInfo;
    ...

4.3.2 ๊ธฐ๋ณธ ์ƒ์„ฑ์ž

์—”ํ‹ฐํ‹ฐ๋Š” ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

protected Receiver(){}

4.3.2 ํ•„๋“œ ์ ‘๊ทผ ๋ฐฉ์‹ ์‚ฌ์šฉ

@Access๋ฅผ ์ด์šฉํ•ด์„œ ๋ช…์‹œ์ ์œผ๋กœ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด @Id๋‚˜ @EmbeddedId์˜ ์œ„์น˜์— ๋”ฐ๋ผ ์ ‘๊ทผ ๋ฐฉ์‹์„ ๊ฒฐ์ •ํ•œ๋‹ค.

@Entity
@Table(name = "purchase_order")
@Access(AccessType.FIELD)
public class Order {
    ...์ƒ๋žต...
}

4.3.4 AttributeConverter๋ฅผ ์ด์šฉํ•œ ๋ฐธ๋ฅ˜ ๋งคํ•‘ ์ฒ˜๋ฆฌ

๋‘ ๊ฐœ ์ด์ƒ์˜ ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง„ ๋ฐธ๋ฅ˜ ํƒ€์ž…์„ ํ•œ ๊ฐœ ์ปฌ๋Ÿผ์— ๋งคํ•‘ํ•˜๋ ค๋ฉด @Embeddable ์• ๋„ˆํ…Œ์ด์…˜์œผ๋กœ๋Š” ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†๋‹ค. ์ด๋Ÿด ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด AttributeConverter์ด๋‹ค.

๋‘๊ฐœํ”„๋Ÿฌํผ์ปฌ๋Ÿผ๋งคํ•‘
@Converter(autoApply = true)
public class MoneyConverter implements AttributeConverter<Money, Integer> { 
    // Money๋Š” ๋ฐธ๋ฅ˜ ํƒ€์ž…, Integer๋Š” DB ํƒ€์ž…

    // ๋ฐธ๋ฅ˜ ํƒ€์ž…์„ DB ์นผ๋Ÿผ ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜
    @Override
    public Integer convertToDatabaseColumn(Money money) {
        return money == null ? null : money.getValue();
    }

    // DB ์นผ๋Ÿผ ๊ฐ’์„ ๋ฐธ๋ฅ˜๋กœ ๋ณ€ํ™˜
    @Override
    public Money convertToEntityAttribute(Integer value) {
        return value == null ? null : new Money(value);
    }
}
// @Converter(autoApply = true) ์ž‘์„ฑํ•œ ํƒ€์ž…์— ๋Œ€ํ•ด ์ž๋™ ์ ์šฉ
public class Order {

    @Column(name = "total_amounts")
    private Money totalAmounts;
}
// @Converter(autoApply = false) ๊ธฐ๋ณธ๊ฐ’, ์ง์ ‘ ์ง€์ •ํ•ด์•ผํ•จ
public class Order {

    @Convert(converter = MoneyConverter.class)
    @Column(name = "total_amounts")
    private Money totalAmounts;
}

4.3.5 ๋ฐธ๋ฅ˜ ์ปฌ๋ ‰์…˜: ๋ณ„๋„ ํ…Œ์ด๋ธ” ๋งคํ•‘

๋ฐธ๋ฅ˜ ์ปฌ๋ ‰์…˜์„ ๋ณ„๋„ ํ…Œ์ด๋ธ”๋กœ ๋งคํ•‘ํ• ๋•Œ๋Š” @ElementCollection๊ณผ @CollectionTable์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•œ๋‹ค.

๋ฐธ๋ฅ˜์ปฌ๋ ‰์…˜ํ…Œ์ด๋ธ”๋งคํ•‘
@Entity
@Table(name = "purchase_order")
public class Order {
    @EmbeddedId
    private OrderNo number;

    @ElementCollection(fetch = FetchType.LAZY)
    @CollectionTable(name = "order_line", joinColumns = @JoinColumn(name = "order_number"))
    @OrderColumn(name = "line_idx")
    private List<OrderLine> orderLines;
    ...์ƒ๋žต...
}
@Embeddable
public class OrderLine {
    @Embedded
    private ProductId productId;

    @Column(name = "price")
    private Money price;

    @Column(name = "quantity")
    private int quantity;

    @Column(name = "amounts")
    private Money amounts;
}

4.3.6 ๋ฐธ๋ฅ˜ ์ปฌ๋ ‰์…˜: ํ•œ ๊ฐœ ์ปฌ๋Ÿผ ๋งคํ•‘

ํ•œ ๊ฐœ ์ปฌ๋Ÿผ์— ์ฝค๋งˆ๋กœ ๊ตฌ๋ถ„ํ•ด์„œ ์ €์žฅํ•  ๋•Œ ์‚ฌ์šฉ. AttributeConverter๋ฅผ ์ด์šฉํ•œ๋‹ค.

public class EmailSet {
    private Set<Email> emails = new HashSet<>();

    public EmailSet(Set<Email> emails) {
        this.emails.addAll(emails);
    }

    public Set<Email> getEmails() {
        return Collections.unmodifiableSet(emails);
    }
}
public class EmailSetConverter implements AttributeConverter<EmailSet, String> {
    @Override
    public String convertToDatabaseColumn(EmailSet attribute) {
        if (attribute == null) return null;
        return attribute.getEmails().stream()
                .map(email -> email.getAddress())
                .collect(Collectors.joining(","));
    }

    @Override
    public EmailSet convertToEntityAttribute(String dbData) {
        if (dbData == null) return null;
        String[] emails = dbData.split(",");
        Set<Email> emailSet = Arrays.stream(emails)
                .map(value -> new Email(value))
                .collect(toSet());
        return new EmailSet(emailSet);
    }
}

4.3.7 ๋ฐธ๋ฅ˜๋ฅผ ์ด์šฉํ•œ ID ๋งคํ•‘

JPA์—์„œ ์‹๋ณ„์ž ํƒ€์ž…์€ Serializable ํƒ€์ž…์ด์–ด์•ผ ํ•˜๋ฏ€๋กœ Serializable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ƒ์†๋ฐ›์•„์•ผ ํ•œ๋‹ค. ๋ฐธ๋ฅ˜ ํƒ€์ž…์œผ๋กœ ์‹๋ณ„์ž๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ ์–ป์„ ์ˆ˜ ์žˆ๋Š” ์žฅ์ ์€ ์‹๋ณ„์ž์— ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด๋‹ค.

@Entity
@Table(name = "purchase_order")
public class Order {
    @EmbeddedId
    private OrderNo number;
    ...์ƒ๋žต...
}
@Embeddable
public class OrderNo implements Serializable {
    @Column(name = "order_number")
    private String number;
    ...์ƒ๋žต...
}

4.3.8 ๋ณ„๋„ ํ…Œ์ด๋ธ”์— ์ €์žฅํ•˜๋Š” ๋ฐธ๋ฅ˜ ๋งคํ•‘

๋ฃจํŠธ ์—”ํ‹ฐํ‹ฐ ์™ธ์— ๋˜ ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์žˆ๋‹ค๋ฉด ์ง„์งœ ์—”ํ‹ฐํ‹ฐ์ธ์ง€ ์˜์‹ฌํ•ด ๋ด์•ผํ•œ๋‹ค. ๋‹จ์ง€ ๋ณ„๋„ ํ…Œ์ด๋ธ”์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅ ํ•œ๋‹ค๊ณ  ํ•ด์„œ ์—”ํ‹ฐํ‹ฐ์ธ ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ๊ฒŒ์‹œ๊ธ€ ๋ฐ์ดํ„ฐ๋ฅผ ARTICLE ํ…Œ์ด๋ธ”๊ณผ ARTICLE_CONTENT ํ…Œ์ด๋ธ”๋กœ ๋‚˜๋ˆ ์„œ ์ €์žฅํ•œ๋‹ค๊ณ  ํ•˜์ž.

์—”ํ‹ฐํ‹ฐ๋กœ ๋งคํ•‘ ์˜ˆ (์ž˜๋ชป ๋จ)

๋ฐธ๋ฅ˜๋งคํ•‘์ž˜๋ชป

ARTICLE_CONTENT์— ID๋Š” ์‹๋ณ„์ž์ด๊ธด ํ•˜์ง€๋งŒ ์ด๋Š” ARTICLE ํ…Œ์ด๋ธ”๊ณผ ์—ฐ๊ฒฐ์„ ์œ„ํ•จ์ด์ง€ ARTICLE_CONTENT๋ฅผ ์œ„ํ•œ ๋ณ„๋„ ์‹๋ณ„์ž๊ฐ€ ํ•„์š”ํ•ด์„œ๊ฐ€ ์•„๋‹ˆ๋‹ค.


๋ณ„๋„ ํ…Œ์ด๋ธ”๋กœ ๋งคํ•‘

๋ฐธ๋ฅ˜๋งคํ•‘๋ณ„๋„ํ…Œ์ด๋ธ”

ARTICLE_CONTENT์„ ๋ฐธ๋ฅ˜๋กœ ๋งŒ๋“ค๊ณ  @Embeddable๋กœ ๋งคํ•‘ํ•œ๋‹ค.

@Entity
@Table(name = "article")
@SecondaryTable(
        name = "article_content",
        pkJoinColumns = @PrimaryKeyJoinColumn(name = "id")
)
public class Article {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @AttributeOverrides({
            @AttributeOverride(
                    name = "content",
                    column = @Column(table = "article_content", name = "content")),
            @AttributeOverride(
                    name = "contentType",
                    column = @Column(table = "article_content", name = "content_type"))
    })
    @Embedded
    private ArticleContent content;
}

๋ฐธ๋ฅ˜ ๋งคํ•‘์„ ๋ณ„๋„ ํ…Œ์ด๋ธ”๋กœ ์ €์žฅ ํ•˜๋ ค๋ฉด @SecondaryTable๊ณผ @AttributeOverride์„ ์‚ฌ์šฉํ•œ๋‹ค. ํ•˜์ง€๋งŒ @SecondaryTable์€ jpa.find("id");(์กฐํšŒ)๋ฅผ ์‹คํ–‰ํ•  ๋•Œ ๋‘ ํ…Œ์ด๋ธ”์„ ์กฐ์ธํ•ด์„œ ๊ฐ€์ ธ์˜จ๋‹ค. ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก ๊ฐ™์€ ํ™”๋ฉด์€ ARTICLE์˜ ๋ฐ์ดํ„ฐ๋งŒ ํ•„์š”ํ•˜์ง€ ARTICLE_CONTENT๋Š” ํ•„์š”์—†๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ARTICLE_CONTENT์„ ์—”ํ‹ฐํ‹ฐ๋กœ ๋งคํ•‘ํ•˜๊ณ  ARTICLE์—์„œ ARTICLE_CONTENT์„ ์ง€์—ฐ๋กœ๋”ฉ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Š” ๋ฐธ๋ฅ˜ ๋ชจ๋ธ์„ ์—”ํ‹ฐํ‹ฐ๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๋ฏ€๋กœ ์ข‹์€ ๋ฐฉ๋ฒ•์€ ์•„๋‹ˆ๋‹ค. ๋Œ€์‹  ์กฐํšŒ ์ „์šฉ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

4.3.9 ๋ฐธ๋ฅ˜ ์ปฌ๋ ‰์…˜์„ @Entity๋กœ ๋งคํ•‘ํ•˜๊ธฐ

@OneToMany ์‚ฌ์šฉํ•˜์—ฌ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋งบ๋Š”๋‹ค.

@Entity
@Table(name = "product")
public class Product {
    ...์ƒ๋žต...
    @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE},
            orphanRemoval = true, fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id")
    @OrderColumn(name = "list_idx")
    private List<Image> images = new ArrayList<>();
    ...์ƒ๋žต...


@Inheritance๋ฅผ ์ด์šฉํ•˜์—ฌ ์—”ํ‹ฐํ‹ฐ ์ƒ์†๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "image_type")
@Table(name = "image")
public abstract class Image {
    ...์ƒ๋žต...
}
@Entity
@DiscriminatorValue("II")
public class InternalImage extends Image {
    ...์ƒ๋žต...
}
@Entity
@DiscriminatorValue("EI")
public class ExternalImage extends Image {
    ...์ƒ๋žต...

4.3.10 ID ์ฐธ์กฐ์™€ ์กฐ์ธ ํ…Œ์ด๋ธ”์„ ์ด์šฉํ•œ ๋‹จ๋ฐฉํ–ฅ M-N ๋งคํ•‘

@Entity
@Table(name = "product")
public class Product {
    ...์ƒ๋žต...
    @ElementCollection(fetch = FetchType.LAZY)
    @CollectionTable(name = "product_category",
            joinColumns = @JoinColumn(name = "product_id"))
    private Set<CategoryId> categoryIds;
    ...์ƒ๋žต...
}

4.4 ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋กœ๋”ฉ ์ „๋žต

// @Embeddable ์ปฌ๋ ‰์…˜์— ๋Œ€ํ•œ ์ฆ‰์‹œ ๋กœ๋”ฉ ์„ค์ •
@ElementCollection(fetch = FetchType.EAGER)

// @Entity ์ปฌ๋ ‰์…˜์— ๋Œ€ํ•œ ์ง€์—ฐ ๋กœ๋”ฉ ์„ค์ •
@OneToMany(fetch = FetchType.LAZY)
  • ์ฆ‰์‹œ ๋กœ๋”ฉ: ์—๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ๋ฅผ ๊ตฌํ•  ๋•Œ ์—ฐ๊ด€๋œ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ DB์—์„œ ํ•จ๊ป˜ ์ฝ์–ด์˜จ๋‹ค.
  • ์ง€์—ฐ ๋กœ๋”ฉ: ์‹ค์ œ ์ปฌ๋ ‰์…˜์— ์ ‘๊ทผํ•  ๋•Œ DB์—์„œ ์กฐํšŒํ•œ๋‹ค.

4.5 ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ์˜์†์„ฑ ์ „ํŒŒ

์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๊ฐ€ ์™„์ „ํ•œ ์ƒํƒœ์—ฌ์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์€ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ๋ฅผ ์กฐํšŒํ•  ๋•Œ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ €์žฅํ•˜๊ณ  ์‚ญ์ œํ•  ๋•Œ๋„ ํ•˜๋‚˜๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•จ์„ ์˜๋ฏธํ•œ๋‹ค.

  • ์ €์žฅ ๋ฉ”์„œ๋“œ๋Š” ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ๋งŒ ์ €์žฅํ•˜๋ฉด ์•ˆ๋˜๊ณ  ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ์†ํ•œ ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•ด์•ผ ํ•œ๋‹ค.
  • ์‚ญ์ œ ๋ฉ”์„œ๋“œ๋Š” ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ๋ฟŒ๋งŒ ์•„๋‹ˆ๋ผ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ์†ํ•œ ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ ์‚ญ์ œํ•ด์•ผ ํ•œ๋‹ค.
  • @Embeddable ๋งคํ•‘ ํƒ€์ž…์€ ํ•จ๊ป˜ ์ €์žฅ๋˜๊ณ  ์‚ญ์ œ ๋˜๋ฏ€๋กœ ์ถ”๊ฐ€ ์„ค์ •์ด ํ•„์š”์—†๋‹ค.
  • @Entity ํƒ€์ž…์— ๋Œ€ํ•œ ๋งคํ•‘์€ cascade ์†์„ฑ์„ ์‚ฌ์šฉํ•œ๋‹ค.
@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})

4.6 ์‹๋ณ„์ž ์ƒ์„ฑ ๊ธฐ๋Šฅ

์‹๋ณ„์ž๋Š” ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€ ๋ฐฉ์‹ ์ค‘ ํ•˜๋‚˜๋กœ ์ƒ์„ฑํ•œ๋‹ค.

  • ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ƒ์„ฑ
  • ๋„๋ฉ”์ธ ๋กœ์ง์œผ๋กœ ์ƒ์„ฑ
  • DB๋ฅผ ์ด์šฉํ•œ ์ผ๋ จ๋ฒˆํ˜ธ ์‚ฌ์šฉ

4.7 ๋„๋ฉ”์ธ ๊ตฌํ˜„๊ณผ DIP

JPA์˜ @Entity๋‚˜ @Table, extends Repository ์ธํ„ฐํŽ˜์ด์Šค๋Š” DIP ์›์น™์„ ์–ด๊ธฐ๊ณ  ์žˆ์ง€๋งŒ ๊ฐœ๋ฐœ ํŽธ์˜์„ฑ๊ณผ ์‹ค์šฉ์„ฑ์„ ๊ฐ€์ง€๊ณ , ๋ณต์žก๋„๋ฅผ ๋†’์ด์ง€ ์•Š์œผ๋ฉด์„œ ๊ธฐ์ˆ ์— ๋”ฐ๋ฅธ ๊ตฌํ˜„ ์ œ์•ฝ์ด ๋‚ฎ๋‹ค๋ฉด ํ•ฉ๋ฆฌ์ ์œผ๋กœ ์„ ํƒํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.




5. ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA๋ฅผ ์ด์šฉํ•œ ์กฐํšŒ ๊ธฐ๋Šฅ

5.1 ์‹œ์ž‘์— ์•ž์„œ

5.2 ๊ฒ€์ƒ‰์„ ์œ„ํ•œ ์ŠคํŽ™

์กฐํšŒ๋ฅผ ์œ„ํ•ด ๋‹ค์–‘ํ•œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด์„ ์กฐํ•ฉํ•ด์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ๋‹ค. ์ด ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด ์ŠคํŽ™(Specification)์ด๋‹ค.

5.3 ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA๋ฅผ ์ด์šฉํ•œ ์ŠคํŽ™ ๊ตฌํ˜„

์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA๊ฐ€ ์ œ๊ณตํ•˜๋Š” Specification ์ธํ„ฐํŽ˜์ด์Šค

public interface Specification<T> {
  Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder);
}


๋ ˆํฌ์ง€ํ† ๋ฆฌ ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์†

public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
    List<Customer> findAll(Specification<Customer> spec);
    List<Customer> findAll(Specification<Customer> spec, Sort sort);
    List<Customer> findAll(Specification<Customer> spec, Pageable pageable);
}


์ŠคํŽ™ ์ƒ์„ฑ ๊ธฐ๋Šฅ์„ ๋ณ„๋„ ํด๋ž˜์Šค๋กœ ๊ตฌํ˜„

public class CustomerSpecs {

  public static Specification<Customer> isLongTermCustomer() {
    return (root, query, builder) -> {
      LocalDate date = LocalDate.now().minusYears(2);
      return builder.lessThan(root.get(Customer_.createdAt), date);
    };
  }

  public static Specification<Customer> hasSalesOfMoreThan(MonetaryAmount value) {
    return (root, query, builder) -> {
      // build query here
    };
  }
}


5.4 ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ/DAO์—์„œ ์ŠคํŽ™ ์‚ฌ์šฉํ•˜๊ธฐ

List<Customer> customers = customerRepository.findAll(isLongTermCustomer());

5.5 ์ŠคํŽ™ ์กฐํ•ฉ

์ŠคํŽ™ ์ธํ„ฐํŽ˜์ด์Šค๋Š” and์™€ or ๋“ฑ ์กฐํ•ฉ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
  isLongTermCustomer().or(hasSalesOfMoreThan(amount)));


null ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” ์ŠคํŽ™ ๊ฐ์ฒด์˜ ์กฐํ•ฉ์€ where๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

Specification<Customer> spec = Specification.where(createNullableSpec()).and(createOtherSpec());

5.6 ์ •๋ ฌ ์ง€์ •ํ•˜๊ธฐ

์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด์„œ ์ •๋ ฌ์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ๋ฉ”์„œ๋“œ ์ด๋ฆ„์— OrderBy๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ •๋ ฌ ๊ธฐ์ค€ ์ง€์ •
  • Sort๋ฅผ ์ธ์ž๋กœ ์ „๋‹ฌ
public interface OrderSummaryDao extends Repository<OrderSummary, String> {
    // OrderBy
    List<OrderSummary> findByOrdererIdOrderByNumberDesc(String ordererId);

    // Sort
    List<OrderSummary> findByOrdererId(String ordererId, Sort sort);
}
Sort sort = Sort.by(Sort.Direction.DESC, "number");
List<OrderSummary> results = orderSummaryDao.findByOrdererId("id", sort);

5.7 ํŽ˜์ด์ง• ์ฒ˜๋ฆฌํ•˜๊ธฐ

์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA๋Š” ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด Pageable ํƒ€์ž…์„ ์ด์šฉํ•œ๋‹ค.

public interface MemberDataDao extends Repository<MemberData, String> {
    List<MemberData> findByNameLike(String name, Pageable pageable);
    Page<MemberData> findByBlocked(boolean blocked, Pageable pageable);
}
// 1์€ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ, 0๋ถ€ํ„ฐ ์‹œ์ž‘ํ•œ๋‹ค.  10์€ ๊ฐœ์ˆ˜
// Sort ์‚ฌ์šฉ ๊ฐ€๋Šฅ
Sort sort = Sort.by(Sort.Direction.DESC, "name");
PageRequest pageReq = PageRequest.of(1, 10, sort);
List<MemberData> user = memberDataDao.findByNameLike("์ด๋ฆ„%", pageReq);


Page ๋ฆฌํ„ด ํƒ€์ž…

PageRequest pageReq = PageRequest.of(1, 5);
Page<MemberData> page = memberDataDao.findByBlocked(false, pageReq);
List<MemberData> content = page.getContent(); // ์กฐํšŒ ๊ฒฐ๊ณผ ๋ชฉ๋ก
long totalElements = page.getTotalElements(); // ์กฐ๊ฑด์— ํ•ด๋‹นํ•˜๋Š” ์ „์ฒด ๊ฐœ์ˆ˜
int totalPages = page.getTotalPages(); // ์ „์ฒด ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ
int number = page.getNumber(); // ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ
int numberOfElements = page.getNumberOfElements() // ์กฐํšŒ ๊ฒฐ๊ณผ ๊ฐœ์ˆ˜
int size = page.getSize(); // ํŽ˜์ด์ง€ ํฌ๊ธฐ

Page ๋ฆฌํ„ด ํƒ€์ž…์€ COUNT ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๋ฏ€๋กœ List๋งŒ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋ฆฌํ„ด ํƒ€์ž…์„ List๋กœ ํ•œ๋‹ค.

5.8 ์ŠคํŽ™ ์กฐํ•ฉ์„ ์œ„ํ•œ ์ŠคํŽ™ ๋นŒ๋” ํด๋ž˜์Šค

์ŠคํŽ™์„ ์กฐํ•ฉํ•  ๋•Œ ์ŠคํŽ™ ๋นŒ๋”๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Specification<MemberData> spec = SpecBuilder.builder(MemberData.class)
                .ifTrue(searchRequest.isOnlyNotBlocked(), () -> MemberDataSpecs.nonBlocked())
                .ifHasText(searchRequest.getName(), name -> MemberDataSpecs.nameLike(searchRequest.getName()))
                .toSpec();
List<MemberData> result = memberDataDao.findAll(spec, PageRequest.of(0, 5));
public class SpecBuilder {
    public static <T> Builder<T> builder(Class<T> type) {
        return new Builder<T>();
    }

    public static class Builder<T> {
        private List<Specification<T>> specs = new ArrayList<>();

        public Builder<T> and(Specification<T> spec) {
            specs.add(spec);
            return this;
        }

        public Builder<T> ifHasText(String str,
                                    Function<String, Specification<T>> specSupplier) {
            if (StringUtils.hasText(str)) {
                specs.add(specSupplier.apply(str));
            }
            return this;
        }

        public Builder<T> ifTrue(Boolean cond,
                                 Supplier<Specification<T>> specSupplier) {
            if (cond != null && cond.booleanValue()) {
                specs.add(specSupplier.get());
            }
            return this;
        }

        public Specification<T> toSpec() {
            Specification<T> spec = Specification.where(null);
            for (Specification<T> s : specs) {
                spec = spec.and(s);
            }
            return spec;
        }
    }
}

5.9 ๋™์  ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ

JPQL์˜ new ํ‚ค์›Œ๋“œ๋ฅผ ํ†ตํ•ด ๊ฐ์ฒด๋ฅผ ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

public interface OrderSummaryDao extends Repository<OrderSummary, String> {
    @Query("""    
            select new com.myshop.order.query.dto.OrderView(
                o.number, o.state, m.name, m.id, p.name
            )
            from Order o join o.orderLines ol, Member m, Product p
            where o.orderer.memberId.id = :ordererId
            and o.orderer.memberId.id = m.id
            and index(ol) = 0
            and ol.productId.id = p.id
            order by o.number.number desc
            """)
    List<OrderView> findOrderView(String ordererId);
}

5.10 ํ•˜์ด๋ฒ„๋„ค์ดํŠธ @Subselect ์‚ฌ์šฉ

  • @Subselect๋Š” ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ @Entity๋กœ ๋งคํ•‘ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • @Immutable, @Subselect, @Synchronize ์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•œ๋‹ค.
  • ๋ทฐ๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋“ฏ @Subselect๋กœ ์กฐํšŒํ•œ @Entity ์—ญ์‹œ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋‹ค.
@Entity
@Immutable
@Subselect(
        """
        select o.order_number as number,
        o.version,
        o.orderer_id,
        o.orderer_name,
        o.total_amounts,
        o.receiver_name,
        o.state,
        o.order_date,
        p.product_id,
        p.name as product_name
        from purchase_order o inner join order_line ol
            on o.order_number = ol.order_number
            cross join product p
        where
        ol.line_idx = 0
        and ol.product_id = p.product_id"""
)
@Synchronize({"purchase_order", "order_line", "product"})
public class OrderSummary {
    @Id
    private String number;
    private long version;
    @Column(name = "orderer_id")
    private String ordererId;
    ...์ƒ๋žต...
    protected OrderSummary() {
    }
}




6. ์‘์šฉ ์„œ๋น„์Šค์™€ ํ‘œํ˜„ ์˜์—ญ

6.1 ํ‘œํ˜„ ์˜์—ญ๊ณผ ์‘์šฉ ์˜์—ญ

ํ‘œํ˜„์˜์—ญ๊ณผ์‘์šฉ์˜์—ญ.png

ํ‘œํ˜„ ์˜์—ญ

  • ํ‘œํ˜„ ์˜์—ญ์€ ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ํ•ด์„ํ•œ๋‹ค.
  • ์‚ฌ์šฉ์ž๊ฐ€ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ์€ ๊ธฐ๋Šฅ์„ ํŒ๋ณ„ํ•˜๊ณ  ๊ทธ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ์‘์šฉ ์„œ๋น„์Šค๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
  • ์‘์šฉ ์„œ๋น„์Šค๊ฐ€ ์š”๊ตฌํ•˜๋Š” ํ˜•์‹์œผ๋กœ ์‚ฌ์šฉ์ž ์š”์ฒญ์„ ๋ณ€ํ™˜ํ•œ๋‹ค.
  • ์‘์šฉ ์„œ๋น„์Šค์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋งž์€ ํ˜•์‹์œผ๋กœ ์‘๋‹ตํ•œ๋‹ค.

์‘์šฉ ์˜์—ญ

  • ์‹ค์ œ ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.
  • ์‘์šฉ ์„œ๋น„์Šค๋Š” ํ‘œํ˜„ ์˜์—ญ์— ์˜์กดํ•˜์ง€ ์•Š๋Š”๋‹ค.

6.2 ์‘์šฉ ์„œ๋น„์Šค์˜ ์—ญํ• 

  • ์‚ฌ์šฉ์ž(ํ‘œํ˜„ ์˜์—ญ)๊ฐ€ ์š”์ฒญํ•œ ๊ธฐ๋Šฅ์„ ์‹คํ–‰ํ•œ๋‹ค.
  • ๋„๋ฉ”์ธ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค.
  • ๋„๋ฉ”์ธ ๊ฐ์ฒด ๊ฐ„์˜ ํ๋ฆ„์„ ์ œ์–ดํ•œ๋‹ค.
  • ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•œ๋‹ค.
  • ์ ‘๊ทผ ์ œ์–ด์™€ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ํ•œ๋‹ค.

6.2.1 ๋„๋ฉ”์ธ ๋กœ์ง ๋„ฃ์ง€ ์•Š๊ธฐ

๋„๋ฉ”์ธ ๋กœ์ง์€ ๋„๋ฉ”์ธ ์˜์—ญ์— ์œ„์น˜ํ•˜๊ณ , ์‘์šฉ ์„œ๋น„์Šค๋Š” ๋„๋ฉ”์ธ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์ง€ ์•Š๋Š”๋‹ค.

์ด์œ 

  • ์ฝ”๋“œ์˜ ์‘์ง‘์„ฑ์ด ๋–จ์–ด์ง„๋‹ค.
  • ์—ฌ๋Ÿฌ ์‘์šฉ ์„œ๋น„์Šค์—์„œ ๋™์ผํ•œ ๋„๋ฉ”์ธ ๋กœ์ง์„ ๊ตฌํ˜„ํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์•„์ง„๋‹ค.
  • ๊ฒฐ๊ณผ์ ์œผ๋กœ ์ฝ”๋“œ ๋ณ€๊ฒฝ์„ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ ๋‹ค.

6.3 ์‘์šฉ ์„œ๋น„์Šค์˜ ๊ตฌํ˜„

์‘์šฉ ์„œ๋น„์Šค๋Š” ํ‘œํ˜„ ์˜์—ญ๊ณผ ๋„๋ฉ”์ธ ์˜์—ญ์„ ์—ฐ๊ฒฐํ•˜๋Š” ๋งค๊ฐœ์ฒด ์—ญํ• ์„ ํ•œ๋‹ค.(ํŒŒ์‚ฌ๋“œ facade)

6.3.1 ์‘์šฉ ์„œ๋น„์Šค์˜ ํฌ๊ธฐ

์‘์šฉ ์„œ๋น„์Šค์˜ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

  • ํ•œ ์‘์šฉ ์„œ๋น„์Šค ํด๋ž˜์Šค์— ํšŒ์› ๋„๋ฉ”์ธ ๋ชจ๋“  ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ
  • ๊ตฌ๋ถ„๋˜๋Š” ๊ธฐ๋Šฅ๋ณ„๋กœ ์‘์šฉ ์„œ๋น„์Šค ํด๋ž˜์Šค๋ฅผ ๋”ฐ๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ(์ถ”์ฒœ) ๐Ÿ‘
// ๊ฐ ์‘์šฉ ์„œ๋น„์Šค์—์„œ ๊ณตํ†ต๋˜๋Š” ๋กœ์ง์„ ๋ณ„๋„ ํด๋ž˜์Šค๋กœ ๊ตฌํ˜„
public final class MemberServiceHelper {
    public static Member findExistingMember(MemberRepository repo, String memberId) {
        Member member = memberRepository.findById(memberId);
        if (member == null) {
            throw new NoMemberException(memberId);
        }
        return member;
    }
}
// ๊ณตํ†ต ๋กœ์ง์„ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์‘์šฉ ์„œ๋น„์Šค์—์„œ ์‚ฌ์šฉ
import static com.myshop.member.application.MemberServiceHelper.*;

public class ChangePasswordService {
    private MemberRepository memberRepository;

    public void changePassword(String memberId, String curPw, String newPw) {
        Member member = findExistingMember(memberRepository, memberId);
        member.changePassword(curPw, newPw);
    }
}

6.3.2 ์‘์šฉ ์„œ๋น„์Šค์˜ ์ธํ„ฐํŽ˜์ด์Šค์™€ ํด๋ž˜์Šค

์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ๋ช…ํ™•ํ•˜๊ฒŒ ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๋ฉด ์‘์šฉ ์„œ๋น„์Šค์˜ ์ธํ„ฐํŽ˜์ด์Šค ์ž‘์„ฑ์€ ์ข‹์€ ์„ ํƒ์ด ์•„๋‹ˆ๋‹ค. ์†Œ์Šค ํŒŒ์ผ๋งŒ ๋งŽ์•„์ง€๊ณ  ๊ฐ„์ ‘ ์ฐธ์กฐ๊ฐ€ ์ฆ๊ฐ€ํ•ด์„œ ์ „์ฒด ๊ตฌ์กฐ๊ฐ€ ๋ณต์žกํ•ด์ง„๋‹ค.

์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ํ•„์š”ํ•œ ์ƒํ™ฉ

  • ๊ตฌํ˜„ ํด๋ž˜์Šค๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ์ธ ๊ฒฝ์šฐ

6.3.3 ๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ๊ฐ’ ๋ฆฌํ„ด

  • ๋„๋ฉ”์ธ์„ ์ด์šฉํ•ด ๊ธฐ๋Šฅ์„ ์‹คํ–‰ํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๊ฐ’์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ๋ฐ›์•„์•ผ ํ•œ๋‹ค.
  • ๊ฐ ๊ฐ’์„ ๊ฐœ๋ณ„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ๋ฐ›์„ ์ˆ˜๋„ ์žˆ๊ณ  DTO๋ฅผ ๋งŒ๋“ค์–ด ์ „๋‹ฌ๋ฐ›์„ ์ˆ˜๋„ ์žˆ๋‹ค.
  • ์‘์šฉ ์„œ๋น„์Šค์˜ ๊ฒฐ๊ณผ๋ฅผ ํ‘œํ˜„ ์˜์—ญ์—์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ์‘์šฉ ์„œ๋น„์Šค์˜ ๊ฒฐ๊ณผ๋กœ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.
  • ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ์ž์ฒด๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” ๊ฒƒ์€ ์‘์ง‘๋„๋ฅผ ๋‚ฎ์ถ”์–ด ๋น„์ถ”์ฒœํ•œ๋‹ค.

6.3.4 ํ‘œํ˜„ ์˜์—ญ์— ์˜์กดํ•˜์ง€ ์•Š๊ธฐ

ํ‘œํ˜„ ์˜์—ญ์— ํ•ด๋‹นํ•˜๋Š” HttpServletRequest, HttpSession ๋“ฑ์„ ์‘์šฉ ์„œ๋น„์Šค์—์— ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•˜๋ฉด ์•ˆ ๋œ๋‹ค.

private ChangePasswordService changePasswordService;

@PostMapping
public String sumbit(HttpServletRequest request) {
    // ์‘์šฉ ์„œ๋น„์Šค๊ฐ€ ํ‘œํ˜„ ์˜์—ญ์„ ์˜์กดํ•˜๋ฉด ์•ˆ๋œ๋‹ค.
    changePasswordService.changePassword(request);
    ...์ƒ๋žต...
}

6.3.5 ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ

์Šคํ”„๋ง๊ณผ ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ์ด์šฉํ•œ๋‹ค.

@Transactional

6.4 ํ‘œํ˜„ ์˜์—ญ

  • ์‚ฌ์šฉ์ž๊ฐ€ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ๋ฆ„(ํ™”๋ฉด)์„ ์ œ๊ณตํ•˜๊ณ  ์ œ์–ดํ•œ๋‹ค.
  • ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ์•Œ๋งž์€ ์‘์šฉ ์„œ๋น„์Šค์— ์ „๋‹ฌํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ œ๊ณตํ•œ๋‹ค.
  • ์‚ฌ์šฉ์ž์˜ ์„ธ์…˜์„ ๊ด€๋ฆฌํ•œ๋‹ค.
ํ‘œํ˜„์˜์—ญ์‚ฌ์šฉ์žํ๋ฆ„

6.5 ๊ฐ’ ๊ฒ€์ฆ

๊ฐ’ ๊ฒ€์ฆ์€ ํ‘œํ˜„ ์˜์—ญ๊ณผ ์‘์šฉ ์„œ๋น„์Šค ๋‘ ๊ณณ์—์„œ ๋ชจ๋‘ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. ์›์น™์ ์œผ๋กœ๋Š” ์‘์šฉ ์„œ๋น„์Šค์—์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.

์‘์šฉ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ‘œํ˜„ ์˜์—ญ์˜ ์ฝ”๋“œ๊ฐ€ ํ•œ ๊ณณ์ด๋ฉด ๊ตฌํ˜„์˜ ํŽธ๋ฆฌํ•จ์„ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์—ญํ• ์„ ๋‚˜๋ˆ„์–ด ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

  • ํ‘œํ˜„ ์˜์—ญ: ํ•„์ˆ˜ ๊ฐ’, ๊ฐ’์˜ ํ˜•์‹, ๋ฒ”์œ„ ๋“ฑ์„ ๊ฒ€์ฆํ•œ๋‹ค.
  • ์‘์šฉ ์„œ๋น„์Šค: ๋ฐ์ดํ„ฐ์˜ ์กด์žฌ ์œ ๋ฌด์™€ ๊ฐ™์€ ๋…ผ๋ฆฌ์  ์˜ค๋ฅ˜๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.

6.6 ๊ถŒํ•œ ๊ฒ€์‚ฌ

๋‹ค์Œ ์„ธ ๊ณณ์—์„œ ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ํ‘œํ˜„ ์˜์—ญ
    • ์„œ๋ธ”๋ฆฟ ํ•„ํ„ฐ
  • ์‘์šฉ ์„œ๋น„์Šค
    • AOP
  • ๋„๋ฉ”์ธ
    • ์ง์ ‘ ๋กœ์ง ๊ตฌํ˜„
// ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ํ™œ์šฉ
@PreAuthorize("hasRole('ADMIN')")

6.7 ์กฐํšŒ ์ „์šฉ ๊ธฐ๋Šฅ๊ณผ ์‘์šฉ ์„œ๋น„์Šค

์‘์šฉ ์„œ๋น„์Šค๊ฐ€ ์‚ฌ์šฉ์ž ์š”์ฒญ ๊ธฐ๋Šฅ์„ ์‹คํ–‰ํ•˜๋Š” ๋ฐ ๋ณ„๋‹ค๋ฅธ ๊ธฐ์—ฌ๋ฅผ ํ•˜์ง€ ๋ชปํ•œ๋‹ค๋ฉด ๊ตณ์ด ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

์‘์šฉ์„œ๋น„์Šค์ƒ๋žต




7. ๋„๋ฉ”์ธ ์„œ๋น„์Šค

7.1 ์—ฌ๋Ÿฌ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๊ฐ€ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ

ํ•œ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ๋„ฃ๊ธฐ ์• ๋งคํ•œ, ์—ฌ๋Ÿฌ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๊ฐ€ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์ด๋ผ๋ฉด ๋ณ„๋„ ๋„๋ฉ”์ธ ์„œ๋น„์Šค๋กœ ๊ตฌํ˜„ํ•œ๋‹ค.

7.2 ๋„๋ฉ”์ธ ์„œ๋น„์Šค

๋„๋ฉ”์ธ ์„œ๋น„์Šค๋Š” ๋„๋ฉ”์ธ ์˜์—ญ์— ์œ„์น˜ํ•œ ๋„๋ฉ”์ธ ๋กœ์ง์„ ํ‘œํ˜„ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. ์ฃผ๋กœ ๋‹ค์Œ ์ƒํ™ฉ์—์„œ ๋„๋ฉ”์ธ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

  • ๊ณ„์‚ฐ ๋กœ์ง: ์—ฌ๋Ÿฌ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๊ฐ€ ํ•„์š”ํ•œ ๊ณ„์‚ฐ๋กœ์ง์ด๋‚˜, ํ•œ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ๋„ฃ๊ธฐ์—๋Š” ๋‹ค์†Œ ๋ณต์žกํ•œ ๊ณ„์‚ฐ ๋กœ์ง
  • ์™ธ๋ถ€ ์‹œ์Šคํ…œ ์—ฐ๋™์ด ํ•„์š”ํ•œ ๋„๋ฉ”์ธ ๋กœ์ง: ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ํƒ€ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๋„๋ฉ”์ธ ๋กœ์ง

7.2.1 ๊ณ„์‚ฐ ๋กœ์ง๊ณผ ๋„๋ฉ”์ธ ์„œ๋น„์Šค

๋„๋ฉ”์ธ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฃผ์ฒด

  • ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ
  • ์‘์šฉ ์„œ๋น„์Šค
// ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๊ฐ€ ์‚ฌ์šฉ
public class OrderService {
    // ๋„๋ฉ”์ธ ์„œ๋น„์Šค
    private DiscountCalculationService discountCalculationService;

    private Order createOrder(OrderNo orderNo, OrderRequest orderReq) {
        Member member = findMember(orderReq.getOrdererId());
        Order order = new Order(orderNo, ...์ƒ๋žต);
        // ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๊ฐ์ฒด์— ๋„๋ฉ”์ธ ์„œ๋น„์Šค ์ „๋‹ฌ
        order.calculateAmounts(this.discountCalculationService, member.getGrade());
    }
}
// ์‘์šฉ ์„œ๋น„์Šค๊ฐ€ ์‚ฌ์šฉ
public class TransferService { // ๋„๋ฉ”์ธ ์„œ๋น„์Šค

    // ๋„๋ฉ”์ธ ์„œ๋น„์Šค์˜ ๊ธฐ๋Šฅ์„ ์‹คํ–‰ํ•  ๋•Œ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋ฅผ ์ „๋‹ฌ
    public void transfer(Account fromAcc, Account toAcc, Money amounts) {
        fromAcc.withdraw(amounts);
        toAcc.credit(amounts);
    }
    ...
}


ํŠน์ • ๊ธฐ๋Šฅ์ด ์‘์šฉ ์„œ๋น„์Šค์ธ์ง€ ๋„๋ฉ”์ธ ์„œ๋น„์Šค์ธ์ง€ ํ™•์ธํ•˜๋Š” ๋ฐฉ๋ฒ•

์•„๋ž˜์˜ ํ–‰์œ„๊ฐ€ ์ผ์–ด๋‚˜๋ฉด ๋„๋ฉ”์ธ ์„œ๋น„์Šค

  • ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ
  • ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์˜ ์ƒํƒœ ๊ฐ’์„ ๊ณ„์‚ฐ

7.2.2 ์™ธ๋ถ€ ์‹œ์Šคํ…œ ์—ฐ๋™๊ณผ ๋„๋ฉ”์ธ ์„œ๋น„์Šค

์‹œ์Šคํ…œ ๊ฐ„ ์—ฐ๋™์€ HTTP API ํ˜ธ์ถœ๋กœ ์ด๋ฃจ์–ด์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ, ๋„๋ฉ”์ธ ์ž…์žฅ์—์„œ๋Š” ๋„๋ฉ”์ธ ๋กœ์ง์œผ๋กœ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ๋„๋ฉ”์ธ ๊ด€์ ์—์„œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

// ๋„๋ฉ”์ธ ์„œ๋น„์Šค
public interface SurveyPermissionChecker {
    boolean hasUserCreationPermission(String userId);
}
//์‘์šฉ ์„œ๋น„์Šค๋Š” ๋„๋ฉ”์ธ ์„œ๋น„์Šค๋ฅผ ์ด์šฉ
public class CreateSurveyService {
    private SurveyPermissionChecker surveyPermissionChecker;

    public Long createSurvey(CreateSurveyRequest req) {
        validate(req);

        // ๋„๋ฉ”์ธ ์„œ๋น„์Šค๋ฅผ ์ด์šฉํ•ด์„œ ์™ธ๋ถ€ ์‹œ์Šคํ…œ ์—ฐ๋™์„ ํ‘œํ˜„
        if (surveyPermissionChecker.hasUserCreationPermission(req.getRequestorId())) {
            throw new NoPermissionException();
        }
        ...์ƒ๋žต...
    }
}

SurveyPermissionChecker ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค๋Š” ์ธํ”„๋ผ์ŠคํŠธ๋Ÿญ์ฒ˜ ์˜์—ญ์— ์œ„์น˜ํ•œ๋‹ค.

7.2.3 ๋„๋ฉ”์ธ ์„œ๋น„์Šค์˜ ํŒจํ‚ค์ง€ ์œ„์น˜

๋„๋ฉ”์ธ์„œ๋น„์Šค์œ„์น˜


๋„๋ฉ”์ธ ์„œ๋น„์Šค์˜ ๊ฐœ์ˆ˜๊ฐ€ ๋งŽ๊ฑฐ๋‚˜ ๋ช…์‹œ์ ์œผ๋กœ ๊ตฌ๋ถ„ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ํ•˜์œ„ ํŒจํ‚ค์ง€๋กœ ๊ตฌ๋ถ„ํ•œ๋‹ค.

  • domain
    • domain.model
    • domain.service
    • domain.repository

7.2.4 ๋„๋ฉ”์ธ ์„œ๋น„์Šค์˜ ์ธํ„ฐํŽ˜์ด์Šค์™€ ํด๋ž˜์Šค

๋„๋ฉ”์ธ ์„œ๋น„์Šค์˜ ๋กœ์ง์ด ๊ณ ์ •๋˜์–ด ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋„๋ฉ”์ธ ์„œ๋น„์Šค ์ž์ฒด๋ฅผ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๊ตฌํ˜„ํ•˜๊ณ  ์ด๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค๋ฅผ ๋‘˜ ์ˆ˜๋„ ์žˆ๋‹ค.

๋„๋ฉ”์ธ์„œ๋น„์Šค์ธํ„ฐํŽ˜์ด์Šค




8. ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ

8.1 ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์™€ ํŠธ๋žœ์žญ์…˜

ํŠธ๋žœ์žญ์…˜

ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ ๋ฐฉ์‹

  • ์„ ์  ์ž ๊ธˆ (Pessimistic Lock, ๋น„๊ด€์  ์ž ๊ธˆ)
  • ๋น„์„ ์  ์ž ๊ธˆ (Optimistic Lock, ๋‚™๊ด€์  ์ž ๊ธˆ)

8.2 ์„ ์  ์ž ๊ธˆ

์„ ์ ์ž ๊ธˆ
// JPA
Order order = entityManager.find(Order.class, orderNo, LockModeType.PESSIMISTIC_WRITE);
// ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA
public interface MemberREpository extends Repository<Member, MemberId> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    Optional<Member> findById(MemberId memberId);
}

8.2.1 ์„ ์  ์ž ๊ธˆ๊ณผ ๊ต์ฐฉ ์ƒํƒœ

๋‹ค์Œ ์ƒํ™ฉ์€ ๊ต์ฐฉ ์ƒํƒœ์— ๋น ์ง„๋‹ค.

  1. ์Šค๋ ˆ๋“œ 1: ๐Ÿณ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ๋Œ€ํ•œ ์„ ์  ์ž ๊ธˆ ๊ตฌํ•จ
  2. ์Šค๋ ˆ๋“œ 2: ๐Ÿฆ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ๋Œ€ํ•œ ์„ ์  ์ž ๊ธˆ ๊ตฌํ•จ
  3. ์Šค๋ ˆ๋“œ 1: ๐Ÿฆ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ๋Œ€ํ•œ ์„ ์  ์ž ๊ธˆ ์‹œ๋„
  4. ์Šค๋ ˆ๋“œ 2: ๐Ÿณ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ๋Œ€ํ•œ ์„ ์  ์ž ๊ธˆ ์‹œ๋„


ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•
ํžŒํŠธ๋ฅผ ์ œ๊ณตํ•ด ์ตœ๋Œ€ ๋Œ€๊ธฐ์‹œ๊ฐ„์„ ์ง€์ •ํ•œ๋‹ค.

// JPA
Map<String, Object> hints = new HashMap<>();
hints.put("javax.persistence.lock.timeout", 2000);
Order order = entityManager.find(Order.class, orderNo, LockModeType.PESSIMISTIC_WRITE, hints);
// ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA
public interface MemberREpository extends Repository<Member, MemberId> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @QueryHints({
        @QueryHint(name = "javax.persistence.lock.timeout", value = "2000")
    })
    Optional<Member> findById(MemberId memberId);
}

DBMS์— ๋”ฐ๋ผ ๊ต์ฐฉ ์ƒํƒœ์— ๋น ์ง„ ์ปค๋„ฅ์…˜์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์ด ๋‹ค๋ฅด๋‹ค. ์‚ฌ์šฉํ•˜๋Š” DBMS์— ๋Œ€ํ•ด JPA๊ฐ€ ์–ด๋–ค ์‹์œผ๋กœ ๋Œ€๊ธฐ์‹œ๊ฐ„์„ ์ฒ˜๋ฆฌํ•˜๋Š”์ง€ ๋ฐ˜๋“œ์‹œ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.

8.3 ๋น„์„ ์  ์ž ๊ธˆ

์„ ์  ์ž ๊ธˆ์œผ๋กœ ๋ชจ๋“  ํŠธ๋žœ์žญ์…˜ ์ถฉ๋Œ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค

๋น„์„ ์ ์ž ๊ธˆ

๋น„์„ ์  ์ž ๊ธˆ์€ ๋™์‹œ์— ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ์„ ๋ง‰๋Š” ๋Œ€์‹  ๋ณ€๊ฒฝํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์‹ค์ œ DBMS์— ๋ฐ˜์˜ํ•˜๋Š” ์‹œ์ ์— ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

๋น„์„ ์  ์ž ๊ธˆ์„ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ๋ฒ„์ „์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆซ์ž ํƒ€์ž… ํ”„๋กœํผํ‹ฐ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค. ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ๋งˆ๋‹ค ๋ฒ„์ „์œผ๋กœ ์‚ฌ์šฉํ•  ํ”„๋กœํผํ‹ฐ ๊ฐ’์ด 1์”ฉ ์ฆ๊ฐ€ํ•œ๋‹ค.

๋น„์„ ์ ์ž ๊ธˆํŠธ๋žœ์žญ์…˜
// JPA
@Entity
@Table(name = "purchase_order")
@Access(AccessType.FIELD)
public class Order {
    ...์ƒ๋žต...
    @Version
    private long version;
    ...์ƒ๋žต...
}

JPA ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด UPDATE ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ @Version์— ๋ช…์‹œํ•œ ํ•„๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ๋น„์„ ์  ์ž ๊ธˆ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. ์‘์šฉ ์„œ๋น„์Šค๋Š” ๋ฒ„์ „์— ๋Œ€ํ•ด ์•Œ ํ•„์š”๊ฐ€ ์—†๋‹ค.

ํŠธ๋žœ์žญ์…˜ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•˜๋ฉด OptimisticLockingFailureException์ด ๋ฐœ์ƒํ•œ๋‹ค.


๋น„์„ ์  ์ž ๊ธˆ ํ™•์žฅ

๋น„์„ ์ ์ž ๊ธˆํŠธ๋žœ์žญ์…˜ํ™•์žฅ


๋น„์„ ์  ์ž ๊ธˆ ๋ฐฉ์‹์„ ์—ฌ๋Ÿฌ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ํ™•์žฅํ•˜๋ ค๋ฉด ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฒ„์ „ ์ •๋ณด๋ฅผ ์‘์šฉ ์„œ๋น„์Šค์— ์ „๋‹ฌํ•œ๋‹ค. ์‘์šฉ ์„œ๋น„์Šค๋Š” ์ „๋‹ฌ๋ฐ›์€ ๋ฒ„์ „ ๊ฐ’์„ ์ด์šฉํ•ด์„œ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฒ„์ „๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ์ผ์น˜ํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

public class StartShippingRequest {
    private String orderNumber;
    private long version;
    ...์ƒ๋žต...
}
// ์‘์šฉ ์„œ๋น„์Šค
public StartShippingService {

    @PreAuthorize("hasRole('ADMIN')")
    @Transactional
    public void startShipping(StartShippingRequest req) {
        Order order = orderRepository.findById(new OrderNo(req.getOrderNumber()));
        // version ํ™•์ธ
        if (!order.matchVersion(req.getVersion())) {
            throw new VersionConflictException();
        }
        ...์ƒ๋žต...
    }
}

8.3.1 ๊ฐ•์ œ ๋ฒ„์ „ ์ฆ๊ฐ€

๊ธฐ๋Šฅ ์‹คํ–‰ ๋„์ค‘ ๋ฃจํŠธ๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ์˜ ๊ฐ’๋งŒ ๋ณ€๊ฒฝ๋œ๋‹ค๋ฉด, JPA๋Š” ๋ฃจํŠธ ์—”ํ‹ฐํ‹ฐ ๋ฒ„์ „ ๊ฐ’์„ ์ฆ๊ฐ€์‹œํ‚ค์ง€ ์•Š๋Š”๋‹ค.

JPA๋Š” ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก EntityManager#find() ๋ฉ”์„œ๋“œ๋กœ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ตฌํ•  ๋•Œ ๊ฐ•์ œ๋กœ ๋ฒ„์ „ ๊ฐ’์„ ์ฆ๊ฐ€์‹œํ‚ค๋Š” ์ž ๊ธˆ ๋ชจ๋“œ๋ฅผ ์ง€์›ํ•œ๋‹ค. LockModeType.OPTIMISTIC_FORCE_INCREMENT๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ์˜ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€์— ์ƒ๊ด€์—†์ด ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ ์‹œ์ ์— ๋ฒ„์ „ ๊ฐ’ ์ฆ๊ฐ€ ์ฒ˜๋ฆฌ๋ฅผ ํ•œ๋‹ค.

// JPA
Order order = entityManager.find(Order.class, orderNo, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
// ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA
public interface MemberREpository extends Repository<Member, MemberId> {

    @Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)
    Optional<Member> findById(MemberId memberId);
}

8.4 ์˜คํ”„๋ผ์ธ ์„ ์  ์ž ๊ธˆ

๋ˆ„๊ตฐ๊ฐ€ ์ˆ˜์ • ํ™”๋ฉด์„ ๋ณด๊ณ  ์žˆ์„ ๋•Œ ์ˆ˜์ • ํ™”๋ฉด ์ž์ฒด๋ฅผ ์‹คํ–‰ํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด ์˜คํ”„๋ผ์ธ ์„ ์  ์ž ๊ธˆ ๋ฐฉ์‹์ด๋‹ค.

์˜คํ”„๋ผ์ธ์„ ์ ์ž ๊ธˆ๋ฐฉ์‹

8.4.1 ์˜คํ”„๋ผ์ธ ์„ ์  ์ž ๊ธˆ์„ ์œ„ํ•œ LockManager ์ธํ„ฐํŽ˜์ด์Šค์™€ ๊ด€๋ จ ํด๋ž˜์Šค

ํ•„์š” ๊ธฐ๋Šฅ

  • ์ž ๊ธˆ ์„ ์  ์‹œ๋„
  • ์ž ๊ธˆ ํ™•์ธ
  • ์ž ๊ธˆ ํ•ด์ œ
  • ์ž ๊ธˆ ์œ ํšจ์‹œ๊ฐ„

๊ตฌํ˜„ ์ฝ”๋“œ๋Š” ์ฑ…์—์„œ ํ™•์ธํ•ด์ฃผ์„ธ์š”. ๐Ÿ”Ž

8.4.2 DB๋ฅผ ์ด์šฉํ•œ LockManager ๊ตฌํ˜„

๊ตฌํ˜„ ์ฝ”๋“œ๋Š” ์ฑ…์—์„œ ํ™•์ธํ•ด์ฃผ์„ธ์š”. ๐Ÿ”Ž




9. ๋„๋ฉ”์ธ ๋ชจ๋ธ๊ณผ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ

9.1 ๋„๋ฉ”์ธ ๋ชจ๋ธ๊ณผ ๊ฒฝ๊ณ„

๋…ผ๋ฆฌ์ ์œผ๋กœ ๊ฐ™์€ ์กด์žฌ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ ํ•˜์œ„ ๋„๋ฉ”์ธ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์šฉ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์žˆ๋‹ค.

ํ•˜์œ„๋„๋ฉ”์ธ๋ณ„์šฉ์–ด

์ด๋ ‡๊ฒŒ ํ•˜์œ„ ๋„๋ฉ”์ธ๋งˆ๋‹ค ๊ฐ™์€ ์šฉ์–ด๋ผ๋„ ์˜๋ฏธ๊ฐ€ ๋‹ค๋ฅด๊ณ  ๊ฐ™์€ ๋Œ€์ƒ์ด๋ผ๋„ ์ง€์นญํ•˜๋Š” ์šฉ์–ด๊ฐ€ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•œ๊ฐœ์˜ ๋ชจ๋ธ๋กœ ๋ชจ๋“  ํ•˜์œ„ ๋„๋ฉ”์ธ์„ ํ‘œํ˜„ํ•˜๋ ค๋Š” ์‹œ๋„๋Š” ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•์ด ์•„๋‹ˆ๋ฉฐ ํ‘œํ˜„ํ•  ์ˆ˜๋„ ์—†๋‹ค.

๋ชจ๋ธ์€ ํŠน์ •ํ•œ ์ปจํ…์ŠคํŠธ(๋ฌธ๋งฅ) ํ•˜์—์„œ ์™„์ „ํ•œ ์˜๋ฏธ๋ฅผ ๊ฐ–๋Š”๋‹ค. ๊ฐ™์€ ์ œํ’ˆ์ด๋ผ๋„ ์นดํƒˆ๋กœ๊ทธ ์ปจํ…์ŠคํŠธ์™€ ์žฌ๊ณ  ์ปจํ…์ŠคํŠธ์—์„œ ์˜๋ฏธ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅด๋‹ค. ์ด๋ ‡๊ฒŒ ๊ตฌ๋ถ„๋˜๋Š” ๊ฒฝ๊ณ„๋ฅผ ๊ฐ–๋Š” ์ปจํ…์ŠคํŠธ๋ฅผ DDD์—์„œ๋Š” ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ(Bounded Context)๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

9.2 ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ

  • ๋„๋ฉ”์ธ ๋ชจ๋ธ์˜ ๊ฒฝ๊ณ„๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.
  • ์šฉ์–ด๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๊ตฌ๋ถ„ํ•œ๋‹ค.
  • ํ•œ ๊ฐœ์˜ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๋Š” ๋…ผ๋ฆฌ์ ์œผ๋กœ ํ•œ ๊ฐœ์˜ ๋ชจ๋ธ์„ ๊ฐ–๋Š”๋‹ค.
  • ํ•œ ๊ฐœ์˜ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๊ฐ€ ์—ฌ๋Ÿฌ ํ•˜์œ„ ๋„๋ฉ”์ธ์„ ํฌํ•จํ•˜๋”๋ผ๋„, ํ•˜์œ„ ๋„๋ฉ”์ธ๋งˆ๋‹ค ๊ตฌ๋ถ„๋˜๋Š” ํŒจํ‚ค์ง€๋ฅผ ๊ฐ–๋„๋ก ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.


์•„์ง ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋‘ ํ•˜์œ„ ๋„๋ฉ”์ธ์„ ํ•˜๋‚˜์˜ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ์—์„œ ๊ตฌํ˜„ํ•˜๊ธฐ๋„ ํ•œ๋‹ค.
๋ฐ”์šด๋””๋“œ์ปจํ…์ŠคํŠธ


ํ•œ ๊ฐœ์˜ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๊ฐ€ ์—ฌ๋Ÿฌ ํ•˜์œ„ ๋„๋ฉ”์ธ์„ ํฌํ•จํ•˜๋”๋ผ๋„, ํ•˜์œ„ ๋„๋ฉ”์ธ๋งˆ๋‹ค ๊ตฌ๋ถ„๋˜๋Š” ํŒจํ‚ค์ง€๋ฅผ ๊ฐ–๋„๋ก ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.
๋ฐ”์šด๋””๋“œ์ปจํ…์ŠคํŠธ


๊ฐ™์€ ์ƒํ’ˆ์ด๋ผ๋„ ๊ฐ์ž ๊ตฌํ˜„ํ•˜๋Š” ํ•˜์œ„ ๋„๋ฉ”์ธ์— ๋งž๋Š” ๋ชจ๋ธ์„ ๊ฐ–๋Š”๋‹ค.
๋ฐ”์šด๋””๋“œ์ปจํ…์ŠคํŠธ

9.3 ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ ๊ตฌํ˜„

๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๋Š” ๋„๋ฉ”์ธ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ œ๊ณตํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ํ‘œํ˜„ ์˜์—ญ, ์‘์šฉ ์„œ๋น„์Šค, ์ธํ”„๋ผ์ŠคํŠธ๋Ÿญ์ฒ˜, ํ…Œ์ด๋ธ” ์˜์—ญ์„ ํฌํ•จํ•œ๋‹ค.

๋ฐ”์šด๋””๋“œ์ปจํ…์ŠคํŠธ์˜์—ญ



CQRS (Command Query Responsibility Segregation, ๋ช…๋ น ์กฐํšŒ ์ฑ…์ž„ ๋ถ„๋ฆฌ) ํŒจํ„ด์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

๋ฐ”์šด๋””๋“œ์ปจํ…์ŠคํŠธCQRS



๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๋Š” UI๋ฅผ ๊ฐ–์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ๋‹ค.

๋ฐ”์šด๋””๋“œ์ปจํ…์ŠคํŠธUI๋ฏธํฌํ•จ



๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๋Š” UI์„œ๋ฒ„๋ฅผ ํ†ตํ•ด ๊ฐ„์ ‘์ ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €์™€ ํ†ต์‹ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์—ฌ๊ธฐ์„œ UI์„œ๋ฒ„๋Š” ํŒŒ์‚ฌ๋“œ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

๋ฐ”์šด๋””๋“œ์ปจํ…์ŠคํŠธUI์„œ๋ฒ„

9.4 ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ ๊ฐ„ ํ†ตํ•ฉ

๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ ๊ฐ„ ํ†ตํ•ฉ์ด ํ•„์š”ํ•  ๋•Œ๋„ ์žˆ๋‹ค.

REST API๋ฅผ ์ด์šฉํ•œ ์ง์ ‘ ํ†ตํ•ฉ

  • ํ†ต์‹ 

๋ฐ”์šด๋””๋“œ์ปจํ…์ŠคํŠธ๊ฐ„ํ†ตํ•ฉ


  • ๋„๋ฉ”์ธ์— ๋งž๋Š” ๋ชจ๋ธ๋กœ ๋ณ€ํ™˜

๋ฐ”์šด๋””๋“œ์ปจํ…์ŠคํŠธ๊ฐ„ํ†ตํ•ฉ



๋ฉ”์‹œ์ง€ ํ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ†ตํ•ฉ ๋ฐ”์šด๋””๋“œ์ปจํ…์ŠคํŠธ๊ฐ„ํ†ตํ•ฉ

๋ฐ”์šด๋””๋“œ์ปจํ…์ŠคํŠธ๊ฐ„ํ†ตํ•ฉ



๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์™€ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ
๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž‘์€ ์„œ๋น„์Šค๋กœ ๋‚˜๋ˆ„์–ด ๊ฐœ๋ฐœํ•˜๋Š” ์•„ํ‚คํ…์ฒ˜ ์Šคํƒ€์ผ์ด๋‹ค. ๊ฐœ๋ณ„ ์„œ๋น„์Šค๋ฅผ ๋…๋ฆฝ๋œ ํ”„๋กœ์„ธ์Šค๋กœ ์‹คํ–‰ํ•˜๊ณ  ๊ฐ ์„œ๋น„์Šค๊ฐ€ REST API๋‚˜ ๋ฉ”์‹œ์ง•์„ ์ด์šฉํ•ด์„œ ํ†ต์‹ ํ•˜๋Š” ๊ตฌ์กฐ๋ฅผ ๊ฐ–๋Š”๋‹ค.

๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๋ฅผ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ปจํ…์ŠคํŠธ๋ณ„๋กœ ๋ชจ๋ธ์ด ๋ถ„๋ฆฌ๋œ๋‹ค. ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋งˆ๋‹ค ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋ฏ€๋กœ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๋งˆ๋‹ค ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ฒŒ ๋œ๋‹ค. ์ด๊ฒƒ์€ ์ฝ”๋“œ ์ˆ˜์ค€์—์„œ ๋ชจ๋ธ์„ ๋ถ„๋ฆฌํ•˜์—ฌ ๋‘ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ์˜ ๋ชจ๋ธ์ด ์„ž์ด์ง€ ์•Š๋„๋ก ํ•ด์ค€๋‹ค.

9.5 ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ ๊ฐ„ ๊ด€๊ณ„

๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๋Š” ์–ด๋–ค ์‹์œผ๋กœ๋“  ์—ฐ๊ฒฐ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋‘ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ์‹์œผ๋กœ ๊ด€๊ณ„๋ฅผ ๋งบ๋Š”๋‹ค.

REST API
๋ฐ”์šด๋””๋“œ์ปจํ…์ŠคํŠธ๊ฐ„ํ†ตํ•ฉ



๋‹จ์ผ API
๋ฐ”์šด๋””๋“œ์ปจํ…์ŠคํŠธ๊ฐ„ํ†ตํ•ฉ



๋…๋ฆฝ ๋ฐฉ์‹
๊ทธ๋ƒฅ ์„œ๋กœ ํ†ตํ•ฉํ•˜์ง€ ์•Š๋Š” ๋ฐฉ์‹์ด๋‹ค. ์„œ๋กœ ๋…๋ฆฝ์ ์œผ๋กœ ๋ชจ๋ธ์„ ๋ฐœ์ „ ์‹œํ‚จ๋‹ค. ํ•˜์ง€๋งŒ ๊ทœ๋ชจ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ํ•œ๊ณ„๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ ๊ทธ ์ „์— ๋‘ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๋ฅผ ํ†ตํ•ฉํ•ด์•ผ ํ•œ๋‹ค.

๋ฐ”์šด๋””๋“œ์ปจํ…์ŠคํŠธ๊ฐ„ํ†ตํ•ฉ

๋ฐ”์šด๋””๋“œ์ปจํ…์ŠคํŠธ๊ฐ„ํ†ตํ•ฉ

9.6 ์ปจํ…์ŠคํŠธ ๋งต

๊ฐœ๋ณ„ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ์— ๋งค๋ชฐ๋˜๋ฉด ์ „์ฒด๋ฅผ ๋ณด์ง€ ๋ชปํ•  ๋•Œ๊ฐ€ ์žˆ๋‹ค. ์ „์ฒด ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ ๊ฐ„ ๊ด€๊ณ„๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋Š” ์ง€๋„๊ฐ€ ํ•„์š”ํ•œ๋ฐ ๊ทธ๊ฒƒ์ด ๋ฐ”๋กœ ์ปจํ…์ŠคํŠธ ๋งต์ด๋‹ค.

์ปจํ…์ŠคํŠธ๋งต




10. ์ด๋ฒคํŠธ

10.1 ์‹œ์Šคํ…œ ๊ฐ„ ๊ฐ•๊ฒฐํ•ฉ ๋ฌธ์ œ

  • ํŠธ๋ Œ์ ์…˜ ๋ฌธ์ œ
  • ์™ธ๋ถ€ ์„œ๋น„์Šค์˜ ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ๋ฐ›์Œ
  • ์„ค๊ณ„์ƒ ๋ฌธ์ œ (๋กœ์ง์ด ์„ž์ž„)
  • ๊ธฐ๋Šฅ ์ถ”๊ฐ€์˜ ์–ด๋ ค์›€

10.2 ์ด๋ฒคํŠธ ๊ฐœ์š”

์—ฌ๊ธฐ์„œ ์‚ฌ์šฉ๋˜๋Š” ์ด๋ฒคํŠธ๋ผ๋Š” ์šฉ์–ด๋Š” โ€˜๊ณผ๊ฑฐ์— ๋ฒŒ์–ด์ง„ ์–ด๋–ค ๊ฒƒโ€™์„ ์˜๋ฏธํ•œ๋‹ค.

  • ์ด๋ฒคํŠธ ๋ฐœ์ƒ
  • ์ด๋ฒคํŠธ์— ๋ฐ˜์‘ํ•˜์—ฌ ๋™์ž‘ ์ˆ˜ํ–‰

10.2.1 ์ด๋ฒคํŠธ ๊ด€๋ จ ๊ตฌ์„ฑ์š”์†Œ

  • ์ด๋ฒคํŠธ
  • ์ด๋ฒคํŠธ ์ƒ์„ฑ ์ฃผ์ฒด
  • ์ด๋ฒคํŠธ ๋””์ŠคํŒจ์ฒ˜ (ํผ๋ธ”๋ฆฌ์…”)
  • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ (๊ตฌ๋…์ž)

์ด๋ฒคํŠธ๊ตฌ์„ฑ์š”์†Œ

๋„๋ฉ”์ธ ๋ชจ๋ธ์—์„œ ์ด๋ฒคํŠธ ์ƒ์„ฑ ์ฃผ์ฒด๋Š” ์—”ํ‹ฐํ‹ฐ, ๋ฐธ๋ฅ˜, ๋„๋ฉ”์ธ ์„œ๋น„์Šค์™€ ๊ฐ™์€ ๋„๋ฉ”์ธ ๊ฐ์ฒด์ด๋‹ค. ๋„๋ฉ”์ธ ๊ฐ์ฒด๋Š” ๋„๋ฉ”์ธ ๋กœ์ง์„ ์‹คํ–‰ํ•ด์„œ ์ƒํƒœ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ๊ด€๋ จ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.

10.2.2 ์ด๋ฒคํŠธ์˜ ๊ตฌ์„ฑ

์ด๋ฒคํŠธ๋Š” ๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋‹ด๋Š”๋‹ค.

  • ์ด๋ฒคํŠธ ์ข…๋ฅ˜: ํด๋ž˜์Šค ์ด๋ฆ„์œผ๋กœ ์ด๋ฒคํŠธ ์ข…๋ฅ˜๋ฅผ ํ‘œํ˜„
  • ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ๊ฐ„
  • ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ: ์ฃผ๋ฌธ๋ฒˆํ˜ธ, ์‹œ๊ทœ๋ฐฐ์†ก์ง€ ์ •๋ณด ๋“ฑ ์ด๋ฒคํŠธ์™€ ๊ด€๋ จ๋œ ์ •๋ณด

10.2.3 ์ด๋ฒคํŠธ ์šฉ๋„

  • ํ›„์ฒ˜๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ํŠธ๋ฆฌ๊ฑฐ
  • ์„œ๋กœ ๋‹ค๋ฅธ ์‹œ์Šคํ…œ ๊ฐ„์˜ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™”

์ด๋ฒคํŠธ์šฉ๋„

10.2.4 ์ด๋ฒคํŠธ ์žฅ์ 

  • ์„œ๋กœ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ ๋กœ์ง์ด ์„ž์ด๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€
  • ๋„๋ฉ”์ธ ๋กœ์ง์— ์˜ํ–ฅ ์—†์ด ๊ธฐ๋Šฅ ํ™•์žฅ

์ด๋ฒคํŠธ์žฅ์ 

10.3 ์ด๋ฒคํŠธ, ํ•ธ๋“ค๋Ÿฌ, ๋””์ŠคํŒจ์ฒ˜ ๊ตฌํ˜„

์ด๋ฒคํŠธ์™€ ๊ด€๋ จ๋œ ์ฝ”๋“œ

  • ์ด๋ฒคํŠธ ํด๋ž˜์Šค: ์ด๋ฒคํŠธ๋ฅผ ํ‘œํ˜„ํ•œ๋‹ค.
  • ๋””์ŠคํŒจ์ฒ˜: ์Šคํ”„๋ง์ด ์ œ๊ณตํ•˜๋Š” ApplicationEventPublisher๋ฅผ ์ด์šฉํ•œ๋‹ค.
  • Events: ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•œ๋‹ค. ์ด๋ฒคํŠธ ๋ฐœํ–‰์„ ์œ„ํ•ด ApplicationEventPublisher๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
  • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ: ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•ด์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค. ์Šคํ”„๋ง์ด ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•œ๋‹ค.

10.3.1 ์ด๋ฒคํŠธ ํด๋ž˜์Šค

// ๊ณตํ†ต ์ถ”์ƒ ํด๋ž˜์Šค
package com.myshop.common.event;

public abstract class Event {
    private long timestamp;

    public Event() {
        this.timestamp = System.currentTimeMillis();
    }
    ...์ƒ๋žต...
}
public class OrderCanceledEvent extends Event {

    // ์ด๋ฒคํŠธ๋Š” ํ•ธ๋“ค๋Ÿฌ์—์„œ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จ
    private String orderNumber;

    public OrderCanceledEvent(String number) {
        super();
        this.orderNumber = number;
    }
    ...์ƒ๋žต...
}

10.3.2 Events ํด๋ž˜์Šค์™€ ApplicationEventPublisher

์ด๋ฒคํŠธ ๋ฐœ์ƒ๊ณผ ์ถœํŒ์„ ์œ„ํ•ด ์Šคํ”„๋ง์ด ์ œ๊ณตํ•˜๋Š” ApplicationEventPublisher๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

// ์„ค์ •
@Configuration
public class EventsConfiguration {
    @Autowired
    private ApplicationContext applicationContext;

    @Bean
    public InitializingBean eventsInitializer() {
        return () -> Events.setPublisher(applicationContext);
    }
}
// Events๋Š” ApplicationEventPublisher๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ
public class Events {
    private static ApplicationEventPublisher publisher;

    static void setPublisher(ApplicationEventPublisher publisher) {
        Events.publisher = publisher;
    }

    public static void raise(Object event) {
        if (publisher != null) {
            publisher.publishEvent(event);
        }
    }
}

10.3.3 ์ด๋ฒคํŠธ ๋ฐœ์ƒ๊ณผ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ

// ์ด๋ฒคํŠธ ๋ฐœ์ƒ
public class Order {
    public void cancel() {
        ...์ƒ๋žต...
        Events.raise(new OrderCanceledEvent(number.getNumber()));
    }
}
// ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
@Service
public class OrderCanceledEventHandler {
    private RefundService refundService;

    public OrderCanceledEventHandler(RefundService refundService) {
        this.refundService = refundService;
    }

    @EventListener(OrderCanceledEvent.class)
    public void handle(OrderCanceledEvent event) {
        refundService.refund(event.getOrderNumber());
    }
}

10.3.4 ํ๋ฆ„ ์ •๋ฆฌ

์ด๋ฒคํŠธํ๋ฆ„

10.4 ๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋ฌธ์ œ

๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

  • ์™ธ๋ถ€ ์—ฐ๋™ ๊ณผ์ •์—์„œ ์ต์…‰์…˜์ด ๋ฐœ์ƒํ•˜๋ฉด ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๋Š”?
  • ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ๋Š๋ ค์ง€๊ฑฐ๋‚˜ ์ต์…‰์…˜์ด ๋ฐœ์ƒํ•˜๋ฉด?

10.5 ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ

๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋กœ ํ•ด๊ฒฐํ•œ๋‹ค.

  • ๋กœ์ปฌ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋น„๋™๊ธฐ๋กœ ์‹คํ–‰ํ•˜๊ธฐ
  • ๋ฉ”์‹œ์ง€ ํ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ
  • ์ด๋ฒคํŠธ ์ €์žฅ์†Œ์™€ ์ด๋ฒคํŠธ ํฌ์›Œ๋” ์‚ฌ์šฉํ•˜๊ธฐ
  • ์ด๋ฒคํŠธ ์ €์žฅ์†Œ์™€ ์ด๋ฒคํŠธ ์ œ๊ณต API ์‚ฌ์šฉํ•˜๊ธฐ

10.5.1 ๋กœ์ปฌ ํ•ธ๋“ค๋Ÿฌ ๋น„๋™๊ธฐ ์‹คํ–‰

์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ณ„๋„ ์Šค๋ ˆ๋“œ๋กœ ์‹คํ–‰ํ•œ๋‹ค. ์Šคํ”„๋ง์ด ์ œ๊ณตํ•˜๋Š” @Async ์—๋„ˆํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ์†์‰ฝ๊ฒŒ ๋น„๋™๊ธฐ๋กœ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

@SpringBootApplication
@EnableAsync  // ๊ธฐ๋Šฅ ํ™œ์„ฑํ™”
public class ShopApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShopApplication.class, args);
    }
}
// ํ•ธ๋“ค๋Ÿฌ
@Service
public class OrderCanceledEventHandler {

    @Async // @Async ์—๋„ˆํ…Œ์ด์…˜ ์‚ฌ์šฉ
    @EventListener(OrderCanceledEvent.class)
    public void handle(OrderCanceledEvent event) {
        refundService.refund(event.getOrderNumber());
    }
}

10.5.2 ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ์„ ์ด์šฉํ•œ ๋น„๋™๊ธฐ ๊ตฌํ˜„

Kafka๋‚˜ RabbitMQ์™€ ๊ฐ™์€ ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉ

  • RabbitMQ
    • ๊ธ€๋กœ๋ฒŒ ํŠธ๋žœ์žญ์…˜ ์ง€์›
  • Kafka
    • ๊ธ€๋กœ๋ฒŒ ํŠธ๋žœ์žญ์…˜ ์ง€์› X
    • ๋‹ค๋ฅธ ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ์— ๋น„ํ•ด ๋†’์€ ์„ฑ๋Šฅ

์ด๋ฒคํŠธ๋ฉ”์‹œ์ง€ํ

10.5.3 ์ด๋ฒคํŠธ ์ €์žฅ์†Œ๋ฅผ ์ด์šฉํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ

์ด๋ฒคํŠธ๋ฅผ ์ผ๋‹จ DB์— ์ €์žฅํ•œ ๋’ค์— ๋ณ„๋„ ํ”„๋กœ๊ทธ๋žจ์„ ์ด์šฉํ•ด์„œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์— ์ „๋‹ฌ

์ด๋ฒคํŠธ์ €์žฅ์†Œ์ด์šฉํฌ์›Œ๋”

์ด๋ฒคํŠธ์ €์žฅ์†Œ์ด์šฉAPI


API๋ฐฉ์‹๊ณผ ํฌ์›Œ๋” ๋ฐฉ์‹์˜ ์ฐจ์ด์ ์€ ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹์— ์žˆ๋‹ค.

  • ํฌ์›Œ๋”
    • ํฌ์›Œ๋”๋ฅผ ์ด์šฉํ•ด์„œ ์ด๋ฒคํŠธ๋ฅผ ์™ธ๋ถ€์— ์ „๋‹ฌ.
    • ์ด๋ฒคํŠธ๋ฅผ ์–ด๋””๊นŒ์ง€ ์ฒ˜๋ฆฌํ–ˆ๋Š”์ง€ ์ถ”์ ํ•˜๋Š” ์—ญํ• ์ด ํฌ์›Œ๋”์— ์žˆ๋‹ค.
  • API
    • ์™ธ๋ถ€ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ API ์„œ๋ฒ„๋ฅผ ํ†ตํ•ด ์ด๋ฒคํŠธ ๋ชฉ๋ก์„ ๊ฐ€์ ธ๊ฐ.
    • ์ด๋ฒคํŠธ๋ฅผ ์–ด๋””๊นŒ์ง€ ์ฒ˜๋ฆฌํ–ˆ๋Š”์ง€ ์ถ”์ ํ•˜๋Š” ์—ญํ• ์ด ์™ธ๋ถ€ ํ•ธ๋“ค๋Ÿฌ์— ์žˆ๋‹ค.


๊ตฌํ˜„ ๋ฐฉ๋ฒ•์€ ์ฑ… ์ฐธ๊ณ 
โ€ฆ์ƒ๋žตโ€ฆ๐Ÿ˜…


์ž๋™ ์ฆ๊ฐ€ ์นผ๋Ÿผ ์ฃผ์˜ ์‚ฌํ•ญ
ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์‹œ์ ์— ๋”ฐ๋ฅธ ์ž๋™ ์ฆ๊ฐ€ ์นผ๋Ÿผ ๋ฌธ์ œ
์ž๋™ ์ฆ๊ฐ€ ์นผ๋Ÿผ ์ฃผ์˜ ์‚ฌํ•ญ ๋งํฌ

// ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์‹œ์ ์— ๋”ฐ๋ฅธ ID ๊ฐ’
์‹œ๊ฐ„ ํ๋ฆ„ --->  
ํŠธ๋žœ์žญ์…˜ 1: 1, 2       5, 6
ํŠธ๋žœ์žญ์…˜ 2:       3, 4      7, 8

10.6 ์ด๋ฒคํŠธ ์ ์šฉ ์‹œ ์ถ”๊ฐ€ ๊ณ ๋ ค ์‚ฌํ•ญ

์ด๋ฒคํŠธ ์ ์šฉ ์‹œ ๋‹ค์Œ ๊ณ ๋ ค์‚ฌํ•ญ์„ ์ƒ๊ฐํ•ด๋ณธ๋‹ค.

  • ์ด๋ฒคํŠธ ์†Œ์Šค๋ฅผ EventEntry์— ์ถ”๊ฐ€ํ• ์ง€?
    • EventEntry๋Š” ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์ฃผ์ฒด์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๊ฐ–์ง€ ์•Š๋Š”๋‹ค.
    • ํŠน์ • ์ฃผ์ฒด๊ฐ€ ๋ฐœ์ƒ์‹œํ‚จ ์ด๋ฒคํŠธ๋งŒ ์กฐํšŒํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์—†๋‹ค.
    • ์ด ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด ์ด๋ฒคํŠธ์— ๋ฐœ์ƒ ์ฃผ์ฒด ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.
  • ํฌ์›Œ๋”์—์„œ ์ „์†ก ์‹คํŒจ๋ฅผ ์–ผ๋งˆ๋‚˜ ํ—ˆ์šฉํ• ์ง€?
    • ํฌ์›Œ๋”๋Š” ์ด๋ฒคํŠธ ์ „์†ก์— ์‹คํŒจํ•˜๋ฉด ์‹คํŒจํ•œ ์ด๋ฒคํŠธ๋ถ€ํ„ฐ ๋‹ค์‹œ ์ฝ์–ด์™€ ์ „์†ก์„ ์‹œ๋„ํ•œ๋‹ค.
    • ํŠน์ • ์ด๋ฒคํŠธ๊ฐ€ ๊ณ„์† ์‹คํŒจํ•œ๋‹ค๋ฉด?
    • ์‹คํŒจํ•œ ์ด๋ฒคํŠธ์˜ ์žฌ์ „์†ก ํšŸ์ˆ˜ ์ œํ•œ์„ ๋‘์–ด์•ผ ํ•œ๋‹ค.
    • ์‹คํŒจํ•œ ์ด๋ฒคํŠธ๋Š” ์‹คํŒจ์šฉ DB๋‚˜ ๋ฉ”์‹œ์ง€ ํ์— ์ €์žฅํ•œ๋‹ค.
  • ์ด๋ฒคํŠธ ์†์‹ค์€?
    • ์ด๋ฒคํŠธ ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋ฒคํŠธ ๋ฐœ์ƒ๊ณผ ์ด๋ฒคํŠธ ์ €์žฅ์„ ํ•œ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํŠธ๋žœ์žญ์…˜์— ์„ฑ๊ณตํ•˜๋ฉด ์ด๋ฒคํŠธ๊ฐ€ ์ €์žฅ์†Œ์— ๋ณด๊ด€๋œ๋‹ค๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์ด๋ฒคํŠธ๋ฅผ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•  ๊ฒฝ์šฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์— ์‹คํŒจํ•˜๋ฉด ์ด๋ฒคํŠธ๋ฅผ ์œ ์‹คํ•˜๊ฒŒ ๋œ๋‹ค.
  • ์ด๋ฒคํŠธ ์ˆœ์„œ๋Š”?
    • ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์ˆœ์„œ๋Œ€๋กœ ์™ธ๋ถ€ ์‹œ์Šคํ…œ์— ์ „๋‹ฌํ•ด์•ผ ํ•  ๊ฒฝ์šฐ๋Š” ์ด๋ฒคํŠธ ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
    • ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ์€ ์‚ฌ์šฉ ๊ธฐ์ˆ ์— ๋”ฐ๋ผ ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์ˆœ์„œ์™€ ๋ฉ”์‹œ์ง€ ์ˆœ์„œ๊ฐ€ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๋‹ค.
  • ์ด๋ฒคํŠธ ์žฌ์ฒ˜๋ฆฌ๋Š”?
    • ์ด๋ฒคํŠธ์˜ ์ˆœ๋ฒˆ์„ ๊ธฐ์–ตํ•œ๋‹ค.
    • ์ด๋ฒคํŠธ ๋ฉฑ๋“ฑ์„ฑ์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค

๋ฉฑ๋“ฑ์„ฑ์ด๋ž€?
์—ฐ์‚ฐ์„ ์—ฌ๋Ÿฌ๋ฒˆ ์ ์šฉํ•ด๋„ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ฌ๋ผ์ง€์ง€ ์•Š๋Š” ์„ฑ์งˆ์„ ๋ฉฑ๋“ฑ์„ฑ์ด๋ผ๊ณ  ํ•œ๋‹ค.

10.6.1 ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์™€ DB ํŠธ๋žœ์žญ์…˜ ๊ณ ๋ ค

์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ๋™๊ธฐ๋กœ ํ•˜๋“  ๋น„๋™๊ธฐ๋กœ ํ•˜๋“  ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์‹คํŒจ์™€ ํŠธ๋žœ์žญ์…˜ ์‹คํŒจ๋ฅผ ํ•จ๊ป˜ ๊ณ ๋ คํ•ด์•ผ ํ•œ๋‹ค. ํŠธ๋žœ์žญ์…˜ ์‹คํŒจ์™€ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์‹คํŒจ ๋ชจ๋‘ ๊ณ ๋ คํ•˜๋ฉด ๋ณต์žกํ•ด์ง€๋ฏ€๋กœ ๊ฒฝ์šฐ์˜ ์ˆ˜๋ฅผ ์ค„์ด๋ฉด ๋„์›€์ด ๋œ๋‹ค. ๊ฒฝ์šฐ์˜ ์ˆ˜๋ฅผ ์ค„์ด๋Š” ๋ฐฉ๋ฒ•์€ ํŠธ๋žœ์žญ์…˜์ด ์„ฑ๊ณตํ•  ๋•Œ๋งŒ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์Šคํ”„๋ง์€ @TransactionalEventListener ์—๋„ˆํ…Œ์ด์…˜์„ ์ง€์›ํ•œ๋‹ค. ์ด ์• ๋„ˆํ…Œ์ด์…˜์€ ํŠธ๋žœ์žญ์…˜ ์ƒํƒœ์— ๋”ฐ๋ผ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค.

@TransactionalEventListener(
        classes = OrderCanceledEvent.class,
        phase = TransactionPhase.AFTER_COMMIT
)
public void handle(OrderCanceledEvent event) {
    refundService.refund(event.getOrderNumber());
}




11. CQRS

11.1 ๋‹จ์ผ ๋ชจ๋ธ์˜ ๋‹จ์ 

์กฐํšŒ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด ์—ฌ๋Ÿฌ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•  ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค. ์ด ๋•Œ ์‹๋ณ„์ž๋ฅผ ์ด์šฉํ•œ ์ฐธ์กฐ ๋ฐฉ์‹์ด๋“ , ์ง์ ‘ ์ฐธ์กฐํ•˜๋Š” ๋ฐฉ์‹์ด๋“  ๊ณ ๋ฏผํ•ด์•ผ ํ•  ๊ฒƒ๋“ค์ด ๋งŽ๋‹ค. ์ด๋Š” ๊ตฌํ˜„ ๋ณต์žก๋„๋ฅผ ๋†’์ธ๋‹ค.

์ด๋Ÿฐ ๊ตฌํ˜„ ๋ณต์žก๋„๋ฅผ ๋‚ฎ์ถ”๋Š” ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์ด ๋ฐ”๋กœ CQRS (Command Query Responsibility Segregation, ๋ช…๋ น ์กฐํšŒ ์ฑ…์ž„ ๋ถ„๋ฆฌ)๋‹ค.

11.2 CQRS

CQRSํŒจํ„ด


CQRS๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ ๋ชจ๋ธ์— ๋งž๋Š” ๊ตฌํ˜„ ๊ธฐ์ˆ ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค.

CQRS

CQRS_2

๋‹จ์ˆœํžˆ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์™€ ์กฐํšŒํ•˜๋Š” ๊ธฐ๋Šฅ์€ ์‘์šฉ ์„œ๋น„์Šค๋ฅผ ์ œ์™ธํ•  ์ˆ˜๋„ ์žˆ๋‹ค.


CQRS๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ ๋ชจ๋ธ์— ๋งž๋Š” ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค.

CQRS

๋ช…๋ น ๋ชจ๋ธ์€ ํŠธ๋žœ์žญ์…˜์„ ์ง€์›ํ•˜๋Š” RDBMS๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ์กฐํšŒ ๋ชจ๋ธ์€ ์กฐํšŒ ์„ฑ๋Šฅ์ด ์ข‹์€ ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜ NoSQL์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

11.2.1 ์›น๊ณผ CQRS

์ผ๋ฐ˜์ ์ธ ์›น ์„œ๋น„์Šค๋Š” ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ์š”์ฒญ๋ณด๋‹ค ์ƒํƒœ๋ฅผ ์กฐํšŒํ•˜๋Š” ์š”์ฒญ์ด ๋งŽ๋‹ค. ๋Œ€๊ทœ๋ชจ ํŠธ๋ž˜ํ”ฝ์ด ๋ฐœ์ƒํ•˜๋Š” ์›น ์„œ๋น„์Šค๋Š” ์•Œ๊ฒŒ ๋ชจ๋ฅด๊ฒŒ CQRS๋ฅผ ์ ์šฉํ•˜๊ฒŒ ๋œ๋‹ค. ์กฐํšŒ ์†๋„๋ฅผ ๋†’์ด๊ธฐ ์œ„ํ•ด ๋ณ„๋„ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด CQRS๋ฅผ ์ ์šฉํ•˜์ž. ์ด๋ฅผ ํ†ตํ•ด ์กฐํšŒ ๊ธฐ๋Šฅ ๋•Œ๋ฌธ์— ๋ช…๋ น ๋ชจ๋ธ์ด ๋ณต์žกํ•ด์ง€๋Š” ๊ฒƒ์„ ๋ง‰์„ ์ˆ˜ ์žˆ๊ณ , ๋ช…๋ น ๋ชจ๋ธ์— ๊ด€๊ณ„์—†์ด ์กฐํšŒ ๊ธฐ๋Šฅ์— ํŠนํ™”๋œ ๊ตฌํ˜„ ๊ธฐ๋ฒ•์„ ๋ณด๋‹ค ์‰ฝ๊ฒŒ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

11.2.2 CQRS ์žฅ๋‹จ์ 

  • ์žฅ์ 
    • ๋ช…๋ น ๋ชจ๋ธ์„ ๊ตฌํ˜„ํ•  ๋•Œ ๋„๋ฉ”์ธ ์ž์ฒด์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์กฐํšŒ ์„ฑ๋Šฅ ํ–ฅ์ƒ์— ์œ ๋ฆฌ
  • ๋‹จ์ 
    • ๊ตฌํ˜„ํ•ด์•ผ ํ•  ์ฝ”๋“œ๊ฐ€ ๋” ๋งŽ๋‹ค.
    • ๋” ๋งŽ์€ ๊ตฌํ˜„ ๊ธฐ์ˆ ์ด ํ•„์š”ํ•˜๋‹ค.

์žฅ๋‹จ์ ์„ ๊ณ ๋ คํ•ด CQRS ํŒจํ„ด ๋„์ž… ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค. ๋„๋ฉ”์ธ์ด ๋ณต์žกํ•˜์ง€ ์•Š์€๋ฐ CQRS๋ฅผ ๋„์ž…ํ•˜๋ฉด ์œ ์ง€ ๋น„์šฉ๋งŒ ๋†’์•„์ง„๋‹ค. ๋ฐ˜๋ฉด ํŠธ๋ž˜ํ”ฝ์ด ๋†’์€ ์„œ๋น„์Šค์ธ๋ฐ ๋‹จ์ผ ๋ชจ๋ธ์„ ๊ณ ์ง‘ํ•˜๋ฉด ์œ ์ง€ ๋ณด์ˆ˜ ๋น„์šฉ์ด ์˜คํžˆ๋ ค ๋†’์•„์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ CQRS ๋„์ž…์„ ๊ณ ๋ คํ•˜์ž.





๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค ๐Ÿ™‡๐Ÿปโ€โ™‚๏ธ

ํƒœ๊ทธ: ,

์นดํ…Œ๊ณ ๋ฆฌ:

์—…๋ฐ์ดํŠธ:

๋Œ“๊ธ€๋‚จ๊ธฐ๊ธฐ