๐ณ๏ธ Test Driven Development
๋ชฉํ
์๊ตฌ ์ฌํญ
- PATCH
/point/{id}/charge
: ํฌ์ธํธ๋ฅผ ์ถฉ์ ํ๋ค. - PATCH
/point/{id}/use
: ํฌ์ธํธ๋ฅผ ์ฌ์ฉํ๋ค. - GET
/point/{id}
: ํฌ์ธํธ๋ฅผ ์กฐํํ๋ค. - GET
/point/{id}/histories
: ํฌ์ธํธ ๋ด์ญ์ ์กฐํํ๋ค. - ์๊ณ ๊ฐ ๋ถ์กฑํ ๊ฒฝ์ฐ, ํฌ์ธํธ ์ฌ์ฉ์ ์คํจํ์ฌ์ผ ํฉ๋๋ค.
- ๋์์ ์ฌ๋ฌ ๊ฑด์ ํฌ์ธํธ ์ถฉ์ , ์ด์ฉ ์์ฒญ์ด ๋ค์ด์ฌ ๊ฒฝ์ฐ ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌ๋์ด์ผ ํฉ๋๋ค.
/point
ํจํค์ง (๋๋ ํ ๋ฆฌ) ๋ด์PointService
๊ธฐ๋ณธ ๊ธฐ๋ฅ ์์ฑ/database
ํจํค์ง์ ๊ตฌํ์ฒด๋ ์์ ํ์ง ์๊ณ , ์ด๋ฅผ ํ์ฉํด ๊ธฐ๋ฅ์ ๊ตฌํ- ๊ฐ ๊ธฐ๋ฅ์ ๋ํ ๋จ์ ํ ์คํธ ์์ฑ
์ด 4๊ฐ์ง ๊ธฐ๋ณธ ๊ธฐ๋ฅ (ํฌ์ธํธ ์กฐํ, ํฌ์ธํธ ์ถฉ์ /์ฌ์ฉ ๋ด์ญ ์กฐํ, ์ถฉ์ , ์ฌ์ฉ) ์ ๊ตฌํํฉ๋๋ค.
- ํฌ์ธํธ ์ถฉ์ , ์ฌ์ฉ์ ๋ํ ์ ์ฑ ์ถ๊ฐ (์๊ณ ๋ถ์กฑ, ์ต๋ ์๊ณ ๋ฑ)
- ๋์์ ์ฌ๋ฌ ์์ฒญ์ด ๋ค์ด์ค๋๋ผ๋ ์์๋๋ก (ํน์ ํ๋ฒ์ ํ๋์ ์์ฒญ์ฉ๋ง) ์ ์ด๋ ์ ์๋๋ก ๋ฆฌํฉํ ๋ง
- ๋์์ฑ ์ ์ด์ ๋ํ ํตํฉ ํ ์คํธ ์์ฑ
- ๋์์ฑ ์ ์ด ๋ฐฉ์์ ๋ํ ๋ถ์ ๋ฐ ๋ณด๊ณ ์ ์์ฑ ( README.md )
๋์์ฑ ๋ฌธ์ ๋ ๋ค์ค ์ฌ์ฉ์ ๋๋ ๋ค์ค ํ๋ก์ธ์ค๊ฐ ๋์์ ๊ณต์ ์์์ ์ ๊ทผํ ๋ ๋ฐ์ํ๋ ๋ฌธ์ ์ ๋๋ค. ํนํ, ๋ค์์ ์์ฒญ์ด ๋์์ ๋ค์ด์ค๋ฉด ๊ฐ์ ์์์ ๋ํด ๊ฒฝํฉ์ด ๋ฐ์ํ ์ ์์ต๋๋ค. ์ด๋ก ์ธํด ๋ฐ์ดํฐ๊ฐ ๋๋ฝ๋๊ฑฐ๋ ์๋ชป๋ ๊ฐ์ผ๋ก ์ฒ๋ฆฌ๋ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์ฌ๋ฌ ์ฌ์ฉ์๊ฐ ๋์์ ํฌ์ธํธ ์ถฉ์ ๋๋ ํฌ์ธํธ ์ฌ์ฉ์ ์์ฒญํ ๋, ๋ฐ์ดํฐ์ ์ผ๊ด์ฑ์ด ์ ์ง๋์ง ์์ผ๋ฉด ์์ก์ด ์๋ชป ๊ณ์ฐ๋ ์ ์์ต๋๋ค.
๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ช ๊ฐ์ง ์ฃผ์ ๊ธฐ๋ฒ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ํ๋ก์ ํธ์์๋ 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();
}
}
ConcurrentHashMap<Long, ReentrantLock>์ ๊ฐ ์ฌ์ฉ์ ID์ ๋ํด Lock ๊ฐ์ฒด๋ฅผ ๊ด๋ฆฌํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋์ผํ ์ฌ์ฉ์์ ๋ํ ์์ฒญ๋ง ๋์์ฑ ์ ์ด๋ฅผ ๋ฐ๊ณ , ๋ค๋ฅธ ์ฌ์ฉ์์ ๋ํ ์์ฒญ์ ๋์์ ์ฒ๋ฆฌ๋ ์ ์์ต๋๋ค. ์ฆ, ์ฌ์ฉ์ ๋จ์๋ก Lock์ ์ ์ฉํ์ฌ ๋ถํ์ํ ๋ณ๋ชฉ์ ์ต์ํํ ์ ์์ต๋๋ค.
private final ConcurrentHashMap<Long, ReentrantLock> userLock = new ConcurrentHashMap<>();
- computeIfAbsent() ๋ฉ์๋๋ ์ฌ์ฉ์๊ฐ ์ฒ์ ์์ฒญํ ๋๋ง Lock์ ์์ฑํ๊ณ , ์ดํ์๋ ์ด๋ฏธ ์์ฑ๋ Lock์ ์ฌ์ฉํ์ฌ ๋ถํ์ํ Lock ์์ฑ ๋น์ฉ์ ์ค์ ๋๋ค.
- ์ฌ์ฉ์์ ๋ํด ๋์ ์์ฒญ์ด ๋ค์ด์ค๋ฉด, Lock์ ํ๋ํ ์์ฒญ๋ง ๋จผ์ ์ฒ๋ฆฌ๋๊ณ , ๋๋จธ์ง ์์ฒญ์ ๋๊ธฐํ๊ฒ ๋ฉ๋๋ค.
- synchronized ํค์๋: ์๋ฐ์ ๊ธฐ๋ณธ ๋์์ฑ ์ ์ด ๋ฉ์ปค๋์ฆ์ด์ง๋ง, ๋ฉ์๋๋ ์ฝ๋ ๋ธ๋ก ์ ์ฒด์ ๊ฑธ๋ฆฌ๋ ๊ฒฝํฅ์ด ์์ด ์ธ๋ฐํ ์ ์ด๊ฐ ์ด๋ ต์ต๋๋ค.
- Atomic ํด๋์ค: ๋จ์ผ ์ฐ์ฐ์ ๋ํด ์์์ฑ์ ๋ณด์ฅํ ๋ ์ฌ์ฉ๋๋ฉฐ, ์ค๋ ๋ ์์ ํ ๊ฐ์ ์ฒ๋ฆฌํ ๋ ์ ์ฉํ์ง๋ง, ์ด ํ๋ก์ ํธ์ฒ๋ผ ๋ณต์กํ ํธ๋์ญ์ ์์๋ ์ ํฉํ์ง ์์ต๋๋ค.
- CompletableFuture ๋ฐ ๋น๋๊ธฐ ์ฒ๋ฆฌ: ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํด๋ ๊ฒฝํฉ์ด ๋ฐ์ํ ์ ์๊ธฐ ๋๋ฌธ์, ๋์์ฑ ์ ์ด๋ฅผ ์ํด์๋ ์ฌ์ ํ Lock๊ณผ ๊ฐ์ ๋ฐฉ๋ฒ์ด ํ์ํฉ๋๋ค.
๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ํ, ์์คํ ์ ๋ค์ค ์์ฒญ์ด ๋ค์ด์๋ ๋ฐ์ดํฐ์ ์ผ๊ด์ฑ์ด ์ ์ง๋๋ฉฐ, ๊ฐ ์์ฒญ์ด ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค. ํนํ ๋์ผํ ์ฌ์ฉ์์ ๋ํด์๋ง ์์ฐจ ์ฒ๋ฆฌ๋๋ฏ๋ก, ๋ค๋ฅธ ์ฌ์ฉ์์ ๋ํ ์์ฒญ์ ๋ณ๋ ฌ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค. ์ด๋ก ์ธํด ์ฑ๋ฅ ์ ํ๋ฅผ ์ต์ํํ ์ ์์ต๋๋ค.
์ฑ๋ฅ ๊ฐ์ ์์
- ์ฌ์ฉ์๋ณ๋ก Lock์ ์ ์ฉํจ์ผ๋ก์จ ๊ฒฝ์ ์์์ ๋ฒ์๋ฅผ ์ต์ํํ๊ณ , ๋ค๋ฅธ ์ฌ์ฉ์์ ์์ฒญ์ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
- ReentrantLock ํ์ฉํ ์ธ๋ฐํ ๋์์ฑ ์ ์ด๋ก, ๋จ์ํ ๋ชจ๋ ์์ฒญ์ ์ฐจ๋จํ๋ ๋ฐฉ์์ด ์๋, ํ์ํ ์์ฒญ๋ง ์ฐจ๋จํ์ฌ ์ฑ๋ฅ์ ๋์์ต๋๋ค.
- ํ๋ฅผ ์ฌ์ฉํ ๋น๋๊ธฐ ์ฒ๋ฆฌ: Lock์ ์ฌ์ฉํ๋ ๋์ , ์์ฒญ์ ํ์ ๋ด์์ ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๋ฐฉ์๋ ๊ณ ๋ คํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ๊ฐ ์์ฒญ์ด ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌ๋๋ฉฐ, ์์ฐจ์ฑ๋ ๋ณด์ฅ๋ฉ๋๋ค.
- Redis ๋ถ์ฐ Lock: ๋ง์ฝ ์์คํ ์ด ๋ถ์ฐ ํ๊ฒฝ์์ ๋์ํ๋ค๋ฉด, Redis๋ฅผ ์ฌ์ฉํ ๋ถ์ฐ Lock์ ๊ณ ๋ คํ ์ ์์ต๋๋ค. ์ด ๋ฐฉ๋ฒ์ ์ฌ๋ฌ ์๋ฒ์์ ๋์ผํ ์์์ ์ ๊ทผํ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค.
- ๋ฐ๋๋ฝ(Deadlock) ๋ฐฉ์ง: ์ฌ๋ฌ ์์์ ๋์์ Lock์ ๊ฑฐ๋ ์์ ์ ํผํ๊ณ , Lock ํ๋ ์์๋ฅผ ๋ณด์ฅํ์ฌ ๋ฐ๋๋ฝ์ ๋ฐฉ์งํด์ผ ํฉ๋๋ค. ์ด ํ๋ก์ ํธ์์๋ ๊ฐ ์ฌ์ฉ์์ ๋ํ Lock๋ง ์ ์ฉํ๋ฏ๋ก ๋ฐ๋๋ฝ ๊ฐ๋ฅ์ฑ์ ๋ฎ์ต๋๋ค.
๋ชฉํ: ์ฌ๋ฌ ์ฌ์ฉ์๊ฐ ๋์์ ํฌ์ธํธ ์ถฉ์ ์์ฒญ์ ํ์ ๋, ๊ฐ ์ฌ์ฉ์์ ํฌ์ธํธ๊ฐ ์ ํํ๊ฒ ์ ๋ฐ์ดํธ๋๋์ง ํ์ธ.
- ์๋๋ฆฌ์ค: 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๊ฐ์ ์ถฉ์ ์์ฒญ์ ๋์์ ๋ณด๋ ๋๋ค.
- ์์ ๊ฒฐ๊ณผ: ์ถฉ์ ์์ฒญ์ด ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌ๋์ด ์ต์ข ํฌ์ธํธ๋ 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์ผ๋ก ํ์ธ๋์์ต๋๋ค.
๋ชฉํ: ์ฌ์ฉ์๊ฐ ์์ก์ด ๋ถ์กฑํ ์ํฉ์์ ๋์ ์ฌ์ฉ ์์ฒญ์ ๋ณด๋์ ๋, ํ๋๋ ์ฑ๊ณตํ๊ณ ๋ค๋ฅธ ํ๋๋ ์คํจํ๋์ง ํ์ธ.
- ์๋๋ฆฌ์ค: ์์ก์ด 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 ํฌ์ธํธ๋ก ์ ํํ๊ฒ ์ฒ๋ฆฌ๋์์ต๋๋ค.
๋ชฉํ: ๋์ผํ ์ฌ์ฉ์๊ฐ ๋์์ ์ถฉ์ ๊ณผ ์ฌ์ฉ ์์ฒญ์ ๋ณด๋์ ๋, ์์๊ฐ ๋ณด์ฅ๋๋์ง ํ์ธ.
- ์๋๋ฆฌ์ค: ์์ก์ด 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์ผ๋ก ์ ํํ๊ฒ ์ฒ๋ฆฌ๋์์ต๋๋ค.
๋ชฉํ: ๋ค์์ ์ฌ์ฉ์๊ฐ ๋์์ ์ถฉ์ ๋ฐ ์ฌ์ฉ ์์ฒญ์ ๋ณด๋ผ ๋, ์์คํ ์ ์ฑ๋ฅ๊ณผ ์ ํ์ฑ์ ํ์ธ.
- ์๋๋ฆฌ์ค: 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๋ก ํ์ธํ ์ ์์์ต๋๋ค.
- ์ฑ๋ฅ ๋ถ์: ์์ฒญ ์๊ฐ ์ฆ๊ฐํ์์๋ ๋ถ๊ตฌํ๊ณ , ๊ฐ ์ฌ์ฉ์์ ์์ฒญ์ด ์ ํํ๊ณ ์ ์ํ๊ฒ ์ฒ๋ฆฌ๋์์ต๋๋ค. ๋ชจ๋ ํ ์คํธ ์ผ์ด์ค์์ ๋์์ฑ ๋ฌธ์ ์์ด ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ ์ ์์์ ํ์ธํ์ต๋๋ค.