# Analytics 서비스 백엔드 개발 결과서

## 1. 개요

### 1.1 서비스 정보
- **서비스명**: Analytics Service
- **포트**: 8086
- **프레임워크**: Spring Boot 3.3.0
- **언어**: Java 21
- **빌드 도구**: Gradle 8.10
- **아키텍처 패턴**: Layered Architecture

### 1.2 주요 기능
1. **이벤트 성과 대시보드**: 이벤트별 통합 성과 데이터 제공
2. **채널별 성과 분석**: 각 배포 채널별 상세 성과 분석
3. **타임라인 분석**: 시간대별 참여 추이 및 트렌드 분석
4. **ROI 상세 분석**: 투자 대비 수익률 상세 계산

### 1.3 기술 스택
- **데이터베이스**: PostgreSQL (analytics_db)
- **캐시**: Redis (database 5, TTL 1시간)
- **메시징**: Kafka (event.created, participant.registered, distribution.completed)
- **회복탄력성**: Resilience4j Circuit Breaker
- **인증**: JWT (common 모듈 공유)
- **API 문서**: Swagger/OpenAPI 3.0
- **모니터링**: Spring Boot Actuator

---

## 2. 구현 내역

### 2.1 패키지 구조
```
analytics-service/
└── src/main/java/com/kt/event/analytics/
    ├── AnalyticsServiceApplication.java      # 메인 애플리케이션
    ├── config/                               # 설정 클래스
    │   ├── KafkaConsumerConfig.java         # Kafka Consumer 설정
    │   ├── RedisConfig.java                 # Redis 캐시 설정
    │   ├── Resilience4jConfig.java          # Circuit Breaker 설정
    │   ├── SecurityConfig.java              # JWT 인증 설정
    │   └── SwaggerConfig.java               # API 문서 설정
    ├── controller/                          # 컨트롤러 계층
    │   ├── AnalyticsDashboardController.java    # 대시보드 API
    │   ├── ChannelAnalyticsController.java     # 채널 분석 API
    │   ├── RoiAnalyticsController.java         # ROI 분석 API
    │   └── TimelineAnalyticsController.java    # 타임라인 분석 API
    ├── dto/                                 # 데이터 전송 객체
    │   ├── event/                           # Kafka 이벤트 DTO
    │   │   ├── DistributionCompletedEvent.java
    │   │   ├── EventCreatedEvent.java
    │   │   └── ParticipantRegisteredEvent.java
    │   └── response/                        # API 응답 DTO
    │       ├── AnalyticsDashboardResponse.java
    │       ├── AnalyticsSummary.java
    │       ├── ChannelAnalyticsResponse.java
    │       ├── ChannelDetail.java
    │       ├── ChannelSummary.java
    │       ├── ComparisonMetrics.java
    │       ├── ConversionFunnel.java
    │       ├── CostAnalysis.java
    │       ├── InvestmentBreakdown.java
    │       ├── PeriodInfo.java
    │       ├── PeakTimeInfo.java
    │       ├── ProjectedMetrics.java
    │       ├── RevenueBreakdown.java
    │       ├── RoiAnalyticsResponse.java
    │       ├── RoiProjection.java
    │       ├── RoiSummary.java
    │       ├── SocialInteractionStats.java
    │       ├── TimelineAnalyticsResponse.java
    │       ├── TimelineDataPoint.java
    │       ├── TimeRangeStats.java
    │       ├── TopPerformer.java
    │       └── TrendAnalysis.java
    ├── entity/                              # 엔티티 계층
    │   ├── ChannelStats.java               # 채널별 통계
    │   ├── EventStats.java                 # 이벤트 통계
    │   └── TimelineData.java               # 타임라인 데이터
    ├── repository/                          # 리포지토리 계층
    │   ├── ChannelStatsRepository.java
    │   ├── EventStatsRepository.java
    │   └── TimelineDataRepository.java
    ├── service/                             # 서비스 계층
    │   ├── AnalyticsService.java           # 대시보드 서비스
    │   ├── ChannelAnalyticsService.java    # 채널 분석 서비스
    │   ├── ExternalChannelService.java     # 외부 API 연동 서비스
    │   ├── RoiAnalyticsService.java        # ROI 분석 서비스
    │   ├── ROICalculator.java              # ROI 계산 유틸리티
    │   └── TimelineAnalyticsService.java   # 타임라인 분석 서비스
    └── consumer/                            # Kafka Consumer
        ├── DistributionCompletedConsumer.java
        ├── EventCreatedConsumer.java
        └── ParticipantRegisteredConsumer.java
```

