[EDMT-457] 회원 포인트 시스템 구현 및 AI 생성 시 포인트 차감 기능 추가#78
Conversation
Walkthrough학생 기록 생성 시 포인트 차감 기능을 도입합니다. 회원 테이블에 포인트 필드를 추가하고, 포인트 서비스를 통해 비관적 잠금으로 트랜잭션 안전성을 보장하며 작업 생성 시 100포인트를 차감합니다. Changes
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: 작업 생성 진행
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20-25 minutes 주의 검토 항목:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
📒 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; // 복구 시 포인트 초기화가 필요한 경우 }
📣 Jira Ticket
EDMT-457
👩💻 작업 내용
회원 포인트 시스템을 구현하고 AI 생성 시 포인트 차감 기능을 추가했습니다.
주요 변경사항
회원 엔티티에 포인트 필드 추가
Member엔티티에point컬럼 추가 (기본값: 1000)포인트 차감 로직 구현
PointService신규 생성INSUFFICIENT_POINTS에러 코드 추가)AI 생성 시 포인트 차감 통합
StudentRecordAIFacade에서 AI 작업 생성 시 100 포인트 차감기술적 구현
@Lock(LockModeType.PESSIMISTIC_WRITE)를 사용하여 포인트 차감 시 동시성 문제 방지@Transactional을 통한 원자적 작업 보장📝 리뷰 요청 & 논의하고 싶은 내용
Test Plan
✅ Manual Testing Checklist
🧪 Automated Tests
./gradlew test./gradlew buildPointService단위 테스트 (포인트 차감, 부족 시 예외)StudentRecordAIFacade통합 테스트🔍 Code Quality
Deployment Notes
관련 커밋
298f392- [feat] add point column to Member entity and initialize with default value44c0097- [feat] implement point validation for member actions6c44296- [feat] implement point deduction logic in Member and PointService0d099c3- [feat] implement pessimistic locking for point deduction in PointService350aeec- [refac] update deductPoints method to return Member object in PointService🤖 Generated with Claude Code
Summary by CodeRabbit