Skip to content

Conversation

@sweatbuckets
Copy link
Contributor

@sweatbuckets sweatbuckets commented Jan 16, 2026

  1. #⃣ 연관된 이슈
  2. 📝 작업 내용
    • 핵심 도메인 작업보다 알림, 추천같은 파생 작업이 워크로드가 더 크므로 이런 부가 작업을 핵심 도메인 트랜잭션 밖에서 수행하도록 비동기 처리 구조 도입(오버헤드 감소)
    • 핵심 도메인 트랜잭션 생성 후 커밋 -> 커밋 이벤트 발행 -> @TransactionalEventListener로 받아서 KafkaTemplate 전송 -> 외부 저장 -> 각 Consumer가 받아와서 안정적인 비동기 처리
    • 이벤트 도메인 파일 경로 수정
    • 도커 안에 kafka 넣어서 운영 (controller zookeeper에서 KRaft 모드로 수정)
  3. 📸 스크린샷 (선택)
    • 작업 내용을 시각적으로 표현할 스크린샷을 포함하세요.
  4. 💬 리뷰 요구사항 (선택)
    • 현재 EC2로 감당 불가, 20일 이후에 머지
    • docker-compose의 _JAVA_OPTIONS 맥북 기준으로 세팅해놨는데 배포 환경 고려해서 수정 필요