### 2.2 엔티티 설계

#### EventStats (이벤트 통계)
```java
@Entity
@Table(name = "event_stats")
public class EventStats {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String eventId;           // 이벤트 ID

    private String eventTitle;        // 이벤트 제목
    private String storeId;          // 매장 ID

    private Integer totalParticipants = 0;      // 총 참여자 수
    private BigDecimal estimatedRoi = BigDecimal.ZERO;  // 예상 ROI
    private BigDecimal totalInvestment = BigDecimal.ZERO;  // 총 투자액

    @CreatedDate private LocalDateTime createdAt;
    @LastModifiedDate private LocalDateTime updatedAt;

    // 참여자 증가 메서드
    public void incrementParticipants() {
        this.totalParticipants++;
    }
}
```

#### ChannelStats (채널별 통계)
```java
@Entity
@Table(name = "channel_stats", indexes = {
    @Index(name = "idx_event_id", columnList = "event_id"),
    @Index(name = "idx_event_channel", columnList = "event_id,channel_name")
})
public class ChannelStats {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String eventId;           // 이벤트 ID

    @Column(nullable = false)
    private String channelName;       // 채널명 (WooriTV, GenieTV, RingoBiz, SNS)

    // 성과 지표
    private Integer views = 0;        // 조회수
    private Integer clicks = 0;       // 클릭수
    private Integer participants = 0; // 참여자수
    private Integer conversions = 0;  // 전환수
    private Integer impressions = 0;  // 노출수

    // SNS 반응 지표
    private Integer likes = 0;        // 좋아요
    private Integer comments = 0;     // 댓글
    private Integer shares = 0;       // 공유

    // 비용 정보
    private BigDecimal distributionCost = BigDecimal.ZERO;  // 배포 비용

    @CreatedDate private LocalDateTime createdAt;
    @LastModifiedDate private LocalDateTime updatedAt;
}
```

#### TimelineData (타임라인 데이터)
```java
@Entity
@Table(name = "timeline_data", indexes = {
    @Index(name = "idx_event_timestamp", columnList = "event_id,timestamp")
})
public class TimelineData {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String eventId;                     // 이벤트 ID

    @Column(nullable = false)
    private LocalDateTime timestamp;            // 시간대

    private Integer participantCount = 0;       // 참여자 수
    private Integer cumulativeCount = 0;        // 누적 참여자 수

    @CreatedDate private LocalDateTime createdAt;
    @LastModifiedDate private LocalDateTime updatedAt;
}
```

### 2.3 서비스 계층

#### AnalyticsService (대시보드 서비스)
- **기능**: 이벤트 성과 대시보드 데이터 통합 제공
- **캐싱**: Redis Cache-Aside 패턴, 1시간 TTL
- **캐시 키**: `analytics:dashboard:{eventId}`
- **데이터 통합**:
  1. Analytics DB에서 이벤트/채널 통계 조회
  2. 외부 채널 API 병렬 호출 (Circuit Breaker 적용)
  3. 대시보드 데이터 구성
  4. Redis 캐싱

**주요 메서드**:
```java
public AnalyticsDashboardResponse getDashboardData(
    String eventId,
    LocalDateTime startDate,
    LocalDateTime endDate,
    boolean refresh
)
```

#### ExternalChannelService (외부 API 연동)
- **기능**: 외부 채널 API 호출로 실시간 데이터 업데이트
- **패턴**: Circuit Breaker (Resilience4j)
- **지원 채널**: WooriTV, GenieTV, RingoBiz, SNS
- **병렬 처리**: CompletableFuture로 4개 채널 동시 호출

