๐Ÿ  ์ผ์ƒ/๐Ÿ““ ์ผ์ƒ ์ผ๊ธฐ

ํ•ญํ•ด ํ”Œ๋Ÿฌ์Šค ์ตœ์ข… ํšŒ๊ณ  (+์ฃผ์ฐจ๋ณ„ ํ•™์Šต๋‚ด์šฉ ์ •๋ฆฌ)

EastShine_ 2024. 12. 15. 00:55

 

๋“ค์–ด๊ฐ€๋ฉฐ

11์›” 30์ผ, 10์ฃผ ๊ฐ„์˜ ํ•ญํ•ด ๊ต์œก ๊ณผ์ •์ด ๋๋‚ฌ๋‹ค.

์—ญ์‹œ ๋Œ์•„๋ณด๋‹ˆ ์งง๊ฒŒ ๋А๊ปด์ง€๋Š” ๊ฑด ์—ฌ๋А ๊ฒฝํ—˜๊ณผ๋„ ๋น„์Šทํ•œ ๊ฐ์ •์„ ๋А๋ผ๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

 

ํ•ญํ•ด ์ˆ˜๊ฐ• ์ „, ์ปค๋ฆฌํ˜๋Ÿผ์„ ๋ณด์•˜์„ ๋• ์ด๋Ÿฐ ์ƒ๊ฐ์„ ํ–ˆ๋‹ค.

 

TDD, Kafka, Redis... ๋‹ค ์ต์ˆ™ํ•œ ํ‚ค์›Œ๋“œ์ธ๋ฐ, ํ˜ผ์ž์„œ๋„ ๊ณต๋ถ€ํ•  ์ˆ˜ ์žˆ์ง€ ์•Š์„๊นŒ? ๊ณผ์—ฐ ํฐ ๋ˆ์„ ๋‚ด๋ฉด์„œ๊นŒ์ง€ ๋ฐฐ์šธ๋งŒํ•œ ๊ฐ€์น˜๊ฐ€ ์žˆ์„๊นŒ?

 

๋ฌผ๋ก  ๋งˆ์Œ๋งŒ ๋จน์œผ๋ฉด ํ˜ผ์ž์„œ๋„ ๊ฐ€๋Šฅํ–ˆ์„์ง€ ๋ชจ๋ฅธ๋‹ค. ํ•˜์ง€๋งŒ ํ˜ผ์ž์„œ๋Š” ํ•˜์ง€ ๋ชปํ•  ์ด์œ ๊ฐ€ ์žˆ์—ˆ๋‹ค.

 

1. 10์ฃผ์•ˆ์— ๊ธฐ์ˆ ์„ ์ตํžˆ๊ณ , ํ”„๋กœ์ ํŠธ์— ๋ฐ˜์˜

2. ๊ด‘๋ฒ”์œ„ํ•œ ์‚ฌ์šฉ ๋ฐฉ๋ฒ• ์ค‘ ์‹ค๋ฌด์— ๊ผญ ํ•„์š”ํ•œ ๋ฐฉ๋ฒ•์„ ์บ์น˜

3. ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋“ค์˜ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ๊ฒฝํ—˜์œผ๋กœ ์–ป์–ด๋‚ธ ๋…ธํ•˜์šฐ

 

์ด ์„ธ ๊ฐ€์ง€๋Š” ํ˜ผ์ž ๊ณต๋ถ€ํ•ด์„œ ์–ป์–ด๋‚ด๊ธฐ์—” ๋„ˆ๋ฌด ์˜ค๋žœ ์‹œ๊ฐ„์ด ํ•„์š”ํ•œ ๊ฒƒ๋“ค์ด์—ˆ๋‹ค.

๊ทธ๋Ÿฐ ์˜๋ฏธ์—์„œ ๋‚˜์—๊ฒŒ ์ง€๋‚œ 10์ฃผ๋Š” ํญ๋ฐœ์ ์œผ๋กœ ์„ฑ์žฅํ•  ์ˆ˜ ์žˆ์—ˆ๋˜ ์‹œ๊ฐ„์ด์—ˆ๋‹ค.

 

 

 

 

 

10์ฃผ ์ „์˜ ๋‚ด ๋ชจ์Šต

0์ฃผ์ฐจ ํšŒ๊ณ ๋ฅผ ๋‹ค์‹œ ์ฝ์–ด๋ณด๋‹ˆ, ๊ทธ ๋‹น์‹œ์˜ ๋‚ด ๊ฐ์ •์ด ์ƒ์ƒํžˆ ๋– ์˜ค๋ฅธ๋‹ค.

ํ˜„์žฌ ๋‹ค๋‹ˆ๊ณ  ์žˆ๋Š” ํšŒ์‚ฌ์—์„œ๋„ ๋งŽ์€ ๊ฒƒ์„ ๋ฐฐ์šฐ๊ณ  ์žˆ์ง€๋งŒ, 'ํ˜น์‹œ ๋‚ด๊ฐ€ ์šฐ๋ฌผ ์•ˆ ๊ฐœ๊ตฌ๋ฆฌ๊ฐ€ ๋˜์–ด๊ฐ€๊ณ  ์žˆ๋Š” ๊ฑด ์•„๋‹๊นŒ?' ํ•˜๋Š” ๋ถˆ์•ˆ๊ฐ์ด ๋Š˜ ๋งˆ์Œ ํ•œ๊ตฌ์„์— ์ž๋ฆฌ ์žก๊ณ  ์žˆ์—ˆ๋‹ค.

๋˜, '๋‚˜๋Š” ๊ณผ์—ฐ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ฑ์žฅํ•˜๊ณ  ์žˆ๋Š” ๊ฐœ๋ฐœ์ž์ธ๊ฐ€?', '์ด ๋ฐฉํ–ฅ์€ ๋งž๋Š” ๊ฑธ๊นŒ?' ๋ผ๋Š” ์งˆ๋ฌธ์ด ๋Š์ž„์—†์ด ๋– ์˜ฌ๋ž๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ ์งˆ๋ฌธ๋“ค์— ๋ช…ํ™•ํ•œ ๋‹ต์„ ์ฐพ์ง€ ๋ชปํ•œ ์ฑ„ ์‹œ๊ฐ„๋งŒ ํ˜๋Ÿฌ๊ฐ”๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

๊ทธ ๋‹ต์„ ์ฐพ๊ธฐ ์œ„ํ•ด์„œ ๊ฒฐ๊ตญ ํ•ญํ•ด๋ฅผ ์‹ ์ฒญํ–ˆ๋‹ค.

 

 

 

 

 

 

10์ฃผ ๊ณผ์ •์„ ํ†ตํ•ด ๋ฐฐ์šด ์ 

 

1. ์„ฑ์žฅ์˜ ๋ฐฉํ–ฅ์„ฑ

์ฃผ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋ผ๋ฉด, ์•„๋‹ˆ ์–ด๋А ์ง์—…์ด๋“  ๊ฐ„์— ์„ฑ์žฅํ•˜๊ณ  ์žˆ๋Š” ์‚ฌ๋žŒ์ด๋ผ๋ฉด ๊ฐ€์žฅ ๊ณ ๋ฏผ๋˜๋Š” ๋ถ€๋ถ„์ด๋ผ๊ณ  ์ƒ๊ฐ๋œ๋‹ค.

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

๊ทธ๋Ÿฐ ๊ณ ๋ฏผ์„ ๊ฐ€์ง€๊ณ  ์‹œ์ž‘ํ•œ ํ•ญํ•ด ๊ณผ์ •์—์„œ, ์ฝ”์น˜๋‹˜๋“ค์€ ๋‹จ์ˆœํžˆ '์ด๋ ‡๊ฒŒ๋งŒ ํ•ด' ํ•˜๊ณ  ์ •๋‹ต์„ ๋‚ด๋ ค์ฃผ๋Š” ๊ฒŒ ์•„๋‹Œ, '์ด๋Ÿฐ ๊ด€์ ์œผ๋กœ ๋ฐ”๋ผ๋ณด๊ณ  ํ•ด๊ฒฐํ•ด๋ณด์ž.' ๋ผ๋Š” ์ œ์•ˆ์„ ๋˜์ ธ์ฃผ์—ˆ๋‹ค.

๋˜ ํฅ๋ฏธ๋กœ์› ๋˜ ์ ์€ ์ฝ”์น˜๋‹˜๋“ค๋งˆ๋‹ค๋„ ๊ฐ๊ฐ์ด ๋ฌธ์ œ๋ฅผ ๋ฐ”๋ผ๋ณด๋Š” ๋ฐฉ์‹์ด๋‚˜ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์ด ๋ชจ๋‘ ๋‹ค๋ฅด๋‹ค๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.

