๋ถํ๋ฅผ ์ ์ ํ๊ฒ ์ถ์ํ๊ธฐ - ์๋น์ค ๋ถ๋ฆฌ feat MSA..
๐ ๊ณผ์ ๊ฐ์
- ๋ด๊ฐ ๊ฐ๋ฐํ ๊ธฐ๋ฅ์ ํธ๋์ญ์ ๋ฒ์์ ๋ํด ์ดํดํ๊ณ , ์๋น์ค์ ๊ท๋ชจ๊ฐ ํ์ฅ๋์ด MSA ํํ๋ก ์๋น์ค๋ฅผ ๋ถ๋ฆฌํ๋ค๋ฉด ์ด๋ค ์๋น์ค๋ก ๋ถ๋ฆฌ ํ์ฅ๋ ์ง ์ค๊ณํ๊ณ , ๊ทธ ๋ถ๋ฆฌ์ ๋ฐ๋ฅธ ํธ๋์ญ์ ์ฒ๋ฆฌ์ ํ๊ณ์ ํด๊ฒฐ๋ฐฉ์์ ๋ํ ์๋น์ค ์ค๊ณ๋ฌธ์ ์์ฑ
ํด๋น ๊ณผ์ ์ ์ด์ ๋ ํ ํธ๋์ญ์ ์ด ๊ธธ์ด์ง ๊ฒฝ์ฐ์ ๋ํ ๋๋น์ด๋ค. ํธ๋์ญ์ ์์ ํธ๋์ญ์ ์ด ๋ถํ์ํ ๋ก์ง์์ ์๊ฐ์ด ๋ง์ด ์๋น ๋ ์ ์๋ค. ๋ํ ๋ก์ง์ด ๊ธธ์ด ์๋ก ์ฐ๊ด๊ด๊ณ๊ฐ ์์ ๋ก์ง์ด ์คํจํ๊ฒ ๋๋ค๋ฉด ์ ์ฒด๊ฐ ๋กค๋ฐฑ ๋์ด๋ฒ๋ฆฌ๊ธฐ๋ ํ๋ค. ์ธ์ ๋ฐ๋๋ฝ ๋ฌธ์ ๋ฑ์ ์ผ๊ธฐ ํ ์ ์๋ค.
์ด ๊ณผ์ ์ ํต์ฌ์ ์๋น์ค ๋ถ๋ฆฌ์ ํ์ฅ์ผ๋ก ๋ณด์ด์ง๋ง, ์ฌ์ค ํธ๋์ญ์ ์ ๋๋ ์ฃผ๋ ์์ ์ด ์ค์ํ ๊ฒ์ด๋ค. (MSA ๋ํ ๊ทธ๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํจ์ด ์์ผ๋..?)
๐ ์์ํ๊ธฐ ์์.. MSA๋?
- ๋๋ฉ์ธ ์ค์ฌ์ ์ค์ฌ์ผ๋ก ์๋น์ค๋ฅผ ๋ชจ๋ธ๋งํ๊ณ ๊ตฌํํ๋ ์ํคํ ์ฒ
- ๋ถ๋ฆฌ๋ ๋๋ฉ์ธ ์๋น์ค๋ค์ HTTP API ๋๋ ๋น๋๊ธฐ ๋ฉ์์ง ๋ฐฉ์์ผ๋ก ํต์
- ์ฌ๋ฌ ์ดํ๋ฆฌ์ผ์ด์ ์ผ๋ก ๋๋์ด์ ๊ฐ ์๋ฒ์ ๋ฐฐํฌํด์ ์๋ก ๊ฐ์ ํต์ ์ ํตํด ์๋น์ค๋ฅผ ์ ๊ณต
(์ผ๋จ์ ๋๋ฉ์ธ์ ๋ฐ๋ผ ๋ฐ๋ก ์๋ฒ๋ฅผ ๊ตฌ์ถํ๋ค..์ ๋๋ก.. ๊ฐ๊ธธ์ด ๋ฉ๋ค..(๋ฒ์จ ์๋ฉ์ด๊ฐ ์ํ์จ๋ค))
๐ป MY ์ฝ์ํธ ๋๊ธฐ์ด ๋๋ฉ์ธ
0. Token (๋๊ธฐ์ด)
> Redis๋ก ๋์ฒด๋์์ (์ ํํ๋ ๋ถ๋ฆฌ๋์ด MSA๊ฐ ํ์ ์์)
1. Wallet (์ฌ์ฉ์ ํฌ์ธํธ)
1.1. ํฌ์ธํธ ์กฐํ
1.2. ํฌ์ธํธ ์ถฉ์
2. Reservation (์์ฝ)
2.1 ์์ฝ ๊ฐ๋ฅ ๋ ์ง ์กฐํ
2.2 ์์ฝ ๊ฐ๋ฅ ์ข์ ์กฐํ
2.3 ์์ฝ
3. Payment (๊ฒฐ์ )
3.1. ๊ฒฐ์
3.1.1. ์ฝ์ํธ ์์์์ฝ -> ์์ฝ์ผ๋ก ํ์
3.1.2. ์ข์ ์์์์ฝ -> ์์ฝ์ผ๋ก ํ์
3.1.3. ํฌ์ธํธ ์ฐจ๊ฐ (๊ธ์ก ๋ถ์กฑ ์ ์ถฉ์ ํ์)
3.1.4. ํฌ์ธํธ ์ฌ์ฉ ํ์คํ ๋ฆฌ ์ ์ฅ
!. ์ ๋ฆฌ
ํด๋น ์๋น์ค๋ 1๋ฒ์ ์ฌ์ฉ์ ํฌ์ธํธ ๋ถ๋ถ๊ณผ 2๋ฒ์ ์์ฝ์ ๊ทธ๋๋ก ๋ผ์ด๋ด๋ ๋ ์ ๋๋ก ํด๋น ๊ธฐ๋ฅ๋ง์ ์ฌ์ฉํ๋ค. ํ์ง๋ง 3๋ฒ์ ๊ฒฐ์ ๋ ์์ฝ ์ ๋ณด๋ ๋ถ๋ฌ์ผํ๋ฉฐ, ํฌ์ธํธ ๋ถ๋ถ๋ ๋ถ๋ฌ์ผ ํ๋ค. ๊ทธ๋ผ ๋ฑ 2๊ฐ์ง๋ก ๋๋ ๋ณผ ์ ์์ ๊ฒ ๊ฐ๋ค.
!. ๋ณ๊ฒฝ
1. Wallet (์ฌ์ฉ์ ํฌ์ธํธ)
1.1. ํฌ์ธํธ ์กฐํ
1.2. ํฌ์ธํธ ์ถฉ์
1.3. ํฌ์ธํธ ์ฐจ๊ฐ (๊ธ์ก ๋ถ์กฑ ์ ์ถฉ์ ํ์)
1.4. ํฌ์ธํธ ์ฌ์ฉ ํ์คํ ๋ฆฌ ์ ์ฅ
2. Reservation (์์ฝ)
2.1 ์์ฝ ๊ฐ๋ฅ ๋ ์ง ์กฐํ
2.2 ์์ฝ ๊ฐ๋ฅ ์ข์ ์กฐํ
2.3 ์์ฝ
2.4. ์ฝ์ํธ ์์์์ฝ -> ์์ฝ์ผ๋ก ํ์
2.5. ์ข์ ์์์์ฝ -> ์์ฝ์ผ๋ก ํ์
3. Payment (๊ฒฐ์ )
2.1 Wallet์ ํฌ์ธํธ ์ฐจ๊ฐ์ ๊ฐ์ ธ์ด
2.2 Reservation์ ์์ฝํ์ ์ ๊ฐ์ ธ์ด
์ด๋ ๊ฒ ๋๊ฐ์ ์ดํ๋ฆฌ์ผ์ด์ ์ผ๋ก ๋ถ๋ฆฌํ๋ฉด ๋์ถฉ ๋ต์ด ๋์ฌ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ค์๋ค. ์์๋์ ์ฝ๋๋ฅผ ํ์ธํด๋ณด์.
ํ์ฌ
public class PaymentService {
@Transactional
public PaymentCommand.getPaymentInfo pay(PaymentCommand.Pay pay) {
LocalDateTime now = LocalDateTime.now();
Long currentTokenUserId = requestTokenUtil.getCurrentTokenUserId();
// ์ฝ์ํธ ์์ฝ (์ํ ํ์ ์ผ๋ก ๋ณ๊ฒฝ)
ConcertReservation concertReservation = concertReservationRepository.findById(pay.concertReservationId());
concertReservation.reserved();
// ์ฝ์ํธ ์๋ฆฌ ํ์ ์ผ๋ก ํด์ค๋ค.
ConcertSeat concertSeat = concertSeatRepository.findById(pay.seatId());
concertSeat.setSeatStatusAssign(now);
// ๊ธ์ก ์ฐจ๊ฐ
usePointWithOptimisticLock result = getUsePointWithOptimisticLock(currentTokenUserId, now);
// ํ์คํ ๋ฆฌ๋ฅผ ์๋๋ค.
walletHistoryRepository.save(getWalletHistory(result.wallet(), result.price(), result.balanceBefore(), result.balanceAfter(), now));
return new PaymentCommand.getPaymentInfo(์์ฝ์ ๋ณด๋ค..);
}
}
- ์ฝ์ํธ๋ฅผ ๋จผ์ ์์ฝํ์ -> ๊ธ์ก์ฐจ๊ฐ์ ํ๋ค ์ด ๋ก์ง์ ๋ถ๋ฆฌํ๋ ค๋ ์ด์ํด ๋ณด์๋ค.
- '๊ฒฐ์ '๋ผ๋ ๋ก์ง์ผ๋ก ๋ถ๋ฆฌํ๋ คํ๋, ํธ์์ ์ ๋ค์ด๊ฐ์ ๋๊ฐ ์๊ฐ์ด ๋ฌ๋ค. ๋ด๋ฐฐ๋ฅผ ์ฌ๋ ค๊ณ "ํ๋ ์น๋ธ๋ ์ฃผ์ธ์."๋ผ๊ณ ๋งํ๋ฉด ๋ด๊ฐ ๊ฒฐ์ ํ ๋๊น์ง ๋ด๋ฐฐ๋ ์ฃผ์ง ์๋๋ค.
- ๊ทธ๋ผ ๊ฒฐ์ ๋ผ๋ ํ์์์ฒด๋ ํฌ์ธํธ ์ฐจ๊ฐ์ ์๋ค. ์์๋ฅผ ๊ธ์ก ์ฐจ๊ฐ ->์ฝ์ํธ ์๋ฆฌ ํ์ ์ผ๋ก ํด์ผํ๋ค.
- '์ฝ์ํธ ์๋ฆฌ ํ์ '์ ๊ฒฐ์ ์๋ ๊ฑฐ๋ฆฌ๊ฐ ์๋ค. ๋ฌผ๋ก ๊ฒฐ์ ๋ ๋ฌผ๊ฑด์ ์ฌ๋ ํ์์ด์ง๋ง, '์๋ฆฌ ํ์ '์ ๋ง์น ์ฟ ํก์์ ๋ฌผ๊ฑด์ ์ฌ๋ ๋ฐฐ์ก์ ๊ธฐ๋ค๋ ค์ผ ํ๋ ๊ฒ ์ฒ๋ผ ๊ทธ ๋ค์์ ํ์๊ฐ ๋ ์ ์๋ค.
- ๋ฌผ๋ก ๋ค๋ฅธ ๋ฐฉ๋ฉด์ผ๋ก๋ '๊ฒฐ์ '์ '์ฝ์ํธ ์๋ฆฌ ํ์ '์ ๋ฌถ์ ์๋ ์๋ค. ๋ค๋ง ์ด๋ฒ์๋ ๋ถ๋ฆฌ์ ๋ชฉ์ ์ด ์์ผ๋ ์ต๋ํ ์ชผ๊ฐ ๋ณด์.
1์ฐจ ์์ ๋ก์ง (๋๋ฉ์ธ ๋ณ๋ก ๋๋๋ค)
public class PaymentPacade {
@Transactional
public PaymentCommand.getPaymentInfo pay(PaymentCommand.Pay pay) {
LocalDateTime now = LocalDateTime.now();
Long currentTokenUserId = requestTokenUtil.getCurrentTokenUserId();
// ๊ธ์ก ์ฐจ๊ฐ ๋ฐ ํ์คํ ๋ฆฌ๋ฅผ ์๋๋ค.
Wallet result = walletService.getUsePointAndSaveHistory(currentTokenUserId, now);
//์์ฝ ํ์
ConcertReservation reservResult = resevationService.reserved(pay.concertReservationId(), pay.seatId(), now);
return new PaymentCommand.getPaymentInfo(์์ฝ์ ๋ณด๋ค..);
}
}
public class ResevationService {
pulbic ConcertReservation reserved(Long concertReservationId, Long seatId){
// ์ฝ์ํธ ์์ฝ (์ํ ํ์ ์ผ๋ก ๋ณ๊ฒฝ)
ConcertReservation concertReservation = concertReservationRepository.findById(concertReservationId);
concertReservation.reserved();
// ์ฝ์ํธ ์๋ฆฌ ํ์ ์ผ๋ก ํด์ค๋ค.
ConcertSeat concertSeat = concertSeatRepository.findById(seatId);
concertSeat.setSeatStatusAssign(now);
retrun concertReservation;
}
}
- PaymentPacade์์ ๊ฐ๊ฐ wallet๊ณผ reservation์ ์๋น์ค๋ฅผ ๋ถ๋ฌ์ค๋ ์์ผ๋ก ๋ง๋ค์๋ค.
- ์ด์ํ ์ ์ด ์๋ค. ๋ถ๋ฆฌ ํ๋ ์ด์ ๋ '๋๋ฉ์ธ ๋ณ๋ก ๋ถ๋ฆฌ ํ์ฅ'๋ ์์ง๋ง, ๋ถํ์ํ ํธ๋์ญ์ ์ ๋ง๋ค์ง ์๋ ๊ฒ์ด๋ค.
- ํ์ง๋ง ์๋ ๊ฐ์ ํธ๋์ญ์ ์ผ๋ก ๋ฌถ์ด๊ฒ ๋๋ค. ๋๋ ๋ณด์.
2์ฐจ ์์ ๋ก์ง (ํธ๋์ญ์ ์ ๋๋๋ค. feat Event)
public class PaymentPacade {
@Transactional
public PaymentCommand.getPaymentInfo pay(PaymentCommand.Pay pay) {
LocalDateTime now = LocalDateTime.now();
Long currentTokenUserId = requestTokenUtil.getCurrentTokenUserId();
// ๊ธ์ก ์ฐจ๊ฐ ๋ฐ ํ์คํ ๋ฆฌ๋ฅผ ์๋๋ค.
Wallet result = walletService.getUsePointAndSaveHistory(currentTokenUserId, now);
//์์ฝ ํ์
reservedEventPublisher.publishEvent(pay);
return new PaymentCommand.getPaymentInfo(์์ฝ์ ๋ณด๋ค..);
}
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
pulbic void reserved(PaymentCommand.Pay pay){
// ์ฝ์ํธ ์์ฝ (์ํ ํ์ ์ผ๋ก ๋ณ๊ฒฝ)
ConcertReservation concertReservation = concertReservationRepository.findById(pay.concertReservationId);
concertReservation.reserved();
// ์ฝ์ํธ ์๋ฆฌ ํ์ ์ผ๋ก ํด์ค๋ค.
ConcertSeat concertSeat = concertSeatRepository.findById(pay.seatId);
concertSeat.setSeatStatusAssign(now);
}
1. ์ ์ฒ๋ผ ํ๋ฉด Event๋ฅผ ์ด์ฉํ๊ธฐ์ ํธ๋์ญ์ ์ ๋ฒ์๊ฐ ๋๋๊ฒ ๋๋ฉฐ, ๋ฐ๋ผ์ ๊ฒฐํฉ๋ ฅ๊น์ง ๋ฎ์ถ๊ฒ ๋๋ค.
2. ๋ค๋ง ๋ฌธ์ ๊ฐ ์๋ค. ๋ง์ฝ '์์ฝ ํ์ ' ๋ก์ง์์ ์์ธ๊ฐ ํฐ์ง๋ค๋ฉด?
3. ์ํฉ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ์ง๋ง ์์ ๊ฒฝ์ฐ, ํฌ์ธํธ ์ฐจ๊ฐ์ ๋กค๋ฐฑ์ด ๋์ด์ผ ํ๋ค.
3์ฐจ ์์ ๋ก์ง (๋กค๋ฐฑ ์ ์ฑ )
public class PaymentPacade {
@Transactional
public PaymentCommand.getPaymentInfo pay(PaymentCommand.Pay pay) {
...
//ํฌ์ธํธ ์ฐจ๊ฐ
pay.Wallet(wallet);
//์์ฝํ์ ์ด๋ฒคํธ ํธ์ถ
}
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
pulbic void reserved(PaymentCommand.Pay pay){
try {
// ์ฝ์ํธ ์์ฝ (์ํ ํ์ ์ผ๋ก ๋ณ๊ฒฝ)
ConcertReservation concertReservation = concertReservationRepository.findById(pay.concertReservationId);
concertReservation.reserved();
// ์ฝ์ํธ ์๋ฆฌ ํ์ ์ผ๋ก ํด์ค๋ค.
ConcertSeat concertSeat = concertSeatRepository.findById(pay.seatId);
concertSeat.setSeatStatusAssign(now);
} catch(Exception e) {
log.warn("์ค๋ฅ๋ฐ์");
ํฌ์ธํธ์ฐจ๊ฐ๋กค๋ฐฑPublisher.publishEvent(pay);
}
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
pulbic void RollbackPoint(PaymentCommand.Pay pay){
try {
Wallet wallet = pay.wallet;
wallet.RollbackPoint(pay);
} catch(Exception e) {
์ฌ์๋ ๋ก์ง..
}
}
1. ์์ธ๊ฐ ํฐ์ง๋ฉด ํฌ์ธํธ๋ฅผ ์ฐจ๊ฐํ๋ ๋กค๋ฐฑ์ ๋ ๋ถ๋ฌ์์ผํ๋ค.
2. ๋กค๋ฐฑ ๋ก์ง์๋ ๋ ์์ธ๊ฐ ํฐ์ง ์ ์๋ค. ๊ทธ๋ผ ์ด๋ป๊ฒ ํ ๊ฒ์ธ๊ฐ???????
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void rollbackPoint(PaymentCommand.Pay pay) {
int attempts = 0;
boolean success = false;
while (attempts < MAX_RETRIES && !success) {
try {
Wallet wallet = pay.wallet;
wallet.rollbackPoint(pay);
success = true; // ์ฑ๊ณตํ๋ฉด ๋ฐ๋ณต๋ฌธ ์ข
๋ฃ
} catch (Exception e) {
attempts++;
if (attempts >= MAX_RETRIES) {
// ๋ก๊น
๋๋ ์๋ฆผ ์ถ๊ฐ ๊ฐ๋ฅ
log.warn("์ฌ์๋ ํ์ ์ด๊ณผ: " + e.getMessage());
//**DB์ ํด๋น ์คํจ ์ ๋ณด๋ฅผ ๋ด์์ค๋ค
} else {
log.warn("์ฌ์๋ ์ค... ์๋ ํ์: " + attempts);
}
}
}
}
1. ์ด๋ ๊ฒ ์ฌ์๋ ํ์๋ ๊ณ์ ์์ธ๊ฐ ํฐ์ง๋ค๋ฉด ์ด์ฉ ์ ์๋ค. ํด๋น ์ ๋ณด๋ฅผ ์ ์กฐํฉํด db์ ๋ด๋๋ค.
2. DB์ ๋ด๊ธด ์ ๋ณด๋ฅผ ํ์ ๋ฐฐ์น์ ๋๋ฆฐ๋ค. ๊ทธ๋๋ ์๋๋ฉด ๊ฐ๋ฐ์์๊ฒ ์๋ฆผ์ ๋ฐ์กํด์ ํด๋น ํ๋ก๊ทธ๋จ์ ์์ ํด์ผ ํ๋ค๋ ์๊ฐ์ด ๋ค์๋ค.
3. ๋ง์ง๋ง์ผ๋ก ํด๋น ๋ก์ง์ด MSA๋ก ์์ ๋๋ค๋ฉด?
4์ฐจ ์์ ๋ก์ง (MSA..?)
public class PaymentPacade {
@Transactional
public PaymentCommand.getPaymentInfo pay(PaymentCommand.Pay pay) {
LocalDateTime now = LocalDateTime.now();
Long currentTokenUserId = requestTokenUtil.getCurrentTokenUserId();
// ๊ธ์ก ์ฐจ๊ฐ ๋ฐ ํ์คํ ๋ฆฌ๋ฅผ ์๋๋ค.
Wallet result = walletService.getUsePointAndSaveHistory(currentTokenUserId, now);
//์์ฝ ํ์
reservedEventPublisher.publishEvent(pay);
return new PaymentCommand.getPaymentInfo(์์ฝ์ ๋ณด๋ค..);
}
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
pulbic void reserved(PaymentCommand.Pay pay){
//์์๋ฐ์ค ํจํด?
String result = reservedClient.reserved(pay);
if(result.equals("fail")){
//์ฌ์๋ ๋ก์ง
// ์ฌ์๋ ์คํจ ์ DB ์ ์ฅ..
}
}
1. ์์ ๋ก์ง์ฒ๋ผ ๋ค๋ฅธ api๋ฅผ ํธ์ถํด์ ์ ๋ณด๋ฅผ ๋ฐ๋๋ค.
2. ์คํจ์ ์ฌ์๋ ๋ก์ง์ ์ฌ์ฉํ๋ค.
3. ๋ง์ฝ api ์์ฒญ ์ค์ ๋์ api์์ ๋ฌธ์ ๊ฐ ์๊ฒจ ์ฐ๊ฒฐ์ด ๋๊ธด๋ค๋ฉด ์์๋ฐ์ค ํจํด์ ์ฌ์ฉํ๋ค๊ณ ํ๋ค.
4. ํธ์ถ ๋ถ๋ถ์ ํด๋น ์์ฒญ์ db์ ๋ฃ๊ณ ์ ๋์ฐฉํ์ ๋์ db์ ๋์ฐฉ์๋ฃ๋ก ์ํ ๊ฐ์ ๋ฃ์ด์ฃผ๋ ์์ผ๋ก ํด๋น ์์ฒญ์ ์ฑ๊ณต์ฌ๋ถ๋ฅผ ํ๋จํ ์ ์๊ฒ ๋๋ค.
๋์ผ๋ก
์์ง ํธ๋์ญ์ ๋ถํ ์ ๋ํด ๋ฏธ์ํ์ฌ ๋ง์ด ๋ฏธํกํ๋ค. ์์ ์ค๊ณ์ด์ง๋ง ํธ๋์ญ์ ์ ์ด๋ป๊ฒ ๋ถ๋ฆฌํด์ผ ๋๋์ง์ ๋ํ ๊ฐ์ ์ก๋ ์์คํ ์๊ฐ์ด์๋ค๊ณ ์๊ฐํ๋ค.