**Circuit Breaker 설정**:
- 실패율 임계값: 50%
- 대기 시간 (Open 상태): 30초
- 슬라이딩 윈도우: 10건

#### ROICalculator (ROI 계산)
- **기능**: 상세 ROI 계산 및 분석
- **투자 분류**:
  - 콘텐츠 제작: 40%
  - 배포 비용: 50%
  - 운영 비용: 10%
- **수익 분류**:
  - 직접 매출: 70%
  - 간접 효과: 20%
  - 브랜드 가치: 10%
- **효율성 지표**:
  - CPA (Cost Per Acquisition): 참여자당 비용
  - CPV (Cost Per View): 조회당 비용
  - CPC (Cost Per Click): 클릭당 비용

### 2.4 컨트롤러 계층

#### 1. AnalyticsDashboardController
```java
@GetMapping("/{eventId}/analytics")
public ResponseEntity<ApiResponse<AnalyticsDashboardResponse>> getEventAnalytics(
    @PathVariable String eventId,
    @RequestParam(required = false) LocalDateTime startDate,
    @RequestParam(required = false) LocalDateTime endDate,
    @RequestParam(required = false, defaultValue = "false") Boolean refresh
)
```

#### 2. ChannelAnalyticsController
```java
@GetMapping("/{eventId}/analytics/channels")
public ResponseEntity<ApiResponse<ChannelAnalyticsResponse>> getChannelAnalytics(
    @PathVariable String eventId,
    @RequestParam(required = false, defaultValue = "participants") String sortBy
)
```

#### 3. TimelineAnalyticsController
```java
@GetMapping("/{eventId}/analytics/timeline")
public ResponseEntity<ApiResponse<TimelineAnalyticsResponse>> getTimelineAnalytics(
    @PathVariable String eventId,
    @RequestParam(required = false) LocalDateTime startDate,
    @RequestParam(required = false) LocalDateTime endDate,
    @RequestParam(required = false, defaultValue = "HOURLY") String granularity
)
```

#### 4. RoiAnalyticsController
```java
@GetMapping("/{eventId}/analytics/roi")
public ResponseEntity<ApiResponse<RoiAnalyticsResponse>> getRoiAnalytics(
    @PathVariable String eventId,
    @RequestParam(required = false, defaultValue = "false") Boolean includeProjection
)
```

### 2.5 Kafka Consumer

#### 1. EventCreatedConsumer
- **토픽**: `event.created`
- **기능**: 새 이벤트 생성 시 통계 테이블 초기화
- **처리 로직**:
  ```java
  @KafkaListener(topics = "event.created", groupId = "analytics-service")
  public void handleEventCreated(String message) {
      // EventStats 초기 레코드 생성
      EventStats eventStats = EventStats.builder()
          .eventId(event.getEventId())
          .eventTitle(event.getEventTitle())
          .storeId(event.getStoreId())
          .totalInvestment(event.getTotalBudget())
          .build();
      eventStatsRepository.save(eventStats);
  }
  ```

#### 2. ParticipantRegisteredConsumer
- **토픽**: `participant.registered`
- **기능**: 참여자 등록 시 실시간 통계 업데이트
- **처리 로직**:
  ```java
  @KafkaListener(topics = "participant.registered", groupId = "analytics-service")
  public void handleParticipantRegistered(String message) {
      // EventStats 참여자 수 증가
      eventStats.incrementParticipants();
      eventStatsRepository.save(eventStats);

      // TimelineData 생성/업데이트
      // 시간대별 참여자 추이 기록
  }
  ```

#### 3. DistributionCompletedConsumer
- **토픽**: `distribution.completed`
- **기능**: 배포 완료 시 채널별 비용 업데이트
- **처리 로직**:
  ```java
  @KafkaListener(topics = "distribution.completed", groupId = "analytics-service")
  public void handleDistributionCompleted(String message) {
      // ChannelStats 배포 비용 업데이트
      channelStats.setDistributionCost(event.getDistributionCost());
      channelStatsRepository.save(channelStats);
  }
  ```

### 2.6 설정 파일

