Files
site11/services/users/backend/main.py
jungwoo choi bf05e173cc Step 7: Kafka 이벤트 시스템 구현
- Kafka 및 Zookeeper 컨테이너 추가
- 공유 Kafka 라이브러리 생성 (Producer/Consumer)
- 이벤트 타입 정의 및 이벤트 모델 구현
- Users 서비스에 이벤트 발행 기능 추가 (USER_CREATED, USER_UPDATED, USER_DELETED)
- PROGRESS.md 및 PLAN.md 문서 생성
- aiokafka 통합 완료
2025-09-10 17:00:57 +09:00

251 lines
6.9 KiB
Python

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime
import uvicorn
import os
import sys
import logging
from contextlib import asynccontextmanager
from database import init_db
from models import User
from beanie import PydanticObjectId
sys.path.append('/app')
from shared.kafka import KafkaProducer, Event, EventType
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Pydantic models for requests
class UserCreate(BaseModel):
username: str
email: str
full_name: Optional[str] = None
class UserUpdate(BaseModel):
username: Optional[str] = None
email: Optional[str] = None
full_name: Optional[str] = None
class UserResponse(BaseModel):
id: str
username: str
email: str
full_name: Optional[str] = None
created_at: datetime
updated_at: datetime
# Global Kafka producer
kafka_producer: Optional[KafkaProducer] = None
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
global kafka_producer
await init_db()
# Initialize Kafka producer
try:
kafka_producer = KafkaProducer(
bootstrap_servers=os.getenv('KAFKA_BOOTSTRAP_SERVERS', 'kafka:9092')
)
await kafka_producer.start()
logger.info("Kafka producer initialized")
except Exception as e:
logger.warning(f"Failed to initialize Kafka producer: {e}")
kafka_producer = None
yield
# Shutdown
if kafka_producer:
await kafka_producer.stop()
app = FastAPI(
title="Users Service",
description="User management microservice with MongoDB",
version="0.2.0",
lifespan=lifespan
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Health check
@app.get("/health")
async def health_check():
return {
"status": "healthy",
"service": "users",
"timestamp": datetime.now().isoformat()
}
# CRUD Operations
@app.get("/users", response_model=List[UserResponse])
async def get_users():
users = await User.find_all().to_list()
return [UserResponse(
id=str(user.id),
username=user.username,
email=user.email,
full_name=user.full_name,
created_at=user.created_at,
updated_at=user.updated_at
) for user in users]
@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: str):
try:
user = await User.get(PydanticObjectId(user_id))
if not user:
raise HTTPException(status_code=404, detail="User not found")
return UserResponse(
id=str(user.id),
username=user.username,
email=user.email,
full_name=user.full_name,
created_at=user.created_at,
updated_at=user.updated_at
)
except Exception:
raise HTTPException(status_code=404, detail="User not found")
@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(user_data: UserCreate):
# Check if username already exists
existing_user = await User.find_one(User.username == user_data.username)
if existing_user:
raise HTTPException(status_code=400, detail="Username already exists")
# Create new user
user = User(
username=user_data.username,
email=user_data.email,
full_name=user_data.full_name
)
await user.create()
# Publish event
if kafka_producer:
event = Event(
event_type=EventType.USER_CREATED,
service="users",
data={
"user_id": str(user.id),
"username": user.username,
"email": user.email
},
user_id=str(user.id)
)
await kafka_producer.send_event("user-events", event)
return UserResponse(
id=str(user.id),
username=user.username,
email=user.email,
full_name=user.full_name,
created_at=user.created_at,
updated_at=user.updated_at
)
@app.put("/users/{user_id}", response_model=UserResponse)
async def update_user(user_id: str, user_update: UserUpdate):
try:
user = await User.get(PydanticObjectId(user_id))
if not user:
raise HTTPException(status_code=404, detail="User not found")
except Exception:
raise HTTPException(status_code=404, detail="User not found")
if user_update.username is not None:
# Check if new username already exists
existing_user = await User.find_one(
User.username == user_update.username,
User.id != user.id
)
if existing_user:
raise HTTPException(status_code=400, detail="Username already exists")
user.username = user_update.username
if user_update.email is not None:
user.email = user_update.email
if user_update.full_name is not None:
user.full_name = user_update.full_name
user.updated_at = datetime.now()
await user.save()
# Publish event
if kafka_producer:
event = Event(
event_type=EventType.USER_UPDATED,
service="users",
data={
"user_id": str(user.id),
"username": user.username,
"email": user.email,
"updated_fields": list(user_update.dict(exclude_unset=True).keys())
},
user_id=str(user.id)
)
await kafka_producer.send_event("user-events", event)
return UserResponse(
id=str(user.id),
username=user.username,
email=user.email,
full_name=user.full_name,
created_at=user.created_at,
updated_at=user.updated_at
)
@app.delete("/users/{user_id}")
async def delete_user(user_id: str):
try:
user = await User.get(PydanticObjectId(user_id))
if not user:
raise HTTPException(status_code=404, detail="User not found")
user_id_str = str(user.id)
username = user.username
await user.delete()
# Publish event
if kafka_producer:
event = Event(
event_type=EventType.USER_DELETED,
service="users",
data={
"user_id": user_id_str,
"username": username
},
user_id=user_id_str
)
await kafka_producer.send_event("user-events", event)
return {"message": "User deleted successfully"}
except Exception:
raise HTTPException(status_code=404, detail="User not found")
if __name__ == "__main__":
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
reload=True
)