Skip to content

jhwon91/hhplus-tdd-java

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

27 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

hhplus-tdd-java

๐Ÿ›ณ๏ธ Test Driven Development

๋ชฉํ‘œ

point ํŒจํ‚ค์ง€์˜ TODO ์™€ ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.

์š”๊ตฌ ์‚ฌํ•ญ

  • PATCH /point/{id}/charge : ํฌ์ธํŠธ๋ฅผ ์ถฉ์ „ํ•œ๋‹ค.
  • PATCH /point/{id}/use : ํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
  • GET /point/{id} : ํฌ์ธํŠธ๋ฅผ ์กฐํšŒํ•œ๋‹ค.
  • GET /point/{id}/histories : ํฌ์ธํŠธ ๋‚ด์—ญ์„ ์กฐํšŒํ•œ๋‹ค.
  • ์ž”๊ณ ๊ฐ€ ๋ถ€์กฑํ•  ๊ฒฝ์šฐ, ํฌ์ธํŠธ ์‚ฌ์šฉ์€ ์‹คํŒจํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๋™์‹œ์— ์—ฌ๋Ÿฌ ๊ฑด์˜ ํฌ์ธํŠธ ์ถฉ์ „, ์ด์šฉ ์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ๊ฒฝ์šฐ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Default

  • /point ํŒจํ‚ค์ง€ (๋””๋ ‰ํ† ๋ฆฌ) ๋‚ด์— PointService ๊ธฐ๋ณธ ๊ธฐ๋Šฅ ์ž‘์„ฑ
  • /database ํŒจํ‚ค์ง€์˜ ๊ตฌํ˜„์ฒด๋Š” ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ , ์ด๋ฅผ ํ™œ์šฉํ•ด ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„
  • ๊ฐ ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

์ด 4๊ฐ€์ง€ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ (ํฌ์ธํŠธ ์กฐํšŒ, ํฌ์ธํŠธ ์ถฉ์ „/์‚ฌ์šฉ ๋‚ด์—ญ ์กฐํšŒ, ์ถฉ์ „, ์‚ฌ์šฉ) ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

Step 1

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

Step 2

  • ๋™์‹œ์„ฑ ์ œ์–ด ๋ฐฉ์‹์— ๋Œ€ํ•œ ๋ถ„์„ ๋ฐ ๋ณด๊ณ ์„œ ์ž‘์„ฑ ( README.md )

๋™์‹œ์„ฑ ์ œ์–ด ๋ฐฉ์‹์— ๋Œ€ํ•œ ๋ถ„์„ ๋ฐ ๋ณด๊ณ ์„œ ์ž‘์„ฑ

1. ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ž€?

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

2. ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•

๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ช‡ ๊ฐ€์ง€ ์ฃผ์š” ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ReentrantLock์„ ์‚ฌ์šฉํ•˜์—ฌ ๋™์ผํ•œ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์š”์ฒญ์ด ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

2.1 ReentrantLock์„ ์‚ฌ์šฉํ•œ ๋™์‹œ์„ฑ ์ œ์–ด

ReentrantLock์€ Java์˜ ๋™์‹œ์„ฑ ์ œ์–ด ๋ฉ”์ปค๋‹ˆ์ฆ˜ ์ค‘ ํ•˜๋‚˜๋กœ, ์Šค๋ ˆ๋“œ ๊ฐ„์˜ ์ž์› ๊ฒฝํ•ฉ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ํŠน์ • ์ž์›์— ๋Œ€ํ•ด **๋ฝ(Lock)**์„ ๊ฑธ๋ฉด, ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๋Š” ๊ทธ ์ž์›์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ณ , ํ•ด๋‹น ์Šค๋ ˆ๋“œ๊ฐ€ ๋ฝ์„ ํ•ด์ œํ•  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

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

private final ConcurrentHashMap<Long, ReentrantLock> userLock = new ConcurrentHashMap<>();

public UserPoint chargeUserPoint(long userId, long amount) {
  ReentrantLock lock = userLock.computeIfAbsent(userId, id -> new ReentrantLock());
  lock.lock();
  try {
    // ํฌ์ธํŠธ ์ถฉ์ „ ๋กœ์ง
  } finally {
    lock.unlock();
  }
}

2.2 ConcurrentHashMap์„ ์‚ฌ์šฉํ•œ ์‚ฌ์šฉ์ž๋ณ„ Lock ๊ด€๋ฆฌ