#### application.yml
```yaml
spring:
  application:
    name: analytics-service

  # PostgreSQL 데이터베이스
  datasource:
    url: jdbc:postgresql://localhost:5432/analytics_db
    username: analytics_user
    password: analytics_pass
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5

  # Redis 캐시 (database 5)
  data:
    redis:
      host: localhost
      port: 6379
      database: 5
      timeout: 2000ms

  # Kafka
  kafka:
    bootstrap-servers: localhost:9092
    consumer:
      group-id: analytics-service
      auto-offset-reset: earliest

# 서버 포트
server:
  port: 8086

# Circuit Breaker
resilience4j:
  circuitbreaker:
    instances:
      wooriTV:
        failure-rate-threshold: 50
        wait-duration-in-open-state: 30s
      genieTV:
        failure-rate-threshold: 50
        wait-duration-in-open-state: 30s
      ringoBiz:
        failure-rate-threshold: 50
        wait-duration-in-open-state: 30s
      sns:
        failure-rate-threshold: 50
        wait-duration-in-open-state: 30s
```

---

## 3. API 명세

### 3.1 이벤트 성과 대시보드 조회
- **엔드포인트**: `GET /api/events/{eventId}/analytics`
- **파라미터**:
  - `startDate` (선택): 조회 시작일
  - `endDate` (선택): 조회 종료일
  - `refresh` (선택, 기본값: false): 캐시 갱신 여부
- **응답**: AnalyticsDashboardResponse
  - period: 기간 정보
  - summary: 성과 요약 (참여자, 조회수, 도달률, 참여율, 전환율)
  - channelPerformance: 채널별 성과 요약
  - roi: ROI 요약
  - lastUpdatedAt: 마지막 업데이트 시각
  - dataSource: 데이터 출처 (cached/realtime)

### 3.2 채널별 성과 분석 조회
- **엔드포인트**: `GET /api/events/{eventId}/analytics/channels`
- **파라미터**:
  - `sortBy` (선택, 기본값: participants): 정렬 기준
- **응답**: ChannelAnalyticsResponse
  - channels: 채널별 상세 성과
  - topPerformers: 상위 성과 채널 (조회수, 참여율, ROI 기준)
  - comparison: 채널 간 비교 지표

### 3.3 타임라인 분석 조회
- **엔드포인트**: `GET /api/events/{eventId}/analytics/timeline`
- **파라미터**:
  - `startDate` (선택): 조회 시작일
  - `endDate` (선택): 조회 종료일
  - `granularity` (선택, 기본값: HOURLY): 시간 단위
- **응답**: TimelineAnalyticsResponse
  - dataPoints: 시간대별 데이터 포인트
  - trends: 트렌드 분석 (성장률, 방향)
  - peakTimes: 피크 시간대 정보
  - timeRangeStats: 시간대별 통계

### 3.4 ROI 상세 분석 조회
- **엔드포인트**: `GET /api/events/{eventId}/analytics/roi`
- **파라미터**:
  - `includeProjection` (선택, 기본값: false): 예측 포함 여부
- **응답**: RoiAnalyticsResponse
  - summary: ROI 요약 (총 ROI, 투자액, 수익)
  - investment: 투자 내역 (콘텐츠, 배포, 운영)
  - revenue: 수익 내역 (직접 매출, 간접 효과, 브랜드 가치)
  - costAnalysis: 비용 효율성 분석 (CPA, CPV, CPC)
  - conversionFunnel: 전환 퍼널 (조회 → 클릭 → 참여 → 전환)
  - projection: ROI 예측 (선택)

---

## 4. 데이터베이스 스키마

### 4.1 event_stats (이벤트 통계)
```sql
CREATE TABLE event_stats (
    id BIGSERIAL PRIMARY KEY,
    event_id VARCHAR(255) NOT NULL UNIQUE,
    event_title VARCHAR(500),
    store_id VARCHAR(255),
    total_participants INT DEFAULT 0,
    estimated_roi DECIMAL(10,2) DEFAULT 0,
    total_investment DECIMAL(15,2) DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```

