diff --git a/.claude/commands/develop-make-run-profile.md b/.claude/commands/develop-make-run-profile.md
index 65740e5..06b2768 100644
--- a/.claude/commands/develop-make-run-profile.md
+++ b/.claude/commands/develop-make-run-profile.md
@@ -1,5 +1,5 @@
@test-backend
-'서비스실행파일작성가이드'에 따라 테스트를 해 주세요.
+'서비스실행프로파일작성가이드'에 따라 테스트를 해 주세요.
프롬프트에 '[작성정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
DB나 Redis의 접근 정보는 지정할 필요 없습니다. 특별히 없으면 '[작성정보]'섹션에 '없음'이라고 하세요.
{안내메시지}
diff --git a/analytics-service/.run/analytics-service.run.xml b/analytics-service/.run/analytics-service.run.xml
index 91ac5be..44dfb98 100644
--- a/analytics-service/.run/analytics-service.run.xml
+++ b/analytics-service/.run/analytics-service.run.xml
@@ -21,9 +21,14 @@
-
+
+
+
+
+
+
diff --git a/analytics-service/src/main/java/com/kt/event/analytics/config/SampleDataLoader.java b/analytics-service/src/main/java/com/kt/event/analytics/config/SampleDataLoader.java
index 60b3ae0..e225ea9 100644
--- a/analytics-service/src/main/java/com/kt/event/analytics/config/SampleDataLoader.java
+++ b/analytics-service/src/main/java/com/kt/event/analytics/config/SampleDataLoader.java
@@ -84,9 +84,13 @@ public class SampleDataLoader implements ApplicationRunner {
try {
// 1. EventCreated 이벤트 발행 (3개 이벤트)
publishEventCreatedEvents();
+ log.info("⏳ EventStats 생성 대기 중... (2초)");
+ Thread.sleep(2000); // EventCreatedConsumer가 EventStats 생성할 시간
// 2. DistributionCompleted 이벤트 발행 (각 이벤트당 4개 채널)
publishDistributionCompletedEvents();
+ log.info("⏳ ChannelStats 생성 대기 중... (1초)");
+ Thread.sleep(1000); // DistributionCompletedConsumer가 ChannelStats 생성할 시간
// 3. ParticipantRegistered 이벤트 발행 (각 이벤트당 다수 참여자)
publishParticipantRegisteredEvents();
@@ -100,9 +104,9 @@ public class SampleDataLoader implements ApplicationRunner {
log.info(" - ParticipantRegistered: 180건 (MVP 테스트용)");
log.info("========================================");
- // Consumer 처리 대기 (3초)
- log.info("⏳ Consumer 처리 대기 중... (3초)");
- Thread.sleep(3000);
+ // Consumer 처리 대기 (2초)
+ log.info("⏳ 참여자 수 업데이트 대기 중... (2초)");
+ Thread.sleep(2000);
// 4. TimelineData 생성 (시간대별 데이터)
createTimelineData();
diff --git a/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/DistributionCompletedConsumer.java b/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/DistributionCompletedConsumer.java
index a7c2a41..1b3d1d1 100644
--- a/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/DistributionCompletedConsumer.java
+++ b/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/DistributionCompletedConsumer.java
@@ -38,7 +38,7 @@ public class DistributionCompletedConsumer {
/**
* DistributionCompleted 이벤트 처리 (설계서 기준 - 여러 채널 배열)
*/
- @KafkaListener(topics = "sample.distribution.completed", groupId = "analytics-service")
+ @KafkaListener(topics = "sample.distribution.completed", groupId = "${spring.kafka.consumer.group-id}")
public void handleDistributionCompleted(String message) {
try {
log.info("📩 DistributionCompleted 이벤트 수신: {}", message);
diff --git a/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/EventCreatedConsumer.java b/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/EventCreatedConsumer.java
index 480125a..c7c7689 100644
--- a/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/EventCreatedConsumer.java
+++ b/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/EventCreatedConsumer.java
@@ -35,7 +35,7 @@ public class EventCreatedConsumer {
/**
* EventCreated 이벤트 처리 (MVP용 샘플 토픽)
*/
- @KafkaListener(topics = "sample.event.created", groupId = "analytics-service")
+ @KafkaListener(topics = "sample.event.created", groupId = "${spring.kafka.consumer.group-id}")
public void handleEventCreated(String message) {
try {
log.info("📩 EventCreated 이벤트 수신: {}", message);
diff --git a/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/ParticipantRegisteredConsumer.java b/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/ParticipantRegisteredConsumer.java
index 6df8e6e..ae33697 100644
--- a/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/ParticipantRegisteredConsumer.java
+++ b/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/ParticipantRegisteredConsumer.java
@@ -35,7 +35,7 @@ public class ParticipantRegisteredConsumer {
/**
* ParticipantRegistered 이벤트 처리 (MVP용 샘플 토픽)
*/
- @KafkaListener(topics = "sample.participant.registered", groupId = "analytics-service")
+ @KafkaListener(topics = "sample.participant.registered", groupId = "${spring.kafka.consumer.group-id}")
public void handleParticipantRegistered(String message) {
try {
log.info("📩 ParticipantRegistered 이벤트 수신: {}", message);
diff --git a/analytics-service/src/main/resources/application.yml b/analytics-service/src/main/resources/application.yml
index 340313c..5a217e2 100644
--- a/analytics-service/src/main/resources/application.yml
+++ b/analytics-service/src/main/resources/application.yml
@@ -41,10 +41,10 @@ spring:
max-wait: -1ms
database: ${REDIS_DATABASE:5}
- # Kafka
+ # Kafka (원격 서버 사용)
kafka:
- enabled: ${KAFKA_ENABLED:false}
- bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:4.217.131.59:9095}
+ enabled: ${KAFKA_ENABLED:true}
+ bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:20.249.182.13:9095,4.217.131.59:9095}
consumer:
group-id: ${KAFKA_CONSUMER_GROUP_ID:analytics-service}
auto-offset-reset: earliest
diff --git a/claude/make-run-profile.md b/claude/make-run-profile.md
index f363a91..2afafe5 100644
--- a/claude/make-run-profile.md
+++ b/claude/make-run-profile.md
@@ -1,4 +1,4 @@
-# 서비스실행파일작성가이드
+# 서비스실행프로파일작성가이드
[요청사항]
- <수행원칙>을 준용하여 수행
diff --git a/claude/test-backend.md b/claude/test-backend.md
new file mode 100644
index 0000000..a5f88e8
--- /dev/null
+++ b/claude/test-backend.md
@@ -0,0 +1,48 @@
+# 백엔드 테스트 가이드
+
+[요청사항]
+- <테스트원칙>을 준용하여 수행
+- <테스트순서>에 따라 수행
+- [결과파일] 안내에 따라 파일 작성
+
+[가이드]
+<테스트원칙>
+- 설정 Manifest(src/main/resources/application*.yml)의 각 항목의 값은 하드코딩하지 않고 환경변수 처리
+- Kubernetes에 배포된 데이터베이스는 LoadBalacer유형의 Service를 만들어 연결
+<테스트순서>
+- 준비:
+ - 설정 Manifest(src/main/resources/application*.yml)와 실행 프로파일({service-name}.run.xml 내부에 있음)의 일치여부 검사 및 수정
+- 실행:
+ - 'curl'명령을 이용한 테스트 및 오류 수정
+ - 서비스 의존관계를 고려하여 테스트 순서 결정
+ - 순서에 따라 순차적으로 각 서비스의 Controller에서 API 스펙 확인 후 API 테스트
+ - API경로와 DTO클래스를 확인하여 정확한 request data 구성
+ - 소스 수정 후 테스트 절차
+ - 컴파일 및 오류 수정: {프로젝트 루트}/gradlew {service-name}:compileJava
+ - 컴파일 성공 후 서비스 재시작 요청: 서비스 시작은 인간에게 요청
+ - 만약 직접 서비스를 실행하려면 '<서비스 시작 방법>'으로 수행
+ - 서비스 중지는 '<서비스 중지 방법>'을 참조 수행
+ - 설정 Manifest 수정 시 민감 정보는 기본값으로 지정하지 않고 '<실행프로파일 작성 가이드>'를 참조하여 실행 프로파일에 값을 지정함
+ - 실행 결과 로그는 'logs' 디렉토리 하위에 생성
+ - 결과: test-backend.md
+<실행프로파일 작성 가이드>
+- {service-name}/.run/{service-name}.run.xml 파일로 작성
+- Kubernetes에 배포된 데이터베이스의 LoadBalancer Service 확인:
+ - kubectl get svc -n {namespace} | grep LoadBalancer 명령으로 LoadBalancer IP 확인
+ - 각 서비스별 데이터베이스의 LoadBalancer External IP를 DB_HOST로 사용
+ - 캐시(Redis)의 LoadBalancer External IP를 REDIS_HOST로 사용
+<서비스 시작 방법>
+- 'IntelliJ서비스실행기'를 'tools' 디렉토리에 다운로드
+- python 또는 python3 명령으로 백그라우드로 실행하고 결과 로그를 분석
+ nohup python3 tools/run-intellij-service-profile.py {service-name} > logs/{service-name}.log 2>&1 & echo "Started {service-name} with PID: $!"
+- 서비스 실행은 다른 방법 사용하지 말고 **반드시 python 프로그램 이용**
+<서비스 중지 방법>
+- Window
+ - netstat -ano | findstr :{PORT}
+ - powershell "Stop-Process -Id {Process number} -Force"
+- Linux/Mac
+ - netstat -ano | grep {PORT}
+ - kill -9 {Process number}
+
+[결과파일]
+- develop/dev/test-backend.md
\ No newline at end of file
diff --git a/develop/dev/test-backend.md b/develop/dev/test-backend.md
new file mode 100644
index 0000000..34497c7
--- /dev/null
+++ b/develop/dev/test-backend.md
@@ -0,0 +1,305 @@
+# 백엔드 테스트 결과서
+
+## 테스트 개요
+- **테스트 일시**: 2025-10-27 13:46
+- **대상 서비스**: analytics-service
+- **서버 포트**: 8086
+- **테스트 환경**: 로컬 개발 환경
+
+## 1. 서버 상태 확인
+
+### 1.1 Health Check
+**요청**:
+```bash
+curl -X GET "http://localhost:8086/actuator/health"
+```
+
+**응답**:
+```json
+{
+ "status": "UP",
+ "components": {
+ "db": {
+ "status": "UP",
+ "details": {
+ "database": "PostgreSQL",
+ "validationQuery": "isValid()"
+ }
+ },
+ "redis": {
+ "status": "UP",
+ "details": {
+ "version": "7.2.3"
+ }
+ },
+ "diskSpace": {"status": "UP"},
+ "livenessState": {"status": "UP"},
+ "readinessState": {"status": "UP"}
+ }
+}
+```
+
+**결과**: ✅ **성공** - 서버 정상 작동, DB 및 Redis 연결 정상
+
+---
+
+## 2. API 테스트 결과
+
+### 2.1 성과 대시보드 조회 API
+**엔드포인트**: `GET /api/v1/events/{eventId}/analytics`
+
+**테스트 케이스**:
+```bash
+curl -X GET "http://localhost:8086/api/v1/events/evt_2025012301/analytics"
+```
+
+**응답**:
+```json
+{
+ "success": false,
+ "errorCode": "EVENT_001",
+ "message": "이벤트를 찾을 수 없습니다",
+ "timestamp": "2025-10-27T13:46:50.7331807"
+}
+```
+
+**결과**: ❌ **실패** - EventStats 데이터 미생성
+- **원인**: Kafka Consumer 미작동으로 EventCreated 이벤트 미처리
+- **근본 원인**: Kafka 브로커 연결 실패
+
+---
+
+### 2.2 시간대별 참여 추이 API
+**엔드포인트**: `GET /api/v1/events/{eventId}/analytics/timeline`
+
+**테스트 케이스**:
+```bash
+curl -X GET "http://localhost:8086/api/v1/events/evt_2025012301/analytics/timeline?interval=daily"
+```
+
+**응답**:
+```json
+{
+ "success": true,
+ "data": {
+ "eventId": "evt_2025012301",
+ "interval": "daily",
+ "dataPoints": [
+ {
+ "timestamp": "2024-09-24T00:00:00",
+ "participants": 36,
+ "views": 108,
+ "engagement": 36,
+ "conversions": 24,
+ "cumulativeParticipants": 36
+ }
+ // ... 150개 데이터 포인트
+ ],
+ "trends": {
+ "overallTrend": "stable",
+ "growthRate": 11.1,
+ "projectedParticipants": 944,
+ "peakPeriod": "2024-09-24"
+ },
+ "peakTimes": [
+ {
+ "timestamp": "2024-09-24T00:00:00",
+ "metric": "participants",
+ "value": 40,
+ "description": "최대 참여자 수"
+ },
+ {
+ "timestamp": "2024-09-27T00:00:00",
+ "metric": "views",
+ "value": 200,
+ "description": "최대 조회수"
+ }
+ ]
+ }
+}
+```
+
+**결과**: ✅ **성공** - TimelineData 정상 조회
+- **데이터 포인트**: 150개 (30일 × 5개 채널)
+- **기간**: 2024-09-24 ~ 2024-10-23
+- **트렌드 분석**: 정상 작동
+- **Peak Time 분석**: 정상 작동
+
+---
+
+### 2.3 채널별 성과 분석 API
+**엔드포인트**: `GET /api/v1/events/{eventId}/analytics/channels`
+
+**테스트 케이스**:
+```bash
+curl -X GET "http://localhost:8086/api/v1/events/evt_2025012301/analytics/channels"
+```
+
+**응답**:
+```json
+{
+ "success": true,
+ "data": {
+ "eventId": "evt_2025012301",
+ "channels": [],
+ "comparison": null,
+ "lastUpdatedAt": "2025-10-27T13:46:55.9759532"
+ }
+}
+```
+
+**결과**: ⚠️ **부분 성공** - 응답은 정상이나 데이터 비어있음
+- **원인**: ChannelStats 데이터 미생성
+- **근본 원인**: Kafka Consumer 미작동으로 DistributionCompleted 이벤트 미처리
+
+---
+
+### 2.4 투자 대비 수익률 분석 API
+**엔드포인트**: `GET /api/v1/events/{eventId}/analytics/roi`
+
+**테스트 케이스**:
+```bash
+curl -X GET "http://localhost:8086/api/v1/events/evt_2025012301/analytics/roi"
+```
+
+**응답**:
+```json
+{
+ "success": false,
+ "errorCode": "EVENT_001",
+ "message": "이벤트를 찾을 수 없습니다",
+ "timestamp": "2025-10-27T13:46:58.6552438"
+}
+```
+
+**결과**: ❌ **실패** - EventStats 데이터 미생성
+- **원인**: Kafka Consumer 미작동으로 EventCreated 이벤트 미처리
+
+---
+
+## 3. 문제 분석
+
+### 3.1 Kafka 연결 실패
+**로그 확인**:
+```
+2025-10-27 13:46:46 [kafka-producer-network-thread] INFO o.apache.kafka.clients.NetworkClient -
+ [Producer clientId=analytics-service-producer-1] Node 101 disconnected.
+2025-10-27 13:46:56 [kafka-producer-network-thread] INFO o.apache.kafka.clients.NetworkClient -
+ [Producer clientId=analytics-service-producer-1] Node 100 disconnected.
+```
+
+**문제점**:
+1. Kafka 브로커(20.249.182.13:9095, 4.217.131.59:9095)에 연결 실패
+2. SampleDataLoader가 이벤트를 발행했지만 브로커에 도달하지 못함
+3. Kafka Consumer가 이벤트를 수신하지 못함
+
+### 3.2 Consumer 설정 확인
+**파일**: `EventCreatedConsumer.java:23`
+```java
+@ConditionalOnProperty(name = "spring.kafka.enabled", havingValue = "true", matchIfMissing = false)
+```
+
+**설정**:
+- `application.yml`: `spring.kafka.enabled: ${KAFKA_ENABLED:false}`
+- 실행 프로파일: `KAFKA_ENABLED=true`
+
+**Consumer 토픽**:
+- `sample.event.created` - EventCreatedConsumer
+- `sample.distribution.completed` - DistributionCompletedConsumer
+- `sample.participant.registered` - ParticipantRegisteredConsumer
+
+### 3.3 데이터 생성 흐름
+
+**정상 흐름**:
+```
+SampleDataLoader (시작 시)
+ ↓ Kafka 이벤트 발행
+ ├─ EventCreated (3개) → EventCreatedConsumer → EventStats 생성
+ ├─ DistributionCompleted (3개) → DistributionCompletedConsumer → ChannelStats 생성
+ ├─ ParticipantRegistered (180개) → ParticipantRegisteredConsumer → ChannelStats 업데이트
+ └─ TimelineData 직접 생성 (90개)
+```
+
+**실제 흐름**:
+```
+SampleDataLoader (시작 시)
+ ↓ Kafka 이벤트 발행 시도
+ ├─ EventCreated → Kafka 연결 실패 → EventStats 미생성 ❌
+ ├─ DistributionCompleted → Kafka 연결 실패 → ChannelStats 미생성 ❌
+ ├─ ParticipantRegistered → Kafka 연결 실패 → 처리 안됨 ❌
+ └─ TimelineData 직접 생성 (90개) ✅
+```
+
+---
+
+## 4. 테스트 결과 요약
+
+| API | 엔드포인트 | 상태 | 비고 |
+|-----|----------|------|------|
+| Health Check | `/actuator/health` | ✅ 성공 | DB, Redis 연결 정상 |
+| 성과 대시보드 | `/api/v1/events/{eventId}/analytics` | ❌ 실패 | EventStats 미생성 |
+| 시간대별 추이 | `/api/v1/events/{eventId}/analytics/timeline` | ✅ 성공 | 150개 데이터 정상 조회 |
+| 채널별 분석 | `/api/v1/events/{eventId}/analytics/channels` | ⚠️ 부분 | 빈 배열 반환 |
+| ROI 분석 | `/api/v1/events/{eventId}/analytics/roi` | ❌ 실패 | EventStats 미생성 |
+
+**성공률**: 1/4 (25%)
+- **완전 성공**: Timeline API
+- **부분 성공**: Channel API (응답 정상, 데이터 없음)
+- **실패**: Dashboard API, ROI API
+
+---
+
+## 5. 개선 사항
+
+### 5.1 즉시 조치 필요
+1. **Kafka 브로커 연결 확인**
+ - 브로커 상태 확인: `20.249.182.13:9095`, `4.217.131.59:9095`
+ - 네트워크 방화벽 규칙 확인
+ - Kubernetes Service 확인: `kubectl get svc -n kafka`
+
+2. **Kafka Consumer autoStartup 확인**
+ ```java
+ @KafkaListener(
+ topics = "sample.event.created",
+ groupId = "analytics-service",
+ autoStartup = "true" // 추가 확인
+ )
+ ```
+
+### 5.2 대안 방안
+**Kafka 없이 테스트하는 방법**:
+1. SampleDataLoader 수정하여 Kafka 없이 직접 Repository에 데이터 생성
+2. 또는 `KAFKA_ENABLED=false` 설정하고 REST API로 데이터 직접 등록
+
+### 5.3 장기 개선
+1. **Resilience 향상**
+ - Kafka 연결 실패 시 Retry 메커니즘 추가
+ - Circuit Breaker 패턴 적용 (Resilience4j 이미 설정됨)
+
+2. **모니터링 강화**
+ - Kafka Consumer Lag 모니터링
+ - 이벤트 처리 실패 알림
+
+3. **테스트 환경 구성**
+ - 로컬 테스트용 Embedded Kafka 설정
+ - Docker Compose로 Kafka 로컬 환경 구성
+
+---
+
+## 6. 결론
+
+### 6.1 현재 상태
+- **기본 기능**: 정상 작동 (서버, DB, Redis 연결)
+- **API 응답**: 구조적으로 정상 (에러 처리 적절)
+- **Timeline API**: 완전 정상 작동
+- **Kafka 의존 API**: Kafka 연결 문제로 데이터 부재
+
+### 6.2 권고 사항
+1. **단기**: Kafka 브로커 연결 문제 해결 후 재테스트
+2. **중기**: Kafka 없이도 테스트 가능한 대안 구현
+3. **장기**: 이벤트 기반 아키텍처 안정성 개선
+
+### 6.3 다음 단계
+1. Kafka 브로커 상태 확인 및 연결 복구
+2. Consumer 활성화 확인 및 이벤트 재처리
+3. 전체 API 재테스트 및 결과 검증