ConcurrentHashMap<Long, ReentrantLock>์€ ๊ฐ ์‚ฌ์šฉ์ž ID์— ๋Œ€ํ•ด Lock ๊ฐ์ฒด๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋™์ผํ•œ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์š”์ฒญ๋งŒ ๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ๋ฐ›๊ณ , ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์š”์ฒญ์€ ๋™์‹œ์— ์ฒ˜๋ฆฌ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ์‚ฌ์šฉ์ž ๋‹จ์œ„๋กœ Lock์„ ์ ์šฉํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ๋ณ‘๋ชฉ์„ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

private final ConcurrentHashMap<Long, ReentrantLock> userLock = new ConcurrentHashMap<>();
  • computeIfAbsent() ๋ฉ”์„œ๋“œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ฒ˜์Œ ์š”์ฒญํ•  ๋•Œ๋งŒ Lock์„ ์ƒ์„ฑํ•˜๊ณ , ์ดํ›„์—๋Š” ์ด๋ฏธ ์ƒ์„ฑ๋œ Lock์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ Lock ์ƒ์„ฑ ๋น„์šฉ์„ ์ค„์ž…๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž์— ๋Œ€ํ•ด ๋™์‹œ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด, Lock์„ ํš๋“ํ•œ ์š”์ฒญ๋งŒ ๋จผ์ € ์ฒ˜๋ฆฌ๋˜๊ณ , ๋‚˜๋จธ์ง€ ์š”์ฒญ์€ ๋Œ€๊ธฐํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

2.3 ๋‹ค๋ฅธ ๋™์‹œ์„ฑ ์ œ์–ด ๋ฐฉ์‹๋“ค

  • synchronized ํ‚ค์›Œ๋“œ: ์ž๋ฐ”์˜ ๊ธฐ๋ณธ ๋™์‹œ์„ฑ ์ œ์–ด ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด์ง€๋งŒ, ๋ฉ”์„œ๋“œ๋‚˜ ์ฝ”๋“œ ๋ธ”๋ก ์ „์ฒด์— ๊ฑธ๋ฆฌ๋Š” ๊ฒฝํ–ฅ์ด ์žˆ์–ด ์„ธ๋ฐ€ํ•œ ์ œ์–ด๊ฐ€ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
  • Atomic ํด๋ž˜์Šค: ๋‹จ์ผ ์—ฐ์‚ฐ์— ๋Œ€ํ•ด ์›์ž์„ฑ์„ ๋ณด์žฅํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋ฉฐ, ์Šค๋ ˆ๋“œ ์•ˆ์ „ํ•œ ๊ฐ’์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ์œ ์šฉํ•˜์ง€๋งŒ, ์ด ํ”„๋กœ์ ํŠธ์ฒ˜๋Ÿผ ๋ณต์žกํ•œ ํŠธ๋žœ์žญ์…˜์—์„œ๋Š” ์ ํ•ฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • CompletableFuture ๋ฐ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ: ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ด๋„ ๊ฒฝํ•ฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ์—ฌ์ „ํžˆ Lock๊ณผ ๊ฐ™์€ ๋ฐฉ๋ฒ•์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

3. ๋™์‹œ์„ฑ ๋ฌธ์ œ ํ•ด๊ฒฐ ํ›„ ์„ฑ๋Šฅ ๋ถ„์„

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

์„ฑ๋Šฅ ๊ฐœ์„  ์š”์†Œ

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

4. ์ถ”๊ฐ€์ ์ธ ๊ฐœ์„  ๋ฐฉ์•ˆ

  1. ํ๋ฅผ ์‚ฌ์šฉํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ: Lock์„ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ , ์š”์ฒญ์„ ํ์— ๋‹ด์•„์„œ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹๋„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๊ฐ ์š”์ฒญ์ด ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋ฉฐ, ์ˆœ์ฐจ์„ฑ๋„ ๋ณด์žฅ๋ฉ๋‹ˆ๋‹ค.
  2. Redis ๋ถ„์‚ฐ Lock: ๋งŒ์•ฝ ์‹œ์Šคํ…œ์ด ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ ๋™์ž‘ํ•œ๋‹ค๋ฉด, Redis๋ฅผ ์‚ฌ์šฉํ•œ ๋ถ„์‚ฐ Lock์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ ์„œ๋ฒ„์—์„œ ๋™์ผํ•œ ์ž์›์— ์ ‘๊ทผํ•˜๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. ๋ฐ๋“œ๋ฝ(Deadlock) ๋ฐฉ์ง€: ์—ฌ๋Ÿฌ ์ž์›์— ๋™์‹œ์— Lock์„ ๊ฑฐ๋Š” ์ž‘์—…์„ ํ”ผํ•˜๊ณ , Lock ํš๋“ ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•˜์—ฌ ๋ฐ๋“œ๋ฝ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๊ฐ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ Lock๋งŒ ์ ์šฉํ•˜๋ฏ€๋กœ ๋ฐ๋“œ๋ฝ ๊ฐ€๋Šฅ์„ฑ์€ ๋‚ฎ์Šต๋‹ˆ๋‹ค.

