최근 진행 중인 FastAPI 프로젝트에서 데이터 처리 로직과 비즈니스 로직이 뒤섞여 유지보수성이 떨어지는 문제가 발생했다.
새로운 기능을 추가하는 과정에서 구조적인 개선이 필요하다는 점을 인지했고, 적절한 디자인 패턴을 적용하여 코드의 가독성과 유지보수성을 향상시키고자 한다.
이번 글에서는 리팩토링을 위해 학습한 Repository Pattern과 Service Layer Pattern에 대해 알아보고, FastAPI에서 어떻게 적용할 수 있는지 실용적인 코드 예제와 함께 설명하겠다.
데이터 접근(DB 쿼리)과 비즈니스 로직을 분리하는 패턴
Repository 패턴은 데이터베이스 접근 로직을 별도 파일로 분리하여 서비스 계층과의 결합도를 낮추는 방식이다. 이를 통해 데이터베이스 접근 방식의 변경이 필요한 경우, 서비스 로직을 수정하지 않고도 변경을 쉽게 적용할 수 있다.
API 엔드포인트에서 직접 DB 접근을 수행하는 경우
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from app.database import get_db
from app.models import Item
from app.schemas import ItemCreate
app = FastAPI()
@app.post("/items/")
def create_item(item_data: ItemCreate, db: Session = Depends(get_db)):
new_item = Item(**item_data.dict())
db.add(new_item)
db.commit()
db.refresh(new_item)
return new_item
Repository 클래스를 만들어 API 엔드포인트에서 DB 접근을 분리
from sqlalchemy.orm import Session
from app.models import Item
from app.schemas import ItemCreate
class ItemRepository:
"""아이템 관련 DB 작업을 처리하는 Repository"""
def __init__(self, db: Session):
self.db = db
def create_item(self, item_data: ItemCreate):
db_item = Item(**item_data.dict())
self.db.add(db_item)
self.db.commit()
self.db.refresh(db_item)
return db_item
def get_item_by_id(self, item_id: int):
return self.db.query(Item).filter(Item.id == item_id).first()
FastAPI 엔드포인트에서 Repository 사용
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.database import get_db
from app.repositories.item_repository import ItemRepository
from app.schemas import ItemCreate
router = APIRouter()
@router.post("/items/")
def create_item(item_data: ItemCreate, db: Session = Depends(get_db)):
repo = ItemRepository(db)
return repo.create_item(item_data)
비즈니스 로직을 한 곳에 모아 관리하는 패턴
- 데이터베이스와의 직접적인 상호작용을 Repository Layer에 맡기고, 비즈니스 로직은 Service Layer에서 담당.
- Service Layer는 여러 개의 Repository를 조합하여 하나의 기능을 처리하는 역할을 한다.
- API 엔드포인트에서는 비즈니스 로직을 직접 처리하지 않고, Service Layer를 호출하여 기능을 수행.
적용 시 장점
API 엔드포인트에서 직접 비즈니스 로직을 처리하는 경우
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from app.database import get_db
from app.models import Item
from app.schemas import ItemCreate
app = FastAPI()
@app.post("/items/")
def create_item(item_data: ItemCreate, db: Session = Depends(get_db)):
"""API 엔드포인트에서 직접 비즈니스 로직을 처리"""
# 같은 이름의 아이템이 존재하면 생성 불가 (비즈니스 로직)
existing_item = db.query(Item).filter(Item.name == item_data.name).first()
if existing_item:
raise HTTPException(status_code=400, detail="이미 존재하는 아이템입니다.")
new_item = Item(**item_data.dict())
db.add(new_item)
db.commit()
db.refresh(new_item)
return new_item
문제점
Service Layer를 추가하여 API에서 비즈니스 로직을 분리
from sqlalchemy.orm import Session
from app.repositories.item_repository import ItemRepository
from app.schemas import ItemCreate
from fastapi import HTTPException
class ItemService:
"""비즈니스 로직을 담당하는 Service Layer"""
def __init__(self, db: Session):
self.db = db
self.repo = ItemRepository(db)
def create_item(self, item_data: ItemCreate):
"""비즈니스 로직: 같은 이름의 아이템이 존재하면 생성 불가"""
existing_item = self.repo.get_item_by_name(item_data.name)
if existing_item:
raise HTTPException(status_code=400, detail="이미 존재하는 아이템입니다.")
return self.repo.create_item(item_data)
FastAPI 엔드포인트에서 Service Layer 사용
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.database import get_db
from app.services.item_service import ItemService
from app.schemas import ItemCreate
router = APIRouter()
@router.post("/items/")
def create_item(item_data: ItemCreate, db: Session = Depends(get_db)):
service = ItemService(db)
return service.create_item(item_data)
이번 글에서는 Repository Pattern과 Service Layer Pattern을 적용하여 FastAPI 프로젝트의 유지보수성과 확장성을 높이는 방법을 살펴보았다.
이 두 가지 패턴을 활용하면 API 엔드포인트가 단순해지고, 데이터 처리, 비즈니스 로직, API 요청 처리를 각각의 계층에서 관리할 수 있다.
결과적으로 코드의 가독성이 향상되고, 새로운 기능을 추가하거나 수정할 때 변경해야 할 범위가 줄어들어 유지보수가 쉬워진다.
디자인 패턴을 적용하는 것은 프로젝트의 복잡도에 따라 선택적으로 고려해야 하지만, 코드의 역할을 명확히 분리하는 것은 장기적인 관점에서 개발 생산성을 높이는 중요한 요소이다.
과거에는 "디자인 패턴이란 프로그램을 설계할 때 발생하는 문제를 객체 간의 상호 관계를 이용해 해결할 수 있도록 정리한 ‘규약’이다." 라는 정의가 명확하게 와닿지 않았다.
하지만 프로젝트를 개발하면서 코드에 대한 답답함과 유지보수가 어렵다는 막연한 불편함을 느꼈다. 무엇이 문제인지 명확히 정의하기 어려웠고 어떻게 개선해야 할지도 막막했는데, 디자인 패턴을 학습하며 고민들을 해결할 수 있었다. 역시 개발자 부지런히 공부해야….
감사합니다 !