๊ทธ ๊ณผ์ •์—์„œ ๊นจ๋‹ฌ์€ ์ ์ด ์žˆ์—ˆ๋‹ค.

 

๊ฐœ๋ฐœ์—๋Š” ์ •๋‹ต์ด ์—†๋‹ค. ๋‹ค๋งŒ, ์ฃผ์–ด์ง„ ์‹œ์Šคํ…œ์— ๊ฐ€์žฅ ์ ํ•ฉํ•œ ์ตœ์ ์˜ ๋ฐฉ์•ˆ์„ ์„ ํƒํ•  ์ค„ ์•„๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.

 

 

์ด ์›์น™์„ ๋ฐ”ํƒ•์œผ๋กœ ์„ฑ์žฅํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ์„ธ ๊ฐ€์ง€ ๋ฐฉํ–ฅ์„ฑ์„ ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

1. ์—ฌ๋Ÿฌ ๋ฐฉ์•ˆ์„ ๋– ์˜ฌ๋ฆด ์ˆ˜ ์žˆ๋Š” ์‚ฌ๊ณ ๋ ฅ

2. ์—ฌ๋Ÿฌ ๋ฐฉ์•ˆ์„ ์‹ค์ œ๋กœ ๊ตฌํ˜„ํ•˜๊ณ , ๊ฒฐ๊ณผ๋ฅผ ๋น„๊ตํ•  ์ˆ˜ ์žˆ๋Š” ์‹คํ–‰๋ ฅ

3. ๊ทธ์ค‘ ์ตœ์ ์˜ ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ํŒ๋‹จ๋ ฅ

 

์ด๊ฑธ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ ํ•ต์‹ฌ์€ ๊ฒฐ๊ตญ ์†Œํ†ต๊ณผ ํ•™์Šต์ด์—ˆ๋‹ค.

 

 

2. ์†Œํ†ต

ํ•ญํ•ด ์ด์ „์—๋Š” ๊ฐœ๋ฐœ์ž ๋„คํŠธ์›Œํ‚น์„ ๊ฒฝํ—˜ํ•ด๋ณธ ์ ์ด ์—†์—ˆ๋‹ค. ๋งˆ๋ƒฅ ๊ฒŒ์„๋ €๋˜ ๊ฒŒ ํ•œ๋ชซํ–ˆ์ง€๋งŒ, ๋น„์ „๊ณต์ž๋กœ์„œ ๊ธฐ๋ณธ๊ธฐ๊ฐ€ ๋ถ€์กฑํ•˜๋‹ค๋ณด๋Š” ์ƒ๊ฐ์— ๊ฐ‡ํ˜€ ํ˜ผ์ž์„œ๋งŒ ๊ณต๋ถ€ํ•ด์™”์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

 

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

 

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

  • ๊ฐ์ฒด์ง€ํ–ฅ์ ์ธ ์ฝ”๋“œ๋ž€ ๋ฌด์—‡์ธ๊ฐ€?
  • ์ข‹์€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์˜ ๊ธฐ์ค€์€ ๋ฌด์—‡์ธ๊ฐ€?
  • ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋Š” ์–ด๋–ป๊ฒŒ ์‹ค๋ฌด์—์„œ ์ ์šฉ๋˜๋Š”๊ฐ€?

์ด๋Ÿฐ ์งˆ๋ฌธ๋“ค์„ ์—ฌ๋Ÿฌ ๋ฒˆ ๋˜์ ธ๋ณด๋ฉฐ ์ถ”์ƒ์ ์ด์—ˆ๋˜ ๊ฐœ๋…๋“ค์ด ์ ์  ๊ตฌ์ฒดํ™”๋˜๊ณ  ํ™•๊ณ ํ•ด์กŒ๋‹ค.

 

 

3. ๊พธ์ค€ํ•จ

10์ฃผ ๋™์•ˆ ํ•ญํ•ด๋ฅผ ํ•˜๋ฉด์„œ ๋‚ด ์ƒํ™œ ํŒจํ„ด์€ ๋‹ค์Œ๊ณผ ๊ฐ™์•˜๋‹ค.

7์‹œ๋ฐ˜ ๊ธฐ์ƒ -> 9์‹œ๋ฐ˜ ์ถœ๊ทผ -> 18์‹œ๋ฐ˜ ํ‡ด๊ทผ -> 20์‹œ ์ €๋…์‹์‚ฌ -> 20์‹œ๋ฐ˜ ํ•™์Šต -> 3์‹œ ์ทจ์นจ

 

ํšŒ์‚ฌ ์ผ์„ ๋ณ‘ํ–‰ํ•˜๋ฉฐ ๊ฐœ์ธ ๊ณผ์ œ์— ์ด๋ ‡๊ฒŒ ๊ธด ์‹œ๊ฐ„ ๋ชฐ๋‘ํ•ด ๋ณธ ๊ฒฝํ—˜์€ ์ฒ˜์Œ์ด์—ˆ๋‹ค.

0์ฃผ์ฐจ ํšŒ๊ณ ์— ์ ์—ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ, ์ด๋ฒˆ ๊ณผ์ •์—์„œ '๋‚ด๊ฐ€ ๋…ธ๋ ฅํ•œ ๋งŒํผ ์–ป์–ด๊ฐˆ ๊ฒƒ'์ด๋ผ๋Š” ์ƒ๊ฐ์œผ๋กœ ์ž„ํ–ˆ๊ณ , ์ฃผ์–ด์ง„ ๊ณผ์ œ์— ํ—ˆํˆฌ๋ฃจ ์‹œ๊ฐ„์„ ์“ฐ์ง€ ์•Š์œผ๋ ค ํ–ˆ๋‹ค. ์ฃผ์ฐจ๋ณ„ ์ฃผ์–ด์ง„ ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ์— ๋ชฐ๋‘ํ–ˆ์ง€๋งŒ, ๊ทธ๊ฒƒ๋„ ์‹œ๊ฐ„์ด ๋Š˜ ๋ถ€์กฑํ•˜๋‹ค๊ณ  ๋А๊ปด์งˆ ์ •๋„์˜€๋‹ค.

 

๊ทธ๋Ÿผ์—๋„ 10์ฃผ๋™์•ˆ์˜ ๋ฐ˜๋ณต์ ์ธ ํ•™์Šต๊ณผ ๋ชฐ์ž… ๋•๋ถ„์— ํ•œ ๊ฐ€์ง€ ํฐ ๊นจ๋‹ฌ์Œ์„ ์–ป์—ˆ๋‹ค.

๋‚ด๊ฐ€ ์Šค์Šค๋กœ์˜ ์‹œ๊ฐ„์„ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๊ณ , ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์„์ง€ ๊ฐ์ด ์žกํ˜”๋‹ค.

'๊พธ์ค€ํ•จ'์ด์•ผ๋ง๋กœ ์„ฑ์žฅ์˜ ์ง€๋ฆ„๊ธธ์ž„์„ ๊นจ๋‹ฌ์„ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

 



 

 

 

์ˆ˜๋ฃŒ ์ดํ›„

1. ์ธ๊ฐ„์€ ๋ง๊ฐ์˜ ๋™๋ฌผ, 10์ฃผ ๊ณผ์ •์„ ๋ณต์Šตํ•˜์ž!

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

 

 

2. ์ฝ”์น˜๋‹˜๋“ค์—๊ฒŒ ์ด๋ ฅ์„œ ํ”ผ๋“œ๋ฐฑ ๋ฐ›๊ธฐ

์ˆ˜๋ฃŒ ์ดํ›„, 3๋ช…์˜ ์ฝ”์น˜๋‹˜์„ ์ฐพ์•„๊ฐ€ ์ด๋ ฅ์„œ์— ๋Œ€ํ•œ ํ”ผ๋“œ๋ฐฑ์„ ์ง์ ‘ ๋ฐ›์„ ๊ธฐํšŒ๊ฐ€ ์žˆ์—ˆ๋‹ค.

 

 

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

 

๋ฌด์‹ ์‚ฌ ์‚ฌ์˜ฅ ๋ฐฉ๋ฌธ!



 

 

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

 

ํ† ํˆฌ ์ฝ”์น˜๋‹˜๊ณผ ํŒ€์›๋“ค



 

 

3. ๊ณ„์†๋˜๋Š” ์†Œํ†ต๊ณผ ํ•™์Šต ํ™˜๊ฒฝ ๋งŒ๋“ค๊ธฐ

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

 

 

 

 

 