5. ๋™์‹œ์„ฑ ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค ๋ฐ ๊ฒฐ๊ณผ

5.1 ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž์˜ ๋™์‹œ ์ถฉ์ „ ์š”์ฒญ ์ฒ˜๋ฆฌ

๋ชฉํ‘œ: ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ํฌ์ธํŠธ ์ถฉ์ „ ์š”์ฒญ์„ ํ–ˆ์„ ๋•Œ, ๊ฐ ์‚ฌ์šฉ์ž์˜ ํฌ์ธํŠธ๊ฐ€ ์ •ํ™•ํ•˜๊ฒŒ ์—…๋ฐ์ดํŠธ๋˜๋Š”์ง€ ํ™•์ธ.

  • ์‹œ๋‚˜๋ฆฌ์˜ค: 10๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ๊ฐ ๋™์‹œ์— 100 ํฌ์ธํŠธ ์ถฉ์ „ ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.
  • ์˜ˆ์ƒ ๊ฒฐ๊ณผ: ๊ฐ ์‚ฌ์šฉ์ž์˜ ํฌ์ธํŠธ๊ฐ€ 100์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
@Test
public void ์—ฌ๋Ÿฌ_์‚ฌ์šฉ์ž์˜_๋™์‹œ_์ถฉ์ „_์š”์ฒญ() throws Exception {
  int userCount = 10;
  long chargeAmount = 100L;
  CountDownLatch latch = new CountDownLatch(userCount);

  List<Future<MvcResult>> futures = new ArrayList<>();
  for (long userId = 1; userId <= userCount; userId++) {
    long finalUserId = userId;
    futures.add(executorService.submit(() -> {
      return mockMvc.perform(patch("/point/" + finalUserId + "/charge")
                      .contentType("application/json")
                      .content(String.valueOf(chargeAmount)))
              .andExpect(status().isOk())
              .andReturn();
    }));
  }

  latch.await();
  for (int i = 0; i < userCount; i++) {
    long userId = i + 1;
    UserPoint userPoint = pointService.getUserPoint(userId);
    assertEquals(chargeAmount, userPoint.point());
  }
}

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

5.2 ๋™์ผ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ๋™์‹œ ์ถฉ์ „ ์š”์ฒญ ์ฒ˜๋ฆฌ

๋ชฉํ‘œ: ๋™์ผํ•œ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ๋™์‹œ ์ถฉ์ „ ์š”์ฒญ์ด ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋Š”์ง€ ํ™•์ธ.

  • ์‹œ๋‚˜๋ฆฌ์˜ค: ๋™์ผํ•œ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•ด 5๊ฐœ์˜ ์ถฉ์ „ ์š”์ฒญ์„ ๋™์‹œ์— ๋ณด๋ƒ…๋‹ˆ๋‹ค.
  • ์˜ˆ์ƒ ๊ฒฐ๊ณผ: ์ถฉ์ „ ์š”์ฒญ์ด ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด ์ตœ์ข… ํฌ์ธํŠธ๋Š” 500์ด ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
@Test
public void ๋™์ผํ•œ_์‚ฌ์šฉ์ž์—_๋Œ€ํ•œ_๋™์‹œ_์ถฉ์ „_์š”์ฒญ์˜_์ˆœ์ฐจ์ _์ฒ˜๋ฆฌ() throws Exception {
    long userId = 1L;
    int requestCount = 5;
    long chargeAmount = 100L;
    CountDownLatch latch = new CountDownLatch(requestCount);

    for (int i = 0; i < requestCount; i++) {
        executorService.submit(() -> {
            mockMvc.perform(patch("/point/" + userId + "/charge")
                            .contentType("application/json")
                            .content(String.valueOf(chargeAmount)))
                    .andExpect(status().isOk());
        });
    }

    latch.await();
    UserPoint userPoint = pointService.getUserPoint(userId);
    assertEquals(chargeAmount * requestCount, userPoint.point());
}

๊ฒฐ๊ณผ: ๋™์ผํ•œ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์ถฉ์ „ ์š”์ฒญ์ด ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด ์˜ˆ์ƒํ•œ ๋Œ€๋กœ ์ตœ์ข… ํฌ์ธํŠธ๋Š” 500์œผ๋กœ ํ™•์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

