O2Sound_ver2_final/backend/docs/Developer_Guide.md

9.8 KiB

O2Sound Backend 개발자 가이드

프로젝트 구조

backend/
├── app/
│   ├── core/               # 핵심 설정 및 유틸리티
│   │   ├── celery_app.py   # Celery 설정
│   │   ├── database.py     # 데이터베이스 연결
│   │   ├── env_setting.py  # 환경 변수 설정
│   │   └── redis/          # Redis 관련 설정
│   ├── domain/             # 도메인 모델
│   │   └── models/         # SQLAlchemy 모델
│   ├── infra/              # 인프라 레이어
│   │   └── google/         # Google OAuth 구현
│   ├── presentation/       # 프레젠테이션 레이어
│   │   ├── api/           # API 라우터
│   │   │   └── v1/        # v1 API 엔드포인트
│   │   └── schemas/       # Pydantic 스키마
│   ├── services/          # 비즈니스 로직
│   ├── shared/            # 공통 유틸리티
│   │   ├── decorator/     # 데코레이터
│   │   ├── logger.py      # 로깅 설정
│   │   └── progress.py    # 진행률 추적
│   └── workers/           # Celery 작업
│       └── tasks.py       # 비동기 작업 정의
├── docs/                  # API 문서
├── uploads/               # 업로드된 파일
├── main.py               # FastAPI 앱 엔트리포인트
├── dependencies.py       # 의존성 주입
├── pyproject.toml        # Poetry 설정
└── Dockerfile           # Docker 설정

핵심 개념

1. DDD (Domain-Driven Design) 구조

프로젝트는 DDD 원칙을 따라 구성되어 있습니다:

  • Domain Layer: 비즈니스 엔티티와 도메인 로직
  • Application Layer: 서비스와 비즈니스 유스케이스
  • Infrastructure Layer: 외부 시스템과의 통합
  • Presentation Layer: API 엔드포인트와 스키마

2. 의존성 주입

FastAPI의 의존성 주입 시스템을 활용:

from app.dependencies import get_user_service

@router.post("/items")
async def get_items(
    request: GetItemsRequest,
    user_service: UserService = Depends(get_user_service)
):
    return user_service.get_items(request)

3. Response Wrapper

모든 API 응답은 일관된 형식으로 래핑됩니다:

from app.shared.decorator.response_wrapper import response_wrapper

@router.post("/login")
@response_wrapper
async def login(request: LoginRequest):
    # 자동으로 성공/실패 형식으로 래핑됨
    return auth_service.login(request)

주요 기능 구현

1. 인증 시스템

세션 기반 인증

# main.py
app.add_middleware(SessionMiddleware, secret_key=settings.SESSION_SECRET_KEY)

OAuth 2.0 (Google)

# infra/google/service.py
class GoogleService:
    async def get_login_url(self, return_url: Optional[str], request: Request):
        # OAuth 플로우 시작
        
    async def handle_callback(self, request: Request):
        # 콜백 처리 및 토큰 교환

2. 비동기 작업 처리 (Celery)

작업 정의

# workers/tasks.py
@celery_app.task(bind=True)
def task1_crawl(self, url: str, root_task_id: str):
    # 웹 크롤링 작업
    process = Process(redis_manager)
    process.update_progress(root_task_id, "metadata", True)

워크플로우 체인

# presentation/api/v1/moviemakers.py
workflow = chain(
    crawl,
    chord(
        [lyrics_to_music, image_to_video],
        task5_merge_results.s(root_task_id)
    )
)

3. 진행률 추적

# shared/progress.py
class Process:
    def __init__(self, redis_manager: RedisManager):
        self.redis = redis_manager
    
    async def init_task_status(self, task_id: str):
        # 초기 상태 설정
    
    async def update_progress(self, task_id: str, step: str, completed: bool):
        # 진행률 업데이트

개발 환경 설정

1. 초기 설정

# Poetry 설치
pip install poetry

# 의존성 설치
poetry install

# 환경 변수 설정
cp .env.local .env

2. 개발 서버 실행

# Redis 실행 (Docker)
docker run -d -p 6379:6379 redis

# PostgreSQL 실행 (Docker)
docker run -d -p 5432:5432 \
  -e POSTGRES_USER=o2sound \
  -e POSTGRES_PASSWORD=password \
  -e POSTGRES_DB=o2sound \
  postgres:14

# Celery Worker 실행
poetry run celery -A app.core.celery_app worker --loglevel=info

# FastAPI 개발 서버
poetry run uvicorn main:app --reload --port 8000

3. 환경 변수 설정 (.env)

# 기본 설정
PROJECT_NAME=O2Sound
API_V1_STR=/api/v1
DEBUG=True

