Skip to content

[FEAT] 라이브 코테 기능 추가 및 멱등성 보강#358

Merged
unifolio0 merged 2 commits into
developfrom
fix/error
Jun 2, 2026
Merged

[FEAT] 라이브 코테 기능 추가 및 멱등성 보강#358
unifolio0 merged 2 commits into
developfrom
fix/error

Conversation

@unifolio0

Copy link
Copy Markdown
Contributor

closed #

작업 내용

스크린샷

참고 사항

@unifolio0 unifolio0 self-assigned this Jun 2, 2026
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@unifolio0, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 58 minutes and 21 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bc5c6b99-bb09-411a-93d5-dc3a59131987

📥 Commits

Reviewing files that changed from the base of the PR and between d777ff7 and 37b0430.

📒 Files selected for processing (34)
  • src/main/java/com/samhap/kokomen/answer/domain/Answer.java
  • src/main/java/com/samhap/kokomen/category/domain/Category.java
  • src/main/java/com/samhap/kokomen/interview/controller/InterviewControllerV2.java
  • src/main/java/com/samhap/kokomen/interview/domain/Interview.java
  • src/main/java/com/samhap/kokomen/interview/domain/InterviewType.java
  • src/main/java/com/samhap/kokomen/interview/external/AnswerFeedbackBedrockClient.java
  • src/main/java/com/samhap/kokomen/interview/external/InterviewProceedBedrockClient.java
  • src/main/java/com/samhap/kokomen/interview/external/dto/request/InterviewBedrockRequestFactory.java
  • src/main/java/com/samhap/kokomen/interview/repository/RootQuestionRepository.java
  • src/main/java/com/samhap/kokomen/interview/service/InterviewStartFacadeService.java
  • src/main/java/com/samhap/kokomen/interview/service/core/InterviewService.java
  • src/main/java/com/samhap/kokomen/interview/service/dto/AnswerRequestV2.java
  • src/main/java/com/samhap/kokomen/interview/service/question/RootQuestionService.java
  • src/main/java/com/samhap/kokomen/interview/tool/CodingInterviewBedrockSystemMessageConstant.java
  • src/main/java/com/samhap/kokomen/interview/tool/CodingInterviewPromptFragments.java
  • src/main/java/com/samhap/kokomen/interview/tool/GptSystemMessageConstant.java
  • src/main/java/com/samhap/kokomen/interview/tool/InterviewMessagesFactory.java
  • src/main/java/com/samhap/kokomen/payment/domain/PaymentState.java
  • src/main/java/com/samhap/kokomen/payment/domain/TosspaymentsPayment.java
  • src/main/java/com/samhap/kokomen/payment/service/PaymentFacadeService.java
  • src/main/java/com/samhap/kokomen/payment/service/TosspaymentsPaymentResultService.java
  • src/main/java/com/samhap/kokomen/payment/service/dto/PaymentResponse.java
  • src/main/resources/db/migration/V45__add_live_coding_category_and_seed.sql
  • src/main/resources/db/migration/V46__widen_answer_content.sql
  • src/test/java/com/samhap/kokomen/category/domain/CategoryTest.java
  • src/test/java/com/samhap/kokomen/interview/domain/InterviewTest.java
  • src/test/java/com/samhap/kokomen/interview/external/dto/request/InterviewBedrockRequestFactoryTest.java
  • src/test/java/com/samhap/kokomen/interview/repository/AnswerRepositoryTest.java
  • src/test/java/com/samhap/kokomen/interview/service/LiveCodingInterviewServiceTest.java
  • src/test/java/com/samhap/kokomen/interview/service/core/InterviewServiceTest.java
  • src/test/java/com/samhap/kokomen/interview/service/question/RootQuestionServiceTest.java
  • src/test/java/com/samhap/kokomen/interview/tool/InterviewMessagesFactoryTest.java
  • src/test/java/com/samhap/kokomen/payment/service/PaymentFacadeServiceTest.java
  • src/test/java/com/samhap/kokomen/token/service/TokenFacadeServiceTest.java
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/error

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.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

본 PR은 라이브 코딩 테스트 기능을 도입하고 결제 시스템의 멱등성을 강화하는 것을 목적으로 합니다. 라이브 코딩 인터뷰를 위한 전용 카테고리와 AI 프롬프트를 추가하여 인터뷰 품질을 개선하였으며, 결제 과정에서 발생할 수 있는 중복 요청 문제를 해결하기 위해 멱등성 처리 로직을 보강하였습니다. 또한, 더 긴 답변을 저장할 수 있도록 데이터베이스 스키마를 조정하였습니다.

Highlights

  • 라이브 코딩 테스트 기능 추가: 새로운 카테고리 'LIVE_CODING'을 도입하고, 이를 지원하기 위한 인터뷰 타입 및 관련 도메인 로직을 구현했습니다.
  • AI 프롬프트 최적화: 라이브 코딩 인터뷰 전용 시스템 메시지와 프롬프트 상수를 추가하여 AI 면접관의 평가 정확도를 높였습니다.
  • 결제 멱등성 보강: 결제 승인 과정에서 중복 요청이 발생할 경우를 대비해 멱등성 처리 로직을 추가하여 시스템 안정성을 강화했습니다.
  • 데이터베이스 스키마 조정: 답변 내용의 최대 길이를 2,000자에서 10,000자로 상향 조정하여 더 상세한 코드 제출이 가능하도록 변경했습니다.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.


