배경
Phase 1 (PR #32) — vocab 도메인 자체 구현 (HTTP 동기 흐름).
PR #36 — Kafka Producer 인프라 구축 (fairytale_generate).
PR #38 — SSE 페이지 스트리밍.
Phase 2: vocab 추출 결과 통신을 동기 HTTP에서 Kafka 메시지 기반 비동기로 전환.
합의 완료 사항 (2026-05-10)
- 토픽명:
vocab_extracted
- 추출 lane:
fairytale_paragraph 컨슈밍 워커가 추출 후 vocab_extracted 발행 (AI = Producer, Spring = Consumer)
- 메시지 스키마:
{ messageId, fairytaleId, pageNo, word, meaning, userId? } (snake_case)
- 페이지당 메시지 1건 정책 (word=null/blank는 "어려운 단어 없음" 표현)
- Kafka URL:
52.78.205.133:8080
작업 범위 (Plan: .omc/plans/vocab-kafka-phase2.md)
Step 1 — Consumer-only DTO
Step 2 — Consumer 인프라
Step 3 — Listener + Service 어댑터
Step 4 — dev/prod 분리
Step 5 — 테스트
핵심 가드레일
- SSE 흐름 무중단 —
markVocabDone이 모든 종착 분기에서 호출됨 (DLT 포함)
MessageInterface 변경 금지 (Consumer-only DTO 별도)
@Profile(\"!dev\") 일관 (@ConditionalOnProperty 사용 금지)
- 페이지당 WordEntry 2개 이상 저장 금지
참고
배경
Phase 1 (PR #32) — vocab 도메인 자체 구현 (HTTP 동기 흐름).
PR #36 — Kafka Producer 인프라 구축 (
fairytale_generate).PR #38 — SSE 페이지 스트리밍.
Phase 2: vocab 추출 결과 통신을 동기 HTTP에서 Kafka 메시지 기반 비동기로 전환.
합의 완료 사항 (2026-05-10)
vocab_extractedfairytale_paragraph컨슈밍 워커가 추출 후vocab_extracted발행 (AI = Producer, Spring = Consumer){ messageId, fairytaleId, pageNo, word, meaning, userId? }(snake_case)52.78.205.133:8080작업 범위 (Plan:
.omc/plans/vocab-kafka-phase2.md)Step 1 — Consumer-only DTO
VocabExtractedMessage(MessageInterface미구현,@JsonIgnoreProperties(ignoreUnknown=true))Step 2 — Consumer 인프라
KafkaConsumerConfig(@Profile("!dev"))ErrorHandlingDeserializer래핑DefaultErrorHandler+ DLT recoverer (DLT publish 직전markVocabDone호출)Step 3 — Listener + Service 어댑터
VocabExtractedListenerVocabService.processExtractedWord(VocabExtractedMessage)추가FairytaleCheckServiceImpl.markImageDoneinline fallback (~15 LoC)Step 4 — dev/prod 분리
KafkaProducerConfig,EventService에@Profile(\"!dev\")application-dev.properties추가 (단,spring.profiles.active=dev작성 금지)Step 5 — 테스트
processExtractedWord단위 테스트 (SAVED / DUPLICATE / NO_DIFFICULT_WORD / RACE_SKIPPED)KafkaProducerTest에@EnabledIfEnvironmentVariable(named=\"KAFKA_URL\")추가build.gradleawaitility 의존성 추가핵심 가드레일
markVocabDone이 모든 종착 분기에서 호출됨 (DLT 포함)MessageInterface변경 금지 (Consumer-only DTO 별도)@Profile(\"!dev\")일관 (@ConditionalOnProperty사용 금지)참고
.omc/plans/vocab-kafka-phase2.md