5.3 ์ž”์•ก ๋ถ€์กฑ ์ƒํ™ฉ์—์„œ์˜ ๋™์‹œ ์‚ฌ์šฉ ์š”์ฒญ ์ฒ˜๋ฆฌ

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

  • ์‹œ๋‚˜๋ฆฌ์˜ค: ์ž”์•ก์ด 500์ธ ์ƒํƒœ์—์„œ ๋‘ ๊ฐœ์˜ 300 ํฌ์ธํŠธ ์‚ฌ์šฉ ์š”์ฒญ์„ ๋™์‹œ์— ๋ณด๋ƒ…๋‹ˆ๋‹ค.
  • ์˜ˆ์ƒ ๊ฒฐ๊ณผ: ํ•˜๋‚˜์˜ ์š”์ฒญ์€ ์„ฑ๊ณตํ•˜๊ณ , ๋‹ค๋ฅธ ํ•˜๋‚˜๋Š” ์ž”์•ก ๋ถ€์กฑ์œผ๋กœ ์‹คํŒจํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
@Test
public void ์ž”์•ก_๋ถ€์กฑ_์ƒํ™ฉ์—์„œ์˜_๋™์‹œ_์‚ฌ์šฉ_์š”์ฒญ_์ฒ˜๋ฆฌ๋ฅผ_๊ฒ€์ฆ() throws Exception {
    long userId = 2L;
    long initialBalance = 500L;
    long useAmount = 300L;
    pointService.chargeUserPoint(userId, initialBalance);

    CountDownLatch latch = new CountDownLatch(2);
    List<Future<MvcResult>> futures = new ArrayList<>();
    for (int i = 0; i < 2; i++) {
        futures.add(executorService.submit(() -> {
            return mockMvc.perform(patch("/point/" + userId + "/use")
                            .contentType("application/json")
                            .content(String.valueOf(useAmount)))
                    .andReturn();
        }));
    }

    latch.await();
    int successCount = 0;
    int failCount = 0;
    for (Future<MvcResult> future : futures) {
        MvcResult result = future.get();
        if (result.getResponse().getStatus() == 200) {
            successCount++;
        } else {
            failCount++;
        }
    }

    assertEquals(1, successCount);
    assertEquals(1, failCount);
    UserPoint finalUserPoint = pointService.getUserPoint(userId);
    assertEquals(200L, finalUserPoint.point());
}

๊ฒฐ๊ณผ: ํ•˜๋‚˜์˜ ์š”์ฒญ๋งŒ ์„ฑ๊ณตํ•˜๊ณ , ๋‹ค๋ฅธ ์š”์ฒญ์€ ์ž”์•ก ๋ถ€์กฑ์œผ๋กœ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ์ตœ์ข… ์ž”์•ก์€ 200 ํฌ์ธํŠธ๋กœ ์ •ํ™•ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

5.4 ๋™์‹œ ์ถฉ์ „ ๋ฐ ์‚ฌ์šฉ ์š”์ฒญ ์ฒ˜๋ฆฌ

๋ชฉํ‘œ: ๋™์ผํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ์ถฉ์ „๊ณผ ์‚ฌ์šฉ ์š”์ฒญ์„ ๋ณด๋ƒˆ์„ ๋•Œ, ์ˆœ์„œ๊ฐ€ ๋ณด์žฅ๋˜๋Š”์ง€ ํ™•์ธ.

  • ์‹œ๋‚˜๋ฆฌ์˜ค: ์ž”์•ก์ด 500์ธ ์ƒํƒœ์—์„œ 300 ํฌ์ธํŠธ ์ถฉ์ „ ์š”์ฒญ๊ณผ 200 ํฌ์ธํŠธ ์‚ฌ์šฉ ์š”์ฒญ์„ ๋™์‹œ์— ๋ณด๋ƒ…๋‹ˆ๋‹ค.
  • ์˜ˆ์ƒ ๊ฒฐ๊ณผ: ์ถฉ์ „ ์š”์ฒญ์ด ๋จผ์ € ์ฒ˜๋ฆฌ๋˜๊ณ , ์ดํ›„ ์‚ฌ์šฉ ์š”์ฒญ์ด ์ฒ˜๋ฆฌ๋˜์–ด ์ตœ์ข… ์ž”์•ก์€ 600์ด ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