코딩의 숲속 라이브 코테 열리고, AI 면접관 꼬리 질문 이어지네. 멱등성 챙겨 결제는 안전하게, 오늘도 코드 한 줄 정성스레 담네.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new 'LIVE_CODING' interview category and type, complete with tailored system prompts, rubrics, and constraints for coding interviews. To support code submissions, the maximum length of answer content has been increased from 2,000 to 10,000 characters. Additionally, idempotent payment confirmation logic has been implemented in PaymentFacadeService to handle duplicate payment requests gracefully. The review feedback focuses on improving this payment idempotency flow: it highlights that temporary server errors on previous attempts could block subsequent retries and suggests using Optional to allow retrying, while also warning about potential Spring transaction rollback-only issues when catching DataIntegrityViolationException.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 44 to +51
public PaymentResponse confirmPayment(ConfirmRequest request) {
TosspaymentsPayment tosspaymentsPayment = tosspaymentsPaymentService.saveTosspaymentsPayment(request);
TosspaymentsPayment tosspaymentsPayment;
try {
tosspaymentsPayment = tosspaymentsPaymentService.saveTosspaymentsPayment(request);
} catch (DataIntegrityViolationException e) {
log.info("동일 paymentKey 결제 재요청 - 멱등 분기 처리, paymentKey: {}", request.paymentKey());
return resolveExistingPayment(tosspaymentsPaymentService.readByPaymentKey(request.paymentKey()));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

⚠️ 일시적 서버 에러 발생 시 결제 승인 재시도(Retry) 불가 문제

현재 구현에서는 동일한 paymentKey로 결제 승인 재요청이 들어왔을 때, 이미 데이터베이스에 해당 결제 정보가 존재하면 DataIntegrityViolationException을 캐치하여 resolveExistingPayment로 분기합니다.

하지만 만약 이전 결제 시도가 외부 Toss API 타임아웃이나 일시적인 서버 에러(5xx) 등으로 인해 실패하여 결제 상태가 SERVER_BAD_REQUEST 등으로 저장되어 있는 경우, 클라이언트가 재시도를 하더라도 resolveExistingPayment에서 무조건 InternalServerErrorException을 던지게 됩니다. 이로 인해 사용자는 결제를 다시 진행하거나 복구할 수 없는 상태에 빠지게 됩니다.

개선 방안:
결제 승인이 완전히 성공(APPROVED/COMPLETED)했거나 클라이언트 오류(CLIENT_BAD_REQUEST)로 최종 실패한 경우가 아니라면, 외부 Toss API로 결제 승인 요청을 다시 시도할 수 있도록 멱등성 처리 흐름을 개선해야 합니다. 이를 위해 resolveExistingPaymentOptional<PaymentResponse>를 반환하도록 수정하고, 빈 Optional이 반환될 경우 아래의 외부 API 승인 요청 흐름을 타도록 수정하는 것을 권장합니다.

    @DistributedLock(prefix = "payment", key = "#request.paymentKey()")
    public PaymentResponse confirmPayment(ConfirmRequest request) {
        TosspaymentsPayment tosspaymentsPayment;
        try {
            tosspaymentsPayment = tosspaymentsPaymentService.saveTosspaymentsPayment(request);
        } catch (DataIntegrityViolationException e) {
            log.info("동일 paymentKey 결제 재요청 - 멱등 분기 처리, paymentKey: {}", request.paymentKey());
            tosspaymentsPayment = tosspaymentsPaymentService.readByPaymentKey(request.paymentKey());
            Optional<PaymentResponse> existingResponse = resolveExistingPayment(tosspaymentsPayment);
            if (existingResponse.isPresent()) {
                return existingResponse.get();
            }
        }

Comment on lines +66 to +79
private PaymentResponse resolveExistingPayment(TosspaymentsPayment payment) {
if (payment.isApprovedOrCompleted()) {
log.info("이미 승인된 결제 재요청 - 멱등 응답 반환, paymentKey: {}, state: {}",
payment.getPaymentKey(), payment.getState());
TosspaymentsPaymentResult result = tosspaymentsPaymentResultService.readByTosspaymentsPaymentId(payment.getId());
return PaymentResponse.fromExisting(payment, result);
}
if (payment.isClientBadRequest()) {
log.info("클라이언트 원인 실패 결제 재요청 - paymentKey: {}", payment.getPaymentKey());
throw new BadRequestException(readClientFailureMessage(payment.getId()));
}
log.error("비정상 상태 결제 재요청 - paymentKey: {}, state: {}", payment.getPaymentKey(), payment.getState());
throw new InternalServerErrorException(PaymentServiceErrorMessage.CONFIRM_SERVER_ERROR.getMessage());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

🛠️ resolveExistingPayment 메서드의 반환 타입 개선 (Optional 도입)

이전 결제 시도가 일시적인 서버 에러 등으로 실패한 상태(SERVER_BAD_REQUEST 등)일 때, 무조건 예외를 던지는 대신 외부 API 승인 요청을 재시도할 수 있도록 Optional<PaymentResponse>를 반환하도록 개선합니다.

  • 성공 상태 (APPROVED/COMPLETED): 기존 결제 결과를 담은 Optional.of(PaymentResponse) 반환
  • 클라이언트 오류 (CLIENT_BAD_REQUEST): 기존과 동일하게 BadRequestException 발생
  • 그 외 재시도 가능 상태: 빈 Optional.empty()를 반환하여 confirmPayment에서 외부 API 호출을 재시도할 수 있도록 허용
Suggested change
private PaymentResponse resolveExistingPayment(TosspaymentsPayment payment) {
if (payment.isApprovedOrCompleted()) {
log.info("이미 승인된 결제 재요청 - 멱등 응답 반환, paymentKey: {}, state: {}",
payment.getPaymentKey(), payment.getState());
TosspaymentsPaymentResult result = tosspaymentsPaymentResultService.readByTosspaymentsPaymentId(payment.getId());
return PaymentResponse.fromExisting(payment, result);
}
if (payment.isClientBadRequest()) {
log.info("클라이언트 원인 실패 결제 재요청 - paymentKey: {}", payment.getPaymentKey());
throw new BadRequestException(readClientFailureMessage(payment.getId()));
}
log.error("비정상 상태 결제 재요청 - paymentKey: {}, state: {}", payment.getPaymentKey(), payment.getState());
throw new InternalServerErrorException(PaymentServiceErrorMessage.CONFIRM_SERVER_ERROR.getMessage());
}
private Optional<PaymentResponse> resolveExistingPayment(TosspaymentsPayment payment) {
if (payment.isApprovedOrCompleted()) {
log.info("이미 승인된 결제 재요청 - 멱등 응답 반환, paymentKey: {}, state: {}",
payment.getPaymentKey(), payment.getState());
TosspaymentsPaymentResult result = tosspaymentsPaymentResultService.readByTosspaymentsPaymentId(payment.getId());
return Optional.of(PaymentResponse.fromExisting(payment, result));
}
if (payment.isClientBadRequest()) {
log.info("클라이언트 원인 실패 결제 재요청 - paymentKey: {}", payment.getPaymentKey());
throw new BadRequestException(readClientFailureMessage(payment.getId()));
}
log.info("이전 결제 시도 실패(서버 에러 등)로 인한 재시도 진행, paymentKey: {}, state: {}",
payment.getPaymentKey(), payment.getState());
return Optional.empty();
}

Comment on lines +46 to +48
try {
tosspaymentsPayment = tosspaymentsPaymentService.saveTosspaymentsPayment(request);
} catch (DataIntegrityViolationException e) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

⚠️ 트랜잭션 롤백 마킹(Rollback-only) 및 예외 전파 주의

현재 confirmPayment 메서드 자체에는 @Transactional이 선언되어 있지 않지만, 만약 이 메서드가 상위 레이어(예: 다른 서비스 클래스의 트랜잭션 메서드)에서 호출되거나 향후 클래스 레벨에 @Transactional이 추가될 경우 트랜잭션 전파 문제가 발생할 수 있습니다.

Spring의 선언적 트랜잭션 환경에서 트랜잭션 내의 특정 작업(saveTosspaymentsPayment)이 데이터베이스 예외(DataIntegrityViolationException)를 발생시키면, 해당 트랜잭션은 즉시 Rollback-only 상태로 마킹됩니다. 이 상태가 되면 try-catch로 예외를 잡아내더라도, 이후에 호출되는 tosspaymentsPaymentService.readByPaymentKey 등의 데이터베이스 조회 작업이 정상적으로 수행되지 않거나, 최종적으로 메서드가 종료될 때 UnexpectedRollbackException이 발생하며 전체 트랜잭션이 강제로 롤백됩니다.

권장 사항:
이러한 트랜잭션 전파 문제를 방지하고 안전하게 멱등성 분기 처리를 수행하기 위해, saveTosspaymentsPayment 메서드의 트랜잭션 전파 속성을 REQUIRES_NEW로 설정하는 것을 권장합니다.

// TosspaymentsPaymentService.java 내의 메서드 선언 예시
@Transactional(propagation = Propagation.REQUIRES_NEW)
public TosspaymentsPayment saveTosspaymentsPayment(ConfirmRequest request) {
    // ...
}

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

Test Results

 55 files   55 suites   1m 35s ⏱️
315 tests 314 ✅ 1 💤 0 ❌
317 runs  316 ✅ 1 💤 0 ❌

Results for commit 37b0430.

@unifolio0 unifolio0 merged commit 01a26d4 into develop Jun 2, 2026
5 checks passed
@unifolio0 unifolio0 deleted the fix/error branch June 9, 2026 02:11
unifolio0 added a commit that referenced this pull request Jun 14, 2026
* fix: 멱등성 보강

* feat: 라이브 코테 기능 추가
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.

1 participant