### 4.2 channel_stats (채널별 통계)
```sql
CREATE TABLE channel_stats (
    id BIGSERIAL PRIMARY KEY,
    event_id VARCHAR(255) NOT NULL,
    channel_name VARCHAR(50) NOT NULL,
    views INT DEFAULT 0,
    clicks INT DEFAULT 0,
    participants INT DEFAULT 0,
    conversions INT DEFAULT 0,
    impressions INT DEFAULT 0,
    likes INT DEFAULT 0,
    comments INT DEFAULT 0,
    shares INT DEFAULT 0,
    distribution_cost DECIMAL(15,2) DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_event_id ON channel_stats(event_id);
CREATE INDEX idx_event_channel ON channel_stats(event_id, channel_name);
```

### 4.3 timeline_data (타임라인 데이터)
```sql
CREATE TABLE timeline_data (
    id BIGSERIAL PRIMARY KEY,
    event_id VARCHAR(255) NOT NULL,
    timestamp TIMESTAMP NOT NULL,
    participant_count INT DEFAULT 0,
    cumulative_count INT DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_event_timestamp ON timeline_data(event_id, timestamp);
```

---

## 5. 빌드 및 테스트

### 5.1 빌드 결과
```
./gradlew analytics-service:build

BUILD SUCCESSFUL in 19s
10 actionable tasks: 6 executed, 4 up-to-date
```

### 5.2 컴파일 결과
```
./gradlew analytics-service:compileJava

BUILD SUCCESSFUL in 14s
```

### 5.3 생성된 아티팩트
- **JAR 파일**: `analytics-service/build/libs/analytics-service.jar`
- **Boot JAR 파일**: `analytics-service/build/libs/analytics-service-boot.jar`

---

## 6. 실행 방법

### 6.1 사전 준비
1. PostgreSQL 실행 (포트: 5432)
   - 데이터베이스: analytics_db
   - 사용자: analytics_user

2. Redis 실행 (포트: 6379)
   - Database: 5

3. Kafka 실행 (포트: 9092)
   - 토픽: event.created, participant.registered, distribution.completed

### 6.2 환경 변수 설정
```bash
# 데이터베이스
DB_HOST=localhost
DB_PORT=5432
DB_NAME=analytics_db
DB_USERNAME=analytics_user
DB_PASSWORD=analytics_pass

# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DATABASE=5

# Kafka
KAFKA_BOOTSTRAP_SERVERS=localhost:9092

# 서버
SERVER_PORT=8086

# JWT (common 모듈과 공유)
JWT_SECRET=your-secret-key
```

### 6.3 서비스 실행
```bash
java -jar analytics-service/build/libs/analytics-service-boot.jar
```

### 6.4 헬스 체크
```bash
curl http://localhost:8086/actuator/health
```

### 6.5 API 문서 확인
- Swagger UI: http://localhost:8086/swagger-ui.html
- OpenAPI Spec: http://localhost:8086/v3/api-docs

---

## 7. 아키텍처 특징

### 7.1 캐싱 전략
- **패턴**: Cache-Aside (Lazy Loading)
- **저장소**: Redis Database 5
- **TTL**: 3600초 (1시간)
- **캐시 키 형식**: `analytics:dashboard:{eventId}`
- **직렬화**: JSON (ObjectMapper)
- **갱신 방법**: `refresh=true` 파라미터로 강제 갱신

### 7.2 외부 API 연동
- **패턴**: Circuit Breaker (Resilience4j)
- **병렬 처리**: CompletableFuture로 4개 채널 동시 호출
- **실패 처리**: Fallback 메서드로 기본값 반환
- **재시도**: Circuit Breaker 상태에 따라 자동 재시도

### 7.3 실시간 데이터 갱신
- **메시징**: Kafka Consumer
- **이벤트 소싱**: 3개 토픽 구독
- **처리 방식**:
  1. EventCreated → 통계 초기화
  2. ParticipantRegistered → 실시간 카운트 증가
  3. DistributionCompleted → 비용 업데이트