Summary by CodeRabbit

  • New Features

    • Event-driven notification system using Kafka for approvals, rejections, comments, replies, hot boards and ticket reservations.
    • Docker setup includes Kafka and Zookeeper for local event streaming.
    • Personalized recommendation notices and new RECOMMEND notice type.
    • Added notice for missing notifications (NOTICE_NOT_FOUND).
  • Bug Fixes

    • Improved image upload resilience: skips invalid items and continues processing.
  • Chores

    • Added supporting infra and consumers/producers for scalable notification delivery.
    • Added repository query to fetch member hashtags.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 16, 2026

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Build & Runtime
\build.gradle`, `docker-compose.yml`, `src/main/resources/application.yml``
Added spring-kafka dependency, expanded docker-compose with Kafka services and UI, and added spring.kafka configuration (bootstrap servers, serializers, consumer group, JSON props).
Kafka Core Config
\src/main/java/.../kafka/KafkaConfig.java``
New Kafka configuration: Producer/ConsumerFactory, KafkaTemplate, listener container factory (concurrency=3), DefaultErrorHandler with DLQ and FixedBackOff.
Event framework
\src/main/java/cc/backend/kafka/event/common/`, `.../common/enums/``
Added DomainEvent interface and DomainEventType enum to standardize event payloads.
Approval / Reject pipeline
\src/main/java/.../approvalShowEvent/`, `.../rejectShowEvent/`, `src/main/java/.../notice/event/ApproveCommitEvent.java``
New ApprovalShowEvent/RejectShowEvent records, producers, consumers; commit-event records and after-commit listeners (Approve/RejectCommitEventListener) bridge DB transaction commits to Kafka.
Comment / Reply pipeline
\src/main/java/.../commentEvent/`, `src/main/java/.../replyEvent/`, `src/main/java/.../notice/event/CommentCommitEvent.java`, `ReplyCommitEvent.java``
New CommentEvent/ReplyEvent records, producers, consumers; commit-event records and listeners publish to Kafka after transaction commit.
HotBoard & Reservation pipeline
\src/main/java/.../hotBoardEvent/`, `.../reservationCompletedEvent/`, `src/main/java/.../notice/event/TicketReservationCommitEvent.java``
New HotBoardEvent / ReservationCompletedEvent records, producers, consumers, and commit-listener for reservations.
Notice service & implementation
\src/main/java/cc/backend/notice/service/NoticeService.java`, `.../NoticeServiceImpl.java``
NoticeService API updated to accept Kafka event types; NoticeServiceImpl reworked to handle Approval/Recommendation/Liker/Reject/Comment/Reply/Ticket flows, batching logic (BATCH_SIZE=50), and new MemberLikeRepository usage.
Transactional bridge listeners
\src/main/java/cc/backend/notice/service/*CommitEventListener.java``
Added multiple TransactionalEventListener classes that convert commit events to Kafka events via producers (approve/comment/reply/reject/reservation).
Service layer refactors
\src/main/java/.../board/service/BoardService.java`, `.../board/service/CommentService.java`, `.../admin/amateurShow/service/AdminApprovalService.java`, `.../ticket/service/*``
Replaced in-process ApplicationEventPublisher flows with commit-event publishing and Kafka producers; admin approval and ticket flows now emit commit events.
Repository & domain adjustments
\src/main/java/cc/backend/amateurShow/repository/AmateurShowRepository.java`, `src/main/java/cc/backend/memberLike/repository/MemberLikeRepository.java`, `src/main/java/cc/backend/notice/entity/MemberNotice.java`, `src/main/java/cc/backend/notice/entity/enums/NoticeType.java`, `src/main/java/cc/backend/apiPayLoad/code/status/ErrorStatus.java``
Added findHashtagsByMemberId and MemberLikeRepository queries; MemberNotice fields/constructor/nullable constraints/isRead-type changed; NoticeType.AD renamed to RECOMMEND; added NOTICE_NOT_FOUND error status.
Event & listener removals
\src/main/java/cc/backend/event/entity/NewShowEvent.java`, `src/main/java/cc/backend/event/service/NewShowEventListener.java`, `src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java``
Removed NewShowEvent and its listener and removed immediate in-process NewShowEvent publishing/liker-notification logic.
Image & misc cleanups
\src/main/java/cc/backend/image/service/ImageService.java`, `src/main/java/cc/backend/admin/amateurShow/service/AdminAmateurShowService.java`, `src/main/java/cc/backend/amateurShow/entity/AmateurShow.java``
Added null/empty key guard and per-item exception handling for image saves; removed unused imports and a trivial whitespace change.

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

"🐰 I hopped in with a Kafka beat,
Events in pockets, messages neat,
Producers chirp, consumers play,
Notices bloom in async array,
Hooray—notifications dance on their feet!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main objective: introducing Kafka for async event-based recommendation notification logic.
Description check ✅ Passed The description covers required sections (issue, work content) and provides detailed explanation of the async event flow and infrastructure changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 new personalMsg field. 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.

  1. Missing health check: depends_on only waits for container start, not readiness. The app may attempt to connect before Kafka is ready to accept connections, causing startup failures.

  2. 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: Use condition: service_healthy for robust startup ordering.

With the health check added to Kafka, update depends_on to 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_healthy
src/main/java/cc/backend/amateurShow/repository/AmateurShowRepository.java (1)

95-96: Add DISTINCT to 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 DISTINCT to 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 call findHashtagsByMemberId. 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

📥 Commits

Reviewing files that changed from the base of the PR and between e904105 and 08c8253.

📒 Files selected for processing (38)
  • build.gradle
  • docker-compose.yml
  • src/main/java/cc/backend/admin/amateurShow/service/AdminAmateurShowService.java
  • src/main/java/cc/backend/admin/amateurShow/service/AdminApprovalService.java
  • src/main/java/cc/backend/amateurShow/entity/AmateurShow.java
  • src/main/java/cc/backend/amateurShow/repository/AmateurShowRepository.java
  • src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java
  • src/main/java/cc/backend/apiPayLoad/code/status/ErrorStatus.java
  • src/main/java/cc/backend/board/service/BoardService.java
  • src/main/java/cc/backend/board/service/CommentService.java
  • src/main/java/cc/backend/event/entity/NewShowEvent.java
  • src/main/java/cc/backend/event/service/NewShowEventListener.java
  • src/main/java/cc/backend/memberLike/repository/MemberLikeRepository.java
  • src/main/java/cc/backend/memberLike/service/MemberLikeServiceImpl.java
  • src/main/java/cc/backend/notice/entity/MemberNotice.java
  • src/main/java/cc/backend/notice/entity/enums/NoticeType.java
  • src/main/java/cc/backend/notice/event/entity/ApproveShowEvent.java
  • src/main/java/cc/backend/notice/event/entity/CommentEvent.java
  • src/main/java/cc/backend/notice/event/entity/NewShowEvent.java
  • src/main/java/cc/backend/notice/event/entity/PromoteHotEvent.java
  • src/main/java/cc/backend/notice/event/entity/RejectShowEvent.java
  • src/main/java/cc/backend/notice/event/entity/ReplyEvent.java
  • src/main/java/cc/backend/notice/event/entity/TicketReservationEvent.java
  • src/main/java/cc/backend/notice/event/service/ApproveShowEventListener.java
  • src/main/java/cc/backend/notice/event/service/CommentEventListener.java
  • src/main/java/cc/backend/notice/event/service/PromoteHotEventListener.java
  • src/main/java/cc/backend/notice/event/service/RejectShowEventListener.java
  • src/main/java/cc/backend/notice/event/service/ReplyEventListener.java
  • src/main/java/cc/backend/notice/event/service/TicketReservationEventListener.java
  • src/main/java/cc/backend/notice/kafka/KafkaRecommendConfig.java
  • src/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationConsumer.java
  • src/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationEvent.java
  • src/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationProducer.java
  • src/main/java/cc/backend/notice/service/MemberNoticeService.java
  • src/main/java/cc/backend/notice/service/NoticeService.java
  • src/main/java/cc/backend/notice/service/NoticeServiceImpl.java
  • src/main/java/cc/backend/ticket/service/TempTicketServiceImpl.java
  • src/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.entity aligns 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! Adding spring-kafka without 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.entity aligns 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 ProcedureOutputs import 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 notice package 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.entity is 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, implement Serializable.

🔧 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 in MemberNoticeService.java is a standalone statement with no method chaining. Changing the return type from MemberNotice to void is 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–100 invokes notifyNewShow() 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.

Comment on lines 90 to 105
//좋아요한 멤버 추출
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)
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 -150

Repository: 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 -100

Repository: SeeATheater/CC_Backend

Length of output: 4923


🏁 Script executed:

fd -t f "MemberRecommendationProducer.java" -x cat -n {} | head -150

Repository: SeeATheater/CC_Backend

Length of output: 6786


🏁 Script executed:

# Also search for NewShowEvent implementation
fd -t f "NewShowEvent.java" -x cat -n {} | head -100

Repository: 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 -200

Repository: 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.

Suggested change
//좋아요한 멤버 추출
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.

Comment on lines 110 to +112
//NOTICE ERROR
MEMBERNOTICE_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBERNOTICE4001", "존재하지 않는 알림입니다."),
NOTICE_NOT_FOUND(HttpStatus.NOT_FOUND, "NOTICE4001", "존재하지 않는 알림입니다."),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 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 2

Repository: 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 -50

Repository: 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 f

Repository: SeeATheater/CC_Backend

Length of output: 953


🏁 Script executed:

# Look at the structure to find notice-related classes
fd -t d "notice" src/main/java

Repository: 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.java

Repository: 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.java

Repository: 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 2

Repository: 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.java

Repository: 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
Copy link

@coderabbitai coderabbitai bot Jan 16, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle backward compatibility for renamed enum value.

Renaming ADRECOMMEND 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

AD 쓰는 코드 없어서 backward compatibility 안챙겨도됨

Copy link

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.

Comment on lines +21 to 27
@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;
Copy link

@coderabbitai coderabbitai bot Jan 16, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 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 -20

Repository: 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 -10

Repository: 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.yml

Repository: 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 -40

Repository: 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 -10

Repository: 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.java

Repository: 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Notice와 연결 안된 MemberNotice row는 존재하지 않으므로 문제 없음

Copy link

Choose a reason for hiding this comment

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

@sweatbuckets, 확인해 주셔서 감사합니다! 데이터 무결성이 보장되어 있다면 스키마 마이그레이션이 안전하게 진행될 것입니다. 👍

Comment on lines 49 to 50
JsonDeserializer<MemberRecommendationEvent> deserializer = new JsonDeserializer<>(MemberRecommendationEvent.class);
deserializer.addTrustedPackages("*"); // 패키지 제한 없앰
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "KafkaRecommendConfig.java" | head -20

Repository: SeeATheater/CC_Backend

Length of output: 131


🏁 Script executed:

cat -n src/main/java/cc/backend/notice/kafka/KafkaRecommendConfig.java

Repository: 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 java

Repository: SeeATheater/CC_Backend

Length of output: 188


🏁 Script executed:

head -10 src/main/java/cc/backend/notice/kafka/NewShowEvent/MemberRecommendationEvent.java

Repository: 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:


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.

Suggested change
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.

Comment on lines 36 to 65
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); // 태그 없으면 추천 불가
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines 134 to 140
// 좋아요한 공연자의 기존 공연 해시태그 조회
List<String> hashtags = amateurShowRepository.findHashtagsByMemberId(likedPerformerId);

for (String existingHashtags : hashtags) {
Set<String> existingTagsSet = Arrays.stream(existingHashtags.split("#"))
.map(String::trim)
.collect(Collectors.toSet());
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +43 to +44
properties:
spring.json.trusted.packages: "*"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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: Restrict spring.json.trusted.packages to 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.

saveImages now swallows GeneralException and 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_NEW via a separate bean or TransactionTemplate). 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_on only 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-based depends_on if your Compose version supports it.

src/main/java/cc/backend/ticket/service/TempTicketServiceImpl.java (1)

43-43: Unused dependency: ReservationCompletedProducer is 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 public modifier), while other consumers like ReplyConsumer, CommentConsumer, and HotBoardConsumer are public. Consider making this public for 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), and EventListener (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), and ApplicationEventPublisher (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) and ApprovalShowEvent (line 12) are imported but not used. The service now publishes ApproveCommitEvent and RejectCommitEvent instead.

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 NoticeService is 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 handleCommentCreate handles ApproveCommitEvent, not comment creation. For consistency with other listeners (e.g., onRejectCommit, onCommentCommit, onReplyCommit), rename to onApproveCommit.

Suggested rename
     `@TransactionalEventListener` (phase = TransactionPhase.AFTER_COMMIT)
-    public void handleCommentCreate(ApproveCommitEvent event) {
+    public void onApproveCommit(ApproveCommitEvent event) {

         //APPROVED 수정 트랜잭션 커밋 완료 후 kafka 이벤트 발송

Additionally, RejectCommitEventListener (in src/main/java/cc/backend/notice/service/RejectCommitEventListener.java) appears to be missing the @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) annotation on its onRejectCommit method, 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}-dlq has 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 partition

Alternatively, 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=all and 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

📥 Commits

Reviewing files that changed from the base of the PR and between 08c8253 and ef4f42a.

📒 Files selected for processing (44)
  • docker-compose.yml
  • src/main/java/cc/backend/admin/amateurShow/service/AdminApprovalService.java
  • src/main/java/cc/backend/amateurShow/service/amateurShowService/AmateurServiceImpl.java
  • src/main/java/cc/backend/board/service/BoardService.java
  • src/main/java/cc/backend/board/service/CommentService.java
  • src/main/java/cc/backend/image/service/ImageService.java
  • src/main/java/cc/backend/kafka/KafkaConfig.java
  • src/main/java/cc/backend/kafka/event/approvalShowEvent/ApprovalShowEvent.java
  • src/main/java/cc/backend/kafka/event/approvalShowEvent/ApprovalShowProducer.java
  • src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/ApprovalConsumer.java
  • src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/LikerConsumer.java
  • src/main/java/cc/backend/kafka/event/approvalShowEvent/consumer/RecommendConsumer.java
  • src/main/java/cc/backend/kafka/event/commentEvent/CommentConsumer.java
  • src/main/java/cc/backend/kafka/event/commentEvent/CommentEvent.java
  • src/main/java/cc/backend/kafka/event/commentEvent/CommentProducer.java
  • src/main/java/cc/backend/kafka/event/common/DomainEvent.java
  • src/main/java/cc/backend/kafka/event/common/enums/DomainEventType.java
  • src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardConsumer.java
  • src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardEvent.java
  • src/main/java/cc/backend/kafka/event/hotBoardEvent/HotBoardProducer.java
  • src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowConsumer.java
  • src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowEvent.java
  • src/main/java/cc/backend/kafka/event/rejectShowEvent/RejectShowProducer.java
  • src/main/java/cc/backend/kafka/event/replyEvent/ReplyConsumer.java
  • src/main/java/cc/backend/kafka/event/replyEvent/ReplyEvent.java
  • src/main/java/cc/backend/kafka/event/replyEvent/ReplyProducer.java
  • src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedConsumer.java
  • src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedEvent.java
  • src/main/java/cc/backend/kafka/event/reservationCompletedEvent/ReservationCompletedProducer.java
  • src/main/java/cc/backend/notice/event/ApproveCommitEvent.java
  • src/main/java/cc/backend/notice/event/CommentCommitEvent.java
  • src/main/java/cc/backend/notice/event/RejectCommitEvent.java
  • src/main/java/cc/backend/notice/event/ReplyCommitEvent.java
  • src/main/java/cc/backend/notice/event/TicketReservationCommitEvent.java
  • src/main/java/cc/backend/notice/service/ApproveCommitEventListener.java
  • src/main/java/cc/backend/notice/service/CommentCommitEventListener.java
  • src/main/java/cc/backend/notice/service/NoticeService.java
  • src/main/java/cc/backend/notice/service/NoticeServiceImpl.java
  • src/main/java/cc/backend/notice/service/RejectCommitEventListener.java
  • src/main/java/cc/backend/notice/service/ReplyCommmitEventListener.java
  • src/main/java/cc/backend/notice/service/ReservationCommitEventListener.java
  • src/main/java/cc/backend/ticket/service/RealTicketService.java
  • src/main/java/cc/backend/ticket/service/TempTicketServiceImpl.java
  • src/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 TicketReservationCommitEvent via ApplicationEventPublisher within the @Transactional method ensures the downstream @TransactionalEventListener can 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 DomainEvent with 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; the JsonDeserializer is properly configured in KafkaConfig.java to 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.
The notifyNewComment method in NoticeServiceImpl is 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 boardId and commentId via 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 Serializable is appropriate for Kafka message payloads, and the getEventType() 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 @Transactional here is appropriate if notifyTicketReservation performs 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 @Transactional on the consumer method, the actual service method notifyNewReply() 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 @Transactional annotations (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 ReplyEvent to 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 HotBoardProducer to publish the event after promoting a board. The event includes the required boardId and writerId.

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) via ApplicationEventPublisher. The @TransactionalEventListener in 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 @Transactional annotation ensures the notification persistence is atomic. This is consistent with other consumers like ReplyConsumer, HotBoardConsumer, and CommentConsumer.

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 ApplicationEventPublisher with CommentCommitEvent correctly defers Kafka publishing until after transaction commit via CommentCommitEventListener.


77-81: LGTM on reply commit-event publishing.

Consistent with the comment flow, correctly uses ReplyCommitEvent for 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). The maxAttempts parameter 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.

Comment on lines +20 to +22
import cc.backend.notice.event.CommentCommitEvent;
import cc.backend.notice.event.ReplyCommitEvent;
import cc.backend.notice.event.TicketReservationCommitEvent;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Comment on lines 40 to 42
private final CommentProducer commentProducer;
private final ReplyProducer replyProducer;
private final ApplicationEventPublisher eventPublisher;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 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.java

Repository: 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.java

Repository: 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.java

Repository: 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.java

Repository: 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.

Suggested change
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.

Comment on lines +41 to +43
if (requestDTO.getKeyName() == null || requestDTO.getKeyName().isEmpty()) {
return null; // 빈 DTO 무시
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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()).


@Service
@RequiredArgsConstructor
public class ReplyCommmitEventListener {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.javaReplyCommitEventListener.java
  • ReplyCommmitEventListenerReplyCommitEventListener
-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.

Suggested change
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.

Comment on lines 46 to 48
properties:
spring.json.trusted.packages: "*"
auto-offset-reset: earliest
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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_healthy won’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_healthy

Also applies to: 94-95


58-61: Avoid exposing 9092 externally if not required.

If host access is already via 29092, mapping 9092 to 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 of latest.

Using latest tags can introduce breaking changes without warning, particularly with KRaft configuration. Pin to stable versions: confluentinc/cp-kafka:7.7.7 and provectuslabs/kafka-ui:0.7.2. Also ensure the spring-kafka dependency in build.gradle is pinned to a specific version rather than relying on the latest.

Comment on lines +29 to 32
depends_on:
- redis
- kafka #추가

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 -20

Repository: 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/null

Repository: 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/null

Repository: 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-stopped

Then update app services:

      depends_on:
-       - redis
-       - kafka  `#추가`
+       redis:
+         condition: service_started
+       kafka:
+         condition: service_healthy

Apply 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.

Suggested change
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants