Skip to content

Conversation

@kgy1008
Copy link
Member

@kgy1008 kgy1008 commented Oct 25, 2025

📣 Jira Ticket

EDMT-457

👩‍💻 작업 내용

회원 포인트 시스템을 구현하고 AI 생성 시 포인트 차감 기능을 추가했습니다.

주요 변경사항

  1. 회원 엔티티에 포인트 필드 추가

    • Member 엔티티에 point 컬럼 추가 (기본값: 1000)
    • DB 마이그레이션 스크립트 작성 (V10)
  2. 포인트 차감 로직 구현

    • PointService 신규 생성
    • 비관적 락(Pessimistic Lock)을 사용한 동시성 제어
    • 포인트 부족 시 예외 처리 (INSUFFICIENT_POINTS 에러 코드 추가)
  3. AI 생성 시 포인트 차감 통합

    • StudentRecordAIFacade에서 AI 작업 생성 시 100 포인트 차감
    • 포인트 검증 및 차감 후 AI 작업 진행

기술적 구현

  • 동시성 제어: @Lock(LockModeType.PESSIMISTIC_WRITE)를 사용하여 포인트 차감 시 동시성 문제 방지
  • 트랜잭션 관리: @Transactional을 통한 원자적 작업 보장
  • 에러 처리: 포인트 부족 시 명확한 에러 메시지 제공

📝 리뷰 요청 & 논의하고 싶은 내용

  1. 포인트 차감량: 현재 AI 생성당 100 포인트를 차감하는데, 이 값이 적절한지 검토 부탁드립니다.
  2. 락 전략: 비관적 락을 사용했는데, 낙관적 락이나 다른 전략도 고려해볼 수 있을 것 같습니다.
  3. 포인트 충전 로직: 현재는 차감만 구현되어 있는데, 향후 포인트 충전/지급 로직도 필요할 것 같습니다.

Test Plan

✅ Manual Testing Checklist

  • 신규 회원 가입 시 기본 1000 포인트 부여 확인
  • AI 생성 요청 시 100 포인트 차감 확인
  • 포인트 부족 시 적절한 에러 메시지 반환 확인
  • 동시에 여러 AI 생성 요청 시 포인트 차감이 정확한지 확인 (동시성 테스트)
  • 기존 회원 데이터 마이그레이션 확인 (기본 1000 포인트 부여)

🧪 Automated Tests

  • Unit tests pass: ./gradlew test
  • Integration tests pass
  • Build succeeds: ./gradlew build
  • PointService 단위 테스트 (포인트 차감, 부족 시 예외)
  • StudentRecordAIFacade 통합 테스트

🔍 Code Quality

  • Code review completed
  • No new warnings or errors
  • Security considerations reviewed (동시성 제어)
  • Exception handling verified

Deployment Notes

  • Database migrations included (V10__Add_member_point.sql)
  • Environment variables updated (N/A)
  • AWS resources configured (N/A)
  • 중요: 배포 시 기존 회원들에게 기본 1000 포인트가 부여되는지 확인 필요

관련 커밋

  • 298f392 - [feat] add point column to Member entity and initialize with default value
  • 44c0097 - [feat] implement point validation for member actions
  • 6c44296 - [feat] implement point deduction logic in Member and PointService
  • 0d099c3 - [feat] implement pessimistic locking for point deduction in PointService
  • 350aeec - [refac] update deductPoints method to return Member object in PointService

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 새 기능
    • 사용자 포인트 시스템 추가 (모든 사용자는 1,000포인트로 시작)
    • 작업 생성 시 자동으로 100포인트 차감
    • 포인트 부족 시 작업 생성 차단 기능 추가

@coderabbitai
Copy link

coderabbitai bot commented Oct 25, 2025

Walkthrough

학생 기록 생성 시 포인트 차감 기능을 도입합니다. 회원 테이블에 포인트 필드를 추가하고, 포인트 서비스를 통해 비관적 잠금으로 트랜잭션 안전성을 보장하며 작업 생성 시 100포인트를 차감합니다.

Changes

Cohort / File(s) 변경 요약
데이터베이스 스키마
db/migration/V10__Add_member_point.sql
회원 테이블에 point 컬럼 추가 (정수형, 기본값 1000)
회원 엔티티 및 저장소
edukit-core/.../Member.java, MemberRepository.java
회원 엔티티에 point 필드 및 deductPoints() 메서드 추가, findByIdWithLock() 메서드로 비관적 쓰기 잠금 구현
회원 오류 처리
MemberErrorCode.java
INSUFFICIENT_POINTS 오류 코드 추가, enum 구조 정리
포인트 서비스
PointService.java
포인트 차감 비즈니스 로직 구현 (검증, 잠금 처리, 예외 처리)
파사드 통합
StudentRecordAIFacade.java
PointService 주입, 작업 생성 시 100포인트 차감 로직 추가

Sequence Diagram(s)

