FastAPI + SQLAlchemy 2.0 Async ORM으로 iOS 앱 백엔드 구축하기

기술 선택 이유

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이 직접 직렬화할 수 있어 변환 코드가 불필요합니다.

글쓴이

admin

https://ivent-admin.storyqbe.top/ 를 운영하고 있는 강프로입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다