@Test
public void ์ถฉ์ „๊ณผ_์‚ฌ์šฉ_์š”์ฒญ์˜_๋™์‹œ์ฒ˜๋ฆฌ์™€_์ˆœ์„œ๋ณด์žฅ์„_ํ™•์ธ() throws Exception {
    long userId = 3L;
    long initialBalance = 500L;
    long chargeAmount = 300L;
    long useAmount = 200L;
    pointService.chargeUserPoint(userId, initialBalance);

    CountDownLatch latch = new CountDownLatch(2);
    Future<MvcResult> chargeFuture = executorService.submit(() -> {
        return mockMvc.perform(patch("/point/" + userId + "/charge")
                        .contentType("application/json")
                        .content(String.valueOf(chargeAmount)))
                .andReturn();
    });

    Future<MvcResult> useFuture = executorService.submit(() -> {
        return mockMvc.perform(patch("/point/" + userId + "/use")
                        .contentType("application/json")
                        .content(String.valueOf(useAmount)))
                .andReturn();
    });

    latch.await();
    assertEquals(200, chargeFuture.get().getResponse().getStatus());
    assertEquals(200, useFuture.get().getResponse().getStatus());

    UserPoint finalUserPoint = pointService.getUserPoint(userId);
    assertEquals(600L, finalUserPoint.point());
}

๊ฒฐ๊ณผ: ์ถฉ์ „ ์š”์ฒญ์ด ๋จผ์ € ์ฒ˜๋ฆฌ๋˜๊ณ , ๊ทธ ๋‹ค์Œ์— ์‚ฌ์šฉ ์š”์ฒญ์ด ์ฒ˜๋ฆฌ๋˜์–ด ์ตœ์ข… ํฌ์ธํŠธ ์ž”์•ก์€ 600์œผ๋กœ ์ •ํ™•ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

5.5 ๋‹ค์ˆ˜ ์‚ฌ์šฉ์ž์˜ ๋™์‹œ ์š”์ฒญ์— ๋Œ€ํ•œ ์„ฑ๋Šฅ ๊ฒ€์ฆ

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

  • ์‹œ๋‚˜๋ฆฌ์˜ค: 10๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ๊ฐ ์ถฉ์ „ ๋ฐ ์‚ฌ์šฉ ์š”์ฒญ์„ ๋™์‹œ์— ๋ณด๋ƒ…๋‹ˆ๋‹ค.
  • ์˜ˆ์ƒ ๊ฒฐ๊ณผ: ๋ชจ๋“  ์š”์ฒญ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋ฉฐ, ์ตœ์ข… ์ž”์•ก์ด ์ •ํ™•ํžˆ ๊ณ„์‚ฐ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
@Test
public void ๋‹ค์ˆ˜_์‚ฌ์šฉ์ž์˜_๋™์‹œ_์š”์ฒญ_์„ฑ๋Šฅ๊ฒ€์ฆ() throws Exception {
  int userCount = 10;
  long initialBalance = 200L;
  long chargeAmount = 100L;
  long useAmount = 50L;
  CountDownLatch latch = new CountDownLatch(userCount * 2);  // ๊ฐ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•ด ์ถฉ์ „๊ณผ ์‚ฌ์šฉ ์š”์ฒญ

  long startTime = System.currentTimeMillis();

  for (long userId = 1; userId <= userCount; userId++) {
    long finalUserId = userId;

    // ์ถฉ์ „ ์š”์ฒญ
    executorService.submit(() -> {
      try {
        mockMvc.perform(patch("/point/" + finalUserId + "/charge")
                        .contentType("application/json")
                        .content(String.valueOf(chargeAmount)))
                .andExpect(status().isOk());
      } catch (Exception e) {
        e.printStackTrace();
      } finally {
        latch.countDown();
      }
    });

    // ์‚ฌ์šฉ ์š”์ฒญ
    executorService.submit(() -> {
      try {
        mockMvc.perform(patch("/point/" + finalUserId + "/use")
                        .contentType("application/json")
                        .content(String.valueOf(useAmount)))
                .andExpect(status().isOk());
      } catch (Exception e) {
        e.printStackTrace();
      } finally {
        latch.countDown();
      }
    });
  }

  latch.await();

  long endTime = System.currentTimeMillis();
  long duration = endTime - startTime;

  System.out.println("๋™์‹œ ์š”์ฒญ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„: " + duration + " ms");

  for (long userId = 1; userId <= userCount; userId++) {
    UserPoint userPoint = pointService.getUserPoint(userId);
    assertEquals(initialBalance + chargeAmount - useAmount, userPoint.point());
  }
}

๊ฒฐ๊ณผ

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

About

๐Ÿ›ณ๏ธ Test Driven Development

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages