Skip to content

[FEAT] 복식부기를 고려한 코인 장부 저장 기능 구현#273

Merged
yooooonshine merged 2 commits intodevelopfrom
feature/269-implement-recording-transaction
Nov 23, 2025
Merged

[FEAT] 복식부기를 고려한 코인 장부 저장 기능 구현#273
yooooonshine merged 2 commits intodevelopfrom
feature/269-implement-recording-transaction

Conversation

@yooooonshine
Copy link
Contributor

@yooooonshine yooooonshine commented Nov 23, 2025

개요

작업사항

기존 Redis 분산 락 로직에 장부 처리 로직 적용하기

image

현재 Redis 분산락은 위와 같이 내부적으로 REQUIRED_NEW 트랜잭션으로 새로운 트랜잭션에서 처리하게 끔 해두었다.(트랜잭션 생명주기가 레디스 분산 락 생명주기보다 짧게 가져가기 위하여)

여기서 Transaction을 분산 락 안에서 처리해야할지 밖에서 처리해도 되는지 고민이 되었다.

밖에서 처리하는 경우

1. Transaction과 Entities를 PENDING 상태로 저장(commit)
2. REDIS 분산  처리(commit)
3. Transaction과 Entities를 POSTED로 처리(commit)

이렇게 세 번의 커밋과정을 거쳐야 한다.

여기서 만약 2번 과정까지 성공했는데, 3번에서 실패하면 실제 돈은 전송됐는데, 장부에는 안남는 문제가 생긴다,

그러므로 분산 락 안에서 장부 처리가 이뤄져야 한다.

레디스 분산락에서 고려해야할 케이스

1. PENDING 실패
->  이상 진행 x

1. PENDING 성공
2. 레디스 분산  처리 실패
-> PENDING은 취소로 바꾸고 실패 처리

1. PENDING 성공
2. 레디스 분산  처리 성공
3. POSTED 실패
-> 

1. PENDING 성공
2. 레디스 분산  처리 성공(Account 변경)
3. POSTED 성공

PG사 API가 아니라 내부적으로 처리되는 로직이므로, 별도의 PENDING을 고려할 필요 없었다.

따라서 이 경우 아래와 같이 동작하게 했다.

2. 레디스 분산  처리 실패
-> 

2. 레디스 분산  처리 성공
3. POSTED 실패
-> 전체 롤백

2. 레디스 분산  처리 성공(Account 변경)
3. POSTED 성공
-> 성공

즉 코인 송금과 Ledger 처리가 Atomicity하게 동작하여야 했다.

따라서 기존 로직을 수정하여

image

분산 락 안에서 거래, Transaction 저장, Entity 저장이 모두 이뤄지게 했다.

그리고 내부적으로, Transaction과 한 쌍의 Entity(debit, credit)을 모두 저장하여 복식부기를 따르게 했다.

또한 알람 같은 경우 락 내에서 이뤄질 필요가 없었기에

image

외부에서 처리되게 했다.

고려사항

  • 당장은 내부 거래이기 떄문에 장부 처리를 쉽게 할 수 있었다. 하지만 외부 API를 사용하는 경우 단일 트랜잭션 내에서 장부처리를 할 수 없으므로 더 많은 고민이 필요할 거 같다.

Summary by CodeRabbit

  • 새로운 기능

    • AI 이미지 구매 시 코인 전송을 원장 기반 전송으로 전환해 동시성 안정성 향상
    • 계정 잔액 표시를 개선해 잔액 추적성 및 일관성 강화
  • 버그 수정

    • 거래 관련 데이터베이스 테이블명 오타 수정
  • Chores

    • API 오류 매핑 확장(사용자 미발견 처리 추가)
    • 변경된 전송/잔액 흐름에 맞춰 관련 테스트 및 시나리오 갱신

✏️ Tip: You can customize this high-level summary in your review settings.

@yooooonshine yooooonshine linked an issue Nov 23, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Nov 23, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Account 엔티티의 잔액/버전 필드 재구성과 LedgerService 도입으로 동전 이체가 분산 잠금 기반 원장(ledger) 방식으로 변경되었고, AiImagePermissionService는 동전 소비(con­sume)에서 이체(transfer) 호출로 대체되었습니다.

Changes

