ํ์ : ๊ฐ์ธ ํ๋ก์ ํธ
๊ธฐ๊ฐ : 2025.01 ~ ์งํ ์ค
๋งํฌ : https://github.com/M-ung/TicToc_Server
์๋น์ค ๋ด์ฉ : ๋น์ ์ ์๊ฐ์ ๊ฐ์น๋ฅผ ๋งค๊ธฐ๋ค, ์๊ฐ ๊ฑฐ๋ ๊ฒฝ๋งค ํ๋ซํผ
๊ฒฝ๋งค(Auction)์ ํ์ฌ ์งํ ์ํ๋ฅผ progress
๋ผ๋ ์นผ๋ผ์ ๋๊ณ ๊ด๋ฆฌ๋ฅผ ํ๊ธฐ๋ก ํ๋ค.
์ด ์นผ๋ผ์ ์๋์ ๊ฐ์ ์ํ๋ฅผ ๊ฐ์ง๋ค.
โข NOT_STARTED
: ์์ง ๊ฒฝ๋งค๊ฐ ์์๋์ง ์์
โข IN_PROGRESS
: ๊ฒฝ๋งค๊ฐ ์งํ ์ค
โข BID
: ๊ฒฝ๋งค ์ข
๋ฃ ๋ฐ ์
์ฐฐ ์๋ฃ
โข NOT_BID
: ๊ฒฝ๋งค ์ข
๋ฃ ๋ฐ ์
์ฐฐ ์คํจ
๊ฒฝ๋งค ์์ ์ ์ญ์ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ฉด์ ๊ณ ๋ฏผ์ด ์๊ฒผ๋ค. ์ด๋ฏธ ๊ฒฝ๋งค๊ฐ ์์๋ ์ํ์์ ์์ /์ญ์ ๋ฅผ ํ๋ ๊ฑด ์๋ชป๋ ๊ธฐํ์ด๋ผ๊ณ ์๊ฐํด์ ์๋์ ๊ฐ์ด ์์ /์ญ์ ์ ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์คํํ๋ค.
ํ์ง๋ง ๊ฒฝ๋งค ์์ /์ญ์ ๊ณผ์ ์์ ์ ์ฐฐ์ด ๋ค์ด์ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น..?
๊ธฐํ์ ์๋์ ๊ฐ์ด ์ ์ํ๋ค.
1. ๊ฒฝ๋งค ์์ ์ค ์
์ฐฐ์ด ๋ค์ด์ค๋ฉด ์
์ฐฐ ์ฑ๊ณต โ
, ์์ ์คํจ โ
2. ๊ฒฝ๋งค ์ญ์ ์ค ์
์ฐฐ์ด ๋ค์ด์ค๋ฉด ์
์ฐฐ ์ฑ๊ณต โ
, ์์ ์คํจ โ
์ ๊ธฐํ๋๋ก ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด ์๋์ ๊ฐ์ ํด๊ฒฐ์ฑ ์ ๊ณ ๋ คํ๋ค.
์์ ๋ฐ ์ญ์ ๋ก์ง์ด ๊ฒฝ๋งค(Auction)์ ๋ณ๊ฒฝ์ ๊ฐ์งํด์ผ ํ๋ค.
๊ฐ์งํ๊ธฐ ์ํด์ "๋น๊ด์ ๋ฝ"์ ์ ์ฉํ๊ธฐ๋ก ํ๋ค.
๋น๊ด์ ๋ฝ์ ์ ์ฉํ๊ธฐ ์ํด delete
์ update
๋ฅผ ์๋์ ๊ฐ์ด ๊ตฌํํ๋ค.
๐ AuctionCommandService.java - update
๐ AuctionCommandService.java - delete
๊ทธ๋ฆฌ๊ณ ์ ๋ฉ์๋ ์์ ์กด์ฌํ๋ findAuctionByIdForUpdate
๋ ์๋์ ๊ฐ์ด ๋น๊ด์ ๋ฝ์ ์ ์ฉํด์ ๊ตฌํํ๋ค.
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT a FROM Auction a WHERE a.id = :auctionId and a.status = :status")
Optional<Auction> findByIdAndStatusForUpdate(@Param("auctionId") Long auctionId, @Param("status") TicTocStatus status);
๋ด๊ฐ ์๊ฐํ ๊ธฐํ ์๋๋ฆฌ์ค์ ๋ง๊ฒ ์๋ ํ ์คํธ๋ฅผ ์์ฑํ๋ค.
๐ ๊ฒฝ๋งค ์์ ๋์์ฑ ํ ์คํธ
@Test
@DisplayName("๊ฒฝ๋งค ์์ ์ ๋์์ฑ ์ด์ ๋ฐ์์ ๋ํ ํ
์คํธ")
public void ๊ฒฝ๋งค_์์ _์_๋์์ฑ_์ด์_๋ฐ์์_๋ํ_ํ
์คํธ() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(() -> {
try {
bidCommandUseCase.bid(2L, new BidUseCaseReqDTO.Bid(auction.getId(), BID_PRICE));
} catch (Exception e) {
assertThat(e.getMessage()).isEqualTo(ErrorCode.BID_FAIL.getMessage());
} finally {
latch.countDown();
}
});
executorService.submit(() -> {
try {
AuctionUseCaseReqDTO.Update updateDTO = new AuctionUseCaseReqDTO.Update(
"Updated Auction Title",
"Updated Auction Description",
1200,
LocalDateTime.now().plusMinutes(15),
LocalDateTime.now().plusHours(1),
LocalDateTime.now().plusHours(2),
Collections.emptyList(),
AuctionType.ONLINE
);
auctionCommandUseCase.update(1L, auction.getId(), updateDTO);
} catch (Exception e) {
assertThat(e).isInstanceOf(ConflictAuctionUpdateException.class);
} finally {
latch.countDown();
}
});
latch.await();
executorService.shutdown();
Auction afterAuction = auctionRepositoryPort.findAuctionById(auction.getId());
assertThat(afterAuction.getTitle()).isEqualTo("Test Auction");
assertThat(afterAuction.getProgress()).isEqualTo(AuctionProgress.IN_PROGRESS);
assertThat(afterAuction.getCurrentPrice()).isEqualTo(BID_PRICE);
}
๐ ๊ฒฝ๋งค ์ญ์ ๋์์ฑ ํ ์คํธ
@Test
@DisplayName("๊ฒฝ๋งค ์ญ์ ์ ๋์์ฑ ์ด์ ๋ฐ์์ ๋ํ ํ
์คํธ")
public void ๊ฒฝ๋งค_์ญ์ _์_๋์์ฑ_์ด์_๋ฐ์์_๋ํ_ํ
์คํธ() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(() -> {
try {
bidCommandUseCase.bid(2L, new BidUseCaseReqDTO.Bid(auction.getId(), BID_PRICE));
} catch (Exception e) {
assertThat(e.getMessage()).isEqualTo(ErrorCode.BID_FAIL.getMessage());
} finally {
latch.countDown();
}
});
executorService.submit(() -> {
try {
auctionCommandUseCase.delete(1L, auction.getId());
} catch (Exception e) {
assertThat(e).isInstanceOf(ConflictAuctionDeleteException.class);
} finally {
latch.countDown();
}
});
latch.await();
executorService.shutdown();
Auction deletedAuction = auctionRepositoryPort.findAuctionById(auction.getId());
assertThat(deletedAuction.getStatus()).isEqualTo(TicTocStatus.ACTIVE);
assertThat(deletedAuction.getProgress()).isEqualTo(AuctionProgress.IN_PROGRESS);
assertThat(deletedAuction.getCurrentPrice()).isEqualTo(BID_PRICE);
}
๊ทธ์น๋ง... ํ
์คํธ๊ฐ ์คํจํ๋ค..๐ญ๐ญ
์ ์ฝ๋์ ๋ํ ์๋๋ฆฌ์ค๋ ์๋ ์ฌ์ง๊ณผ ๊ฐ๋ค.
๋จผ์ ์์ /์ญ์ ์ ํด๋น ๊ฒฝ๋งค๋ฅผ ์กฐํํ๊ณ ์ด๋ฅผ Lock ํ๋ค. ๊ทธ ํ ์ ์ฐฐ์ ์๋ํ๊ฒ ๋๋ฉด ์ ๊ฒจ ์๊ธฐ ๋๋ฌธ์ ์ ์ฐฐ์ ์คํจํ๊ฒ ๋๋ค.
๊ทธ๋ผ ์ฝ๋๋ฅผ ๋ค๋ฅธ ๋ฐฉํฅ์ผ๋ก ์๊ฐํด ๋ณด์์ผ ํ๋ค.
๋น๊ด์ ๋ฝ์ด ์๋, ๋๊ด์ ๋ฝ์ ์ ์ฉํด ๋ณด๊ธฐ๋ก ํ๋ค.
๐ Auction.java
๋จผ์ Auction ์ํฐํฐ์ @Verison
์ ์ถ๊ฐํ๋ค.
๊ทธ ํ update, delete ๋ฉ์๋์ ๋๊ด์ ๋ฝ ๋ก์ง์ ์ ์ฉํ๋ค.
๐ AuctionCommandService.java - update
๐ AuctionCommandService.java - delete
๐ AuctionRepository.java - findByIdAndStatusForUpdate
์ ๊ตฌํ์ ์๋๋ฆฌ์ค๋ ์๋์ ๊ฐ๋ค.
์ ๊ตฌํ ๊ฒฐ๊ณผ, ์์์ ์คํจํ๋ ํ
์คํธ ์ฝ๋๋ฅผ ํต๊ณผํ ์ ์์๋ค.
์ฆ, ๋ฌธ์ ์์ ์ ๊ธฐํ ๊ธฐํ๋๋ก ๊ตฌํํ ๊ฒ์ด๋ค.
1. ๊ฒฝ๋งค ์์ ์ค ์
์ฐฐ์ด ๋ค์ด์ค๋ฉด ์
์ฐฐ ์ฑ๊ณต โ
, ์์ ์คํจ โ
2. ๊ฒฝ๋งค ์ญ์ ์ค ์
์ฐฐ์ด ๋ค์ด์ค๋ฉด ์
์ฐฐ ์ฑ๊ณต โ
, ์์ ์คํจ โ
์ด๋ฒ ๊ฒฝํ์ ํตํด ๋ฌด์กฐ๊ฑด ๋น๊ด์ ๋ฝ์ด ์ข๋ค! ๋๊ด์ ๋ฝ์ด ์ข๋ค! ๋ณด๋จ ์ธ์ ์ด๋ค ์ํฉ์์ ์ด๋ค ๋ฐฉ์์ผ๋ก ํ์ด๋๊ฐ๋ ๊ฒ ๋ง๋์ง๋ฅผ ๋ฐฐ์ธ ์ ์์๋ค.
๋จ์ํ ๋์์ฑ์ ํผํ๊ธฐ ์ํด ๋น๊ด์ ๋ฝ์ ์ ์ฉํด DB๋ฅผ ์ ๊ถ๋ฒ๋ฆฌ๋ ค ํ์ง๋ง, ์ด๋ ์ ์ฐฐ ์์ฒญ์ ๋ฐฉํดํ๋ ์ผ์ด ๋์ด๋ฒ๋ ธ๋ค.
๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์ ๋๊ด์ ๋ฝ, ๋น๊ด์ ๋ฝ, ๋ถ์ฐ ๋ฝ, ๋ฑ ์ด๋ค ์ค๊ณ๋ฅผ ์ ์ฉํ๋ ์ ์๊ณ ์ํฉ์ ๋ง๊ฒ ์ฐ๋ ๊ฒ์ด ์ค์ํ๋ค๋ ๊ฒ์ ๋ฐฐ์ธ ์ ์์๋ค.