-
Notifications
You must be signed in to change notification settings - Fork 1
#28 송금 요청 만료 기능 #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
donggi-lee-bit
wants to merge
30
commits into
main
Choose a base branch
from
feature/28
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
#28 송금 요청 만료 기능 #29
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
382ad0a
feat: 송금 요청 만료 기능
donggi-lee-bit 25d0a89
refactor: 송금 요청 검증 메서드 분리
donggi-lee-bit 6e33f1b
refactor: 출금 대기 금액 취소 로직을 도메인 서비스로 캡슐화
donggi-lee-bit f2aa57f
feat: 송금 요청 만료 시 송금자의 출금 대기 금액 롤백 처리
donggi-lee-bit e4336a4
feat: 송금 요청 만료 시간을 계산하여 관리하는 필드 추가
donggi-lee-bit fd80157
refactor: 송금 요청 만료 처리 메서드 내에서 송금자의 송금 대기 금액 롤백 처리
donggi-lee-bit a059aa9
feat: 송금 요청 만료 갱신을 위한 ExpirationQueueManager 추가
donggi-lee-bit 28c7245
feat: 송금 요청 생성 시 만료 갱신을 위해 송금 요청을 큐에 등록
donggi-lee-bit ed56581
feat: 만료된 송금 요청을 큐에서 가져와서 만료 갱신
donggi-lee-bit 3f8a236
feat: 서비스 재시작 시 아직 처리되지 않은 요청들을 조회하여 만료 갱신 큐에 등록
donggi-lee-bit f78de68
refactor: 만료 시각을 송금 요청이 생성되는 시점에 확정되도록 생성 방식 변경
donggi-lee-bit abd9645
remove: DelayQueue 기반 만료 처리 로직 제거
donggi-lee-bit 06ae7fc
feat: 잔액 조회 시 만료된 요청이 존재할 경우 해당 요청을 만료 처리
donggi-lee-bit 4458744
feat: 비동기 처리용 스레드풀 추가
donggi-lee-bit fd51ec6
feat: 기본 스레드풀 정의
donggi-lee-bit cc3843c
feat: 만료된 요청 조회 기능
donggi-lee-bit 5542170
feat: 송금 요청 만료 상태로 일괄 갱신
donggi-lee-bit 93da03b
feat: 송금 요청 상태 변경 이력 일괄 저장
donggi-lee-bit 8a378d1
feat: 네임드락 실행 템플릿
donggi-lee-bit 31a3dba
feat: 네임드락 정책을 관리하는 enum 클래스
donggi-lee-bit 391c921
feat: 송금 요청 만료 등으로 인한 계좌의 송금 대기 금액 롤백
donggi-lee-bit 1aa9a1d
test: Account 도메인의 롤백 검증 로직 추가에 따른 테스트 환경 보완
donggi-lee-bit 06a7a1b
feat: 송금 요청 만료 일괄 처리 기능 추가
donggi-lee-bit 144cbff
refactor: 만료 처리 로직 개선
donggi-lee-bit 5353361
feat: 송금 요청 만료 처리를 위한 스케줄러 로직 추가
donggi-lee-bit 1dc05c6
test: 만료 요청 배치 갱신 로직 테스트 추가
donggi-lee-bit 85929c4
refactor: 상태별 정적 팩토리 메서드 도입하여 RemittanceStatusHistory 생성 로직 개선
donggi-lee-bit 7629744
test: TestContainer로 MySQL DB를 실행시켜 네임드락 경합 테스트 추가
donggi-lee-bit ca3e030
test: SpringBoot 실행이 필요한 테스트에 커스텀 어노테이션 IntegrationTest 을 사용하도록 변경
donggi-lee-bit dc319c9
test: 네임드락 경합 테스트에서 SpyBean 없이 동작하도록 테스트 구조 변경
donggi-lee-bit File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
src/main/java/com/donggi/sendzy/account/application/AccountBalanceQueryService.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package com.donggi.sendzy.account.application; | ||
|
|
||
| import com.donggi.sendzy.account.domain.AccountService; | ||
| import com.donggi.sendzy.account.dto.AccountBalanceResponse; | ||
| import com.donggi.sendzy.remittance.application.RemittanceExpirationService; | ||
| import com.donggi.sendzy.remittance.domain.RemittanceRequest; | ||
| import com.donggi.sendzy.remittance.domain.repository.RemittanceRequestRepository; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @Service | ||
| public class AccountBalanceQueryService { | ||
|
|
||
| private final AccountService accountService; | ||
| private final RemittanceRequestRepository remittanceRequestRepository; | ||
| private final RemittanceExpirationService remittanceExpirationService; | ||
|
|
||
| @Transactional(readOnly = true) | ||
| public AccountBalanceResponse getBalanceWithRequestExpiredCheck(final long memberId) { | ||
| final var account = accountService.getByMemberId(memberId); | ||
| final var now = LocalDateTime.now(); | ||
| final List<RemittanceRequest> pendingRequests = remittanceRequestRepository.findPendingRequestsBySenderId(memberId); | ||
|
|
||
| final Map<Boolean, List<RemittanceRequest>> requestsByExpiration = pendingRequests.stream() | ||
| .collect(Collectors.partitioningBy(r -> r.isExpired(now))); | ||
| final var expiredRequests = requestsByExpiration.get(true); | ||
| final var activeRequests = requestsByExpiration.get(false); | ||
|
|
||
| // 만료된 요청 갱신 | ||
| expiredRequests.forEach(remittanceExpirationService::expireRequest); | ||
|
|
||
| return AccountBalanceResponse.from(account, activeRequests); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
src/main/java/com/donggi/sendzy/account/domain/RollbackTarget.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.donggi.sendzy.account.domain; | ||
|
|
||
| /** | ||
| * 송금 요청 만료 등으로 인해 계좌의 송금 대기 금액을 롤백할 때 사용되는 도메인 객체 | ||
| * | ||
| * @param senderId 송금자 계좌 ID | ||
| * @param amount 롤백할 송금 대기 금액 | ||
| */ | ||
| public record RollbackTarget(long senderId, long amount) { | ||
| } |
34 changes: 33 additions & 1 deletion
34
src/main/java/com/donggi/sendzy/account/dto/AccountBalanceResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,36 @@ | ||
| package com.donggi.sendzy.account.dto; | ||
|
|
||
| public record AccountBalanceResponse(long balance) { | ||
| import com.donggi.sendzy.account.domain.Account; | ||
| import com.donggi.sendzy.remittance.domain.RemittanceRequest; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record AccountBalanceResponse( | ||
| long balance, | ||
| long holdAmount, | ||
| long availableAmount, | ||
| List<HoldDetail> holdDetails | ||
| ) { | ||
| /** | ||
| * 잔액 조회 응답 객체 | ||
| * @param account 계좌 정보 | ||
| * @param activeRequests 진행중인 송금 요청 리스트 | ||
| * @return AccountBalanceResponse | ||
| */ | ||
| public static AccountBalanceResponse from(final Account account, final List<RemittanceRequest> activeRequests) { | ||
| final var holdAmount = activeRequests.stream() | ||
| .mapToLong(RemittanceRequest::getAmount) | ||
| .sum(); | ||
|
|
||
| final var availableAmount = account.getBalance() - holdAmount; | ||
|
|
||
| final List<HoldDetail> holdDetails = activeRequests.stream() | ||
| .map(request -> new HoldDetail(request.getReceiverId(), request.getAmount())) | ||
| .toList(); | ||
|
|
||
| return new AccountBalanceResponse(account.getBalance(), holdAmount, availableAmount, holdDetails); | ||
| } | ||
|
|
||
| private record HoldDetail(long receiverId, long amount) { | ||
| } | ||
| } |
8 changes: 8 additions & 0 deletions
8
src/main/java/com/donggi/sendzy/account/exception/InsufficientPendingAmountException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.donggi.sendzy.account.exception; | ||
|
|
||
| public class InsufficientPendingAmountException extends RuntimeException { | ||
| public InsufficientPendingAmountException(final long pendingAmount, final long rollbackAmount) { | ||
| super("롤백할 수 있는 대기 금액이 부족합니다. 현재 대기 금액: " + | ||
| pendingAmount + ", 롤백 시도 금액: " + rollbackAmount); | ||
| } | ||
| } |
7 changes: 7 additions & 0 deletions
7
src/main/java/com/donggi/sendzy/account/exception/InvalidRollbackAmountException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.donggi.sendzy.account.exception; | ||
|
|
||
| public class InvalidRollbackAmountException extends RuntimeException { | ||
| public InvalidRollbackAmountException(final long amount) { | ||
| super("롤백 금액은 0보다 커야 합니다. 입력된 금액: " + amount); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
src/main/java/com/donggi/sendzy/common/async/AsyncConfig.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package com.donggi.sendzy.common.async; | ||
|
|
||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.scheduling.annotation.AsyncConfigurer; | ||
| import org.springframework.scheduling.annotation.EnableAsync; | ||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.concurrent.Executor; | ||
|
|
||
| @EnableAsync | ||
| @Configuration | ||
| public class AsyncConfig implements AsyncConfigurer { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(AsyncConfig.class); | ||
|
|
||
| @Bean("remittanceExpireExecutor") | ||
| public ThreadPoolTaskExecutor remittanceExpireExecutor() { | ||
| ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); | ||
|
|
||
| taskExecutor.setCorePoolSize(4); // 항상 살아있을 스레드 | ||
| taskExecutor.setMaxPoolSize(16); // 최대 확장 | ||
| taskExecutor.setQueueCapacity(5_000); // 대기 큐 | ||
|
|
||
| taskExecutor.setThreadNamePrefix("expire-"); | ||
| taskExecutor.setAwaitTerminationSeconds(30); | ||
| taskExecutor.setWaitForTasksToCompleteOnShutdown(true); | ||
|
|
||
| taskExecutor.initialize(); | ||
| return taskExecutor; | ||
| } | ||
|
|
||
| @Bean("defaultTaskExecutor") | ||
| public ThreadPoolTaskExecutor defaultTaskExecutor() { | ||
| ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); | ||
| taskExecutor.setCorePoolSize(8); | ||
| taskExecutor.setMaxPoolSize(32); | ||
| taskExecutor.setQueueCapacity(10_000); | ||
| taskExecutor.setThreadNamePrefix("async-"); | ||
| taskExecutor.initialize(); | ||
| return taskExecutor; | ||
| } | ||
|
|
||
| @Override | ||
| public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { | ||
| return (ex, method, params) -> | ||
| log.error("[async] {}({}) 실패", method.getName(), Arrays.toString(params), ex); | ||
| } | ||
|
|
||
| @Override | ||
| public Executor getAsyncExecutor() { | ||
| return defaultTaskExecutor(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
src/main/java/com/donggi/sendzy/common/lock/NamedLockAcquisitionException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.donggi.sendzy.common.lock; | ||
|
|
||
| import com.donggi.sendzy.common.exception.BusinessException; | ||
|
|
||
| public class NamedLockAcquisitionException extends RuntimeException { | ||
|
|
||
| public NamedLockAcquisitionException(final String lockName) { | ||
| super("Lock 획득 실패: " + lockName); | ||
| } | ||
| } |
12 changes: 12 additions & 0 deletions
12
src/main/java/com/donggi/sendzy/common/lock/NamedLockMapper.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.donggi.sendzy.common.lock; | ||
|
|
||
| import org.apache.ibatis.annotations.Mapper; | ||
| import org.apache.ibatis.annotations.Param; | ||
|
|
||
| @Mapper | ||
| public interface NamedLockMapper { | ||
|
|
||
| int getLock(@Param("lockName") final String lockName, @Param("timeout") final int timeout); | ||
|
|
||
| int releaseLock(@Param("lockName") final String lockName); | ||
| } |
23 changes: 23 additions & 0 deletions
23
src/main/java/com/donggi/sendzy/common/lock/NamedLockPolicy.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package com.donggi.sendzy.common.lock; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| /** | ||
| * 네임드 락의 식별자(key)와 타임아웃 설정을 정의합니다. | ||
| * | ||
| * - 락 이름은 "도메인:의도:세부내용" 형식으로 구성하여 명확한 의미 전달과 충돌 방지를 유도합니다. | ||
| * - 타임아웃 값은 락 점유 대기 시간(seconds)으로, 상황에 따라 설정할 수 있습니다. | ||
| * | ||
| * 공통 락 정의를 enum으로 관리함으로써, 일관성 있는 락 네이밍과 정책 적용을 지원합니다. | ||
| */ | ||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public enum NamedLockPolicy { | ||
|
|
||
| REMITTANCE_EXPIRE_BATCH("remittance:expiration:batch", 0), | ||
| ; | ||
|
|
||
| private final String key; | ||
| private final int timeout; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 메소드에서는 만료된 요청을 갱신하는 DML작업이 포함되어있으므로 readOnly옵션을 적용하시면 예외가 발생할 수 있습니다~