sequenceDiagram
    participant StudentRecordAIFacade
    participant PointService
    participant MemberRepository
    participant Member
    
    StudentRecordAIFacade->>PointService: deductPoints(memberId, 100)
    PointService->>MemberRepository: findByIdWithLock(memberId)
    rect rgb(200, 220, 255)
        Note over MemberRepository: PESSIMISTIC_WRITE 잠금 획득
        MemberRepository-->>PointService: Member (잠금 상태)
    end
    
    alt 회원 존재 여부
        PointService->>PointService: 회원 없음 검증
        PointService-->>StudentRecordAIFacade: MEMBER_NOT_FOUND 예외
    else 포인트 충분 여부
        PointService->>Member: 포인트 < 100?
        Member-->>PointService: true
        PointService-->>StudentRecordAIFacade: INSUFFICIENT_POINTS 예외
    else 정상 처리
        PointService->>Member: deductPoints(100)
        rect rgb(200, 255, 220)
            Note over Member: point 차감 (예: 1000 → 900)
        end
        PointService-->>StudentRecordAIFacade: 업데이트된 Member 반환
    end
    
    StudentRecordAIFacade->>StudentRecordAIFacade: 작업 생성 진행
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20-25 minutes

주의 검토 항목:

  • 비관적 잠금 구현: findByIdWithLock()의 PESSIMISTIC_WRITE 잠금이 동시성 경쟁 조건을 올바르게 방지하는지 확인 (트랜잭션 범위, 데드락 가능성)
  • 포인트 검증 로직: PointService.deductPoints()의 포인트 충분성 검증 순서 및 예외 처리 안정성
  • 트랜잭션 경계: StudentRecordAIFacade에서 PointService 호출 시 트랜잭션 전파 설정 및 롤백 시나리오 검증
  • 데이터베이스 마이그레이션: 기존 회원 데이터에 대한 point 기본값(1000) 적용 및 null 제약 조건 확인

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 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed 제목은 PR의 주요 변경사항을 정확히 반영하고 있습니다. Raw summary에 따르면 회원 포인트 시스템 구현(Member 엔티티에 point 필드 추가, PointService 생성)과 AI 생성 시 포인트 차감 기능(StudentRecordAIFacade에 100포인트 차감 로직 추가)이 주요 변경사항인데, 제목이 이 두 가지를 명확하게 포함하고 있습니다. Jira 티켓 번호도 포함되어 있어 트래킹이 용이하며, 개발자의 관점에서 변경사항의 핵심을 잘 전달하고 있습니다.
Description Check ✅ Passed PR 설명이 템플릿의 필수 섹션을 모두 충족하고 있습니다. Jira Ticket 섹션에 EDMT-457이 포함되어 있고, 작업 내용 섹션에서 회원 포인트 시스템 구현, 포인트 차감 로직, AI 생성 통합 등을 주요 변경사항으로 상세히 설명하고 있으며, 기술적 구현 세부사항도 명시되어 있습니다. 리뷰 요청 및 논의 내용도 포인트 차감량, 락 전략, 향후 기능에 대한 구체적인 질문을 포함하고 있습니다. 추가로 Test Plan, Deployment Notes, 관련 커밋 등 상세한 정보도 포함되어 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/EDMT-457

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

@kgy1008 kgy1008 self-assigned this Oct 25, 2025
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: 0

🧹 Nitpick comments (3)
edukit-core/src/main/java/com/edukit/core/member/service/PointService.java (1)

17-30: 포인트 차감 로직이 올바르게 구현되었습니다.

트랜잭션과 비관적 잠금을 통한 동시성 제어가 적절하며, 포인트 부족 시 명확한 예외 처리도 잘 되어 있습니다. JPA dirty checking을 활용한 자동 저장 방식도 정상적으로 동작합니다.

선택적으로 Line 22의 currentPoint 변수는 제거하고 Line 24에서 직접 member.getPoint()를 사용할 수 있습니다:

