기술 선택 이유
iEvent 백엔드는 Python 3.12 + FastAPI + SQLAlchemy 2.0으로 구성됩니다. FastAPI를 선택한 이유는 자동 OpenAPI 문서 생성, Pydantic v2 통합, async 네이티브 지원입니다. iOS 클라이언트와의 계약을 OpenAPI 스키마로 자동 관리할 수 있어 협업 오버헤드가 크게 줄었습니다.
프로젝트 구조
backend/
├── app/
│ ├── main.py # FastAPI 앱 초기화
│ ├── core/
│ │ ├── config.py # 환경변수 Settings
│ │ └── security.py # JWT, utcnow() 헬퍼
│ ├── models/
│ │ ├── base.py # SQLAlchemy Base
│ │ ├── mixins.py # TimestampMixin
│ │ └── child.py # Child 모델 등
│ ├── schemas/ # Pydantic 스키마
│ ├── api/v1/ # API 라우터
│ └── services/ # 비즈니스 로직
└── alembic/ # DB 마이그레이션
TimestampMixin — 공통 필드
# app/models/mixins.py
from sqlalchemy import Column, DateTime
from sqlalchemy.sql import func
from app.core.security import utcnow
class TimestampMixin:
created_at = Column(DateTime(timezone=True),
default=utcnow, server_default=func.now(), nullable=True)
updated_at = Column(DateTime(timezone=True),
default=utcnow, server_default=func.now(),
onupdate=utcnow, nullable=True)
deleted_at = Column(DateTime(timezone=True), nullable=True)
모델 정의 패턴
# app/models/child.py
import uuid
from sqlalchemy import Column, String, ForeignKey
from sqlalchemy.orm import relationship
from app.models.base import Base
from app.models.mixins import TimestampMixin
class Child(TimestampMixin, Base):
__tablename__ = "children"
id = Column(String(36), primary_key=True,
default=lambda: str(uuid.uuid4()))
user_id = Column(String(36), ForeignKey("users.id"), nullable=False)
name = Column(String(100), nullable=False)
gender = Column(String(10), nullable=True)
academies = relationship("Academy", back_populates="child",
cascade="all, delete-orphan")
Pydantic v2 스키마
# app/schemas/child.py
from pydantic import BaseModel, ConfigDict
from typing import Optional
from datetime import datetime
class ChildResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
name: str
gender: Optional[str] = None
created_at: Optional[datetime] = None
deleted_at: Optional[datetime] = None
FastAPI 라우터 + DB 의존성
# app/api/v1/children.py
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.db.session import get_db
from app.api.deps import get_current_user
router = APIRouter()
@router.get("/children", response_model=list[ChildResponse])
def list_children(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
return db.query(Child).filter(
Child.user_id == current_user.id,
Child.deleted_at.is_(None) # soft delete 필터
).all()
@router.post("/children", response_model=ChildResponse, status_code=201)
def create_child(
data: ChildCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
child = Child(
id=str(uuid.uuid4()),
user_id=current_user.id,
name=data.name,
gender=data.gender
)
db.add(child)
db.commit()
db.refresh(child)
return child
Dockerfile
FROM python:3.12-slim
WORKDIR /app
ENV TZ=Asia/Seoul
RUN apt-get update && apt-get install -y build-essential libpq-dev tzdata
COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY backend/ .
ENV PYTHONPATH=/app
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
교훈
FastAPI의 자동 OpenAPI 문서는 iOS 개발자와의 API 계약을 명확히 합니다. model_config = ConfigDict(from_attributes=True)로 SQLAlchemy ORM 객체를 Pydantic이 직접 직렬화할 수 있어 변환 코드가 불필요합니다.