[FEATURE] 강사 시안 수정 요청시 메일 알림 이벤트 추가#102
Conversation
📝 WalkthroughWalkthrough강사가 시안 수정을 요청하면 디자이너에게 메일 알림을 예약 발송하는 기능이 추가되었다. RevisionRequestedEvent 레코드, 강사/배정 디자이너 조회 서비스 메서드, 이벤트 발행 로직이 InstructorRevisionFacade에 추가되었고, RevisionRequestedNotifier가 이벤트를 받아 NotificationOutbox를 저장하며, 관련 메일 템플릿이 신규 작성되었다. Changes수정 요청 알림 기능
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Instructor
participant InstructorRevisionFacade
participant InstructorCommissionService
participant EventPublisher
participant RevisionRequestedNotifier
participant NotificationOutboxRepository
Instructor->>InstructorRevisionFacade: createRevision(commissionId, ...)
InstructorRevisionFacade->>InstructorCommissionService: getOwnedCommissionWithInstructorAndAssignedDesigner(commissionId, instructorId)
InstructorCommissionService-->>InstructorRevisionFacade: Commission(designer 정보 포함)
InstructorRevisionFacade->>InstructorRevisionFacade: publishRevisionRequestedEvent(...)
InstructorRevisionFacade->>EventPublisher: publishEvent(RevisionRequestedEvent)
EventPublisher->>RevisionRequestedNotifier: onRevisionRequested(event)
RevisionRequestedNotifier->>NotificationOutboxRepository: save(NotificationOutbox)
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/main/java/ditda/backend/domain/commission/core/notification/RevisionRequestedNotifier.java (1)
14-27: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win알림 실패가 핵심 트랜잭션을 롤백시킬 수 있음.
@EventListener는 발행자(InstructorRevisionFacade.createRevision,@Transactional)의 트랜잭션 내에서 동기 실행됩니다.outboxRepository.save(...)에서 예외가 발생하면 수정 요청 저장 자체가 롤백되어, 메일 아웃박스 저장 실패가 핵심 비즈니스 로직 실패로 이어집니다.@TransactionalEventListener(phase = AFTER_COMMIT)으로 전환해 커밋 성공 이후에만 알림을 발행하도록 분리하는 것을 권장합니다.🔧 제안 수정
-import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; `@Component` `@RequiredArgsConstructor` public class RevisionRequestedNotifier { private final NotificationOutboxRepository outboxRepository; - `@EventListener` + `@TransactionalEventListener`(phase = TransactionPhase.AFTER_COMMIT) public void onRevisionRequested(RevisionRequestedEvent event) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/ditda/backend/domain/commission/core/notification/RevisionRequestedNotifier.java` around lines 14 - 27, `RevisionRequestedNotifier.onRevisionRequested` is currently using `@EventListener`, so `NotificationOutboxRepository.save(...)` runs inside the caller’s transaction and can roll back the main use case on notification failure. Change the listener to `@TransactionalEventListener(phase = AFTER_COMMIT)` so it only runs after the transaction from `InstructorRevisionFacade.createRevision` commits successfully, and keep the outbox save logic in `registerDesignerRevisionRequested`/`onRevisionRequested` unchanged aside from the listener annotation.src/main/java/ditda/backend/domain/commission/core/service/InstructorCommissionService.java (1)
101-126: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win소유권 검증 로직 중복.
getOwnedCommission과getOwnedCommissionWithInstructorAndAssignedDesigner가 조회 방식만 다르고 존재/소유권 검증 로직이 동일합니다. 검증 정책 변경 시 두 메서드를 모두 수정해야 하는 부담이 있습니다.♻️ 공통 검증 로직 추출 예시
+ private Commission validateOwnership(Commission commission, Long instructorId) { + if (!commission.isOwnedBy(instructorId)) { + throw new GeneralException(CommissionErrorCode.COMMISSION_ACCESS_DENIED); + } + return commission; + } + public Commission getOwnedCommission(Long commissionId, Long instructorId) { Commission commission = commissionRepository.findById(commissionId) .orElseThrow(() -> new GeneralException(CommissionErrorCode.COMMISSION_NOT_FOUND)); - if (!commission.isOwnedBy(instructorId)) { - throw new GeneralException(CommissionErrorCode.COMMISSION_ACCESS_DENIED); - } - return commission; + return validateOwnership(commission, instructorId); } public Commission getOwnedCommissionWithInstructorAndAssignedDesigner(Long commissionId, Long instructorId) { Commission commission = commissionRepository.findWithInstructorAndAssignedDesignerById(commissionId) .orElseThrow(() -> new GeneralException(CommissionErrorCode.COMMISSION_NOT_FOUND)); - if (!commission.isOwnedBy(instructorId)) { - throw new GeneralException(CommissionErrorCode.COMMISSION_ACCESS_DENIED); - } - return commission; + return validateOwnership(commission, instructorId); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/ditda/backend/domain/commission/core/service/InstructorCommissionService.java` around lines 101 - 126, `InstructorCommissionService`의 `getOwnedCommission`과 `getOwnedCommissionWithInstructorAndAssignedDesigner`에 있는 존재 확인 및 `commission.isOwnedBy(instructorId)` 소유권 검증이 중복됩니다. 공통 검증 흐름을 하나의 private helper로 추출하고, 두 public 메서드는 조회 방식만 다르게 한 뒤 그 helper를 재사용하도록 정리하세요. `CommissionRepository`, `findById`, `findWithInstructorAndAssignedDesignerById`, `GeneralException`, `CommissionErrorCode`를 기준으로 위치를 찾으면 됩니다.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@src/main/java/ditda/backend/domain/commission/core/notification/RevisionRequestedNotifier.java`:
- Around line 14-27: `RevisionRequestedNotifier.onRevisionRequested` is
currently using `@EventListener`, so `NotificationOutboxRepository.save(...)`
runs inside the caller’s transaction and can roll back the main use case on
notification failure. Change the listener to `@TransactionalEventListener(phase
= AFTER_COMMIT)` so it only runs after the transaction from
`InstructorRevisionFacade.createRevision` commits successfully, and keep the
outbox save logic in `registerDesignerRevisionRequested`/`onRevisionRequested`
unchanged aside from the listener annotation.
In
`@src/main/java/ditda/backend/domain/commission/core/service/InstructorCommissionService.java`:
- Around line 101-126: `InstructorCommissionService`의 `getOwnedCommission`과
`getOwnedCommissionWithInstructorAndAssignedDesigner`에 있는 존재 확인 및
`commission.isOwnedBy(instructorId)` 소유권 검증이 중복됩니다. 공통 검증 흐름을 하나의 private
helper로 추출하고, 두 public 메서드는 조회 방식만 다르게 한 뒤 그 helper를 재사용하도록 정리하세요.
`CommissionRepository`, `findById`, `findWithInstructorAndAssignedDesignerById`,
`GeneralException`, `CommissionErrorCode`를 기준으로 위치를 찾으면 됩니다.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro Plus
Run ID: bddb219a-1855-4804-aa9e-825aedaea374
📒 Files selected for processing (5)
src/main/java/ditda/backend/domain/commission/core/event/RevisionRequestedEvent.javasrc/main/java/ditda/backend/domain/commission/core/notification/RevisionRequestedNotifier.javasrc/main/java/ditda/backend/domain/commission/core/service/InstructorCommissionService.javasrc/main/java/ditda/backend/domain/commission/revision/facade/InstructorRevisionFacade.javasrc/main/resources/templates/email/revision-requested-designer.html
Jong0128
left a comment
There was a problem hiding this comment.
수고하셨습니다!!
바로 merge하셔도 좋을꺼같아요 😃
🚀 Related issue
Closes #100
#️⃣ Summary
🔧 Changes
RevisionRequestedEvent추가revision-requested-designer.html추가📸 Test Evidence
💬 Reviewer Notes
Summary by CodeRabbit
New Features
Bug Fixes