-
Notifications
You must be signed in to change notification settings - Fork 1
[FEAT] #143 kafka 도입 비동기 이벤트 기반 추천알림 로직 개발 #144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughAdds Kafka support and integrates asynchronous event-driven flows: dependency, Docker services, Kafka config, producers/consumers, DomainEvent types, transactional commit-event listeners, and refactors existing in-process events/notice flows to publish/consume via Kafka; also adjusts notice/member-related domain and repository APIs. Changes
Sequence Diagram(s)sequenceDiagram
actor Admin as AdminApprovalService
participant Tx as Spring Transactional Event
participant CommitListener as ApproveCommitEventListener
participant Kafka as Kafka Broker (approval-show-topic)
participant Consumers as Approval/Recommend/Liker Consumers
participant Notice as NoticeService
Admin->>Admin: approveShow (DB save)
Admin->>Tx: publishEvent(ApproveCommitEvent)
Tx-->>Tx: transaction commits
Tx->>CommitListener: onApproveCommit(event)
CommitListener->>Kafka: publish ApprovalShowEvent
Kafka->>Consumers: deliver ApprovalShowEvent
Consumers->>Notice: notifyApproval / notifyRecommendation / notifyLikers(event)
Notice->>Notice: construct notices, batch-save
sequenceDiagram
actor App as CommentService
participant Tx as Spring Transactional Event
participant CommitListener as CommentCommitEventListener
participant Kafka as Kafka Broker (comment-created-topic)
participant Consumer as CommentConsumer
participant Notice as NoticeService
App->>App: createComment (DB save)
App->>Tx: publishEvent(CommentCommitEvent)
Tx-->>Tx: transaction commits
Tx->>CommitListener: onCommentCommit(event)
CommitListener->>Kafka: publish CommentEvent
Kafka->>Consumer: deliver CommentEvent
Consumer->>Notice: notifyNewComment(event)
Notice->>Notice: create and persist notice
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/cc/backend/notice/service/MemberNoticeService.java (1)
46-54: Use personalMsg when present to preserve per-user notifications.Line 50 and Line 91 still use
notice.getNotice().getMessage(), which ignores the newpersonalMsgfield. This prevents personalized messages from being returned.🛠️ Suggested fix (fallback to Notice.message)
- .message(notice.getNotice().getMessage()) + .message(notice.getPersonalMsg() != null + ? notice.getPersonalMsg() + : notice.getNotice().getMessage())- .message(memberNotice.getNotice().getMessage()) + .message(memberNotice.getPersonalMsg() != null + ? memberNotice.getPersonalMsg() + : memberNotice.getNotice().getMessage())Also applies to: 88-95
🤖 Fix all issues with AI agents
In
`@src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java`:
- Around line 90-105: enrollShow is transactional so calling
memberRecommendationProducer.recommendByHashtag(...) emits Kafka events before
the DB commit; move that publish to an after‑commit hook to avoid orphaned
events. Replace the direct call to
memberRecommendationProducer.recommendByHashtag(new NewShowEvent(...)) inside
enrollShow with either (a) publish the NewShowEvent via a Spring application
event and handle it in a method annotated with `@TransactionalEventListener`(phase
= AFTER_COMMIT), or (b) register a TransactionSynchronization.afterCommit that
invokes memberRecommendationProducer.recommendByHashtag(...) with the same
NewShowEvent; keep notifyNewShow(...) as-is or also call it after-commit if you
prefer both side effects to run only after commit.
In `@src/main/java/cc/backend/apiPayLoad/code/status/ErrorStatus.java`:
- Around line 110-112: The enum constant NOTICE_NOT_FOUND in ErrorStatus is
unused; remove NOTICE_NOT_FOUND from ErrorStatus (leaving MEMBERNOTICE_NOT_FOUND
intact) unless you intend to use it later—if you do intend to keep it, update
usages in MemberNoticeService or NoticeServiceImpl to reference NOTICE_NOT_FOUND
instead of MEMBERNOTICE_NOT_FOUND; otherwise simply delete the NOTICE_NOT_FOUND
enum entry to eliminate dead code and redundant error codes.
In `@src/main/java/cc/backend/notice/entity/enums/NoticeType.java`:
- Line 4: NoticeType enum was renamed from AD to RECOMMEND and will break
deserialization/persistence; add a compatibility shim and optional backfill:
implement a static fromString factory on NoticeType (e.g.,
NoticeType.fromString(String)) that returns RECOMMEND when input equals "AD"
(case-insensitive) and use it for JSON deserialization via a `@JsonCreator` on
NoticeType and for JPA by adding an AttributeConverter<NoticeType,String> (or
update the existing converter) to map "AD" → RECOMMEND and otherwise delegate to
name(); additionally add a one-off DB migration/backfill script to update
persisted "AD" values to "RECOMMEND" to eliminate future compatibility handling.
In `@src/main/java/cc/backend/notice/entity/MemberNotice.java`:
- Around line 21-27: The MemberNotice entity now marks associations Notice and
Member as non-nullable (JoinColumn name="notice_id" and name="member_id");
before deploying with spring.jpa.hibernate.ddl-auto=update, ensure the
member_notice table contains no NULLs in notice_id or member_id by running a
pre-deployment data migration: identify rows where notice_id IS NULL or
member_id IS NULL, either delete or backfill them with valid foreign keys (or
create placeholder records in Notice/Member and set those FKs), and verify
referential integrity; alternatively, if immediate schema change is unsafe,
temporarily keep the JoinColumn nullable in MemberNotice and add a controlled
migration that cleans data then flips the constraint.
In `@src/main/java/cc/backend/notice/kafka/KafkaRecommendConfig.java`:
- Around line 49-50: Replace the insecure wildcard trusted package call on the
JsonDeserializer by restricting it to the actual package of the event class:
instead of deserializer.addTrustedPackages("*"), set the trusted package to the
package that contains MemberRecommendationEvent (e.g., use the explicit package
name string or derive it from
MemberRecommendationEvent.class.getPackage().getName()/getPackageName()) so only
that package is allowed for JSON deserialization; locate the
JsonDeserializer<MemberRecommendationEvent> instance and update the
deserializer.addTrustedPackages call accordingly.
In
`@src/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationProducer.java`:
- Around line 134-140: The code in MemberRecommendationProducer that calls
amateurShowRepository.findHashtagsByMemberId and then does
existingHashtags.split("#") can NPE or produce empty tokens; update the loop to
guard null/blank rows by skipping any null or blank existingHashtags before
splitting, then filter out empty tokens after splitting (e.g., trim and discard
empty strings) before collecting into existingTagsSet so intersection logic only
uses non-empty tags.
- Around line 36-65: createNoticeForNewShow currently throws
ErrorStatus.AMATEURSHOW_NOT_FOUND when the show has no hashtags which aborts
enrollment; change the behavior so it returns null (no Notice) instead of
throwing for empty newTagsSet, and update recommendByHashtag to check if
createNoticeForNewShow(...) returned null and simply return (skip
recommendation) before calling sendRecommendations; keep the `@Transactional` on
createNoticeForNewShow and preserve existing exceptions for true missing
AmateurShow (amateurShowRepository.findById(...).orElseThrow(...)) so only the
empty-tag case becomes a no-op.
In `@src/main/resources/application.yml`:
- Around line 43-44: The spring.json.trusted.packages property is currently set
to "*" which is unsafe; change spring.json.trusted.packages from "*" to an
explicit comma-separated list of your domain event packages (e.g., the packages
that contain your Kafka event/model classes) so only those packages are allowed
for Jackson deserialization; update the properties block where
spring.json.trusted.packages is defined to reference the exact package names
(for example "com.yourorg.events,com.yourorg.messages") instead of a wildcard.
🧹 Nitpick comments (6)
docker-compose.yml (2)
65-80: Consider adding health check and volume for Kafka.
Missing health check:
depends_ononly waits for container start, not readiness. The app may attempt to connect before Kafka is ready to accept connections, causing startup failures.No data persistence: Unlike Redis, Kafka has no volume mount, so topic data and offsets will be lost on container restart.
♻️ Proposed improvements
kafka: image: confluentinc/cp-kafka:7.5.0 container_name: kafka depends_on: - zookeeper ports: - "9092:9092" environment: KAFKA_BROKER_ID: 1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 # 내부/외부 통신 분리 (중요) KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 restart: unless-stopped + healthcheck: + test: ["CMD", "kafka-broker-api-versions", "--bootstrap-server", "localhost:9092"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s + volumes: + - kafka_data:/var/lib/kafka/data volumes: redis_data: driver: local + kafka_data: + driver: local
29-31: Usecondition: service_healthyfor robust startup ordering.With the health check added to Kafka, update
depends_onto wait for healthy status rather than just container start.♻️ Proposed fix for app-blue (apply similarly to app-green)
depends_on: - - redis - - kafka `#추가` + redis: + condition: service_started + kafka: + condition: service_healthysrc/main/java/cc/backend/amateurShow/repository/AmateurShowRepository.java (1)
95-96: AddDISTINCTto the query to avoid duplicate hashtags.If a member has multiple shows with the same hashtag, the current query returns duplicates, causing redundant processing in the recommendation logic. Add
DISTINCTto eliminate this overhead:♻️ Suggested change
- `@Query`("SELECT s.hashtag FROM AmateurShow s WHERE s.member.id = :memberId") + `@Query`("SELECT DISTINCT s.hashtag FROM AmateurShow s WHERE s.member.id = :memberId") List<String> findHashtagsByMemberId(`@Param`("memberId") Long memberId);src/main/java/cc/backend/memberLike/repository/MemberLikeRepository.java (1)
41-47: Consider paging for distinct-member scans.
findAllDistinctMembers()can load a large dataset into memory when used for batch recommendation fan‑out. Consider adding a paged overload (Slice/Page) and using that in batch jobs.♻️ Proposed addition (paged overload)
+ // Paged variant to avoid loading all likers into memory at once + `@Query`("SELECT DISTINCT ml.liker FROM MemberLike ml") + Slice<Member> findAllDistinctMembers(Pageable pageable);src/main/java/cc/backend/notice/service/NoticeServiceImpl.java (1)
121-141: Guard against duplicate notifications if likes aren’t unique.Line 122 pulls all likes by performer; if duplicates exist, Line 134–139 could create multiple notices per member. Please verify repository uniqueness or dedupe by memberId before saveAll.
src/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationProducer.java (1)
89-125: Potential N+1 DB query pattern in recommendation loop.Each member triggers
findByLikerId, and for each liked performer you callfindHashtagsByMemberId. This will scale poorly as member counts grow. Consider batching likes/hashtags in bulk, precomputing tag sets, or moving the intersection to the DB to reduce per-member round‑trips.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (38)
build.gradledocker-compose.ymlsrc/main/java/cc/backend/admin/amateurShow/service/AdminAmateurShowService.javasrc/main/java/cc/backend/admin/amateurShow/service/AdminApprovalService.javasrc/main/java/cc/backend/amateurShow/entity/AmateurShow.javasrc/main/java/cc/backend/amateurShow/repository/AmateurShowRepository.javasrc/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.javasrc/main/java/cc/backend/apiPayLoad/code/status/ErrorStatus.javasrc/main/java/cc/backend/board/service/BoardService.javasrc/main/java/cc/backend/board/service/CommentService.javasrc/main/java/cc/backend/event/entity/NewShowEvent.javasrc/main/java/cc/backend/event/service/NewShowEventListener.javasrc/main/java/cc/backend/memberLike/repository/MemberLikeRepository.javasrc/main/java/cc/backend/memberLike/service/MemberLikeServiceImpl.javasrc/main/java/cc/backend/notice/entity/MemberNotice.javasrc/main/java/cc/backend/notice/entity/enums/NoticeType.javasrc/main/java/cc/backend/notice/event/entity/ApproveShowEvent.javasrc/main/java/cc/backend/notice/event/entity/CommentEvent.javasrc/main/java/cc/backend/notice/event/entity/NewShowEvent.javasrc/main/java/cc/backend/notice/event/entity/PromoteHotEvent.javasrc/main/java/cc/backend/notice/event/entity/RejectShowEvent.javasrc/main/java/cc/backend/notice/event/entity/ReplyEvent.javasrc/main/java/cc/backend/notice/event/entity/TicketReservationEvent.javasrc/main/java/cc/backend/notice/event/service/ApproveShowEventListener.javasrc/main/java/cc/backend/notice/event/service/CommentEventListener.javasrc/main/java/cc/backend/notice/event/service/PromoteHotEventListener.javasrc/main/java/cc/backend/notice/event/service/RejectShowEventListener.javasrc/main/java/cc/backend/notice/event/service/ReplyEventListener.javasrc/main/java/cc/backend/notice/event/service/TicketReservationEventListener.javasrc/main/java/cc/backend/notice/kafka/KafkaRecommendConfig.javasrc/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationConsumer.javasrc/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationEvent.javasrc/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationProducer.javasrc/main/java/cc/backend/notice/service/MemberNoticeService.javasrc/main/java/cc/backend/notice/service/NoticeService.javasrc/main/java/cc/backend/notice/service/NoticeServiceImpl.javasrc/main/java/cc/backend/ticket/service/TempTicketServiceImpl.javasrc/main/resources/application.yml
💤 Files with no reviewable changes (4)
- src/main/java/cc/backend/event/service/NewShowEventListener.java
- src/main/java/cc/backend/event/entity/NewShowEvent.java
- src/main/java/cc/backend/memberLike/service/MemberLikeServiceImpl.java
- src/main/java/cc/backend/admin/amateurShow/service/AdminAmateurShowService.java
🧰 Additional context used
🧬 Code graph analysis (5)
src/main/java/cc/backend/notice/service/NoticeServiceImpl.java (1)
src/main/java/cc/backend/notice/dto/NoticeResponseDTO.java (1)
NoticeResponseDTO(11-25)
src/main/java/cc/backend/notice/event/entity/NewShowEvent.java (6)
src/main/java/cc/backend/notice/event/entity/ApproveShowEvent.java (1)
Getter(7-16)src/main/java/cc/backend/notice/event/entity/CommentEvent.java (1)
Getter(5-19)src/main/java/cc/backend/notice/event/entity/PromoteHotEvent.java (1)
Getter(8-17)src/main/java/cc/backend/notice/event/entity/RejectShowEvent.java (1)
Getter(7-16)src/main/java/cc/backend/notice/event/entity/ReplyEvent.java (1)
Getter(5-18)src/main/java/cc/backend/notice/event/entity/TicketReservationEvent.java (1)
Getter(9-20)
src/main/java/cc/backend/notice/entity/MemberNotice.java (3)
src/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationEvent.java (1)
Builder(10-18)src/main/java/cc/backend/notice/dto/MemberNoticeResponseDTO.java (1)
Builder(14-26)src/main/java/cc/backend/notice/dto/NoticeResponseDTO.java (1)
Builder(12-24)
src/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationEvent.java (7)
src/main/java/cc/backend/notice/event/entity/ApproveShowEvent.java (1)
Getter(7-16)src/main/java/cc/backend/notice/event/entity/CommentEvent.java (1)
Getter(5-19)src/main/java/cc/backend/notice/event/entity/NewShowEvent.java (1)
Getter(10-17)src/main/java/cc/backend/notice/event/entity/PromoteHotEvent.java (1)
Getter(8-17)src/main/java/cc/backend/notice/event/entity/RejectShowEvent.java (1)
Getter(7-16)src/main/java/cc/backend/notice/event/entity/ReplyEvent.java (1)
Getter(5-18)src/main/java/cc/backend/notice/event/entity/TicketReservationEvent.java (1)
Getter(9-20)
src/main/java/cc/backend/notice/service/NoticeService.java (1)
src/main/java/cc/backend/notice/service/NoticeServiceImpl.java (1)
Service(29-266)
🔇 Additional comments (21)
src/main/java/cc/backend/notice/event/entity/TicketReservationEvent.java (1)
1-1: Package relocation looks consistent.No functional concerns with the move into the notice namespace.
src/main/java/cc/backend/notice/event/service/TicketReservationEventListener.java (1)
1-3: Namespace alignment is OK.Listener stays unchanged and now references the relocated event type.
Also applies to: 15-18
src/main/java/cc/backend/amateurShow/entity/AmateurShow.java (1)
170-171: LGTM!Minor formatting adjustment with no functional impact.
src/main/java/cc/backend/notice/event/entity/CommentEvent.java (1)
1-1: LGTM!Package relocation to
cc.backend.notice.event.entityaligns with the broader reorganization of event-related classes under the notice module.src/main/java/cc/backend/notice/event/entity/ReplyEvent.java (1)
1-1: LGTM!Consistent package relocation matching other event entity classes.
src/main/java/cc/backend/board/service/CommentService.java (1)
14-15: LGTM!Import paths correctly updated to reflect the relocated event entity packages.
build.gradle (1)
74-76: LGTM! Addingspring-kafkawithout an explicit version correctly leverages Spring Boot's dependency management. Spring Boot 3.4.4 includes Spring for Apache Kafka 3.3.4 and automatically manages the kafka-clients version, ensuring compatibility.src/main/java/cc/backend/notice/event/entity/ApproveShowEvent.java (1)
1-16: LGTM!The package relocation to
cc.backend.notice.event.entityaligns with the PR's objective to reorganize event domain files under the notice package. The event class itself is well-structured as an immutable POJO.src/main/java/cc/backend/notice/event/entity/PromoteHotEvent.java (1)
1-17: LGTM!The package relocation is consistent with the reorganization of event classes under
cc.backend.notice.event.entity.src/main/java/cc/backend/notice/event/service/PromoteHotEventListener.java (1)
1-20: LGTM!The package relocation and updated import path are consistent with the event reorganization. The unused
ProcedureOutputsimport removal (per AI summary) is a good cleanup.src/main/java/cc/backend/board/service/BoardService.java (1)
16-16: LGTM!The import path update aligns with the PR's package relocation of event classes to
cc.backend.notice.event.entity. The usage at line 364 remains consistent.src/main/java/cc/backend/notice/event/service/ReplyEventListener.java (1)
1-3: LGTM!Package and import path updates are consistent with the PR's event class relocations to the
noticepackage structure.src/main/java/cc/backend/notice/event/service/ApproveShowEventListener.java (1)
1-3: LGTM!Package and import path updates are consistent with the broader event class relocation to
cc.backend.notice.event.src/main/java/cc/backend/notice/event/entity/RejectShowEvent.java (1)
1-1: LGTM!Package relocation to
cc.backend.notice.event.entityis consistent with the PR's reorganization of event classes under the notice module.src/main/java/cc/backend/notice/event/entity/NewShowEvent.java (1)
7-16: Verify Kafka serialization compatibility.If the Kafka producer uses Java serialization (or any serializer that requires
Serializable), this event will fail at runtime. If you’re using a JSON serializer, you’re fine—please confirm the serializer configuration. If Java serialization is in play, implementSerializable.🔧 Optional fix if Java serialization is required
-public class NewShowEvent { +public class NewShowEvent implements Serializable { + private static final long serialVersionUID = 1L; private Long amateurShowId; // 공연 ID private Long performerId; // 공연 등록자 ID private List<Long> likerIds; // 좋아요한 유저 ID 리스트 }src/main/java/cc/backend/ticket/service/TempTicketServiceImpl.java (1)
11-11: Import update looks correct.Aligns with the event package relocation.
src/main/java/cc/backend/notice/entity/MemberNotice.java (2)
29-38: Personal message fields look OK.The new personalMsg and isRead fields integrate cleanly with the builder/constructor.
41-42: No call sites depend on the removed return value.The single call to
updateIsRead()at line 86 inMemberNoticeService.javais a standalone statement with no method chaining. Changing the return type fromMemberNoticetovoidis safe.src/main/java/cc/backend/notice/service/NoticeService.java (1)
12-12: All callers properly handle the void return type—no issues found.The only identifiable call site in
AmateurServiceImpl.java:98–100invokesnotifyNewShow()as a standalone statement without capturing or using the return value, confirming compatibility with the void contract.src/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationEvent.java (1)
10-17: LGTM — simple event DTO.The Lombok builder/getters are a clean fit for a Kafka payload.
src/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationConsumer.java (1)
30-79: LGTM — batch lookup + saveAll keeps DB trips low.The bulk fetches and single batch insert are appropriate for Kafka batch consumption.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| //좋아요한 멤버 추출 | ||
| List<Long> likerIds = memberLikeRepository.findByPerformerId(memberId) | ||
| .stream() | ||
| .map(l -> l.getLiker().getId()) | ||
| .toList(); | ||
|
|
||
| eventPublisher.publishEvent(new NewShowEvent(newAmateurShow.getId(), memberId, likers)); //공연등록 이벤트 생성 | ||
| // 좋아요 알림: 좋아요한 멤버들만 | ||
| if (!likerIds.isEmpty()) { | ||
| noticeService.notifyNewShow( | ||
| new NewShowEvent(newAmateurShow.getId(), memberId, likerIds) | ||
| ); | ||
| } | ||
| // 추천: 해시태그 기반 추천 → 모든 회원 대상 | ||
| memberRecommendationProducer.recommendByHashtag( | ||
| new NewShowEvent(newAmateurShow.getId(), memberId, null) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
fd -t f "AmateurServiceImpl.java" --exec wc -l {} \;Repository: SeeATheater/CC_Backend
Length of output: 159
🏁 Script executed:
fd -t f "AmateurServiceImpl.java" -x cat -n {} | head -150Repository: SeeATheater/CC_Backend
Length of output: 7794
🏁 Script executed:
# Search for the enrollShow method to verify the `@Transactional` annotation
rg -A 20 "def enrollShow|void enrollShow|AmateurShow enrollShow" src/main/java/cc/backend/amateurShow/Repository: SeeATheater/CC_Backend
Length of output: 48
🏁 Script executed:
# Check for imports related to Kafka/async producers and transaction listeners
rg "@Transactional|@TransactionalEventListener|TransactionSynchronization|@KafkaListener|@TopicPartition" src/main/java/cc/backend/amateurShow/service/amateurShowService/Repository: SeeATheater/CC_Backend
Length of output: 506
🏁 Script executed:
fd -t f "NoticeService.java" -x cat -n {} | head -100Repository: SeeATheater/CC_Backend
Length of output: 4923
🏁 Script executed:
fd -t f "MemberRecommendationProducer.java" -x cat -n {} | head -150Repository: SeeATheater/CC_Backend
Length of output: 6786
🏁 Script executed:
# Also search for NewShowEvent implementation
fd -t f "NewShowEvent.java" -x cat -n {} | head -100Repository: SeeATheater/CC_Backend
Length of output: 587
🏁 Script executed:
rg -l "notifyNewShow" src/main/java/Repository: SeeATheater/CC_Backend
Length of output: 275
🏁 Script executed:
rg -B5 -A15 "void notifyNewShow|notifyNewShow\(" src/main/java/cc/backend/notice/Repository: SeeATheater/CC_Backend
Length of output: 3433
🏁 Script executed:
fd -t f "NoticeServiceImpl.java" -x cat -n {} | head -200Repository: SeeATheater/CC_Backend
Length of output: 9890
Move Kafka publish to after-commit to prevent orphaned events.
enrollShow is transactional. Calling memberRecommendationProducer.recommendByHashtag() inside it emits Kafka events before the DB commit (via kafkaTemplate.send() at line 115). If the transaction rolls back, consumers see events referencing non‑existent records. Move this side effect to an after‑commit hook (e.g., @TransactionalEventListener(AFTER_COMMIT) or TransactionSynchronization) so Kafka stays consistent with the DB.
Note: notifyNewShow() performs only database operations (Notice/MemberNotice inserts), so it has no direct Kafka issue, but can remain in the same after-commit block if needed.
✅ Example after-commit hook using TransactionSynchronization
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
...
- // 좋아요 알림: 좋아요한 멤버들만
- if (!likerIds.isEmpty()) {
- noticeService.notifyNewShow(
- new NewShowEvent(newAmateurShow.getId(), memberId, likerIds)
- );
- }
- // 추천: 해시태그 기반 추천 → 모든 회원 대상
- memberRecommendationProducer.recommendByHashtag(
- new NewShowEvent(newAmateurShow.getId(), memberId, null)
- );
+ TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+ `@Override`
+ public void afterCommit() {
+ if (!likerIds.isEmpty()) {
+ noticeService.notifyNewShow(
+ new NewShowEvent(newAmateurShow.getId(), memberId, likerIds)
+ );
+ }
+ memberRecommendationProducer.recommendByHashtag(
+ new NewShowEvent(newAmateurShow.getId(), memberId, null)
+ );
+ }
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| //좋아요한 멤버 추출 | |
| List<Long> likerIds = memberLikeRepository.findByPerformerId(memberId) | |
| .stream() | |
| .map(l -> l.getLiker().getId()) | |
| .toList(); | |
| eventPublisher.publishEvent(new NewShowEvent(newAmateurShow.getId(), memberId, likers)); //공연등록 이벤트 생성 | |
| // 좋아요 알림: 좋아요한 멤버들만 | |
| if (!likerIds.isEmpty()) { | |
| noticeService.notifyNewShow( | |
| new NewShowEvent(newAmateurShow.getId(), memberId, likerIds) | |
| ); | |
| } | |
| // 추천: 해시태그 기반 추천 → 모든 회원 대상 | |
| memberRecommendationProducer.recommendByHashtag( | |
| new NewShowEvent(newAmateurShow.getId(), memberId, null) | |
| ); | |
| //좋아요한 멤버 추출 | |
| List<Long> likerIds = memberLikeRepository.findByPerformerId(memberId) | |
| .stream() | |
| .map(l -> l.getLiker().getId()) | |
| .toList(); | |
| TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { | |
| `@Override` | |
| public void afterCommit() { | |
| if (!likerIds.isEmpty()) { | |
| noticeService.notifyNewShow( | |
| new NewShowEvent(newAmateurShow.getId(), memberId, likerIds) | |
| ); | |
| } | |
| memberRecommendationProducer.recommendByHashtag( | |
| new NewShowEvent(newAmateurShow.getId(), memberId, null) | |
| ); | |
| } | |
| }); |
🤖 Prompt for AI Agents
In
`@src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java`
around lines 90 - 105, enrollShow is transactional so calling
memberRecommendationProducer.recommendByHashtag(...) emits Kafka events before
the DB commit; move that publish to an after‑commit hook to avoid orphaned
events. Replace the direct call to
memberRecommendationProducer.recommendByHashtag(new NewShowEvent(...)) inside
enrollShow with either (a) publish the NewShowEvent via a Spring application
event and handle it in a method annotated with `@TransactionalEventListener`(phase
= AFTER_COMMIT), or (b) register a TransactionSynchronization.afterCommit that
invokes memberRecommendationProducer.recommendByHashtag(...) with the same
NewShowEvent; keep notifyNewShow(...) as-is or also call it after-commit if you
prefer both side effects to run only after commit.
| //NOTICE ERROR | ||
| MEMBERNOTICE_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBERNOTICE4001", "존재하지 않는 알림입니다."), | ||
| NOTICE_NOT_FOUND(HttpStatus.NOT_FOUND, "NOTICE4001", "존재하지 않는 알림입니다."), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n src/main/java/cc/backend/apiPayLoad/code/status/ErrorStatus.java | sed -n '100,120p'Repository: SeeATheater/CC_Backend
Length of output: 1396
🏁 Script executed:
# Search for usages of these constants
rg "MEMBERNOTICE_NOT_FOUND|NOTICE_NOT_FOUND" -B 2 -A 2Repository: SeeATheater/CC_Backend
Length of output: 1303
🏁 Script executed:
# Check for any comments or documentation about notice vs membernotice
rg -i "notice" src/main/java/cc/backend/apiPayLoad/code/status/ErrorStatus.java -B 1 -A 1 | head -50Repository: SeeATheater/CC_Backend
Length of output: 270
🏁 Script executed:
# Search for all usages of NOTICE_NOT_FOUND more broadly
rg "NOTICE_NOT_FOUND" -r "Found in: $FILE"Repository: SeeATheater/CC_Backend
Length of output: 498
🏁 Script executed:
# Check for Notice service/repository files
fd -t f "Notice" src/main/java --type fRepository: SeeATheater/CC_Backend
Length of output: 953
🏁 Script executed:
# Look at the structure to find notice-related classes
fd -t d "notice" src/main/javaRepository: SeeATheater/CC_Backend
Length of output: 98
🏁 Script executed:
# Check NoticeService for how it handles not found cases
cat -n src/main/java/cc/backend/notice/service/NoticeService.javaRepository: SeeATheater/CC_Backend
Length of output: 933
🏁 Script executed:
# Check NoticeServiceImpl for how it handles not found cases
cat -n src/main/java/cc/backend/notice/service/NoticeServiceImpl.javaRepository: SeeATheater/CC_Backend
Length of output: 12662
🏁 Script executed:
# Check if there's a controller that might retrieve Notice directly
rg "noticeRepository\.findById|getNotice|Notice.*not.*found" -i -A 2 -B 2Repository: SeeATheater/CC_Backend
Length of output: 10769
🏁 Script executed:
# Check MemberNoticeController to understand the API patterns
head -100 src/main/java/cc/backend/notice/controller/MemberNoticeController.javaRepository: SeeATheater/CC_Backend
Length of output: 2516
Remove unused NOTICE_NOT_FOUND error status.
NOTICE_NOT_FOUND is defined in ErrorStatus but never used anywhere in the codebase. Only MEMBERNOTICE_NOT_FOUND is actually used (in MemberNoticeService). The Notice entity is created through internal event handling in NoticeServiceImpl and is only accessed via MemberNotice relationships. Remove the unused constant or clarify if it should be used in a future feature.
🤖 Prompt for AI Agents
In `@src/main/java/cc/backend/apiPayLoad/code/status/ErrorStatus.java` around
lines 110 - 112, The enum constant NOTICE_NOT_FOUND in ErrorStatus is unused;
remove NOTICE_NOT_FOUND from ErrorStatus (leaving MEMBERNOTICE_NOT_FOUND intact)
unless you intend to use it later—if you do intend to keep it, update usages in
MemberNoticeService or NoticeServiceImpl to reference NOTICE_NOT_FOUND instead
of MEMBERNOTICE_NOT_FOUND; otherwise simply delete the NOTICE_NOT_FOUND enum
entry to eliminate dead code and redundant error codes.
|
|
||
| public enum NoticeType { | ||
| AMATEURSHOW, HOT, COMMENT, REPLY, TICKET, REMIND, AD | ||
| AMATEURSHOW, HOT, COMMENT, REPLY, TICKET, REMIND, RECOMMEND |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle backward compatibility for renamed enum value.
Renaming AD → RECOMMEND will break any persisted values or payloads that still use "AD" (JPA EnumType.STRING, JSON, Kafka events). Please add a migration/backfill and/or a compatibility shim (e.g., custom converter/JSON creator) so legacy data and messages continue to deserialize.
🤖 Prompt for AI Agents
In `@src/main/java/cc/backend/notice/entity/enums/NoticeType.java` at line 4,
NoticeType enum was renamed from AD to RECOMMEND and will break
deserialization/persistence; add a compatibility shim and optional backfill:
implement a static fromString factory on NoticeType (e.g.,
NoticeType.fromString(String)) that returns RECOMMEND when input equals "AD"
(case-insensitive) and use it for JSON deserialization via a `@JsonCreator` on
NoticeType and for JPA by adding an AttributeConverter<NoticeType,String> (or
update the existing converter) to map "AD" → RECOMMEND and otherwise delegate to
name(); additionally add a one-off DB migration/backfill script to update
persisted "AD" values to "RECOMMEND" to eliminate future compatibility handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AD 쓰는 코드 없어서 backward compatibility 안챙겨도됨
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for any usage of NoticeType.AD in the codebase
rg -n 'NoticeType\.AD' --type=java
# Search for any string literal "AD" in notice-related contexts
rg -n '"AD"' --type=java -C2 -g '**/notice/**'
# Search for any references to AD in the enum context
rg -n '\bAD\b' --type=java -C2 -g '**/NoticeType.java'
# Check for any persisted AD values in test fixtures or migrations
fd -e sql -e yml -e yaml -e json | xargs rg -l '\bAD\b' 2>/dev/null || echo "No SQL/config files with AD found"
Length of output: 33
@sweatbuckets 확인했습니다! 코드베이스에서 NoticeType.AD의 사용이 전혀 없는 것을 확인했으니, backward compatibility 고려 없이 RECOMMEND로 변경하셔도 문제없습니다. 👍
✏️ Learnings added
Learnt from: sweatbuckets
Repo: SeeATheater/CC_Backend PR: 144
File: src/main/java/cc/backend/notice/entity/enums/NoticeType.java:4-4
Timestamp: 2026-01-16T20:03:01.596Z
Learning: The NoticeType enum value AD was never used in the codebase and was safely replaced with RECOMMEND without backward compatibility concerns.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "notice_id", nullable = false) | ||
| private Notice notice; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) | ||
| @JoinColumn(name = "member_id") | ||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "member_id", nullable = false) | ||
| private Member member; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find migrations touching member_notice schema
fd -t f \( -e sql -e yml -e yaml \) | xargs rg -l "member_notice|notice_id|member_id" 2>/dev/null | head -20Repository: SeeATheater/CC_Backend
Length of output: 775
🏁 Script executed:
#!/bin/bash
# Find migration files and schema management
echo "=== Searching for SQL migration files ==="
find . -type f \( -name "*.sql" -o -name "V*.sql" -o -name "U*.sql" \) 2>/dev/null | head -20
echo "=== Checking for Flyway/Liquibase directories ==="
find . -type d \( -name "db" -o -name "migration" -o -name "migrations" -o -name "liquibase" -o -name "flyway" \) 2>/dev/null
echo "=== Searching for application configuration files ==="
find . -type f \( -name "application*.yml" -o -name "application*.yaml" -o -name "application*.properties" \) 2>/dev/null | head -10Repository: SeeATheater/CC_Backend
Length of output: 331
🏁 Script executed:
#!/bin/bash
# Check application configuration for DDL auto settings
echo "=== application.yml ==="
cat ./src/main/resources/application.yml
echo -e "\n=== application-dev.yml ==="
cat ./src/main/resources/application-dev.yml
echo -e "\n=== application-prod.yml ==="
cat ./src/main/resources/application-prod.ymlRepository: SeeATheater/CC_Backend
Length of output: 2606
🏁 Script executed:
#!/bin/bash
# Read the full MemberNotice.java entity
echo "=== MemberNotice.java entity ==="
cat -n ./src/main/java/cc/backend/notice/entity/MemberNotice.java
# Check if there are other entities with similar join columns to compare patterns
echo -e "\n=== Check similar entities for patterns ==="
rg -A 5 "@JoinColumn.*nullable" ./src/main/java/cc/backend --type java | head -40Repository: SeeATheater/CC_Backend
Length of output: 4699
🏁 Script executed:
#!/bin/bash
# Check for data cleanup or migration utilities
echo "=== Searching for data migration/cleanup utilities ==="
find ./src -type f -name "*Migration*" -o -name "*Cleanup*" -o -name "*Backfill*" 2>/dev/null
echo -e "\n=== Searching for `@Sql` or schema initialization scripts ==="
rg -l "schema|INSERT|UPDATE.*member_notice|UPDATE.*notice_id" ./src --type java -i | head -10
echo -e "\n=== Checking MemberNotice repository/service for data handling ==="
find ./src -path "*notice*" -name "*Repository.java" -o -path "*notice*" -name "*Service.java" 2>/dev/null | head -10Repository: SeeATheater/CC_Backend
Length of output: 1243
🏁 Script executed:
#!/bin/bash
# Check MemberNoticeService for data handling
echo "=== MemberNoticeService.java ==="
wc -l ./src/main/java/cc/backend/notice/service/MemberNoticeService.java
cat ./src/main/java/cc/backend/notice/service/MemberNoticeService.java
echo -e "\n=== MemberNoticeRepository.java ==="
cat ./src/main/java/cc/backend/notice/repository/MemberNoticeRepository.javaRepository: SeeATheater/CC_Backend
Length of output: 5433
Verify database has no NULL rows in notice_id or member_id columns before deploying.
With spring.jpa.hibernate.ddl-auto: update, Hibernate will automatically attempt to add NOT NULL constraints on lines 22 and 26. If the member_notice table contains any existing rows with NULL values in these columns, the schema update will fail and prevent application startup. Ensure all existing records have both notice_id and member_id populated, or add a data cleanup/backfill script before deployment to production.
🤖 Prompt for AI Agents
In `@src/main/java/cc/backend/notice/entity/MemberNotice.java` around lines 21 -
27, The MemberNotice entity now marks associations Notice and Member as
non-nullable (JoinColumn name="notice_id" and name="member_id"); before
deploying with spring.jpa.hibernate.ddl-auto=update, ensure the member_notice
table contains no NULLs in notice_id or member_id by running a pre-deployment
data migration: identify rows where notice_id IS NULL or member_id IS NULL,
either delete or backfill them with valid foreign keys (or create placeholder
records in Notice/Member and set those FKs), and verify referential integrity;
alternatively, if immediate schema change is unsafe, temporarily keep the
JoinColumn nullable in MemberNotice and add a controlled migration that cleans
data then flips the constraint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notice와 연결 안된 MemberNotice row는 존재하지 않으므로 문제 없음
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sweatbuckets, 확인해 주셔서 감사합니다! 데이터 무결성이 보장되어 있다면 스키마 마이그레이션이 안전하게 진행될 것입니다. 👍
| JsonDeserializer<MemberRecommendationEvent> deserializer = new JsonDeserializer<>(MemberRecommendationEvent.class); | ||
| deserializer.addTrustedPackages("*"); // 패키지 제한 없앰 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "KafkaRecommendConfig.java" | head -20Repository: SeeATheater/CC_Backend
Length of output: 131
🏁 Script executed:
cat -n src/main/java/cc/backend/notice/kafka/KafkaRecommendConfig.javaRepository: SeeATheater/CC_Backend
Length of output: 4367
🏁 Script executed:
# Search for MemberRecommendationEvent definition
fd -t f -e java | xargs grep -l "class MemberRecommendationEvent"Repository: SeeATheater/CC_Backend
Length of output: 147
🏁 Script executed:
# Check for other addTrustedPackages usages in the codebase
rg "addTrustedPackages" --type javaRepository: SeeATheater/CC_Backend
Length of output: 188
🏁 Script executed:
head -10 src/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationEvent.javaRepository: SeeATheater/CC_Backend
Length of output: 264
🌐 Web query:
Spring Kafka JsonDeserializer addTrustedPackages security best practices
💡 Result:
Key recommendations for secure use of Spring Kafka's JsonDeserializer / trusted packages
- Whitelist only the minimal packages required (do not use ALL). Configure spring.json.trusted.packages or call JsonDeserializer.addTrustedPackages(...) with explicit package names (e.g., com.myapp.dto). (codestudy.net)
- Avoid enabling global “allow-all” deserialization (ALL or "*") in production — it defeats the whitelist protection and can enable deserialization attacks. (codestudy.net)
- Prefer JSON (Jackson) and the JsonDeserializer over Java native serialization; never accept arbitrary serialized Java objects from untrusted sources. If you must accept Java-serialized data, apply strict whitelisting and other mitigations. (securitylab.github.com)
- Use ErrorHandlingDeserializer and the container settings recommended by Spring to protect header/exception deserialization; follow vendor guidance on checkDeserExWhenKeyNull/checkDeserExWhenValueNull and related properties. (spring.io)
- Keep Spring Kafka (and Spring dependencies) up to date; install fixes for known deserialization issues (e.g., CVE-2023-34040 — upgrade to Spring Kafka 2.9.11 / 3.0.10 or later). (spring.io)
- Additional hardening: validate incoming messages at the application layer, minimize classes on the classpath that can be abused, run security scans/pen tests, and apply least privilege to services that consume Kafka. (securitylab.github.com)
If you want, I can:
- Show exact configuration examples for spring.json.trusted.packages or JsonDeserializer.addTrustedPackages(...), or
- Review your current config and suggest package whitelist text.
Citations:
- 1: https://www.codestudy.net/blog/springkafkaconsumerpropertiesspringjsontrustedpackages/?utm_source=openai
- 2: https://www.codestudy.net/blog/springkafkaconsumerpropertiesspringjsontrustedpackages/?utm_source=openai
- 3: https://securitylab.github.com/research/spring_amqp_exploit_CVE-2017-8045/?utm_source=openai
- 4: https://spring.io/security/cve-2023-34040?utm_source=openai
- 5: https://spring.io/security/cve-2023-34040?utm_source=openai
- 6: https://securitylab.github.com/research/spring_amqp_exploit_CVE-2017-8045/?utm_source=openai
Avoid addTrustedPackages("*") in JSON deserialization.
Trusting all packages allows arbitrary type deserialization and is a known security risk that defeats whitelist protections. Restrict to the specific package containing the event class.
🔒 Proposed fix
- deserializer.addTrustedPackages("*"); // 패키지 제한 없앰
+ deserializer.addTrustedPackages("cc.backend.notice.kafka.NewShowEvent");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| JsonDeserializer<MemberRecommendationEvent> deserializer = new JsonDeserializer<>(MemberRecommendationEvent.class); | |
| deserializer.addTrustedPackages("*"); // 패키지 제한 없앰 | |
| JsonDeserializer<MemberRecommendationEvent> deserializer = new JsonDeserializer<>(MemberRecommendationEvent.class); | |
| deserializer.addTrustedPackages("cc.backend.notice.kafka"); |
🤖 Prompt for AI Agents
In `@src/main/java/cc/backend/notice/kafka/KafkaRecommendConfig.java` around lines
49 - 50, Replace the insecure wildcard trusted package call on the
JsonDeserializer by restricting it to the actual package of the event class:
instead of deserializer.addTrustedPackages("*"), set the trusted package to the
package that contains MemberRecommendationEvent (e.g., use the explicit package
name string or derive it from
MemberRecommendationEvent.class.getPackage().getName()/getPackageName()) so only
that package is allowed for JSON deserialization; locate the
JsonDeserializer<MemberRecommendationEvent> instance and update the
deserializer.addTrustedPackages call accordingly.
| public void recommendByHashtag(NewShowEvent event) { | ||
| if (event == null) return; | ||
|
|
||
| Long showId = event.getAmateurShowId(); | ||
| Long performerId = event.getPerformerId(); | ||
|
|
||
| Notice notice = createNoticeForNewShow(showId); | ||
|
|
||
| // 모든 회원 조회 | ||
| List<Member> allMembers = memberLikeRepository.findAllDistinctMembers(); | ||
|
|
||
| // 추천 대상 회원 필터링 및 Kafka 이벤트 발행 | ||
| sendRecommendations(allMembers, notice, showId); | ||
|
|
||
| } | ||
|
|
||
| //DB 트랜잭션 안에서 Notice 생성 | ||
| @Transactional | ||
| protected Notice createNoticeForNewShow(Long showId) { | ||
| AmateurShow newShow = amateurShowRepository.findById(showId) | ||
| .orElseThrow(() -> new GeneralException(ErrorStatus.AMATEURSHOW_NOT_FOUND)); | ||
|
|
||
| Set<String> newTagsSet = Arrays.stream(Optional.ofNullable(newShow.getHashtag()).orElse("").split("[#,\\s]+")) | ||
| .map(String::trim) | ||
| .filter(s -> !s.isEmpty()) | ||
| .collect(Collectors.toSet()); | ||
|
|
||
| if (newTagsSet.isEmpty()) { | ||
| throw new GeneralException(ErrorStatus.AMATEURSHOW_NOT_FOUND); // 태그 없으면 추천 불가 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Skip recommendation when hashtags are missing instead of failing enrollment.
createNoticeForNewShow throws AMATEURSHOW_NOT_FOUND when tags are empty, which will abort show enrollment for a valid show and uses a misleading error. Prefer a no‑op recommendation path when tags are absent.
✅ Suggested fix
public void recommendByHashtag(NewShowEvent event) {
if (event == null) return;
@@
- Notice notice = createNoticeForNewShow(showId);
+ Notice notice = createNoticeForNewShow(showId);
+ if (notice == null) {
+ return; // 해시태그 없으면 추천 스킵
+ }
@@
- if (newTagsSet.isEmpty()) {
- throw new GeneralException(ErrorStatus.AMATEURSHOW_NOT_FOUND); // 태그 없으면 추천 불가
- }
+ if (newTagsSet.isEmpty()) {
+ return null; // 태그 없으면 추천 스킵
+ }🤖 Prompt for AI Agents
In
`@src/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationProducer.java`
around lines 36 - 65, createNoticeForNewShow currently throws
ErrorStatus.AMATEURSHOW_NOT_FOUND when the show has no hashtags which aborts
enrollment; change the behavior so it returns null (no Notice) instead of
throwing for empty newTagsSet, and update recommendByHashtag to check if
createNoticeForNewShow(...) returned null and simply return (skip
recommendation) before calling sendRecommendations; keep the `@Transactional` on
createNoticeForNewShow and preserve existing exceptions for true missing
AmateurShow (amateurShowRepository.findById(...).orElseThrow(...)) so only the
empty-tag case becomes a no-op.
| // 좋아요한 공연자의 기존 공연 해시태그 조회 | ||
| List<String> hashtags = amateurShowRepository.findHashtagsByMemberId(likedPerformerId); | ||
|
|
||
| for (String existingHashtags : hashtags) { | ||
| Set<String> existingTagsSet = Arrays.stream(existingHashtags.split("#")) | ||
| .map(String::trim) | ||
| .collect(Collectors.toSet()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against null/blank hashtag rows.
existingHashtags.split("#") will NPE if any stored hashtag row is null. Add null/blank checks and filter empties before intersection.
✅ Suggested fix
for (String existingHashtags : hashtags) {
- Set<String> existingTagsSet = Arrays.stream(existingHashtags.split("#"))
- .map(String::trim)
- .collect(Collectors.toSet());
+ if (existingHashtags == null || existingHashtags.isBlank()) {
+ continue;
+ }
+ Set<String> existingTagsSet = Arrays.stream(existingHashtags.split("#"))
+ .map(String::trim)
+ .filter(s -> !s.isEmpty())
+ .collect(Collectors.toSet());🤖 Prompt for AI Agents
In
`@src/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationProducer.java`
around lines 134 - 140, The code in MemberRecommendationProducer that calls
amateurShowRepository.findHashtagsByMemberId and then does
existingHashtags.split("#") can NPE or produce empty tokens; update the loop to
guard null/blank rows by skipping any null or blank existingHashtags before
splitting, then filter out empty tokens after splitting (e.g., trim and discard
empty strings) before collecting into existingTagsSet so intersection logic only
uses non-empty tags.
| properties: | ||
| spring.json.trusted.packages: "*" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security risk: Restrict spring.json.trusted.packages to specific packages.
Using "*" allows deserialization of any class, which can expose the application to deserialization attacks if untrusted data reaches the Kafka consumer. Restrict this to only the packages containing your event classes.
🔒 Proposed fix
properties:
- spring.json.trusted.packages: "*"
+ spring.json.trusted.packages: "cc.backend.notice.kafka"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| properties: | |
| spring.json.trusted.packages: "*" | |
| properties: | |
| spring.json.trusted.packages: "cc.backend.notice.kafka" |
🤖 Prompt for AI Agents
In `@src/main/resources/application.yml` around lines 43 - 44, The
spring.json.trusted.packages property is currently set to "*" which is unsafe;
change spring.json.trusted.packages from "*" to an explicit comma-separated list
of your domain event packages (e.g., the packages that contain your Kafka
event/model classes) so only those packages are allowed for Jackson
deserialization; update the properties block where spring.json.trusted.packages
is defined to reference the exact package names (for example
"com.yourorg.events,com.yourorg.messages") instead of a wildcard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
🤖 Fix all issues with AI agents
In
`@src/main/java/cc/backend/admin/amateurShow/service/AdminApprovalService.java`:
- Around line 30-32: AdminApprovalService has duplicate and unused injected
fields: remove the redundant ApplicationEventPublisher field named
applicationEventPublisher and remove the unused approvalShowProducer injection;
keep the single eventPublisher field (ApplicationEventPublisher) and ensure any
Kafka producer usage remains in ApproveCommitEventListener, then also delete
their corresponding unused import statements at the top of the file.
In `@src/main/java/cc/backend/board/service/CommentService.java`:
- Around line 20-22: Remove the unused import TicketReservationCommitEvent from
the CommentService.java imports; locate the import block in the CommentService
class (the lines that include cc.backend.notice.event.CommentCommitEvent,
ReplyCommitEvent, TicketReservationCommitEvent) and delete the
TicketReservationCommitEvent import so only used imports remain, then recompile
or run static checks to ensure no other references exist.
- Around line 40-42: The CommentService class contains unused fields and imports
for CommentProducer, ReplyProducer, CommentEvent, and ReplyEvent; remove the
CommentProducer and ReplyProducer field declarations and any imports for
CommentEvent and ReplyEvent from the top of the file, leaving
ApplicationEventPublisher and the event types actually used (CommentCommitEvent,
ReplyCommitEvent) intact; ensure CommentService still publishes via
eventPublisher.publishEvent(...) and compile to verify no remaining references
to the removed symbols (CommentProducer, ReplyProducer, CommentEvent,
ReplyEvent) remain in the class or its imports.
In `@src/main/java/cc/backend/image/service/ImageService.java`:
- Around line 41-43: The service method in ImageService currently returns null
when requestDTO.getKeyName() is missing; instead either throw a validation
exception (e.g., IllegalArgumentException or a custom ValidationException) or
change the method to return Optional and return Optional.empty(), so callers
don't receive a raw null; locate the conditional that checks
requestDTO.getKeyName() and replace the null return with a clear failure signal
(throw new ValidationException("keyName is required") or return
Optional.empty()) and update the method signature and callers accordingly
(references: ImageService, requestDTO.getKeyName()).
In `@src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardConsumer.java`:
- Around line 14-23: The consume method in HotBoardConsumer is missing a
transactional boundary: annotate HotBoardConsumer.consume with `@Transactional`
(from org.springframework.transaction.annotation.Transactional) so that the
downstream noticeService.notifyHotBoard operations run in a single transaction;
ensure the annotation is applied on the consume method (and import the
annotation) to match the pattern used by ApprovalConsumer, RecommendConsumer,
RejectShowConsumer, LikerConsumer, and ReservationCompletedConsumer.
In `@src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardProducer.java`:
- Around line 14-21: The publish method in HotBoardProducer may throw an NPE
because it calls event.boardId().toString() without guarding for a null boardId;
update the publish(HotBoardEvent event) implementation to check event.boardId()
for null (or require non-null) before calling toString and either return/noop or
log and abort, e.g., validate inside publish, and use kafkaTemplate.send(TOPIC,
key, event) only when boardId is present; reference methods/vars: publish,
HotBoardEvent.boardId(), kafkaTemplate, TOPIC.
In `@src/main/java/cc/backend/kafka/event/replyEvent/ReplyEvent.java`:
- Around line 6-11: The inline comment for the replyId component of the
ReplyEvent record is misleading (it reads "// 대댓글 commentId") — update the
comment on the replyId field in the ReplyEvent record to accurately describe the
field (for example "// 대댓글 id" or "// 대댓글 작성자 id" if that was intended) so the
name replyId and its comment are consistent; locate the ReplyEvent record and
change only the comment for the replyId component.
In `@src/main/java/cc/backend/kafka/event/replyEvent/ReplyProducer.java`:
- Around line 16-23: The publish(ReplyEvent event) method currently calls
event.commentId().toString() which will NPE if commentId is null; add a guard
that returns early or handles the case when event.commentId() == null (or throw
a clear IllegalArgumentException) before calling toString(), e.g., check event
!= null && event.commentId() != null, then call kafkaTemplate.send(TOPIC,
event.commentId().toString(), event); ensure the null-check references the
publish method and ReplyEvent.commentId() so the TOPIC/kafkaTemplate.send call
is only executed with a non-null key.
In
`@src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedConsumer.java`:
- Line 3: The file ReservationCompletedConsumer.java contains an unused import
of the class RejectShowEvent; remove the import line "import
cc.backend.kafka.event.rejectShowEvent.RejectShowEvent;" from
ReservationCompletedConsumer to eliminate the unused dependency and let the
compiler/IDE clean imports for that class.
In `@src/main/java/cc/backend/kafka/KafkaConfig.java`:
- Around line 51-53: The JsonDeserializer<DomainEvent> instance currently calls
deserializer.addTrustedPackages("*"), which is unsafe; replace the wildcard with
an explicit list of your application domain packages (e.g., the package(s) that
contain DomainEvent and related classes) by calling
deserializer.addTrustedPackages("your.domain.package", "another.package.if.any")
or a single explicit package string, ensuring only known packages are trusted;
keep deserializer.setUseTypeMapperForKey(false) as needed and remove the "*"
entry so only the specified package names are allowed.
In `@src/main/java/cc/backend/notice/service/NoticeServiceImpl.java`:
- Around line 269-285: Compute the hashtag set from show.getHashtag (newTagsSet)
before creating/persisting the Notice in NoticeServiceImpl and only call
noticeRepository.save(Notice.builder()...) when newTagsSet is non-empty; remove
the early return after saving and ensure the Notice is not created if
newTagsSet.isEmpty(), e.g., move the Arrays.stream(...) logic above the
Notice.builder/save block and branch on newTagsSet.isEmpty() to return null
before any persistence.
- Around line 263-326: The class-level `@Transactional`(readOnly = true) prevents
write operations from flushing; add a method-level `@Transactional` (without
readOnly=true) on each write method—notifyRecommendation, notifyApproval, and
notifyLikers in NoticeServiceImpl—so their save/saveAll calls
(noticeRepository.save, memberNoticeRepository.saveAll, etc.) run in a
read-write transaction and commits occur normally.
In `@src/main/java/cc/backend/notice/service/RejectCommitEventListener.java`:
- Around line 15-19: The onRejectCommit method in RejectCommitEventListener is
missing the `@TransactionalEventListener`(phase = TransactionPhase.AFTER_COMMIT)
annotation so it isn't registered to run after a successful transaction commit;
add this annotation to the onRejectCommit(RejectCommitEvent event) method in the
RejectCommitEventListener class and import
org.springframework.transaction.event.TransactionalEventListener and
org.springframework.transaction.event.TransactionPhase to ensure the listener
executes after commit and triggers rejectShowProducer.publish(...) as other
listeners (e.g., ApproveCommitEventListener) do.
In `@src/main/java/cc/backend/notice/service/ReplyCommmitEventListener.java`:
- Line 13: The class name ReplyCommmitEventListener contains a typo (three m's);
rename the class and its file from ReplyCommmitEventListener to
ReplyCommitEventListener and update all references/usages/imports accordingly
(constructor, type references, bean registrations, tests, and any
XML/annotations that reference ReplyCommmitEventListener) so compilation and
wiring continue to work with the corrected symbol ReplyCommitEventListener.
In `@src/main/java/cc/backend/ticket/service/TempTicketServiceImpl.java`:
- Around line 95-98: Remove the premature publication of
TicketReservationCommitEvent from TempTicketServiceImpl.createTempTicket():
delete (or comment out) the eventPublisher.publishEvent(...) call that
constructs TicketReservationCommitEvent so the event is not emitted during
TempTicket creation; ensure the single publication remains in
RealTicketService.createRealTicketFromTempTicket() where the real ticket is
confirmed so ReservationCommitEventListener only receives one commit event per
reservation.
In `@src/main/resources/application.yml`:
- Around line 46-48: The auto-offset-reset property is incorrectly nested under
spring.json properties (near the spring.json.trusted.packages setting); move it
out and set it as the consumer property (e.g.,
spring.kafka.consumer.auto-offset-reset: earliest) so the consumer honors
earliest instead of defaulting to latest—locate the spring.json.trusted.packages
entry and remove auto-offset-reset from that block, adding/setting
spring.kafka.consumer.auto-offset-reset with value earliest.
♻️ Duplicate comments (1)
src/main/resources/application.yml (1)
46-47: Security risk: Restrictspring.json.trusted.packagesto specific packages.Using
"*"allows deserialization of any class, exposing the application to deserialization attacks. Restrict to your event packages.
🧹 Nitpick comments (12)
src/main/java/cc/backend/image/service/ImageService.java (1)
77-93: Confirm transaction semantics for per-item failures.
saveImagesnow swallowsGeneralExceptionand continues, but the method still runs in a single transaction. If the intent is best-effort per item even when later steps fail, consider isolating each save in its own transaction (e.g.,REQUIRES_NEWvia a separate bean orTransactionTemplate). If the intent is all-or-nothing, the exception should not be swallowed.docker-compose.yml (1)
29-83: Kafka readiness gating for app containers.
depends_ononly orders startup; it doesn’t wait for Kafka to be ready. Please verify the app has broker retry/backoff on startup, or add a Kafka healthcheck and readiness-baseddepends_onif your Compose version supports it.src/main/java/cc/backend/ticket/service/TempTicketServiceImpl.java (1)
43-43: Unused dependency:ReservationCompletedProduceris injected but never used.This field is declared but not referenced anywhere in this class. Remove it to avoid confusion, or clarify if it's intended for future use.
♻️ Proposed fix
private final RealTicketService realTicketService; private final MemberRepository memberRepository; - private final ReservationCompletedProducer reservationCompletedProducer;src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedConsumer.java (1)
10-12: Class visibility differs from other consumers.This class is package-private (no
publicmodifier), while other consumers likeReplyConsumer,CommentConsumer, andHotBoardConsumerarepublic. Consider making thispublicfor consistency, unless package-private visibility is intentional.Suggested fix
`@Component` `@RequiredArgsConstructor` -class ReservationCompletedConsumer { +public class ReservationCompletedConsumer {src/main/java/cc/backend/notice/service/CommentCommitEventListener.java (1)
3-9: Remove unused imports.
HotBoardEvent(line 5),NoticeResponseDTO(line 6), andEventListener(line 9) are imported but not used in this file.Suggested fix
package cc.backend.notice.service; import cc.backend.kafka.event.commentEvent.CommentEvent; import cc.backend.kafka.event.commentEvent.CommentProducer; -import cc.backend.kafka.event.hotBoardEvent.HotBoardEvent; -import cc.backend.notice.dto.NoticeResponseDTO; import cc.backend.notice.event.CommentCommitEvent; import lombok.RequiredArgsConstructor; -import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.transaction.event.TransactionPhase; import org.springframework.transaction.event.TransactionalEventListener;src/main/java/cc/backend/board/service/BoardService.java (1)
16-17: Remove unused imports.
CommentProducer(line 16),ReplyProducer(line 25), andApplicationEventPublisher(line 32) are imported but not used in this file.Suggested fix
-import cc.backend.kafka.event.commentEvent.CommentProducer; import cc.backend.kafka.event.hotBoardEvent.HotBoardEvent; ... import cc.backend.kafka.event.hotBoardEvent.HotBoardProducer; -import cc.backend.kafka.event.replyEvent.ReplyProducer; import cc.backend.member.entity.Member; ... -import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.*;Also applies to: 24-25, 32-32
src/main/java/cc/backend/admin/amateurShow/service/AdminApprovalService.java (1)
10-12: Remove unused imports.
RejectShowEvent(line 10) andApprovalShowEvent(line 12) are imported but not used. The service now publishesApproveCommitEventandRejectCommitEventinstead.Suggested fix
-import cc.backend.kafka.event.rejectShowEvent.RejectShowEvent; import cc.backend.member.entity.Member; -import cc.backend.kafka.event.approvalShowEvent.ApprovalShowEvent; -import cc.backend.kafka.event.approvalShowEvent.ApprovalShowProducer; import cc.backend.notice.event.ApproveCommitEvent; import cc.backend.notice.event.RejectCommitEvent;src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowConsumer.java (1)
3-14: Remove unused imports.Only
NoticeServiceis used in this consumer. The following imports are unused and should be removed:AmateurShow,AmateurShowRepository,ErrorStatus,GeneralException,Member,MemberRepository,MemberNotice,Notice,NoticeType,MemberNoticeRepository,NoticeRepository.Suggested fix
package cc.backend.kafka.event.rejectShowEvent; -import cc.backend.amateurShow.entity.AmateurShow; -import cc.backend.amateurShow.repository.AmateurShowRepository; -import cc.backend.apiPayLoad.code.status.ErrorStatus; -import cc.backend.apiPayLoad.exception.GeneralException; -import cc.backend.member.entity.Member; -import cc.backend.member.repository.MemberRepository; -import cc.backend.notice.entity.MemberNotice; -import cc.backend.notice.entity.Notice; -import cc.backend.notice.entity.enums.NoticeType; -import cc.backend.notice.repository.MemberNoticeRepository; -import cc.backend.notice.repository.NoticeRepository; import cc.backend.notice.service.NoticeService; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor;src/main/java/cc/backend/notice/service/ApproveCommitEventListener.java (1)
16-23: Misleading method name: should reflect approval handling, not comment creation.The method
handleCommentCreatehandlesApproveCommitEvent, not comment creation. For consistency with other listeners (e.g.,onRejectCommit,onCommentCommit,onReplyCommit), rename toonApproveCommit.Suggested rename
`@TransactionalEventListener` (phase = TransactionPhase.AFTER_COMMIT) - public void handleCommentCreate(ApproveCommitEvent event) { + public void onApproveCommit(ApproveCommitEvent event) { //APPROVED 수정 트랜잭션 커밋 완료 후 kafka 이벤트 발송Additionally,
RejectCommitEventListener(insrc/main/java/cc/backend/notice/service/RejectCommitEventListener.java) appears to be missing the@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)annotation on itsonRejectCommitmethod, which would prevent it from firing after commit. Please verify this is intentional or add the missing annotation.src/main/java/cc/backend/kafka/KafkaConfig.java (2)
68-70: DLQ partition routing may fail if DLQ topic has fewer partitions.The recoverer uses
record.partition()for the DLQ topic, but if{topic}-dlqhas fewer partitions than the source topic, this will fail. Consider using partition 0 or letting Kafka choose.Proposed fix
DeadLetterPublishingRecoverer recoverer = new DeadLetterPublishingRecoverer(kafkaTemplate, - (record, ex) -> new TopicPartition(record.topic() + "-dlq", record.partition())); + (record, ex) -> new TopicPartition(record.topic() + "-dlq", -1)); // -1 lets Kafka choose partitionAlternatively, ensure DLQ topics are pre-created with at least as many partitions as source topics.
32-42: Consider adding producer reliability settings.For event-driven notification flows, you may want to ensure messages are durably written. Consider adding
acks=alland enabling idempotence to prevent duplicates on retries.Proposed enhancement
Map<String, Object> props = new HashMap<>(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); + props.put(ProducerConfig.ACKS_CONFIG, "all"); + props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true); props.put(JsonSerializer.ADD_TYPE_INFO_HEADERS, true);src/main/java/cc/backend/notice/service/NoticeServiceImpl.java (1)
329-347: Consider batching lookups and unifying hashtag parsing.Current per-member/per-performer queries can get expensive, and the tag split logic differs from
notifyRecommendation, which can miss matches. A small helper to parse tags consistently plus a batch fetch for liked performers’ hashtags would reduce load.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (44)
docker-compose.ymlsrc/main/java/cc/backend/admin/amateurShow/service/AdminApprovalService.javasrc/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.javasrc/main/java/cc/backend/board/service/BoardService.javasrc/main/java/cc/backend/board/service/CommentService.javasrc/main/java/cc/backend/image/service/ImageService.javasrc/main/java/cc/backend/kafka/KafkaConfig.javasrc/main/java/cc/backend/kafka/event/approvalShowEvent/ApprovalShowEvent.javasrc/main/java/cc/backend/kafka/event/approvalShowEvent/ApprovalShowProducer.javasrc/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/ApprovalConsumer.javasrc/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/LikerConsumer.javasrc/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/RecommendConsumer.javasrc/main/java/cc/backend/kafka/event/commentEvent/CommentConsumer.javasrc/main/java/cc/backend/kafka/event/commentEvent/CommentEvent.javasrc/main/java/cc/backend/kafka/event/commentEvent/CommentProducer.javasrc/main/java/cc/backend/kafka/event/common/DomainEvent.javasrc/main/java/cc/backend/kafka/event/common/enums/DomainEventType.javasrc/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardConsumer.javasrc/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardEvent.javasrc/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardProducer.javasrc/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowConsumer.javasrc/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowEvent.javasrc/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowProducer.javasrc/main/java/cc/backend/kafka/event/replyEvent/ReplyConsumer.javasrc/main/java/cc/backend/kafka/event/replyEvent/ReplyEvent.javasrc/main/java/cc/backend/kafka/event/replyEvent/ReplyProducer.javasrc/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedConsumer.javasrc/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedEvent.javasrc/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedProducer.javasrc/main/java/cc/backend/notice/event/ApproveCommitEvent.javasrc/main/java/cc/backend/notice/event/CommentCommitEvent.javasrc/main/java/cc/backend/notice/event/RejectCommitEvent.javasrc/main/java/cc/backend/notice/event/ReplyCommitEvent.javasrc/main/java/cc/backend/notice/event/TicketReservationCommitEvent.javasrc/main/java/cc/backend/notice/service/ApproveCommitEventListener.javasrc/main/java/cc/backend/notice/service/CommentCommitEventListener.javasrc/main/java/cc/backend/notice/service/NoticeService.javasrc/main/java/cc/backend/notice/service/NoticeServiceImpl.javasrc/main/java/cc/backend/notice/service/RejectCommitEventListener.javasrc/main/java/cc/backend/notice/service/ReplyCommmitEventListener.javasrc/main/java/cc/backend/notice/service/ReservationCommitEventListener.javasrc/main/java/cc/backend/ticket/service/RealTicketService.javasrc/main/java/cc/backend/ticket/service/TempTicketServiceImpl.javasrc/main/resources/application.yml
💤 Files with no reviewable changes (1)
- src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java
🧰 Additional context used
🧬 Code graph analysis (17)
src/main/java/cc/backend/kafka/event/approvalShowEvent/ApprovalShowProducer.java (5)
src/main/java/cc/backend/kafka/event/commentEvent/CommentProducer.java (1)
Component(8-26)src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardProducer.java (1)
Component(8-24)src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowProducer.java (1)
Component(9-27)src/main/java/cc/backend/kafka/event/replyEvent/ReplyProducer.java (1)
Component(9-26)src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedProducer.java (1)
Component(9-27)
src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardProducer.java (5)
src/main/java/cc/backend/kafka/event/approvalShowEvent/ApprovalShowProducer.java (1)
Component(8-25)src/main/java/cc/backend/kafka/event/commentEvent/CommentProducer.java (1)
Component(8-26)src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardConsumer.java (1)
Component(8-24)src/main/java/cc/backend/kafka/event/replyEvent/ReplyProducer.java (1)
Component(9-26)src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedProducer.java (1)
Component(9-27)
src/main/java/cc/backend/kafka/event/commentEvent/CommentConsumer.java (5)
src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/ApprovalConsumer.java (1)
Component(9-27)src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardConsumer.java (1)
Component(8-24)src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowConsumer.java (1)
Component(20-37)src/main/java/cc/backend/kafka/event/replyEvent/ReplyConsumer.java (1)
Component(8-25)src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedConsumer.java (1)
Component(10-27)
src/main/java/cc/backend/notice/service/RejectCommitEventListener.java (4)
src/main/java/cc/backend/notice/service/ApproveCommitEventListener.java (1)
Service(11-24)src/main/java/cc/backend/notice/service/CommentCommitEventListener.java (1)
Service(14-27)src/main/java/cc/backend/notice/service/ReplyCommmitEventListener.java (1)
Service(11-29)src/main/java/cc/backend/ticket/service/TempTicketServiceImpl.java (1)
Service(31-151)
src/main/java/cc/backend/notice/service/ReservationCommitEventListener.java (4)
src/main/java/cc/backend/notice/service/ApproveCommitEventListener.java (1)
Service(11-24)src/main/java/cc/backend/notice/service/CommentCommitEventListener.java (1)
Service(14-27)src/main/java/cc/backend/notice/service/RejectCommitEventListener.java (1)
Service(10-21)src/main/java/cc/backend/notice/service/ReplyCommmitEventListener.java (1)
Service(11-29)
src/main/java/cc/backend/kafka/event/commentEvent/CommentProducer.java (4)
src/main/java/cc/backend/kafka/event/approvalShowEvent/ApprovalShowProducer.java (1)
Component(8-25)src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardProducer.java (1)
Component(8-24)src/main/java/cc/backend/kafka/event/replyEvent/ReplyProducer.java (1)
Component(9-26)src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedProducer.java (1)
Component(9-27)
src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedProducer.java (6)
src/main/java/cc/backend/kafka/event/approvalShowEvent/ApprovalShowProducer.java (1)
Component(8-25)src/main/java/cc/backend/kafka/event/commentEvent/CommentProducer.java (1)
Component(8-26)src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardProducer.java (1)
Component(8-24)src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowProducer.java (1)
Component(9-27)src/main/java/cc/backend/kafka/event/replyEvent/ReplyProducer.java (1)
Component(9-26)src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedConsumer.java (1)
Component(10-27)
src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/RecommendConsumer.java (3)
src/main/java/cc/backend/kafka/event/approvalShowEvent/ApprovalShowProducer.java (1)
Component(8-25)src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/ApprovalConsumer.java (1)
Component(9-27)src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/LikerConsumer.java (1)
Component(12-31)
src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/LikerConsumer.java (3)
src/main/java/cc/backend/kafka/event/approvalShowEvent/ApprovalShowProducer.java (1)
Component(8-25)src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/ApprovalConsumer.java (1)
Component(9-27)src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/RecommendConsumer.java (1)
Component(15-32)
src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowConsumer.java (9)
src/main/java/cc/backend/kafka/event/approvalShowEvent/ApprovalShowProducer.java (1)
Component(8-25)src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/ApprovalConsumer.java (1)
Component(9-27)src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/RecommendConsumer.java (1)
Component(15-32)src/main/java/cc/backend/kafka/event/commentEvent/CommentConsumer.java (1)
Component(22-40)src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardConsumer.java (1)
Component(8-24)src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowProducer.java (1)
Component(9-27)src/main/java/cc/backend/kafka/event/replyEvent/ReplyConsumer.java (1)
Component(8-25)src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedConsumer.java (1)
Component(10-27)src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedProducer.java (1)
Component(9-27)
src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowProducer.java (3)
src/main/java/cc/backend/kafka/event/commentEvent/CommentProducer.java (1)
Component(8-26)src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowConsumer.java (1)
Component(20-37)src/main/java/cc/backend/kafka/event/replyEvent/ReplyProducer.java (1)
Component(9-26)
src/main/java/cc/backend/notice/service/CommentCommitEventListener.java (5)
src/main/java/cc/backend/notice/service/ApproveCommitEventListener.java (1)
Service(11-24)src/main/java/cc/backend/notice/service/NoticeServiceImpl.java (1)
Service(36-425)src/main/java/cc/backend/notice/service/RejectCommitEventListener.java (1)
Service(10-21)src/main/java/cc/backend/notice/service/ReplyCommmitEventListener.java (1)
Service(11-29)src/main/java/cc/backend/notice/service/ReservationCommitEventListener.java (1)
Service(11-24)
src/main/java/cc/backend/kafka/event/replyEvent/ReplyConsumer.java (4)
src/main/java/cc/backend/kafka/event/commentEvent/CommentConsumer.java (1)
Component(22-40)src/main/java/cc/backend/kafka/event/commentEvent/CommentProducer.java (1)
Component(8-26)src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardConsumer.java (1)
Component(8-24)src/main/java/cc/backend/kafka/event/replyEvent/ReplyProducer.java (1)
Component(9-26)
src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardConsumer.java (6)
src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/ApprovalConsumer.java (1)
Component(9-27)src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/RecommendConsumer.java (1)
Component(15-32)src/main/java/cc/backend/kafka/event/commentEvent/CommentConsumer.java (1)
Component(22-40)src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardProducer.java (1)
Component(8-24)src/main/java/cc/backend/kafka/event/replyEvent/ReplyConsumer.java (1)
Component(8-25)src/main/java/cc/backend/kafka/event/replyEvent/ReplyProducer.java (1)
Component(9-26)
src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedConsumer.java (9)
src/main/java/cc/backend/kafka/event/approvalShowEvent/ApprovalShowProducer.java (1)
Component(8-25)src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/ApprovalConsumer.java (1)
Component(9-27)src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/RecommendConsumer.java (1)
Component(15-32)src/main/java/cc/backend/kafka/event/commentEvent/CommentConsumer.java (1)
Component(22-40)src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardConsumer.java (1)
Component(8-24)src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowConsumer.java (1)
Component(20-37)src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowProducer.java (1)
Component(9-27)src/main/java/cc/backend/kafka/event/replyEvent/ReplyConsumer.java (1)
Component(8-25)src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedProducer.java (1)
Component(9-27)
src/main/java/cc/backend/notice/service/NoticeServiceImpl.java (4)
src/main/java/cc/backend/notice/dto/NoticeResponseDTO.java (1)
NoticeResponseDTO(11-25)src/main/java/cc/backend/notice/service/CommentCommitEventListener.java (1)
Service(14-27)src/main/java/cc/backend/notice/service/ReplyCommmitEventListener.java (1)
Service(11-29)src/main/java/cc/backend/notice/service/ReservationCommitEventListener.java (1)
Service(11-24)
src/main/java/cc/backend/notice/service/ApproveCommitEventListener.java (4)
src/main/java/cc/backend/notice/service/CommentCommitEventListener.java (1)
Service(14-27)src/main/java/cc/backend/notice/service/RejectCommitEventListener.java (1)
Service(10-21)src/main/java/cc/backend/notice/service/ReplyCommmitEventListener.java (1)
Service(11-29)src/main/java/cc/backend/notice/service/ReservationCommitEventListener.java (1)
Service(11-24)
🔇 Additional comments (39)
src/main/java/cc/backend/notice/event/ApproveCommitEvent.java (1)
1-6: LGTM — clear commit-event payload.Simple, immutable data carrier fits the commit-event flow well.
src/main/java/cc/backend/notice/event/ReplyCommitEvent.java (1)
1-8: LGTM — concise commit-event record.The record shape is straightforward and appropriate for commit-time dispatch.
src/main/java/cc/backend/notice/event/CommentCommitEvent.java (1)
1-8: LGTM — clean commit-event definition.Looks good as a lightweight payload for post-commit processing.
src/main/java/cc/backend/kafka/event/common/enums/DomainEventType.java (1)
1-10: LGTM — clear domain event taxonomy.Enum names are consistent and descriptive.
src/main/java/cc/backend/ticket/service/RealTicketService.java (1)
73-81: LGTM! Correct use of Spring's event publishing for transactional guarantees.Publishing the
TicketReservationCommitEventviaApplicationEventPublisherwithin the@Transactionalmethod ensures the downstream@TransactionalEventListenercan safely publish to Kafka only after the transaction commits. This prevents orphaned Kafka messages if the transaction rolls back.src/main/java/cc/backend/notice/event/RejectCommitEvent.java (1)
1-7: LGTM!Clean record definition following the commit event pattern used throughout this PR.
src/main/java/cc/backend/kafka/event/approvalShowEvent/ApprovalShowEvent.java (1)
6-15: LGTM! Clean DomainEvent implementation.The record correctly implements
DomainEventwith the appropriate event type and follows the established pattern used throughout the codebase. Spring Boot 3.4.4 with Jackson provides seamless support for record deserialization in Kafka consumers; theJsonDeserializeris properly configured inKafkaConfig.javato handle record types.src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardEvent.java (1)
6-14: LGTM. The event type mapping is clear and consistent with the domain event pattern.src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/LikerConsumer.java (1)
18-29: LGTM. The listener wiring and transactional boundary are consistent with the approval event flow.src/main/java/cc/backend/kafka/event/commentEvent/CommentConsumer.java (1)
28-38: Transaction handling is already in place.
ThenotifyNewCommentmethod inNoticeServiceImplis already decorated with@Transactional, ensuring that any multiple entity writes are properly handled within a single transaction. No changes needed.src/main/java/cc/backend/kafka/event/commentEvent/CommentEvent.java (1)
7-17: LGTM!The record is well-structured and follows the pattern established by other domain events in the PR. Using a Java record here is appropriate for an immutable event payload.
Consider whether null checks should be enforced for critical fields like
boardIdandcommentIdvia a compact constructor, but this can be deferred if validation is handled upstream before event creation.src/main/java/cc/backend/kafka/event/common/DomainEvent.java (1)
7-9: LGTM!Clean interface design. Extending
Serializableis appropriate for Kafka message payloads, and thegetEventType()method provides a consistent way to identify event types across all implementations.src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedConsumer.java (1)
16-26: LGTM on the consumer logic.The Kafka listener configuration and null-check pattern are consistent with other consumers. Using
@Transactionalhere is appropriate ifnotifyTicketReservationperforms database operations.src/main/java/cc/backend/kafka/event/replyEvent/ReplyConsumer.java (1)
14-24: The implementation is correct and does not require changes. While the code lacks@Transactionalon the consumer method, the actual service methodnotifyNewReply()already has@Transactional, which provides the necessary transactional context for database operations. Spring's proxy mechanism ensures that when the consumer calls the transactional service method, the transaction is properly applied.The pattern of consumers without
@Transactionalannotations (CommentConsumer, HotBoardConsumer, ReplyConsumer) is intentional and consistent with relying on service-layer transaction management, which is a standard practice.Likely an incorrect or invalid review comment.
src/main/java/cc/backend/notice/service/CommentCommitEventListener.java (1)
14-27: LGTM!The listener correctly uses
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)to ensure Kafka events are only published after the database transaction commits successfully. This pattern is consistent with other commit event listeners in the codebase.src/main/java/cc/backend/notice/service/ReplyCommmitEventListener.java (1)
17-28: LGTM!The listener correctly publishes the
ReplyEventto Kafka after the transaction commits, following the established pattern.src/main/java/cc/backend/board/service/BoardService.java (1)
367-368: LGTM!The hot board notification correctly uses the new Kafka-based
HotBoardProducerto publish the event after promoting a board. The event includes the requiredboardIdandwriterId.src/main/java/cc/backend/admin/amateurShow/service/AdminApprovalService.java (1)
34-50: LGTM!The approve and reject flows correctly publish commit-phase events (
ApproveCommitEvent,RejectCommitEvent) viaApplicationEventPublisher. The@TransactionalEventListenerin the corresponding listeners will ensure Kafka events are only sent after the transaction commits successfully.Also applies to: 52-68
src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowConsumer.java (1)
20-37: LGTM!The consumer correctly follows the established pattern: null-check the event, then delegate to
NoticeService. The@Transactionalannotation ensures the notification persistence is atomic. This is consistent with other consumers likeReplyConsumer,HotBoardConsumer, andCommentConsumer.src/main/java/cc/backend/kafka/event/commentEvent/CommentProducer.java (1)
15-23: LGTM — consistent producer pattern.src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/ApprovalConsumer.java (1)
15-25: LGTM — straightforward consumer delegation.src/main/java/cc/backend/notice/event/TicketReservationCommitEvent.java (1)
4-8: LGTM — simple, focused commit-event payload.src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/RecommendConsumer.java (1)
21-31: LGTM — clean consumer flow.src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowEvent.java (1)
7-17: LGTM!Clean implementation of the domain event record. The structure is consistent with other event types in the codebase.
src/main/java/cc/backend/kafka/event/approvalShowEvent/ApprovalShowProducer.java (1)
8-25: LGTM!The producer follows the established pattern consistently with other producers (
RejectShowProducer,CommentProducer, etc.). Null guard, topic naming, and partition key usage are all appropriate.src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedEvent.java (1)
7-17: LGTM!The event record is well-structured and consistent with other domain events in the PR. Fields are appropriately typed and the event type is correctly mapped.
src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedProducer.java (1)
9-27: LGTM!The producer is consistent with other Kafka producers in the codebase. The
memberId-based partitioning key ensures messages for the same member are processed in order.src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowProducer.java (1)
9-27: LGTM!The producer follows the established pattern used by other producers in this PR (
ApprovalShowProducer,CommentProducer,ReplyProducer). The null check, topic constant, and partition key strategy are all consistent.src/main/java/cc/backend/board/service/CommentService.java (2)
58-61: LGTM on commit-event publishing pattern.The transition to using
ApplicationEventPublisherwithCommentCommitEventcorrectly defers Kafka publishing until after transaction commit viaCommentCommitEventListener.
77-81: LGTM on reply commit-event publishing.Consistent with the comment flow, correctly uses
ReplyCommitEventfor transaction-safe Kafka publishing.src/main/java/cc/backend/notice/service/ReservationCommitEventListener.java (1)
11-24: LGTM!The listener correctly follows the established pattern: listens for commit events after transaction commit, then publishes to Kafka. This ensures Kafka messages are only sent for successfully committed transactions.
src/main/java/cc/backend/kafka/KafkaConfig.java (1)
72-73: Remove this comment—the code is correct.
FixedBackOff(1000L, 3)means 1 initial attempt + 3 retries = 4 total attempts (not 3 total attempts as stated). ThemaxAttemptsparameter counts retries after the first delivery attempt. This aligns with the code comment's intention of "최대 3회 재시도" (maximum 3 retries).Likely an incorrect or invalid review comment.
src/main/java/cc/backend/notice/service/NoticeService.java (1)
3-21: Looks good: interface updated for Kafka event types.Signatures align with the new event payloads and notification entry points.
src/main/java/cc/backend/notice/service/NoticeServiceImpl.java (6)
13-49: Good wiring for new Kafka events and like-based targeting.Imports, repository injection, and batch size constant are clear.
53-79: LGTM for hot-board notification flow.
84-137: Comment notification flow looks solid.
144-179: Rejection notification flow looks correct.
184-227: Reply notification flow looks good.
233-260: Ticket reservation notice flow is good.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
src/main/java/cc/backend/admin/amateurShow/service/AdminApprovalService.java
Outdated
Show resolved
Hide resolved
| import cc.backend.notice.event.CommentCommitEvent; | ||
| import cc.backend.notice.event.ReplyCommitEvent; | ||
| import cc.backend.notice.event.TicketReservationCommitEvent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unused import TicketReservationCommitEvent.
This import is not used anywhere in CommentService.
Proposed fix
import cc.backend.notice.event.CommentCommitEvent;
import cc.backend.notice.event.ReplyCommitEvent;
-import cc.backend.notice.event.TicketReservationCommitEvent;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import cc.backend.notice.event.CommentCommitEvent; | |
| import cc.backend.notice.event.ReplyCommitEvent; | |
| import cc.backend.notice.event.TicketReservationCommitEvent; | |
| import cc.backend.notice.event.CommentCommitEvent; | |
| import cc.backend.notice.event.ReplyCommitEvent; |
🤖 Prompt for AI Agents
In `@src/main/java/cc/backend/board/service/CommentService.java` around lines 20 -
22, Remove the unused import TicketReservationCommitEvent from the
CommentService.java imports; locate the import block in the CommentService class
(the lines that include cc.backend.notice.event.CommentCommitEvent,
ReplyCommitEvent, TicketReservationCommitEvent) and delete the
TicketReservationCommitEvent import so only used imports remain, then recompile
or run static checks to ensure no other references exist.
| private final CommentProducer commentProducer; | ||
| private final ReplyProducer replyProducer; | ||
| private final ApplicationEventPublisher eventPublisher; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, check if the file exists and get its basic info
wc -l src/main/java/cc/backend/board/service/CommentService.javaRepository: SeeATheater/CC_Backend
Length of output: 128
🏁 Script executed:
#!/bin/bash
# Verify CommentProducer and ReplyProducer are not used in CommentService
echo "=== Checking commentProducer usage ==="
rg -n 'commentProducer\.' src/main/java/cc/backend/board/service/CommentService.java || echo "No matches found"
echo ""
echo "=== Checking replyProducer usage ==="
rg -n 'replyProducer\.' src/main/java/cc/backend/board/service/CommentService.java || echo "No matches found"Repository: SeeATheater/CC_Backend
Length of output: 176
🏁 Script executed:
#!/bin/bash
# Read the file to see field declarations and imports
echo "=== Lines 1-50 of CommentService.java ==="
sed -n '1,50p' src/main/java/cc/backend/board/service/CommentService.javaRepository: SeeATheater/CC_Backend
Length of output: 2324
🏁 Script executed:
#!/bin/bash
# Check if ReplyEvent and CommentEvent are used in the service
echo "=== Checking CommentEvent usage ==="
rg -n 'CommentEvent' src/main/java/cc/backend/board/service/CommentService.java || echo "No matches found"
echo ""
echo "=== Checking ReplyEvent usage ==="
rg -n 'ReplyEvent' src/main/java/cc/backend/board/service/CommentService.java || echo "No matches found"Repository: SeeATheater/CC_Backend
Length of output: 252
🏁 Script executed:
#!/bin/bash
# Let's see more of the file to understand how these classes are used
echo "=== Lines 40-150 of CommentService.java ==="
sed -n '40,150p' src/main/java/cc/backend/board/service/CommentService.javaRepository: SeeATheater/CC_Backend
Length of output: 4789
🏁 Script executed:
#!/bin/bash
# Check for all usages of CommentEvent and ReplyEvent (excluding import statements)
echo "=== CommentEvent usages (excluding imports) ==="
rg -n 'CommentEvent' src/main/java/cc/backend/board/service/CommentService.java | grep -v '^[0-9]*:import'
echo ""
echo "=== ReplyEvent usages (excluding imports) ==="
rg -n 'ReplyEvent' src/main/java/cc/backend/board/service/CommentService.java | grep -v '^[0-9]*:import'Repository: SeeATheater/CC_Backend
Length of output: 160
🏁 Script executed:
#!/bin/bash
# Let's check the entire file length and verify all imports
echo "=== First 30 lines to see all imports ==="
sed -n '1,30p' src/main/java/cc/backend/board/service/CommentService.javaRepository: SeeATheater/CC_Backend
Length of output: 1457
Remove unused CommentProducer, ReplyProducer, CommentEvent, and ReplyEvent dependencies.
These imports and field declarations are unused in this service. The service uses ApplicationEventPublisher to publish CommentCommitEvent and ReplyCommitEvent directly, which are then handled by listeners that consume the producers, not this service itself.
Proposed fix
-import cc.backend.kafka.event.commentEvent.CommentEvent;
-import cc.backend.kafka.event.commentEvent.CommentProducer;
-import cc.backend.kafka.event.replyEvent.ReplyEvent;
-import cc.backend.kafka.event.replyEvent.ReplyProducer;And at lines 40-42:
- private final CommentProducer commentProducer;
- private final ReplyProducer replyProducer;
private final ApplicationEventPublisher eventPublisher;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private final CommentProducer commentProducer; | |
| private final ReplyProducer replyProducer; | |
| private final ApplicationEventPublisher eventPublisher; | |
| private final ApplicationEventPublisher eventPublisher; |
🤖 Prompt for AI Agents
In `@src/main/java/cc/backend/board/service/CommentService.java` around lines 40 -
42, The CommentService class contains unused fields and imports for
CommentProducer, ReplyProducer, CommentEvent, and ReplyEvent; remove the
CommentProducer and ReplyProducer field declarations and any imports for
CommentEvent and ReplyEvent from the top of the file, leaving
ApplicationEventPublisher and the event types actually used (CommentCommitEvent,
ReplyCommitEvent) intact; ensure CommentService still publishes via
eventPublisher.publishEvent(...) and compile to verify no remaining references
to the removed symbols (CommentProducer, ReplyProducer, CommentEvent,
ReplyEvent) remain in the class or its imports.
| if (requestDTO.getKeyName() == null || requestDTO.getKeyName().isEmpty()) { | ||
| return null; // 빈 DTO 무시 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid returning null for invalid input.
Returning null from a service method changes the contract and can surface as NPEs in callers that expect a DTO. Prefer an explicit validation error (or change the signature to an Optional and handle it at the API layer) so the failure is clear and consistent.
🤖 Prompt for AI Agents
In `@src/main/java/cc/backend/image/service/ImageService.java` around lines 41 -
43, The service method in ImageService currently returns null when
requestDTO.getKeyName() is missing; instead either throw a validation exception
(e.g., IllegalArgumentException or a custom ValidationException) or change the
method to return Optional and return Optional.empty(), so callers don't receive
a raw null; locate the conditional that checks requestDTO.getKeyName() and
replace the null return with a clear failure signal (throw new
ValidationException("keyName is required") or return Optional.empty()) and
update the method signature and callers accordingly (references: ImageService,
requestDTO.getKeyName()).
src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardConsumer.java
Show resolved
Hide resolved
src/main/java/cc/backend/notice/service/RejectCommitEventListener.java
Outdated
Show resolved
Hide resolved
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class ReplyCommmitEventListener { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in class name: triple 'm' instead of double.
The class is named ReplyCommmitEventListener (three m's) but should be ReplyCommitEventListener (two m's) to maintain consistency with other event listeners like CommentCommitEventListener and ApproveCommitEventListener.
Suggested fix
Rename the class and file:
ReplyCommmitEventListener.java→ReplyCommitEventListener.javaReplyCommmitEventListener→ReplyCommitEventListener
-public class ReplyCommmitEventListener {
+public class ReplyCommitEventListener {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public class ReplyCommmitEventListener { | |
| public class ReplyCommitEventListener { |
🤖 Prompt for AI Agents
In `@src/main/java/cc/backend/notice/service/ReplyCommmitEventListener.java` at
line 13, The class name ReplyCommmitEventListener contains a typo (three m's);
rename the class and its file from ReplyCommmitEventListener to
ReplyCommitEventListener and update all references/usages/imports accordingly
(constructor, type references, bean registrations, tests, and any
XML/annotations that reference ReplyCommmitEventListener) so compilation and
wiring continue to work with the corrected symbol ReplyCommitEventListener.
src/main/java/cc/backend/ticket/service/TempTicketServiceImpl.java
Outdated
Show resolved
Hide resolved
src/main/resources/application.yml
Outdated
| properties: | ||
| spring.json.trusted.packages: "*" | ||
| auto-offset-reset: earliest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auto-offset-reset is misplaced under spring.json properties.
The auto-offset-reset property at line 48 is nested under spring.json properties, but it should be a direct consumer property. This configuration will be ignored, and the consumer will default to latest instead of earliest.
🔧 Proposed fix
consumer:
group-id: cc-backend-group
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
+ auto-offset-reset: earliest
properties:
spring.json.trusted.packages: "*"
- auto-offset-reset: earliest📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| properties: | |
| spring.json.trusted.packages: "*" | |
| auto-offset-reset: earliest | |
| consumer: | |
| group-id: cc-backend-group | |
| key-deserializer: org.apache.kafka.common.serialization.StringDeserializer | |
| value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer | |
| auto-offset-reset: earliest | |
| properties: | |
| spring.json.trusted.packages: "*" |
🤖 Prompt for AI Agents
In `@src/main/resources/application.yml` around lines 46 - 48, The
auto-offset-reset property is incorrectly nested under spring.json properties
(near the spring.json.trusted.packages setting); move it out and set it as the
consumer property (e.g., spring.kafka.consumer.auto-offset-reset: earliest) so
the consumer honors earliest instead of defaulting to latest—locate the
spring.json.trusted.packages entry and remove auto-offset-reset from that block,
adding/setting spring.kafka.consumer.auto-offset-reset with value earliest.
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In `@docker-compose.yml`:
- Around line 29-32: The docker-compose uses depends_on for kafka but no Kafka
healthcheck exists and app-blue/app-green should wait for Kafka readiness; add a
healthcheck to the kafka service (e.g., using kafka-ready check command or
curl/kcat against broker/port and appropriate interval/retries/start_period) and
then change the app-blue and app-green depends_on entries to reference kafka
with condition: service_healthy so they only start after the kafka healthcheck
passes; update both app services to gate on kafka's service_healthy and ensure
the healthcheck command and timings are appropriate for your Kafka startup.
🧹 Nitpick comments (3)
docker-compose.yml (3)
55-83: Add a Kafka healthcheck to support readiness-based startup.Without a healthcheck,
service_healthywon’t work and kafka-ui may still race Kafka. Consider adding a lightweight broker check and tying kafka-ui to readiness.🔧 Proposed healthcheck and kafka-ui gating
kafka: image: confluentinc/cp-kafka:latest container_name: kafka @@ volumes: - kafka_data:/var/lib/kafka/data # <- Docker 내부 볼륨만 사용 + healthcheck: + test: ["CMD", "bash", "-c", "kafka-broker-api-versions --bootstrap-server localhost:9092 >/dev/null 2>&1"] + interval: 10s + timeout: 5s + retries: 12 + start_period: 20s restart: unless-stopped @@ kafka-ui: @@ depends_on: - - kafka + kafka: + condition: service_healthyAlso applies to: 94-95
58-61: Avoid exposing 9092 externally if not required.If host access is already via
29092, mapping9092to the host can be unnecessary and may conflict with local Kafka installs. Consider removing it or binding to localhost only.🔒 Safer port exposure
- ports: - - "9092:9092" # 도커 내부 통신용 - - "29092:29092" # 로컬호스트 접속용 + ports: + - "29092:29092" # 로컬호스트 접속용
56-56: Pin Kafka and kafka-ui images to specific tested versions instead oflatest.Using
latesttags can introduce breaking changes without warning, particularly with KRaft configuration. Pin to stable versions:confluentinc/cp-kafka:7.7.7andprovectuslabs/kafka-ui:0.7.2. Also ensure thespring-kafkadependency inbuild.gradleis pinned to a specific version rather than relying on the latest.
| depends_on: | ||
| - redis | ||
| - kafka #추가 | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, find and display the docker-compose.yml file
find . -name "docker-compose.yml" -type f 2>/dev/null | head -20Repository: SeeATheater/CC_Backend
Length of output: 188
🏁 Script executed:
#!/bin/bash
# Let's look at the docker-compose.yml content
find . -name "docker-compose.yml" -type f -exec wc -l {} \; 2>/dev/nullRepository: SeeATheater/CC_Backend
Length of output: 123
🏁 Script executed:
#!/bin/bash
# Read the docker-compose.yml file to see full structure
find . -name "docker-compose.yml" -type f -exec cat -n {} \; 2>/dev/nullRepository: SeeATheater/CC_Backend
Length of output: 5816
Add readiness gating for Kafka and define Kafka healthcheck.
depends_on only enforces start order, not readiness. Without a Kafka healthcheck, the suggested condition: service_healthy cannot work. The Kafka service (lines 55–83) lacks a healthcheck entirely, and app-blue/app-green (lines 29–32, 50–52) depend on Kafka without gating on readiness. This risks startup failures if the app attempts connections before Kafka is ready.
Define a Kafka healthcheck first, then gate the app services on it:
✅ Suggested adjustment
Add to Kafka service:
kafka:
image: confluentinc/cp-kafka:latest
container_name: kafka
ports:
- "9092:9092"
- "29092:29092"
environment:
...
+ healthcheck:
+ test: ["CMD-SHELL", "kafka-broker-api-versions.sh --bootstrap-server=localhost:9092 || exit 1"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ start_period: 30s
volumes:
- kafka_data:/var/lib/kafka/data
restart: unless-stoppedThen update app services:
depends_on:
- - redis
- - kafka `#추가`
+ redis:
+ condition: service_started
+ kafka:
+ condition: service_healthyApply to both app-blue and app-green.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| depends_on: | |
| - redis | |
| - kafka #추가 | |
| depends_on: | |
| redis: | |
| condition: service_started | |
| kafka: | |
| condition: service_healthy |
🤖 Prompt for AI Agents
In `@docker-compose.yml` around lines 29 - 32, The docker-compose uses depends_on
for kafka but no Kafka healthcheck exists and app-blue/app-green should wait for
Kafka readiness; add a healthcheck to the kafka service (e.g., using kafka-ready
check command or curl/kcat against broker/port and appropriate
interval/retries/start_period) and then change the app-blue and app-green
depends_on entries to reference kafka with condition: service_healthy so they
only start after the kafka healthcheck passes; update both app services to gate
on kafka's service_healthy and ensure the healthcheck command and timings are
appropriate for your Kafka startup.
Summary by CodeRabbit
New Features
Bug Fixes
Chores
✏️ Tip: You can customize this high-level summary in your review settings.