# 데이터베이스
DATABASE_URL=postgresql://o2sound:password@localhost:5432/o2sound

# Redis
REDIS_URL=redis://localhost:6379

# 세션
SESSION_SECRET_KEY=your-secret-key-here

# Google OAuth
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
GOOGLE_REDIRECT_URI=http://localhost:8000/social/google/callback

# Celery
CELERY_BROKER_URL=redis://localhost:6379/0
CELERY_RESULT_BACKEND=redis://localhost:6379/0

새로운 기능 추가하기

1. 새 엔드포인트 추가

Step 1: 스키마 정의

# presentation/schemas/new_feature_schema.py
from pydantic import BaseModel
from uuid import UUID

class NewFeatureRequest(BaseModel):
    user_id: UUID
    feature_data: str

class NewFeatureResponse(BaseModel):
    success: bool
    result: str

Step 2: 서비스 구현

# services/new_feature_service.py
class NewFeatureService:
    def __init__(self, db_session):
        self.db = db_session
    
    def process_feature(self, request: NewFeatureRequest):
        # 비즈니스 로직 구현
        return NewFeatureResponse(success=True, result="processed")

Step 3: 라우터 추가

# presentation/api/v1/new_feature.py
from fastapi import APIRouter, Depends

router = APIRouter(prefix="/new-feature", tags=["new-feature"])

@router.post("/process")
@response_wrapper
async def process_feature(
    request: NewFeatureRequest,
    service: NewFeatureService = Depends(get_new_feature_service)
):
    return service.process_feature(request)

Step 4: 메인 앱에 등록

# main.py
from app.presentation.api.v1.new_feature import router as new_feature_router

app.include_router(new_feature_router)

2. 새 Celery 작업 추가

# workers/tasks.py
@celery_app.task(bind=True)
def new_async_task(self, data: dict, task_id: str):
    try:
        # 작업 시작
        update_progress(task_id, "started", 0)
        
        # 처리 로직
        result = process_data(data)
        
        # 완료
        update_progress(task_id, "completed", 100)
        return result
        
    except Exception as e:
        update_progress(task_id, "failed", -1)
        raise

테스트

1. 단위 테스트

# tests/test_auth.py
import pytest
from app.services.auth_service import AuthService

def test_user_registration():
    service = AuthService(mock_db_session)
    result = service.join(JoinRequest(
        user_id="test_user",
        name="Test User",
        password="password123"
    ))
    assert result.name == "Test User"

2. API 테스트

# tests/test_api.py
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_health_check():
    response = client.get("/health")
    assert response.status_code == 200
    assert response.json()["status"] == "healthy"

3. 통합 테스트

# Postman 컬렉션 실행
newman run tests/postman/O2Sound.postman_collection.json

배포

1. Docker 빌드

docker build -t o2sound-backend .

2. Docker Compose

# docker-compose.yml
version: '3.8'

services:
  backend:
    image: o2sound-backend
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/o2sound
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis
  
  worker:
    image: o2sound-backend
    command: celery -A app.core.celery_app worker
    environment:
      - CELERY_BROKER_URL=redis://redis:6379/0
    depends_on:
      - redis
  
  db:
    image: postgres:14
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=o2sound
  
  redis:
    image: redis:7

3. 프로덕션 체크리스트

  • 환경 변수 확인
  • 데이터베이스 마이그레이션
  • Redis 연결 확인
  • Celery Worker 실행
  • 로그 설정
  • 모니터링 설정
  • 백업 전략 수립

모니터링

1. 로그 확인

# FastAPI 로그
tail -f logs/app.log

# Celery 로그
tail -f logs/celery.log

2. Celery Flower (웹 UI)

celery -A app.core.celery_app flower --port=5555

3. 성능 모니터링

  • APM 도구 (예: New Relic, DataDog)
  • Prometheus + Grafana
  • ELK Stack (Elasticsearch, Logstash, Kibana)

문제 해결

일반적인 문제

  1. Redis 연결 실패

    ❌ Redis 연결 실패: Connection refused
    

    해결: Redis 서버가 실행 중인지 확인

  2. 데이터베이스 연결 오류

    sqlalchemy.exc.OperationalError
    

    해결: DATABASE_URL 및 PostgreSQL 서버 상태 확인

  3. Celery 작업 실패

    Task task1_crawl[...] raised unexpected
    

    해결: Celery Worker 로그 확인 및 재시작

디버깅 팁

  1. FastAPI 자동 문서

    • Swagger UI: http://localhost:8000/docs
    • ReDoc: http://localhost:8000/redoc
  2. 대화형 디버깅

    import pdb; pdb.set_trace()
    
  3. 로그 레벨 조정

    # 개발 환경에서 상세 로그
    logger.setLevel(logging.DEBUG)
    

추가 리소스