๋งˆ์น˜๋ฉฐ

์ •์‹ ์—†์ด ์ง€๋‚˜๊ฐ„ 10์ฃผ. ํž˜๋“ค์—ˆ์ง€๋งŒ ๋™๊ธฐ๋“ค๊ณผ ํ•จ๊ป˜์—ฌ์„œ ์žฌ๋ฐŒ์—ˆ๊ณ , ๊ธฐ์–ต์— ๋‚จ๋Š” ์ˆœ๊ฐ„๋“ค์ด ๋งŽ์•˜๋‹ค.
์ด ๊ณผ์ •์„ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ๊ฐ•์š”ํ•˜๊ณ  ์‹ถ์ง„ ์•Š์ง€๋งŒ, ์•„๋ž˜ ์กฐ๊ฑด์— ํ•ด๋‹นํ•˜๋Š” ์‚ฌ๋žŒ(= ์ˆ˜๊ฐ• ์ „์˜ ๋‚˜)์ด๋ผ๋ฉด ๊ผญ ๊ฒฝํ—˜ํ•ด๋ณด๊ธธ ์ถ”์ฒœํ•˜๋ฉฐ ์ด ๊ธ€์„ ๋งˆ์นœ๋‹ค. 

 

  1. ๋น„์ „๊ณต์ž๋กœ, ์ „๊ณต์ž ๊ฐœ๋ฐœ์ž์™€ ์†Œํ†ตํ•˜๋ฉฐ ์ž์‹ ์˜ ์ˆ˜์ค€์„ ์ ๊ฒ€ํ•˜๊ณ  ์‹ถ์€ ์‚ฌ๋žŒ
  2. ํšŒ์‚ฌ์— ๋‹ค๋‹ˆ๊ณ  ์žˆ์ง€๋งŒ ๋น„์Šทํ•œ ์—ฐ์ฐจ์˜ ๊ฐœ๋ฐœ์ž๋“ค๊ณผ ๋น„๊ตํ•ด ์ž์‹ ์˜ ์‹ค๋ ฅ์„ ํ™•์ธํ•ด๋ณด๊ณ  ์‹ถ์€ ์‚ฌ๋žŒ
  3. ๋น…ํ…Œํฌ ๊ธฐ์—…์ด๋‚˜ ๋‹ค๋ฅธ ๋ถ„์•ผ์˜ ๊ธฐ์—…์œผ๋กœ ์ด์ง์„ ํฌ๋งํ•˜๋‚˜, ํ˜„์žฌ ํšŒ์‚ฌ์˜ ๊ธฐ์ˆ  ์Šคํƒ์ด ๋‹ฌ๋ผ ๊ณ ๋ฏผ ์ค‘์ธ ์‚ฌ๋žŒ
  4. ํ˜ผ์ž ๊ณต๋ถ€ ์ค‘์ด์ง€๋งŒ ์–ด๋””์„œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์•ผ ํ• ์ง€ ๋ชฐ๋ผ ๋ง‰๋ง‰ํ•œ ์‚ฌ๋žŒ

 

 

 

 

 


 

 

๋ณ„์ฒจ: ์ฃผ์ฐจ ๋ณ„ ํšŒ๊ณ 

๋‚˜์ค‘์— 10์ฃผ ๊ณผ์ • ๋•Œ ์–ด๋–ค ๊ฑธ ๋ฐฐ์šฐ๊ณ  ๋А๊ผˆ๋Š” ์ง€ ๊ธฐ์–ตํ•˜๊ธฐ ์œ„ํ•ด ์ฃผ์ฐจ๋ณ„๋กœ ํ‚ค์›Œ๋“œ์™€ ๊ณ ๋ฏผํ–ˆ๋˜ ์ ๋“ค์„ ์ •๋ฆฌํ•ด๋ณด์•˜๋‹ค.

 

 

1์ฃผ์ฐจ

๋”๋ณด๊ธฐ

์ฃผ์ œ

ํฌ์ธํŠธ ์ถฉ์ „, ์‚ฌ์šฉ์— ๋Œ€ํ•œ ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ์™€ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ

 

ํ‚ค์›Œ๋“œ

TDD, Lock, ํ…Œ์ŠคํŠธ ์ฝ”๋“œ

 

ํ•™์Šต๋‚ด์šฉ

 

1. ReentrantLock์„ ์‚ฌ์šฉํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ Lock ์ ์šฉ

์ฒซ ๊ณผ์ œ์—์„œ๋Š” ํฌ์ธํŠธ ์ถฉ์ „, ์‚ฌ์šฉ ์‹œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Lock์„ ๋„์ž…ํ•˜๋Š” ๊ณผ์ œ๊ฐ€ ์ฃผ์–ด์กŒ๋‹ค.

์ด ๊ณผ์ œ์—์„œ๋Š” ๋ฉ€ํ‹ฐ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ณ ๋ คํ•˜์ง€ ์•Š์•„๋„ ๋˜์—ˆ๊ธฐ์—, DB ๋ ˆ๋ฒจ Lock ๋Œ€์‹  Application ๋ ˆ๋ฒจ์˜ Lock์„ ์‚ฌ์šฉํ•ด ๋™์‹œ ์ ‘๊ทผ์„ ์ œ์–ดํ–ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ์˜ ํ๋ฆ„๊ณผ ์ž‘๋™ ์›๋ฆฌ๋ฅผ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜๋Š” ReentrantLock์„ ํ™œ์šฉํ•œ ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ ์ฝ”๋“œ์ด๋‹ค.

@Component
class UserLockManager : IUserLockManager {
    private val lockMap: ConcurrentHashMap<Long, ReentrantLock> = ConcurrentHashMap()

    override fun <T> executeWithLock(
        userId: Long,
        action: () -> T,
    ): T {
        val lock = lockMap.computeIfAbsent(userId) { ReentrantLock() }

        if (!lock.tryLock(10, TimeUnit.SECONDS)) {
            throw LockAcquisitionException("๋ฝ ํš๋“์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. userId: $userId")
        }

        return try {
            action()
        } finally {
            lock.unlock()
        }
    }
}

 

 

์ด ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•ด ๋ฌธ์„œ๋กœ ์ž‘์„ฑํ•œ ์ž๋ฃŒ๋ฅผ ์ฝ”์น˜๋‹˜๊ป˜ ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์„ ๊ธฐํšŒ๊ฐ€ ์žˆ์—ˆ๋‹ค.

 

"๋™์‹œ์„ฑ ์ด์Šˆ์— ๋Œ€ํ•œ ๊ณต๋ถ€๋ฅผ ๋งŽ์ด ํ•˜์…จ๋„ค์š”. ์ž๋ฃŒ๊ฐ€ ๋ณด๊ธฐ ์ข‹์•„์š” ์ดํ•ด๊ฐ€ ์™์™ ๋ฉ๋‹ˆ๋‹ค."
- ๋ฒ„ํ‚ทํ”Œ๋ ˆ์ด์Šค ์ด์„๋ฒ” ์ฝ”์น˜๋‹˜

 

์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋กœ๋ถ€ํ„ฐ ์ด๋Ÿฐ ํ”ผ๋“œ๋ฐฑ์„ ๋“ค์„ ์ˆ˜ ์žˆ์—ˆ๋˜ ๊ฑด ๊ฐ’์ง„ ๊ฒฝํ—˜์ด์—ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ ์ž‘์„ฑํ–ˆ๋˜ ์ž๋ฃŒ๋Š” ๋‹ค๋“ฌ์–ด์„œ ๋ธ”๋กœ๊ทธ์— ์žฌ์ž‘์„ฑํ–ˆ๋‹ค.

 

์•„๋ž˜๋Š” ๋ธ”๋กœ๊ทธ๋กœ ์˜ฎ๊ธด ๊ธ€ ๋งํฌ์ด๋‹ค

 

๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ ์‰ฝ๊ฒŒ ์ดํ•ดํ•˜๊ธฐ (synchronized, reentrantLock)

1. ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ๋Š” ์™œ ํ•˜๋Š”๊ฑธ๊นŒ?๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š์œผ๋ฉด ์œ ์ €๊ฐ€ ํฌ์ธํŠธ๋ฅผ ๋™์‹œ์— ์ถฉ์ „ ๋˜๋Š” ์‚ฌ์šฉํ•  ๋•Œ ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ฐ™์€ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•˜๋ฉด์„œ ๋ฐœ์ƒ

eastshine12.tistory.com

 

 

 

