Step 9: 고급 이벤트 처리 시스템 구현

주요 기능:
- Kafka 이벤트 컨슈머 및 프로듀서 통합
- 지수 백오프 재시도 메커니즘 구현
- Dead Letter Queue (DLQ) 설정
- 이벤트 스키마 레지스트리 (Pydantic v2 호환)
- Console 서비스에 이벤트 관리 API 추가
- 실시간 이벤트 통계 및 모니터링
- 엔드-투-엔드 테스트 스크립트

구현된 이벤트 타입:
- USER_CREATED, USER_UPDATED, USER_DELETED
- OAUTH_APP_CREATED, OAUTH_TOKEN_ISSUED
- IMAGE_UPLOADED, IMAGE_PROCESSED

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
jungwoo choi
2025-09-11 14:51:08 +09:00
parent a38fcc204c
commit 1ca9ca1b5d
12 changed files with 1863 additions and 3 deletions

View File

@ -5,7 +5,10 @@ import uvicorn
from datetime import datetime, timedelta
import httpx
import os
import asyncio
import logging
from typing import Any
from contextlib import asynccontextmanager
from auth import (
Token, UserLogin, UserInDB,
verify_password, get_password_hash,
@ -13,10 +16,51 @@ from auth import (
ACCESS_TOKEN_EXPIRE_MINUTES
)
# Import event consumer
from event_consumer import AdvancedEventConsumer
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Global event consumer instance
event_consumer = None
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
global event_consumer
try:
# Initialize and start event consumer
event_consumer = AdvancedEventConsumer(
topics=["user-events", "oauth-events"],
group_id="console-consumer-group",
redis_url=os.getenv("REDIS_URL", "redis://redis:6379"),
bootstrap_servers=os.getenv("KAFKA_BOOTSTRAP_SERVERS", "kafka:9092"),
enable_dlq=True,
dlq_topic="dead-letter-queue"
)
await event_consumer.start()
logger.info("Event consumer started successfully")
except Exception as e:
logger.error(f"Failed to start event consumer: {e}")
# Continue without event consumer (degraded mode)
event_consumer = None
yield
# Shutdown
if event_consumer:
await event_consumer.stop()
logger.info("Event consumer stopped")
app = FastAPI(
title="Console API Gateway",
description="Central orchestrator for microservices",
version="0.1.0"
version="0.1.0",
lifespan=lifespan
)
# Service URLs from environment
@ -45,6 +89,66 @@ async def health_check():
return {
"status": "healthy",
"service": "console",
"timestamp": datetime.now().isoformat(),
"event_consumer": "running" if event_consumer else "not running"
}
# Event Management Endpoints
@app.get("/api/events/stats")
async def get_event_stats(current_user = Depends(get_current_user)):
"""Get event consumer statistics"""
if not event_consumer:
raise HTTPException(status_code=503, detail="Event consumer not available")
return {
"stats": event_consumer.stats,
"timestamp": datetime.now().isoformat()
}
@app.get("/api/events/dlq")
async def get_dlq_messages(
limit: int = 10,
current_user = Depends(get_current_user)
):
"""Get messages from Dead Letter Queue"""
if not event_consumer:
raise HTTPException(status_code=503, detail="Event consumer not available")
messages = await event_consumer.get_dlq_messages(limit=limit)
return {
"messages": messages,
"count": len(messages),
"timestamp": datetime.now().isoformat()
}
@app.post("/api/events/dlq/{event_id}/retry")
async def retry_dlq_message(
event_id: str,
current_user = Depends(get_current_user)
):
"""Manually retry a message from DLQ"""
if not event_consumer:
raise HTTPException(status_code=503, detail="Event consumer not available")
success = await event_consumer.retry_dlq_message(event_id)
if not success:
raise HTTPException(status_code=404, detail="Event not found in DLQ")
return {
"status": "retry_initiated",
"event_id": event_id,
"timestamp": datetime.now().isoformat()
}
@app.get("/api/events/schemas")
async def get_event_schemas():
"""Get all event schemas documentation"""
from shared.kafka.schema_registry import SchemaRegistry
schemas = SchemaRegistry.get_all_schemas()
return {
"schemas": schemas,
"version": "1.0.0",
"timestamp": datetime.now().isoformat()
}