-        int currentPoint = member.getPoint();
-
-        if (currentPoint < pointsToDeduct) {
+        if (member.getPoint() < pointsToDeduct) {
             throw new MemberException(MemberErrorCode.INSUFFICIENT_POINTS);
         }
edukit-api/src/main/java/com/edukit/studentrecord/facade/StudentRecordAIFacade.java (1)

38-40: 작업 생성 로직 순서 개선을 제안합니다.

현재 포인트 차감(Line 38)이 학생 기록 검증(Line 40)보다 먼저 실행됩니다. 학생 기록이 존재하지 않거나 권한이 없는 경우, 포인트가 부족한 상황에서도 RECORD_NOT_FOUND 같은 에러가 먼저 발생하여 사용자에게 혼란을 줄 수 있습니다. 트랜잭션 롤백으로 데이터 정합성은 보장되지만, UX 개선을 위해 검증 로직을 먼저 수행하는 것을 권장합니다.

다음과 같이 순서를 변경하는 것을 고려해보세요:

     public StudentRecordTaskResponse createTaskId(final long memberId, final long recordId, final int byteCount,
                                                   final String userPrompt) {
+        StudentRecord studentRecord = studentRecordService.getRecordDetail(memberId, recordId);
+
         Member member = pointService.deductPoints(memberId, DEDUCTED_POINTS);

-        StudentRecord studentRecord = studentRecordService.getRecordDetail(memberId, recordId);
-
         String requestPrompt = AIPromptGenerator.createStreamingPrompt(studentRecord.getStudentRecordType(), byteCount, userPrompt);
         StudentRecordAITask task = aiTaskService.createAITask(member, userPrompt);
edukit-core/src/main/java/com/edukit/core/member/db/entity/Member.java (1)

150-152: 포인트 차감 메서드에 방어적 검증 추가를 제안합니다.

현재 PointService에서 검증이 이루어지지만, 엔티티 메서드 자체에 음수 방지 로직을 추가하면 다른 곳에서 직접 호출 시 무결성을 보장할 수 있습니다.

다음과 같은 방어 로직을 고려해보세요:

 public void deductPoints(final int pointsToDeduct) {
+    if (pointsToDeduct < 0) {
+        throw new IllegalArgumentException("차감 포인트는 0 이상이어야 합니다.");
+    }
+    if (this.point < pointsToDeduct) {
+        throw new IllegalStateException("포인트가 부족하여 차감할 수 없습니다.");
+    }
     this.point -= pointsToDeduct;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3013dc7 and 350aeec.

📒 Files selected for processing (6)
  • edukit-api/src/main/java/com/edukit/studentrecord/facade/StudentRecordAIFacade.java (2 hunks)
  • edukit-api/src/main/resources/db/migration/V10__Add_member_point.sql (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/member/db/entity/Member.java (4 hunks)
  • edukit-core/src/main/java/com/edukit/core/member/db/repository/MemberRepository.java (2 hunks)
  • edukit-core/src/main/java/com/edukit/core/member/exception/MemberErrorCode.java (1 hunks)
  • edukit-core/src/main/java/com/edukit/core/member/service/PointService.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
edukit-core/src/main/java/com/edukit/core/member/service/PointService.java (2)
edukit-core/src/main/java/com/edukit/core/member/exception/MemberException.java (1)
  • MemberException (6-11)
edukit-api/src/main/java/com/edukit/studentrecord/facade/StudentRecordAIFacade.java (1)
  • Service (21-62)
🔇 Additional comments (6)
edukit-api/src/main/resources/db/migration/V10__Add_member_point.sql (1)

1-3: 마이그레이션이 올바르게 구현되었습니다.

기존 회원 데이터에 대해 자동으로 기본값 1000이 적용되며, NOT NULL 제약조건도 적절합니다.

edukit-core/src/main/java/com/edukit/core/member/db/repository/MemberRepository.java (1)

26-28: 비관적 잠금 구현이 적절합니다.

포인트 차감 시 동시성 제어를 위한 PESSIMISTIC_WRITE 락 사용이 올바르며, 소프트 삭제된 회원을 필터링하는 쿼리도 정확합니다.

edukit-core/src/main/java/com/edukit/core/member/exception/MemberErrorCode.java (1)

18-19: 에러 코드 추가가 적절합니다.

코드 체계와 메시지 작성이 기존 패턴을 잘 따르고 있으며, 사용자에게 명확한 안내를 제공합니다.

edukit-api/src/main/java/com/edukit/studentrecord/facade/StudentRecordAIFacade.java (1)

32-32: 포인트 차감량 상수가 명확하게 정의되었습니다.

100포인트 차감이 적절한지에 대한 PR 목표의 질문과 관련하여, 현재 상수로 정의된 방식은 향후 조정이 용이합니다. 필요시 향후 외부 설정(application.yml)으로 이동하여 배포 없이 조정 가능하도록 개선할 수 있습니다.

edukit-core/src/main/java/com/edukit/core/member/db/entity/Member.java (2)

61-62: 포인트 필드 추가가 올바르게 구현되었습니다.

원시 타입 int 사용으로 null 안전성을 확보했으며, INITIAL_POINT 상수를 통한 기본값 관리도 적절합니다.

Also applies to: 72-72


106-115: 회원 복구 시 포인트 처리 정책 확인이 필요합니다.

restore() 메서드가 포인트 필드를 수정하지 않아, 탈퇴 후 복구 시 기존 포인트 잔액이 그대로 유지됩니다. 의도된 동작이라면 문제없으나, 복구 시 INITIAL_POINT로 초기화해야 하는 정책이라면 수정이 필요합니다.

비즈니스 정책을 확인하고, 필요시 다음과 같이 포인트를 초기화하세요:

 public void restore(final String password, final Subject subject, final String nickname, final School school) {
     this.isDeleted = false;
     this.deletedAt = null;
     this.password = password;
     this.subject = subject;
     this.nickname = nickname;
     this.school = school;
     this.role = MemberRole.PENDING_TEACHER;
     this.verifiedAt = null;
+    this.point = INITIAL_POINT;  // 복구 시 포인트 초기화가 필요한 경우
 }

@kgy1008 kgy1008 merged commit 01d65dc into develop Oct 25, 2025
2 checks passed
@kgy1008 kgy1008 deleted the feat/EDMT-457 branch October 25, 2025 08:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants