새로운 사용자 요청을 받을 때마다, 그 요청을 인증하고 권한을 부여하는 과정은 필수적입니다. 하지만, 이러한 인증과 권한 부여 로직이 우리의 주요 비즈니스 로직과 혼합되어 있으면 코드를 이해하고 관리하기가 어렵습니다. 그래서 우리는 이러한 로직들을 분리하여 관리할 계획입니다.
우리의 앱을 클린하고 유지보수하기 쉽게 만들기 위해, 어떻게 앱을 스케일링할지 생각해 보았습니다. 현재 main 파일이 너무 복잡해지고 있어, router를 사용해 별도의 파일로 분리하기로 결정했습니다. 이렇게 하면 각각의 관심사가 명확히 분리되어, 코드의 가독성과 유지보수성이 크게 향상됩니다.
예를 들어, 인증 로직은 auth.py로 분리하여 관리합니다. 아래는 간단한 예시입니다:
from fastapi import APIRouter
router = APIRouter()
@router.get("/auth/")
async def get_user():
return {'user': 'authenticated'}
또한, 우리의 할 일 목록(todos) 관리 로직도 todos.py로 분리했습니다. 이렇게 하면 코드가 더욱 깔끔해지고, 각 기능별로 파일을 나누어 관리할 수 있어, 유지보수성이 크게 향상됩니다. 아래는 todos.py 파일의 예시입니다:
from fastapi import Depends, HTTPException, Path, APIRouter
from typing import Annotated
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from fastapi import status
from models import Todos
from database import SessionLocal
router = APIRouter()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
db_dependency = Annotated[Session, Depends(get_db)]
class TodoRequest(BaseModel):
title: str = Field(min_length=3)
description: str = Field(min_length=3, max_length=100)
priority: int = Field(gt=0, lt=6)
complete: bool
@router.get('/')
async def read_all(db: Annotated[Session, Depends(get_db)]):
return db.query(Todos).all()
@router.get('/todo/{todo_id}', status_code=status.HTTP_200_OK)
async def read_todo(db: db_dependency, todo_id: int = Path(gt=0)):
todo_model = db.query(Todos).filter(Todos.id == todo_id).first()
if todo_model is not None:
return todo_model
raise HTTPException(status_code=404, detail='Todo not found')
@router.post("/todo", status_code=status.HTTP_201_CREATED)
async def create_todo(db: db_dependency, todo_request: TodoRequest):
todo_model = Todos(**todo_request.dict())
db.add(todo_model)
db.commit()
@router.put("/todo/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
async def update_todo(db: db_dependency, todo_reqeust: TodoRequest, todo_id: int = Path(gt=0)):
todo_model = db.query(Todos).filter(Todos.id==todo_id).first()
if todo_model is None:
raise HTTPException(status_code=404, detail='Todo not found')
todo_model.title = todo_reqeust.title
todo_model.description = todo_reqeust.description
todo_model.priority = todo_reqeust.priority
todo_model.complete = todo_reqeust.complete
db.add(todo_model)
db.commit()
@router.delete("/todo/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_todo(db: db_dependency, todo_id: int = Path(gt=0)):
todo_model = db.query(Todos).filter(todo_id==Todos.id).first()
if todo_model is None:
raise HTTPException(status_code=404, detail='Todo not found')
db.query(Todos).filter(Todos.id==todo_id).delete()
db.commit()
위의 변경 사항을 적용하고 나면, main 파일은 다음과 같이 단순화됩니다:
from fastapi import FastAPI
import models
from database import engine
from routers import auth, todos
app = FastAPI()
models.Base.metadata.create_all(bind=engine)
app.include_router(auth.router)
app.include_router(todos.router)
우리의 Todo 프로젝트는 one-to-many 관계입니다. 즉, 한 명의 사용자가 여러 개의 Todo 항목을 가질 수 있습니다. 물론, 한 명의 사용자만 있는 것은 아니며, 여러 사용자가 각각 여러 개의 Todo 항목을 가질 수 있습니다.
이를 위해 우리는 두 개의 테이블을 사용합니다: 하나는 사용자 정보를 담는 User 테이블이고, 다른 하나는 Todo 항목을 담는 Todos 테이블입니다. Todos 테이블에는 사용자 ID를 참조하는 외래 키(ForeignKey)가 있습니다. 이는 두 테이블 사이의 관계를 설정하는 데 사용됩니다.
그래서 데이터 모델은 이렇게 재정의할 것이다.
from database import Base
from sqlalchemy import Column, Integer, String, Boolean, ForeignKey
class Users(Base):
__tablename__= 'users'
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True)
username = Column(String, unique=True)
first_name = Column(String)
last_name = Column(String)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
role = Column(String)
class Todos(Base):
__tablename__ = 'todos'
id = Column(Integer, primary_key=True, index=True)
title = Column(String)
description = Column(String)
priority = Column(Integer)
complete = Column(Boolean, default=False)
owner_id = Column(Integer, ForeignKey("users.id"))