### 7.4 성능 최적화
1. **데이터베이스 인덱스**:
   - event_stats: event_id (UNIQUE)
   - channel_stats: event_id, (event_id, channel_name)
   - timeline_data: (event_id, timestamp)

2. **캐싱**:
   - 대시보드 데이터 1시간 캐싱
   - 외부 API 호출 최소화

3. **병렬 처리**:
   - 4개 외부 채널 API 동시 호출
   - CompletableFuture.allOf()로 대기 시간 단축

4. **커넥션 풀**:
   - HikariCP (최대: 20, 최소: 5)
   - 유휴 타임아웃: 10분
   - 최대 수명: 30분

---

## 8. 보안

### 8.1 인증
- **방식**: JWT Bearer Token
- **공유**: common 모듈의 JwtAuthenticationFilter 사용
- **토큰 검증**: 모든 API 엔드포인트에 적용
- **예외**: Actuator 헬스 체크, Swagger UI

### 8.2 CORS
- **허용 Origin**: 환경 변수로 설정 (`CORS_ALLOWED_ORIGINS`)
- **기본값**: `http://localhost:*`
- **허용 메서드**: GET, POST, PUT, DELETE, OPTIONS
- **허용 헤더**: Authorization, Content-Type

---

## 9. 모니터링

### 9.1 Spring Boot Actuator
- **엔드포인트**: `/actuator`
- **노출 항목**: health, info, metrics, prometheus
- **헬스 체크**:
  - Liveness: `/actuator/health/liveness`
  - Readiness: `/actuator/health/readiness`

### 9.2 로깅
- **레벨**:
  - 애플리케이션: DEBUG
  - Spring Web: INFO
  - Hibernate SQL: DEBUG
  - Hibernate Type: TRACE
- **출력**:
  - 콘솔: `%d{yyyy-MM-dd HH:mm:ss} - %msg%n`
  - 파일: `logs/analytics-service.log`

---

## 10. 개발 표준 준수

### 10.1 패키지 구조
- Layered Architecture 패턴 적용
- Controller → Service → Repository → Entity 계층 분리
- DTO 별도 패키지로 관리

### 10.2 주석 표준
- 모든 클래스, 메서드에 한글 JavaDoc 주석
- 비즈니스 로직 핵심 부분 인라인 주석

### 10.3 코딩 컨벤션
- Lombok 활용 (Builder, Getter, Setter, NoArgsConstructor, AllArgsConstructor)
- JPA Auditing (@CreatedDate, @LastModifiedDate)
- 불변 객체 지향 (DTO는 @Builder로 생성)

---

## 11. 향후 개선 사항

### 11.1 기능 개선
1. **배치 작업**: 매일 자정 통계 집계 배치
2. **알림**: ROI 목표 달성 시 알림 발송
3. **예측 모델**: ML 기반 ROI 예측 정확도 향상
4. **A/B 테스트**: 채널별 전략 A/B 테스트 지원

### 11.2 성능 개선
1. **읽기 전용 DB**: 조회 성능 향상을 위한 Read Replica
2. **캐시 워밍**: 서비스 시작 시 자주 조회되는 데이터 사전 캐싱
3. **비동기 처리**: 무거운 집계 작업 비동기화

### 11.3 운영 개선
1. **메트릭 수집**: Prometheus + Grafana 대시보드
2. **분산 추적**: OpenTelemetry 적용
3. **로그 집중화**: ELK 스택 연동

---

## 12. 결론

Analytics 서비스는 이벤트 성과를 실시간으로 분석하고 ROI를 계산하는 핵심 서비스로, 다음과 같은 특징을 가집니다:

1. **실시간성**: Kafka를 통한 실시간 데이터 갱신
2. **성능**: Redis 캐싱 + 병렬 외부 API 호출로 응답 시간 최소화
3. **안정성**: Circuit Breaker 패턴으로 외부 API 장애 격리
4. **확장성**: Layered Architecture로 기능 확장 용이
5. **표준 준수**: 백엔드 개발 가이드 표준 완벽 적용

빌드와 컴파일이 모두 성공적으로 완료되어, 서비스 실행 준비가 완료되었습니다.