Cohort / File(s) Summary
Account 엔티티 재구성
src/main/java/hanium/modic/backend/domain/transaction/entity/Account.java
coin, version 필드 제거. ledgerVersion, postedBalance 추가. addCoin()updateBalance(Long)로 시그니처 및 로직 변경(새 잔액 검증, ledgerVersion 증가). DB 컬럼 매핑 업데이트
원장 기반 이체 서비스 추가
src/main/java/hanium/modic/backend/domain/transaction/service/LedgerService.java
분산 잠금으로 다중 계정 잠금 후 송/수금 계정의 postedBalance 조정, CoinTransaction 및 CoinTransactionEntity 생성·저장하는 transfer(amount) 메서드 추가
AccountService 리팩토링
src/main/java/hanium/modic/backend/domain/transaction/service/AccountService.java
LedgerService 주입. 기존 consumeCoin/chargeCoin/addCoin 제거. transferCoin(now @Transactional) 은 ledgerService.transfer 호출로 변경. 잔액 조회는 postedBalance 사용
잠금 인터페이스 확장
src/main/java/hanium/modic/backend/infra/redis/distributedLock/LockManager.java
단일 계정 잠금 제거, multipleAccountLock(List<Long>, Runnable) 추가(다중 키로 withMultiLock 호출)
AI 구매 흐름 변경
src/main/java/hanium/modic/backend/domain/ai/aiChat/service/AiImagePermissionService.java
buyAiImagePermissionByCoin 내부에서 consumeCoin(userId, price)transferCoin(buyerId, sellerId, price) 호출로 변경
프로파일 및 컨트롤러 소소 변경
src/main/java/hanium/modic/backend/domain/profile/service/ProfileService.java, src/main/java/hanium/modic/backend/web/transaction/controller/AccountController.java
Account 잔액 접근을 getCoin()getPostedBalance()로 변경. AccountController transferCoins의 ApiErrorMapping에 USER_NOT_FOUND_EXCEPTION 추가
트랜잭션 엔티티 및 리포지토리 변경
src/main/java/hanium/modic/backend/domain/transaction/entity/CoinTransactionEntity.java, src/main/java/hanium/modic/backend/domain/transaction/repository/...
CoinTransactionEntity의 @Table 이름 오타 수정. CoinTransactionRepository, CoinTransactionEntityRepository 추가. AccountRepositoryOptional<Account> findByUserId(Long) 추가
테스트 업데이트
src/test/java/hanium/modic/backend/.../AiImagePermissionServiceTest.java, .../AccountServiceIntegrationTest.java, .../AiImagePermissionControllerIntegrationTest.java
consumeCoin 호출을 transferCoin으로 전환(파라미터 변경), Account.addCoin → updateBalance, getCoin → getPostedBalance로 조정. 동시성/시나리오 테스트 수정 및 판매자 계정 추가

Sequence Diagram(s)

sequenceDiagram
    participant Buyer as 구매자
    participant AiService as AiImagePermissionService
    participant Ledger as LedgerService
    participant LockMgr as LockManager
    participant AccRepo as AccountRepository
    participant TxRepo as CoinTransactionEntityRepository

    Buyer->>AiService: buyAiImagePermissionByCoin(postId)
    AiService->>Ledger: transfer(buyerId, sellerId, price)
    Ledger->>LockMgr: multipleAccountLock([buyerId,sellerId], block)

    rect rgb(230,245,255)
      Note over LockMgr: 잠금 획득 및 원장 업데이트
      LockMgr->>AccRepo: load(buyerId), load(sellerId)
      AccRepo-->>LockMgr: accountBuyer, accountSeller
      LockMgr->>LockMgr: accountBuyer.updateBalance(newBuyerBalance)
      LockMgr->>LockMgr: accountSeller.updateBalance(newSellerBalance)
      LockMgr->>TxRepo: save(transactionEntity: debit), save(transactionEntity: credit)
      LockMgr->>AccRepo: save(accountBuyer), save(accountSeller)
    end

    LockMgr-->>Ledger: 완료
    Ledger-->>AiService: transfer 완료
    AiService->>AiService: upsertAndIncrease(permission)
    AiService->>AiService: sendNotification()
    AiService-->>Buyer: 권한 구매 완료
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • 주의 깊게 검토할 파일/영역:
    • Account 엔티티(필드 제거·추가, updateBalance 검증)와 모든 호출부 호환성
    • LedgerService의 동시성/잠금·예외 처리 및 트랜잭션 경계 (@Transactional)
    • AccountService에서 기존 소비 로직 제거로 인한 사이드 이펙트(다른 모듈 호출 지점)
    • LockManager의 multi-lock 사용 시 데드락 방지/키 정렬 보장 여부
    • 테스트 변경사항이 실제 프로덕션 로직과 일치하는지(특히 통합 테스트)