2. ํ…Œ์ŠคํŠธ ๋”๋ธ”์„ ์‚ฌ์šฉํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์—๋Š” ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ๋ฉ”์„œ๋“œ์˜ ๋…๋ฆฝ์„ฑ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด Mock๊ณผ Stub ๊ฐ์ฒด๋ฅผ ํ™œ์šฉํ–ˆ๋‹ค.

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

 

์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜ checkUserExists ๋ฉ”์„œ๋“œ๋Š” ์œ ์ €๋ฅผ ์ฐพ์ง€ ๋ชปํ•˜๋ฉด ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ํฌํ•จํ•˜๊ณ  ์žˆ๋‹ค

fun checkUserExists(userId: Long): User {
    return userRepository.findByIdOrNull(userId)
        ?: throw CoreException(
            errorType = ErrorType.USER_NOT_FOUND,
            details =
                mapOf(
                    "userId" to userId,
                ),
        )
}

 

์œ„ ๋กœ์ง์„ ํ…Œ์ŠคํŠธํ•  ๋•Œ๋Š” ์™ธ๋ถ€ ์˜์กด์„ฑ์ธ userRepository๋ฅผ Mock์œผ๋กœ ๋Œ€์ฒดํ•ด ์ˆœ์ˆ˜ ๋น„์ฆˆ๋‹ˆ์Šค๋ฅผ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋‹ค.

 

class UserServiceTest {
    private val userRepository = mockk<UserRepository>(relaxed = true)
    private val balanceHistoryRepository = mockk<BalanceHistoryRepository>(relaxed = true)
    private val userService = UserService(userRepository, balanceHistoryRepository)

    @Test
    fun `should return user by id`() {
        // given
        val userId = 1L
        val user = mockk<User>()
        every { userRepository.findByIdOrNull(userId) } returns user

        // when
        val result = userService.checkUserExists(userId)

        // then
        assertEquals(user, result)
    }

    @Test
    fun `must throw exception when user not found`() {
        // given
        val userId = 1L
        every { userRepository.findByIdOrNull(userId) } returns null

        // when / then
        assertThrows<CoreException> {
            userService.checkUserExists(userId)
        }
    }
 }

 

 

 

๊ณ ๋ฏผ ํฌ์ธํŠธ

 

1. ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ์˜ ์ž‘์„ฑ ๋ฒ”์œ„

  • ๋‹จ์œ„ ํ…Œ์ŠคํŠธ: ๋ฉ”์„œ๋“œ ์ˆ˜์ค€์˜ ์„ธ๋ถ€์ ์ธ ๋กœ์ง ๊ฒ€์ฆ → ํ™”์ดํŠธ๋ฐ•์Šค ํ…Œ์ŠคํŠธ
  • ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ: ์—ฌ๋Ÿฌ ์„œ๋น„์Šค ๊ฐ„์˜ ๊ฒฐํ•ฉ์„ ๊ฒ€์ฆํ•˜๋ฉฐ ์ „์ฒด usecase๋ฅผ ํ™•์ธ → ๋ธ”๋ž™๋ฐ•์Šค ํ…Œ์ŠคํŠธ

 

2. ์ข‹์€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ž€?

  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ์š”๊ตฌ์‚ฌํ•ญ์„ ๋ช…ํ™•ํžˆ ๋‚˜์—ดํ•œ ๋ฌธ์„œ์™€ ๊ฐ™๋‹ค.
  • ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์ด ์–ด๋ ค์šด ์ฝ”๋“œ๋Š” ์„ค๊ณ„์˜ ๋ฌธ์ œ์ผ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๋‹ค. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ์„ ํ†ตํ•ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊ฐ์ฒด์ง€ํ–ฅ ์„ค๊ณ„๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์Œ์„ ์ฒด๊ฐํ–ˆ๋‹ค.

 

3. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๋„ค์ด๋ฐ

 

  • Kotlin์—์„œ๋Š” ๋ฐฑํ‹ฑ(`)์„ ํ™œ์šฉํ•ด ๋ฉ”์„œ๋“œ๋ช…์„ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ฝ”์น˜๋‹˜๋“ค๋งˆ๋‹ค ์„ ํ˜ธํ•˜๋Š” ๋ฐฉ์‹์ด ๋‹ฌ๋ž์ง€๋งŒ, must, should์™€ ๊ฐ™์€ ๊ฐ•ํ•œ ์–ด์กฐ๋ฅผ ์„ ํ˜ธํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์š”๊ตฌ์‚ฌํ•ญ์„ ์„ค๋ช…ํ•˜๋Š” ๋ฌธ์„œ๋ผ๋Š” ์ ์—์„œ, ํ•œ๊ธ€๊ณผ ์˜์–ด ์ค‘ ์–ด๋А ์ชฝ์ด๋“  ๋ช…ํ™•ํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด ํ‘œํ˜„์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค๊ณ  ๋А๊ผˆ๋‹ค.

 

์ถ”๊ฐ€

 

1. ๋‹น์‹œ ํšŒ๊ณ ๊ธ€

 

TDD, ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ - ํ•ญํ•ด ํ”Œ๋Ÿฌ์Šค ํšŒ๊ณ  (1์ฃผ์ฐจ)

๋“ค์–ด๊ฐ€๋ฉฐ ๋“œ๋””์–ด 1์ฃผ ์ฐจ๊ฐ€ ๋๋‚ฌ๋‹ค..! (์ด์ œ 10ํผ ํ–ˆ๋‹ค..) ์ •๋ง ์ง€๋‚œ ํ•œ ์ฃผ๋Š” ๋‚ด ๋จธ๋ฆฌ๋กœ ๋“ค์–ด์˜ค๋Š” ์ธํ’‹ ๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋งˆ์–ด๋งˆํ•œ ์ผ์ฃผ์ผ์ด์—ˆ๋‹ค.์ง€๋‚œ ํ† ์š”์ผ, ์˜คํ”„๋ผ์ธ ์„ธ์…˜ ๋ชจ์ž„์—์„œ 10์ฃผ๋™์•ˆ ํ•จ๊ป˜ ํ• 

eastshine12.tistory.com

 

2. ํ”„๋กœ์ ํŠธ ์†Œ์Šค

 

GitHub - eastshine12/hhplus-tdd-jvm: Point ์„œ๋น„์Šค / ํ•ญํ•ด ํ”Œ๋Ÿฌ์Šค 1์ฃผ์ฐจ / TDD & Concurrency Control

Point ์„œ๋น„์Šค / ํ•ญํ•ด ํ”Œ๋Ÿฌ์Šค 1์ฃผ์ฐจ / TDD & Concurrency Control - eastshine12/hhplus-tdd-jvm

github.com

 

 


 

2์ฃผ์ฐจ

๋”๋ณด๊ธฐ

์ฃผ์ œ

์„ ์ฐฉ์ˆœ ํŠน๊ฐ•์‹ ์ฒญ ์„œ๋น„์Šค

 

ํ‚ค์›Œ๋“œ

ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜, ๋ ˆ์ด์–ด๋“œ ์•„ํ‚คํ…์ฒ˜, DB Lock

 

ํ•™์Šต๋‚ด์šฉ

 

1. ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜์™€ ๋ ˆ์ด์–ด๋“œ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ ์šฉ

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

์ธํ”„๋ผ์ŠคํŠธ๋Ÿญ์ฒ˜ ๋ ˆ์ด์–ด์—์„œ๋Š” JPA ๋ ˆํŒŒ์ง€ํ† ๋ฆฌ ๊ตฌํ˜„์ฒด๋ฅผ ๋งŒ๋“ค์–ด ๋„๋ฉ”์ธ์— ์˜์กด๋˜์ง€ ์•Š๋Š” ํ˜•์‹์„ ์ทจํ–ˆ๋‹ค.

 

 

2. DB ๋ ˆ๋ฒจ์—์„œ์˜ Lock์„ ๊ตฌํ˜„

์„ ์ฐฉ์ˆœ์œผ๋กœ ํŠน๊ฐ• ์‹ ์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด DB Lock์„ ์‚ฌ์šฉํ–ˆ๋‹ค.

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

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT l FROM Lecture l WHERE l.id= :lectureId")
fun findByIdOrNullWithLock(lectureId: Long): Lecture?

 

 

๊ณ ๋ฏผ ํฌ์ธํŠธ

 

1. ๊ณ„์ธต๊ฐ„์˜ DTO ๋ถ„๋ฆฌ

๊ณ„์ธต ๊ฐ„์˜ ๋‹จ๋ฐฉํ–ฅ ์˜์กด์„ฑ์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” DTO๋ฅผ ๊ฐ ๊ณ„์ธต๋ณ„๋กœ ์ •์˜ํ–ˆ๋‹ค.

  • ๊ณ ๋ฏผ
    ๋„๋ฉ”์ธ ๋ ˆ์ด์–ด์—์„œ ์ •์˜ํ•œ ๊ฐ์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ์ด์–ด์™€ ์ธํ„ฐํŽ˜์ด์Šค ๋ ˆ์ด์–ด์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€ ์•Š์„๊นŒ? ๊ณ„์ธต๋งˆ๋‹ค DTO๋ฅผ ๋”ฐ๋กœ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š” ์ด์œ ๊ฐ€ ๋ฌด์—‡์ธ์ง€ ๊ถ๊ธˆํ–ˆ๋‹ค.
  • ํ•ด๊ฒฐ
    ๊ฐ ๋ ˆ์ด์–ด์˜ ์—ญํ• ๊ณผ ์ฑ…์ž„์— ๋งž๊ฒŒ DTO๋ฅผ ๋ถ„๋ฆฌํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒฐ๋ก ์— ๋„๋‹ฌํ–ˆ๋‹ค.
    • ๋„๋ฉ”์ธ ๋ ˆ์ด์–ด: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ค๋ฃจ๋Š” ํ’๋ถ€ํ•œ ๊ฐ์ฒด
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ์ด์–ด: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณต
    • ์ธํ„ฐํŽ˜์ด์Šค ๋ ˆ์ด์–ด: HTTP ์‘๋‹ต์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ํฌํ•จ
    ์ฆ‰, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ์ด์–ด์˜ Facade์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ์ฒด๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ๊ฒฐ๊ณผ๋ฅผ ๋‹ด๊ณ , ์ธํ„ฐํŽ˜์ด์Šค ๋ ˆ์ด์–ด์˜ ์ปจํŠธ๋กค๋Ÿฌ๋Š” ๊ทธ ์ค‘ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•˜์—ฌ HTTP ์‘๋‹ต ๊ฐ’์œผ๋กœ ์ „๋‹ฌํ•œ๋‹ค.
  • ๊ฒฐ๋ก : ๊ฐ ๊ณ„์ธต๋งˆ๋‹ค DTO๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์„ค๊ณ„์˜ ๋ช…ํ™•์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋†’์ธ๋‹ค.

 

2. TDD์˜ ํ™œ์šฉ ๋นˆ๋„

TDD(Test-Driven Development)๋Š” ๋กœ์ง์˜ ์•ˆ์ •์„ฑ๊ณผ ์„ค๊ณ„์˜ ๊ฒฌ๊ณ ์„ฑ์„ ๋†’์ด์ง€๋งŒ, ๋ชจ๋“  ์ƒํ™ฉ์—์„œ ์ ์šฉํ•˜๊ธฐ๋ž€ ์‰ฝ์ง€ ์•Š์•˜๋‹ค.

  • ๊ณ ๋ฏผ
    ๋ณต์žกํ•œ ๋กœ์ง์„ ์ž‘์„ฑํ•  ๋•Œ๋Š” ๋กœ์ง ๊ตฌํ˜„ ์ž์ฒด๊ฐ€ ์–ด๋ ค์›Œ TDD๋ฅผ ์ ์šฉํ•  ์—ฌ์œ ๊ฐ€ ์—†์—ˆ๋‹ค. ๊ฒฐ๊ตญ ๋‹จ์ˆœํ•œ ๋กœ์ง์—๋งŒ TDD๋ฅผ ์ ์šฉํ•˜๊ณ , ๋ณต์žกํ•œ ๋กœ์ง์€ TLD(Test Last Development) ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜๊ฒŒ ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•˜๋‹ค.
  • ํ•ด๊ฒฐ
    ์˜คํžˆ๋ ค ๋ณต์žกํ•œ ๋กœ์ง์ผ์ˆ˜๋ก TDD์˜ ์œ„๋ ฅ์„ ๋ฐœํœ˜ํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์„ ๊นจ๋‹ฌ์•˜๋‹ค.
    • ์ƒ์†Œํ•˜๊ฑฐ๋‚˜ ๋ณต์žกํ•œ ๋กœ์ง์ผ์ˆ˜๋ก, ์š”๊ตฌ์‚ฌํ•ญ์„ ํ…Œ์ŠคํŠธ๋กœ ์ •์˜ํ•˜๋ฉฐ ๊ตฌํ˜„ํ•˜๋ฉด ๋”์šฑ ์•ˆ์ •์ ์ธ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
    • ๋ฐ˜๋ฉด, ๋‹จ์ˆœ CRUD์™€ ๊ฐ™์€ ์ต์ˆ™ํ•œ ๋กœ์ง์—์„œ๋Š” TLD ๋ฐฉ์‹์„ ํ™œ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
  • ๊ฒฐ๋ก : TDD๋Š” ์ƒํ™ฉ์— ๋”ฐ๋ผ ์ ์žฌ์ ์†Œ์— ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.

 

 

์ถ”๊ฐ€

 

๋‹น์‹œ ํšŒ๊ณ ๊ธ€

 

Clean Architecture - ํ•ญํ•ด ํ”Œ๋Ÿฌ์Šค ํšŒ๊ณ  (2์ฃผ์ฐจ)

๋“ค์–ด๊ฐ€๋ฉฐ ์ด๋ฒˆ 2์ฃผ ์ฐจ ํ•ญํ•ด๋„ ๋ฌด์‚ฌํžˆ ๋งˆ์ณค๋‹ค. ์ด๋ฒˆ ๊ณผ์ œ์—์„œ๋Š” ์•„ํ‚คํ…์ฒ˜ ๊ตฌ์กฐ ์„ค๊ณ„์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ ์ค‘์ ์ ์œผ๋กœ ๊ณต๋ถ€ํ•˜์˜€๋‹ค.๊ทธ๋™์•ˆ ์ฃผ๋กœ 3-tier ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ฃผ๋กœ ์‚ฌ์šฉํ•ด ์™”์—ˆ๋Š”๋ฐ, ์ด๋ฒˆ์—๋Š”ํด๋ฆฐ ์•„

eastshine12.tistory.com

 

 

ํ”„๋กœ์ ํŠธ ์†Œ์Šค

 

GitHub - eastshine12/hhplus-lecture: ํŠน๊ฐ• ์‹ ์ฒญ ์„œ๋น„์Šค / ํ•ญํ•ด ํ”Œ๋Ÿฌ์Šค 2์ฃผ์ฐจ / Clean Architecture

ํŠน๊ฐ• ์‹ ์ฒญ ์„œ๋น„์Šค / ํ•ญํ•ด ํ”Œ๋Ÿฌ์Šค 2์ฃผ์ฐจ / Clean Architecture. Contribute to eastshine12/hhplus-lecture development by creating an account on GitHub.

github.com

 

 


 

3~5์ฃผ์ฐจ

๋”๋ณด๊ธฐ

 

6์ฃผ์ฐจ

๋”๋ณด๊ธฐ

์ฃผ์ œ

์‹œ๋‚˜๋ฆฌ์˜ค ๋‚ด์— ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋™์‹œ์„ฑ ์ด์Šˆ ์ œ์–ด ๋ฐฉ์‹ ๋น„๊ต ๋ฐ ์„ ์ •

 

ํ‚ค์›Œ๋“œ

x-lock, ๋‚™๊ด€์  ๋ฝ, ๋น„๊ด€์  ๋ฝ, ๋ถ„์‚ฐ ๋ฝ

 

ํ•™์Šต๋‚ด์šฉ

 

1. ๋™์‹œ์„ฑ ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๋กœ์ง์—์„œ ๋‚™๊ด€์  ๋ฝ, ๋น„๊ด€์  ๋ฝ, ๋ถ„์‚ฐ ๋ฝ ์ ์šฉ

 

์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค์—์„œ Lock ์„ฑ๋Šฅ ๋น„๊ตํ•ด๋ณด๊ธฐ (feat. ๋‚™๊ด€์  ๋ฝ, ๋น„๊ด€์  ๋ฝ, ๋ถ„์‚ฐ ๋ฝ)

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

eastshine12.tistory.com

 

 

๊ณ ๋ฏผ ํฌ์ธํŠธ

 

1. ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ์„ค์ •

DB ๋ฝ(H2)๊ณผ Redis๋ฅผ ์‚ฌ์šฉํ•œ ๋ถ„์‚ฐ ๋ฝ์˜ ์„ฑ๋Šฅ ๋น„๊ต๋ฅผ ์ง„ํ–‰ํ•˜๋ ค๊ณ  ํ–ˆ์œผ๋‚˜, ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์˜ ์ฐจ์ด๋กœ ์ธํ•ด ๊ณ ๋ฏผ์ด ์ƒ๊ฒผ๋‹ค.

  • H2 DB ๋ฝ: ์Šคํ”„๋ง ๋ถ€ํŠธ์™€ ๋™์ผ ํ”„๋กœ์„ธ์Šค์—์„œ ์‹คํ–‰๋˜์–ด ๋„คํŠธ์›Œํฌ๋ฅผ ํƒ€์ง€ ์•Š๋Š”๋‹ค.
  • Redis ๋ถ„์‚ฐ ๋ฝ: ๋กœ์ปฌ ์ปจํ…Œ์ด๋„ˆ์—์„œ ์‹คํ–‰๋˜๋ฉฐ, ๋„คํŠธ์›Œํฌ๋ฅผ ํƒ€๋Š” ๋ฐฉ์‹์ด๋‹ค.

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

๊ฒฐ๋ก ์ ์œผ๋กœ, DB์™€ Redis ๋ชจ๋‘ ์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ํ•˜์—ฌ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ์ผ๊ด€๋˜๊ฒŒ ์„ค์ •ํ–ˆ๋‹ค.

 

2. ๋ถ„์‚ฐ๋ฝ AOP์™€ @Transactional ์˜ ์‹คํ–‰ ์ˆœ์„œ

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

 

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

 

๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜ ์„ธ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ–ˆ๋‹ค.

  • ๋ฐฉ๋ฒ• 1: ํŠธ๋žœ์žญ์…˜ ๋ฉ”์„œ๋“œ ์ƒ์œ„(e.g., Facade)์—์„œ Lock์„ ๊ฑฐ๋Š” ๋ฐฉ์‹
  • ๋ฐฉ๋ฒ• 2: Lock AOP์˜ @Order๋ฅผ ๋‚ฎ์€ ๊ฐ’์œผ๋กœ ์„ค์ •ํ•ด, ํŠธ๋žœ์žญ์…˜๋ณด๋‹ค ๋จผ์ € ์‹คํ–‰๋˜๋„๋ก ํ•˜๋Š” ๋ฐฉ์‹
  • ๋ฐฉ๋ฒ• 3: Lock AOP ๋‚ด๋ถ€์—์„œ REQUIRES_NEW ํŠธ๋žœ์žญ์…˜ ์†์„ฑ์„ ์‚ฌ์šฉํ•ด, ๋…๋ฆฝ์ ์ธ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ์‹คํ–‰๋˜๋„๋ก ์„ค์ •ํ•˜๋Š” ๋ฐฉ์‹

์ตœ์ข…์ ์œผ๋กœ ๋ฐฉ๋ฒ• 2๋ฅผ ์„ ํƒํ–ˆ๋‹ค.


Lock AOP์˜ @Order๋ฅผ ๋‚ฎ๊ฒŒ ์„ค์ •ํ•˜๋ฉด, ์Šคํ”„๋ง ํ”„๋ ˆ์ž„์›Œํฌ์˜ ์–ด๋“œ๋ฐ”์ด์Šค ์‹คํ–‰ ์ˆœ์„œ ๊ทœ์น™์— ๋”ฐ๋ผ ํŠธ๋žœ์žญ์…˜๋ณด๋‹ค ๋จผ์ € ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • @Transactional: Ordered.LOWEST_PRECEDENCE๋กœ ์„ค์ •๋˜์–ด ์žˆ์–ด, ๋ชจ๋“  AOP ์–ด๋“œ๋ฐ”์ด์Šค ์ค‘ ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰์— ์‹คํ–‰๋œ๋‹ค.
  • Lock AOP: @Order๋ฅผ Ordered.HIGHEST_PRECEDENCE์œผ๋กœ ์„ค์ •ํ•˜๋ฉด, ํŠธ๋žœ์žญ์…˜๋ณด๋‹ค ๋จผ์ € ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋‹ค.

 

 

 

ํ”„๋กœ์ ํŠธ ์†Œ์Šค

 

GitHub - eastshine12/concert-reservation: ์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค / ํ•ญํ•ดํ”Œ๋Ÿฌ์Šค / Queue, Token, Concurrency

์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค / ํ•ญํ•ดํ”Œ๋Ÿฌ์Šค / Queue, Token, Concurrency. Contribute to eastshine12/concert-reservation development by creating an account on GitHub.

github.com

 

 


 

7์ฃผ์ฐจ

๋”๋ณด๊ธฐ

์ฃผ์ œ

Redis๋ฅผ ์‚ฌ์šฉํ•œ ์บ์‹œ ์ ์šฉ ๋ฐ ๋Œ€๊ธฐ์—ด ๋กœ์ง ์ด๊ด€

 

ํ‚ค์›Œ๋“œ

Redis, ์บ์‹œ, ๋Œ€๊ธฐ์—ด

 

ํ•™์Šต๋‚ด์šฉ

 

1. ์บ์‹œ ์ ์šฉ ๊ธฐ์ค€์„ ์„ธ์šฐ๊ณ  ์‹ค์ œ ๋กœ์ง์— ์บ์‹œ ์ ์šฉ ๋ฐ ๋Œ€๊ธฐ์—ด ๋กœ์ง์„ Redis๋กœ ์ด๊ด€

 

Redis ๊ธฐ๋ฐ˜์˜ ์บ์‹ฑ ๋ฐ ๋Œ€๊ธฐ์—ด ๊ด€๋ฆฌ๋ฅผ ํ†ตํ•œ ์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค ์„ฑ๋Šฅ ๊ฐœ์„ 

๋“ค์–ด๊ฐ€๋ฉฐ์ด๋ฒˆ ์‹œ๊ฐ„์—๋Š” ์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค์˜ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด ํ˜„์žฌ ์‹œ๋‚˜๋ฆฌ์˜ค์˜ ์กฐํšŒ API ์ค‘ ์บ์‹ฑ์„ ์ ์šฉํ•  ๋ถ€๋ถ„์— ๋Œ€ํ•ด ๊ณ ๋ฏผํ•ด ๋ณด๊ณ , ๊ธฐ์กด RDB์—์„œ ์ž‘๋™๋˜๊ณ  ์žˆ๋˜ ๋Œ€๊ธฐ์—ด ๋กœ์ง์„ Redis๋กœ ์ด

eastshine12.tistory.com

 


 

๊ณ ๋ฏผ ํฌ์ธํŠธ

 

1. Redis ๋Œ€๊ธฐ์—ด ๊ด€๋ฆฌ ๋ฐฉ์‹

๋Œ€๊ธฐ์—ด์„ WaitingToken(๋Œ€๊ธฐ ํ† ํฐ)๊ณผ ActiveToken(์˜ˆ์•ฝ ๊ฐ€๋Šฅํ•œ ํ™œ์„ฑ ํ† ํฐ)์œผ๋กœ ๋‚˜๋ˆ„์–ด Sorted Set(ZSet)์œผ๋กœ ๊ด€๋ฆฌํ–ˆ๋Š”๋ฐ, ๊ฐ ํ† ํฐ ์ƒํƒœ ์กฐํšŒ ์‹œ ๋‘ ๊ฐœ์˜ ZSet์„ ๋ชจ๋‘ ํƒ์ƒ‰ํ•ด์•ผ ํ•˜๋Š” ๋น„ํšจ์œจ์ด ๋ฐœ์ƒํ–ˆ๋‹ค.

  • ํ•ด๊ฒฐ
    ๊ฐ ํ† ํฐ ์ •๋ณด๋ฅผ Hash ๊ตฌ์กฐ๋กœ ์ „ํ™˜ํ•˜์—ฌ, ํ† ํฐ์„ key๋กœ ํ•˜๊ณ  ์ฝ˜์„œํŠธ ์Šค์ผ€์ค„ ID, ์ƒํƒœ, ๋งŒ๋ฃŒ์ผ์ž ๋“ฑ์„ value๋กœ ์ €์žฅํ–ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋‹จ์ผ ์กฐํšŒ๋กœ ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๊ฒŒ ํ–ˆ๋‹ค.

 

  • ๊ฒฐ๋ก 
    Hash๋ฅผ ํ™œ์šฉํ•œ ์„ค๊ณ„ ๋ณ€๊ฒฝ์œผ๋กœ ํ† ํฐ ์ƒํƒœ ์กฐํšŒ ์„ฑ๋Šฅ์ด ํ–ฅ์ƒ๋˜์–ด, ๋Œ€๊ธฐ์—ด ๊ด€๋ฆฌ๊ฐ€ ๋”์šฑ ํšจ์œจ์ ์œผ๋กœ ์ด๋ฃจ์–ด์กŒ๋‹ค.

 

 

 

ํ”„๋กœ์ ํŠธ ์†Œ์Šค

 

GitHub - eastshine12/concert-reservation: ์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค / ํ•ญํ•ดํ”Œ๋Ÿฌ์Šค / Queue, Token, Concurrency

์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค / ํ•ญํ•ดํ”Œ๋Ÿฌ์Šค / Queue, Token, Concurrency. Contribute to eastshine12/concert-reservation development by creating an account on GitHub.

github.com

 


 

8์ฃผ์ฐจ

๋”๋ณด๊ธฐ

์ฃผ์ œ

์ž์ฃผ ์กฐํšŒ๋˜๋Š” ์ฟผ๋ฆฌ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์ธ๋ฑ์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ ์„ฑ๋Šฅ ๊ฐœ์„ 

 

ํ‚ค์›Œ๋“œ

์ธ๋ฑ์Šค

 

ํ•™์Šต๋‚ด์šฉ

1. ์ขŒ์„ ์กฐํšŒ ์ฟผ๋ฆฌ์— ์—ฌ๋Ÿฌ ๋ฐฉ์‹์˜ ์ธ๋ฑ์Šค๋ฅผ ๊ฑธ์–ด๋ณด๊ณ  ์ตœ์ ์˜ ์ธ๋ฑ์Šค ์‚ฌ์šฉ

ํ•ด๋‹น ์ฃผ์ฐจ์˜ ๊ด€๋ จ ๋‚ด์šฉ์„ ์•„๋ž˜ ๊ธ€์— ์ž‘์„ฑํ•˜์˜€๋‹ค.

 

์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค์˜ ์ธ๋ฑ์Šค ์„ค๊ณ„์™€ ์„ฑ๋Šฅ ๋น„๊ต

๋“ค์–ด๊ฐ€๋ฉฐ ์ด๋ฒˆ ๊ธ€์—์„œ ์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค์˜ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ์‚ฌ์šฉ๋œ ์ฟผ๋ฆฌ๋ฅผ ๋ถ„์„ํ•˜๊ณ , ์ธ๋ฑ์Šค ์ถ”๊ฐ€ ์ „ํ›„์˜ ์„ฑ๋Šฅ์„ ๋น„๊ตํ•ด ๋ณด๋ฉฐ ์ตœ์ ํ™” ๊ณผ์ •์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. MariaDB๋ฅผ ์‚ฌ์šฉํ•ด ์ธ๋ฑ์Šค ์ถ”๊ฐ€ ์ „ํ›„

eastshine12.tistory.com

 

 

ํ”„๋กœ์ ํŠธ ์†Œ์Šค

 

GitHub - eastshine12/concert-reservation: ์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค / ํ•ญํ•ดํ”Œ๋Ÿฌ์Šค / Queue, Token, Concurrency

์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค / ํ•ญํ•ดํ”Œ๋Ÿฌ์Šค / Queue, Token, Concurrency. Contribute to eastshine12/concert-reservation development by creating an account on GitHub.

github.com

 


 

9์ฃผ์ฐจ

๋”๋ณด๊ธฐ

์ฃผ์ œ

Docker๋ฅผ ํ™œ์šฉํ•œ Kafka ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๊ตฌ์„ฑ ๋ฐ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ๋กœ์ง ์ „ํ™˜

 

ํ‚ค์›Œ๋“œ

Docker, Kafka, Transactional Outbox Pattern, TestContainers

 

 

ํ•™์Šต๋‚ด์šฉ

 

1. ์˜ˆ์•ฝ ์ƒ์„ฑ ๋กœ์ง์„ Kafka ์ด๋ฒคํŠธ ๋ฐœํ–‰ ๊ตฌ์กฐ๋กœ ์ „ํ™˜

  • ๊ธฐ์กด ์ง์ ‘ ํ˜ธ์ถœ ๊ตฌ์กฐ๋ฅผ ์ด๋ฒคํŠธ ๋ฐœํ–‰ → ์†Œ๋น„ ํŒจํ„ด์œผ๋กœ ๊ฐœ์„ 

2. Transactional Outbox Pattern ์ ์šฉ

  • Outbox ํ…Œ์ด๋ธ”์— ์ด๋ฒคํŠธ ๋ฐœํ–‰ ๊ธฐ๋ก์„ ๋‚จ๊ธฐ๊ณ , ์žฌ์ฒ˜๋ฆฌ ์Šค์ผ€์ค„๋Ÿฌ๋กœ ๋ฐœํ–‰ ์‹คํŒจ ์ด๋ฒคํŠธ๋ฅผ ์žฌ์ „์†ก
  • @TransactionalEventListener(BEFORE_COMMIT)๋กœ ๋ฉ”์ธ ํŠธ๋žœ์žญ์…˜๊ณผ ๊ด€์‹ฌ์‚ฌ๋ฅผ ๋ถ„๋ฆฌ
@Component
class ReservationEventListener(
    private val outboxRepository: OutboxRepository,
    private val externalEventPublisher: ReservationExternalEventPublisher,
    private val objectMapper: ObjectMapper,
) {
    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
    fun saveOutbox(event: ReservationEvent.Created) {
        outboxRepository.save(
            Outbox(
                topic = "concert.reservation.created",
                key = event.reservationId.toString(),
                eventType = "RESERVATION_CREATED",
                payload = objectMapper.writeValueAsString(event),
            ),
        )
    }

    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    fun publishKafkaMessage(event: ReservationEvent.Created) {
        externalEventPublisher.publish(
            topic = "concert.reservation.created",
            key = event.reservationId.toString(),
            payload = event,
        )
    }
}

 

 

๊ณ ๋ฏผ ํฌ์ธํŠธ

 

1. ๋กœ์ง ๋ถ„๋ฆฌ

@Transactional
fun ๊ฒฐ์ œ์ฒ˜๋ฆฌ(command): ๊ฒฐ์ œ์ •๋ณด {
    return runCatching {
        val ์˜ˆ์•ฝ์ •๋ณด = ์˜ˆ์•ฝํ™•์ธ๋ฐํ™•์ •์ฒ˜๋ฆฌ(์˜ˆ์•ฝID)  // 1
        val ์ขŒ์„์ •๋ณด = ์ขŒ์„์ •๋ณด๊ฒ€์ฆ๋ฐ๊ฐ€์ ธ์˜ค๊ธฐ(์˜ˆ์•ฝ์ •๋ณด.์ขŒ์„ID) // 2
        ์‚ฌ์šฉ์ž์ž”์•ก์ฐจ๊ฐ(์‚ฌ์šฉ์žID, ์ขŒ์„์ •๋ณด.๊ฐ€๊ฒฉ, USE) // 3
        ๋Œ€๊ธฐ์—ดํ† ํฐ๋งŒ๋ฃŒ์ฒ˜๋ฆฌ(ํ† ํฐ) // 4
        ๊ฒฐ์ œ์ด๋ ฅ์ €์žฅ(์‚ฌ์šฉ์žID, ์˜ˆ์•ฝID, ์ขŒ์„์ •๋ณด.๊ฐ€๊ฒฉ)  // 5
    }.getOrElse { ์˜ˆ์™ธ ->
        throw ์ต์…‰์…˜()
    }
}

 

๊ฒฐ์ œ ๋กœ์ง์—์„œ ์˜ˆ์•ฝ ํ™•์ •(์˜ˆ์•ฝํ™•์ธ๋ฐํ™•์ •์ฒ˜๋ฆฌ)๊ณผ ์‚ฌ์šฉ์ž ์ž”์•ก ์ฐจ๊ฐ(์‚ฌ์šฉ์ž์ž”์•ก์ฐจ๊ฐ)์€ ํ•„์ˆ˜ ๋กœ์ง์œผ๋กœ ๋ฐ˜๋“œ์‹œ ์„ฑ๊ณตํ•ด์•ผ ํ•œ๋‹ค.
๋ฐ˜๋ฉด, ๋Œ€๊ธฐ์—ด ํ† ํฐ ๋งŒ๋ฃŒ๋‚˜ ๊ฒฐ์ œ ์ด๋ ฅ ์ €์žฅ์€ ์ตœ์ข… ์ผ๊ด€์„ฑ์„ ๋งž์ถ”๊ธฐ ์œ„ํ•œ ๋ณด์กฐ ๋กœ์ง์ด๋ฏ€๋กœ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋กœ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
์ด๋•Œ, ์˜ˆ์•ฝ ํ™•์ •๊ณผ ์ž”์•ก ์ฐจ๊ฐ ๊ฐ™์€ ํ•„์ˆ˜ ๋กœ์ง์„ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ์ง€, ์‹คํŒจ ์ƒํ™ฉ์—์„œ ๋ณด์žฅ์„ฑ์„ ์–ด๋–ป๊ฒŒ ์œ ์ง€ํ• ์ง€ ๊ณ ๋ฏผ์ด ํ•„์š”ํ–ˆ๋‹ค.

 

 

ํ•ด๊ฒฐ

  1. ๋ฐฉ๋ฒ• 1: ์˜ˆ์•ฝ ํ™•์ •๊ณผ ์ž”์•ก ์ฐจ๊ฐ์„ ๊ฐ™์€ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ์ฒ˜๋ฆฌ
    • ๋‘ ๋กœ์ง์„ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜์—์„œ ์ฒ˜๋ฆฌํ•˜์—ฌ ํ•„์ˆ˜ ๋กœ์ง์˜ ์„ฑ๊ณต์„ ๋ณด์žฅํ•œ๋‹ค.
    • ๋Œ€๊ธฐ์—ด ํ† ํฐ ๋งŒ๋ฃŒ์™€ ๊ฒฐ์ œ ์ด๋ ฅ ์ €์žฅ์€ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฒ˜๋ฆฌํ•ด ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„๋ฅผ ์ค„์ด๊ณ , ์‹คํŒจ ์‹œ ์žฌ์ฒ˜๋ฆฌ๋ฅผ ์Šค์ผ€์ค„๋Ÿฌ๋กœ ๋ณด์™„
  2. ๋ฐฉ๋ฒ• 2: ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜ ์ ์šฉ
    • ์˜ˆ์•ฝ ํ™•์ •๊ณผ ์ž”์•ก ์ฐจ๊ฐ์„ ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋˜, ์ž”์•ก ์ฐจ๊ฐ ๋กœ์ง์ด ์‹คํŒจํ•  ๊ฒฝ์šฐ ์˜ˆ์•ฝ ํ™•์ • ์ฒ˜๋ฆฌ์— ๋Œ€ํ•ด ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜์„ ์‹คํ–‰(e.g. ์˜ˆ์•ฝ ์ƒํƒœ๋ฅผ ์ทจ์†Œ๋กœ ๋˜๋Œ๋ฆผ)
    • ์ด๋ฅผ ํ†ตํ•ด ์ „์ฒด ๋กœ์ง์˜ ์•ˆ์ •์„ฑ๊ณผ ์œ ์—ฐ์„ฑ์„ ํ™•๋ณด

 

๊ฒฐ๋ก 
๋ฐฉ๋ฒ• 1์€ ํ•„์ˆ˜ ๋กœ์ง์˜ ์„ฑ๊ณต์„ ํ™•์‹คํžˆ ๋ณด์žฅํ•˜๋Š” ์•ˆ์ „ํ•œ ์„ ํƒ์ด์ง€๋งŒ, ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„๊ฐ€ ๋„“์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.
๋ฐฉ๋ฒ• 2๋Š” ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„๋ฅผ ์ค„์ด๊ณ  ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•˜์ง€๋งŒ, ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜ ์„ค๊ณ„์™€ ์šด์˜์˜ ๋ณต์žก๋„๊ฐ€ ๋†’์•„์งˆ ์ˆ˜ ์žˆ๋‹ค.

๊ฐ ๋ฐฉ๋ฒ•์˜ ์žฅ๋‹จ์ ์„ ์ดํ•ดํ•˜๊ณ  ์ƒํ™ฉ์— ๋งž๊ฒŒ ์•Œ๋งž์€ ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

 

 

 

ํ”„๋กœ์ ํŠธ ์†Œ์Šค

 

GitHub - eastshine12/concert-reservation: ์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค / ํ•ญํ•ดํ”Œ๋Ÿฌ์Šค / Queue, Token, Concurrency

์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค / ํ•ญํ•ดํ”Œ๋Ÿฌ์Šค / Queue, Token, Concurrency. Contribute to eastshine12/concert-reservation development by creating an account on GitHub.

github.com

 


 

10์ฃผ์ฐจ

๋”๋ณด๊ธฐ

์ฃผ์ œ

์œ ์ € ํ–‰๋™ ๊ธฐ๋ฐ˜ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๋ฐ ๊ฐ€์ƒ ์žฅ์•  ๋Œ€์‘ ๋ฌธ์„œ ์ž‘์„ฑ

 

ํ‚ค์›Œ๋“œ

๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ, K6

 

ํ•™์Šต๋‚ด์šฉ

 

1. ์œ ์ € ์‹œ๋‚˜๋ฆฌ์˜ค ๊ธฐ๋ฐ˜ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค ์ž‘์„ฑ

  • Docker๋กœ Spring App, Kafka, Redis, MySQL, InfluxDB, Grafana ๋“ฑ์„ ๋„์šฐ๊ณ ,
    k6 ์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ†ตํ•ด ์‹ค์ œ ์œ ์ € ํ–‰๋™์„ ๋ชจ๋ฐฉํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์‹คํ–‰

 

  • ์‹œ๋‚˜๋ฆฌ์˜ค 1: ํ† ํฐ ๋ฐœ๊ธ‰ → ํ† ํฐ ์ƒํƒœ ์กฐํšŒ
export default function () {
     // ํ† ํฐ ๋ฐœ๊ธ‰
     let token = getWaitingToken()

     // ํ† ํฐ ์ƒํƒœ ์กฐํšŒ
     for (let i = 0; i < 5; i++) { // 5ํšŒ ๋ฐ˜๋ณต
         getQueueStatus(token)
         sleep(1);
     }
 }

 

 

  • ์‹œ๋‚˜๋ฆฌ์˜ค 2: ์ฝ˜์„œํŠธ ์ขŒ์„ ์กฐํšŒ → ์˜ˆ์•ฝ → ๊ฒฐ์ œ
    ์ด๋ฅผ ํ†ตํ•ด ์‹œ์Šคํ…œ ํ•œ๊ณ„์น˜ ํŒŒ์•… ๋ฐ SLA/SLO ๊ฒ€์ฆ
export default function () {
     let currentId = exec.scenario.iterationInTest + 1;
     let token = getWaitingToken(currentId);

     getConcertSeats(token); // ์ขŒ์„ ์กฐํšŒ
     sleep(2);
     let reservationId = reserveSeat(token, currentId); // ์˜ˆ์•ฝ
     sleep(2);
     processPayment(token, reservationId, currentId); // ๊ฒฐ์ œ
     sleep(2);
 }

 

 

 

 

๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ: ์œ ์ € ํ–‰๋™ ๊ธฐ๋ฐ˜ ์‹œ๋‚˜๋ฆฌ์˜ค๋กœ ์‹œ์Šคํ…œ ํ•œ๊ณ„ ์ธก์ •๊ณผ SLA, SLO ๊ฒ€์ฆ

๋“ค์–ด๊ฐ€๋ฉฐ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ๋Š” ์‹œ์Šคํ…œ์˜ ์ตœ๋Œ€ ์ฒ˜๋ฆฌ๋Ÿ‰(TPS), ์•ˆ์ •์„ฑ, SLA(์„œ๋น„์Šค ์ˆ˜์ค€ ๊ณ„์•ฝ) ๋ฐ SLO(์„œ๋น„์Šค ์ˆ˜์ค€ ๋ชฉํ‘œ)๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ์ค‘์š”ํ•œ ๊ณผ์ •์ž…๋‹ˆ๋‹ค. ์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค์—์„œ ์œ ์ € ํ–‰๋™์„

eastshine12.tistory.com

 

 

ํ”„๋กœ์ ํŠธ ์†Œ์Šค

 

GitHub - eastshine12/concert-reservation: ์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค / ํ•ญํ•ดํ”Œ๋Ÿฌ์Šค / Queue, Token, Concurrency

์ฝ˜์„œํŠธ ์˜ˆ์•ฝ ์„œ๋น„์Šค / ํ•ญํ•ดํ”Œ๋Ÿฌ์Šค / Queue, Token, Concurrency. Contribute to eastshine12/concert-reservation development by creating an account on GitHub.

github.com