Possibly related PRs

Poem

🐰
동전 한 알, 건네주며 뛰어가요,
잠금은 두 손에, 원장은 한켠에,
송금은 깔끔히, 기록은 찰칵찰칵,
당근 한 입에 코드가 빛나네 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.10% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항을 명확히 요약하고 있습니다. '복식부기를 고려한 코인 장부 저장 기능 구현'은 코드 변경의 핵심 목적을 잘 드러냅니다.
Description check ✅ Passed PR 설명이 저장소의 템플릿(개요, 작업사항)을 따르고 있으며, 상세한 기술적 배경과 설계 결정사항을 포함하고 있습니다.
Linked Issues check ✅ Passed 코드 변경사항이 #269 이슈의 요구사항(Transaction과 Entity 기능 도입)을 충족합니다. Account 엔티티 개선, LedgerService 신규 작성, 리포지토리 추가 등이 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 분산 락 내에서 거래 처리의 원자성을 보장하는 범위 내에 있으며, #269 이슈의 목표와 일관성이 있습니다.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92c32e0 and f16b373.

📒 Files selected for processing (1)
  • src/main/java/hanium/modic/backend/infra/redis/distributedLock/LockManager.java (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
src/test/java/hanium/modic/backend/domain/transaction/AccountServiceIntegrationTest.java (1)

35-85: 동시성 시나리오에서 송신자(B,C)의 최종 잔액도 함께 검증하는 것이 좋습니다

현재 테스트는 B,C가 동시에 A에게 500씩 전송했을 때 A의 postedBalance가 1000이 되는지만 검증합니다. 복식부기 관점에서:

  • B,C 각각의 최종 잔액이 0이 되었는지
  • (필요하다면) 관련 CoinTransaction/엔트리 개수까지

를 추가로 검증하면, LedgerService와 Account 엔티티 변경이 더 잘 보호됩니다. 또한 테스트 종료 후 executor.shutdown() 정도로 스레드를 정리해 두면 장기적으로 테스트 안정성에도 도움이 됩니다.

src/test/java/hanium/modic/backend/web/ai/controller/AiImagePermissionControllerIntegrationTest.java (2)

66-109: 성공 케이스에서 판매자 계좌의 증가도 함께 검증하는 것을 추천합니다

buyAiImagePermissionWithCoin_Success에서

  • buyer 계좌를 updateBalance(10000L)로 초기화하고,
  • seller 유저/계좌/포스트를 분리해 생성한 뒤,
  • buyer의 postedBalance == 5000L만 검증

하고 있습니다. 복식부기/코인 양도 관점에서는 seller 계좌의 postedBalance가 5000L로 증가했는지도 같이 검증하면, LedgerService까지 포함한 전체 플로우가 더 견고하게 커버됩니다.

예시:

-        Account updatedAccount = accountRepository.findById(user.getId()).orElse(null);
-        assertThat(updatedUser).isNotNull();
-        assertThat(updatedAccount.getPostedBalance()).isEqualTo(5000L); // 10000 - 5000
+        Account updatedBuyerAccount = accountRepository.findById(user.getId()).orElse(null);
+        Account updatedSellerAccount = accountRepository.findById(user2.getId()).orElse(null);
+
+        assertThat(updatedUser).isNotNull();
+        assertThat(updatedBuyerAccount.getPostedBalance()).isEqualTo(5000L); // 10000 - 5000
+        assertThat(updatedSellerAccount).isNotNull();
+        assertThat(updatedSellerAccount.getPostedBalance()).isEqualTo(5000L); // 0 + 5000

150-178: 코인 부족 케이스에서 권한 upsert 롤백 여부를 assert로 명시해 주세요

buyAiImagePermissionWithCoin_InsufficientCoin 테스트에서:

boolean permissionExists = aiChatRoomRepository.existsByUserIdAndPostId(user.getId(), post.getId());
// upsert가 먼저 실행되므로 권한은 생성되지만 코인 차감에서 실패해야 함
// 실제로는 트랜잭션 롤백이 되어야 하지만, 현재 구조상 체크

까지 계산/주석은 있지만, permissionExists에 대한 assert가 없어 실제 기대 동작이 테스트에 드러나지 않습니다.

  • 이번 PR에서 코인 양도/원장 처리가 트랜잭션 안으로 들어갔다면, 권한 upsert도 함께 롤백되어 permissionExistsfalse 가 되는 것이 이상적일 수 있고,
  • 기존 구조를 유지한다면 반대로 true를 기대할 수도 있습니다.

어느 쪽이든 현재 의도를 assertThat(permissionExists).isFalse() 혹은 isTrue() 로 명확히 드러내 두면, 이후 트랜잭션 구조 변경 시 회귀를 잘 잡을 수 있습니다.

src/main/java/hanium/modic/backend/domain/transaction/service/LedgerService.java (1)

51-75: ledgerVersion 캡처 시점과 락/예외 처리 방식 의도 확인 제안

몇 가지 설계 포인트를 한 번 더 확인해 보시는 것이 좋겠습니다.

  1. ledgerVersion 캡처 시점

    // 3) 버전 캡처, 반드시 Account 업데이트 이후에 호출되어야 함
    long fromVersionBefore = fromAccount.getLedgerVersion();
    long toVersionBefore = toAccount.getLedgerVersion();

    주석은 “업데이트 이후” 캡처를 의도하지만 변수명은 *Before라 약간 혼동의 여지가 있습니다. 또한 updateBalanceledgerVersion 을 증가시키는지, 아니면 별도 메서드가 있는지 이 코드만으로는 보이지 않습니다.
    → 엔트리의 version 필드가 “업데이트 전”인지 “업데이트 후”인지를 명확히 정의하고, 그에 맞춰 변수명/주석을 정리해 두면 이후 리팩토링 때 버그를 막는 데 도움이 됩니다.

  2. multipleAccountLock 의 예외 전달 방식

    • 현재는 LockException만 잡아 COIN_TRANSFER_FAIL_EXCEPTION으로 래핑하고 있고,
    • 콜백 내부에서 발생하는 AppException은 그대로 전파된다는 가정을 두고 있습니다.

    실제 구현에서 콜백 예외를 LockException으로 감싸지 않는지, 획득 실패와 비즈니스 예외가 명확히 구분되는지 한 번 더 확인해 두면 장애 원인 파악이 쉬워집니다.

이 부분들은 기능 버그라기보다는 향후 유지보수성을 높이기 위한 설계 확인/정리 포인트로 보입니다.

src/main/java/hanium/modic/backend/domain/transaction/service/AccountService.java (1)

23-64: LedgerService 위임 구조는 명확하지만, 알림을 동일 트랜잭션에 포함하는 의도는 한 번 더 정리해 두면 좋겠습니다

이번 변경으로:

  • 계좌/원장 관련 변경은 모두 LedgerService.transfer로 위임되고,
  • AccountService.transferCoin
    • 자기 자신 양도 방지,
    • 수신자 존재 확인,
    • LedgerService 호출,
    • 알림 발송

의 역할만 가지는 구조가 되어 책임 분리가 깔끔합니다.

다만 @TransactionaltransferCoin 전체에 걸려 있어, 알림 생성 실패 시 코인 양도/원장 기록까지 롤백되는 구조입니다. “양도는 반드시 성공해야 하고, 알림은 부가 기능” 이라는 요구사항이라면:

  • LedgerService 호출 부분만을 포함하는 별도 트랜잭션으로 두고,
  • 알림은 트랜잭션 밖(또는 별도 비동기 플로우)에서 처리하는 방향

을 고려해 볼 여지가 있습니다. 반대로 “알림까지 포함해서 모두 하나의 트랜잭션으로 간주”하는 것이 의도라면, 이 사실을 메서드 주석 등으로 명시해 두면 이후 유지보수 시 혼동을 줄일 수 있습니다.

src/main/java/hanium/modic/backend/domain/transaction/entity/Account.java (2)

31-36: ledgerVersion/postedBalance 기본값 및 타입 선택 점검 제안

두 필드를 nullable = false + Long 래퍼 + 필드 초기값 0L로 두신 설계는 기능상 문제는 없어 보입니다. 다만 DB 컬럼이 이미 존재한다면, 기존 레코드에서 NULL 값이 남아 있지 않은지 한 번 더 확인해 두는 게 안전합니다. 또한 절대 null이 될 수 없는 값이라면 필드 타입을 long(primitive)으로 두는 것도 NPE 리스크를 줄이는 선택지입니다.


47-55: updateBalance 파라미터 null 처리 및 검증 강화 권장

updateBalance(Long newBalance)에서 newBalance < 0 비교는 호출 측에서 null을 넘기면 NPE가 날 수 있습니다. 비즈니스 상 null이 유효하지 않다면:

  • 시그니처를 updateBalance(long newBalance)로 바꾸거나,
  • 메서드 초기에 Objects.requireNonNull(newBalance, "...") 등으로 명시적으로 검증하는 것을 권장합니다.

또한 현재는 음수만 막고 있어 오버플로 등은 상위 레이어에서 이미 방어한다고 가정하는 설계로 보이는데, 만약 장부에서 직접 합산하여 newBalance를 계산한다면 이 메서드가 “최종 방어선”인지 역할을 명확히 해 두면 유지보수에 도움이 됩니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1fdb8f and 92c32e0.

📒 Files selected for processing (14)
  • src/main/java/hanium/modic/backend/domain/ai/aiChat/service/AiImagePermissionService.java (1 hunks)
  • src/main/java/hanium/modic/backend/domain/profile/service/ProfileService.java (2 hunks)
  • src/main/java/hanium/modic/backend/domain/transaction/entity/Account.java (2 hunks)
  • src/main/java/hanium/modic/backend/domain/transaction/entity/CoinTransactionEntity.java (1 hunks)
  • src/main/java/hanium/modic/backend/domain/transaction/repository/AccountRepository.java (1 hunks)
  • src/main/java/hanium/modic/backend/domain/transaction/repository/CoinTransactionEntityRepository.java (1 hunks)
  • src/main/java/hanium/modic/backend/domain/transaction/repository/CoinTransactionRepository.java (1 hunks)
  • src/main/java/hanium/modic/backend/domain/transaction/service/AccountService.java (3 hunks)
  • src/main/java/hanium/modic/backend/domain/transaction/service/LedgerService.java (1 hunks)
  • src/main/java/hanium/modic/backend/infra/redis/distributedLock/LockManager.java (1 hunks)
  • src/main/java/hanium/modic/backend/web/transaction/controller/AccountController.java (1 hunks)
  • src/test/java/hanium/modic/backend/domain/ai/service/AiImagePermissionServiceTest.java (4 hunks)
  • src/test/java/hanium/modic/backend/domain/transaction/AccountServiceIntegrationTest.java (3 hunks)
  • src/test/java/hanium/modic/backend/web/ai/controller/AiImagePermissionControllerIntegrationTest.java (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/test/java/hanium/modic/backend/domain/transaction/AccountServiceIntegrationTest.java (1)
src/test/java/hanium/modic/backend/domain/user/factory/UserFactory.java (1)
  • UserFactory (9-29)
src/main/java/hanium/modic/backend/domain/transaction/entity/CoinTransactionEntity.java (1)
src/main/java/hanium/modic/backend/domain/transaction/entity/CoinTransaction.java (1)
  • Table (24-50)
src/main/java/hanium/modic/backend/domain/transaction/service/LedgerService.java (3)
src/main/java/hanium/modic/backend/domain/ai/aiChat/service/AiImagePermissionService.java (1)
  • Service (25-139)
src/main/java/hanium/modic/backend/domain/transaction/service/AccountService.java (1)
  • Service (19-65)
src/main/java/hanium/modic/backend/common/error/exception/LockException.java (1)
  • LockException (3-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (10)
src/main/java/hanium/modic/backend/domain/transaction/entity/CoinTransactionEntity.java (1)

26-26: 테이블 이름 오타 수정 완료

coin_tranaction_entities에서 coin_transaction_entities로 수정되었습니다. 이 수정은 데이터베이스 테이블명과의 일치를 보장하며, 런타임 오류를 방지합니다.

src/main/java/hanium/modic/backend/domain/transaction/repository/AccountRepository.java (1)

10-11: LGTM!

findByUserId 메서드는 Spring Data JPA 규칙에 따라 올바르게 정의되었으며, Optional<Account>를 반환하여 null 안전성을 제공합니다. 이는 새로운 ledger 기반 전송 흐름에서 userId로 계좌를 조회하는 데 필요한 메서드입니다.

src/main/java/hanium/modic/backend/domain/profile/service/ProfileService.java (1)

52-52: LGTM!

Account 엔티티의 필드명 변경(coinpostedBalance)에 맞춰 올바르게 수정되었습니다. 이는 복식부기 시스템에서 게시된(posted) 잔액을 나타내는 의미론적으로 더 명확한 이름입니다.

src/main/java/hanium/modic/backend/domain/ai/aiChat/service/AiImagePermissionService.java (1)

50-51: LGTM! 코인 전송 로직이 올바르게 변경됨

기존의 consumeCoin에서 transferCoin으로 변경되어, 이제 구매자(userId)로부터 원작자(post.getUserId())에게 코인이 전송됩니다. 이는 경제적 흐름을 정확히 반영하며, 주석("코인을 원작자에게 전송")과도 일치합니다.

transferCoin이 별도의 트랜잭션으로 동작하며, 실패 시 예외가 전파되어 외부 트랜잭션을 롤백시키는 것으로 보입니다. PR 목표에 명시된 대로 분산 락 내에서 원자적으로 처리되는 것으로 이해됩니다.

src/main/java/hanium/modic/backend/domain/transaction/repository/CoinTransactionEntityRepository.java (1)

1-8: LGTM!

표준 Spring Data JPA 리포지토리가 올바르게 정의되었습니다. CoinTransactionEntity에 대한 기본 CRUD 작업을 제공하며, ledger 시스템에서 복식부기의 개별 항목(debit/credit entries)을 저장하는 데 사용됩니다.

src/main/java/hanium/modic/backend/domain/transaction/repository/CoinTransactionRepository.java (1)

1-8: LGTM!

표준 Spring Data JPA 리포지토리가 올바르게 정의되었습니다. CoinTransaction에 대한 기본 CRUD 작업을 제공하며, ledger 시스템에서 거래 전체를 나타내는 레코드를 저장하는 데 사용됩니다.

src/main/java/hanium/modic/backend/web/transaction/controller/AccountController.java (1)

47-53: LGTM!

USER_NOT_FOUND_EXCEPTION이 에러 매핑에 추가되었습니다. 이는 transferCoin이 이제 수신자 사용자를 조회해야 하므로, 수신자가 존재하지 않을 경우 발생할 수 있는 예외를 적절히 문서화한 것입니다.

src/main/java/hanium/modic/backend/infra/redis/distributedLock/LockManager.java (1)

36-40: 원본 리뷰 댓글은 부정확합니다. 데드락 위험은 이미 내부 구현에서 방지되고 있습니다.

DistributionLockExecutor.withMultiLock() 메서드(line 44)에서 이미 keys.stream().sorted().toList()로 키를 정렬하고 있습니다. 따라서 multipleAccountLock()에서 입력값을 정렬하지 않아도 내부적으로 정렬이 수행되어 데드락이 방지됩니다. 제안된 수정사항(.sorted() 추가)은 불필요합니다.

Likely an incorrect or invalid review comment.

src/test/java/hanium/modic/backend/domain/ai/service/AiImagePermissionServiceTest.java (1)

79-124: transferCoin 호출 시그니처 검증이 잘 반영됨

세 테스트 모두에서 AccountService.consumeCointransferCoin(fromUserId, toUserId, amount) 변경을 정확히 따라가고 있고,

  • 성공 케이스: (buyerId, post.getUserId(), post.getNonCommercialPrice())로 정확히 검증
  • 포스트 없음 케이스: transferCoin이 전혀 호출되지 않음을 검증
  • 코인 부족 케이스: transferCoin에서 COIN_NOT_ENOUGH_EXCEPTION을 던지도록 mock 후, 서비스가 해당 에러 코드를 그대로 노출하는지 검증

까지 흐름이 잘 잡혀 있습니다. 단위 테스트 수준에서는 충분히 안정적으로 API 변경을 커버하고 있다고 보입니다.

src/main/java/hanium/modic/backend/domain/transaction/service/LedgerService.java (1)

41-49: 리뷰 코멘트의 핵심 주장이 코드 현실과 맞지 않습니다.

검색 결과에 따르면, Account.updateBalance() 메서드(라인 48-51)에 이미 다음과 같은 검증이 있습니다:

public void updateBalance(Long newBalance) {
    if (newBalance < 0) {
        throw new AppException(COIN_NOT_ENOUGH_EXCEPTION);
    }
    // ...
}

따라서 LedgerService.transfer()의 라인 47-48에서 updateBalance() 호출 시, 자동으로 음수 잔액 검증이 작동합니다. 리뷰에서 "마이너스 잔액이 그대로 저장될 수 있다"고 지적한 것은 부정확합니다.

현재 코드 구조는 계층별 책임 분리 패턴으로, Account 엔티티 계층에서 최종 검증을 담당하고 있습니다. 따라서 제안된 추가 검증은 중복될 수 있습니다.

Likely an incorrect or invalid review comment.

@yooooonshine yooooonshine merged commit 98827df into develop Nov 23, 2025
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FEAT: 기존 거래 기능에 Transaction, Entity 기능 도입

1 participant