[FastAPI 시리즈 8편] Depends로 의존성 주입 익히기

English version

라우터를 분리한 뒤에는 각 엔드포인트에 퍼져 있는 공통 코드(인증, DB 연결, 환경 설정)를 한곳에 모으고 싶습니다. 의존성 주입은 "필요한 도구를 함수 안에서 직접 만들지 말고, 밖에서 넣어 준다"라는 설계 습관입니다. FastAPI는 Depends라는 간결한 인터페이스를 제공해 의존성을 인자로 주입합니다. 덕분에 인증·설정처럼 중요한 로직을 한곳에서 관리하고 테스트에서 교체하기 쉬워집니다.

준비: Depends를 쉬운 말로 이해하기

  • Depends는 "이 함수가 실행되기 전에 딱 한 번 도우미 함수를 불러요"라는 약속입니다.
  • 의존성 함수는 보통 설정 값, DB 연결, 현재 사용자 같은 반복해서 쓰는 값을 준비합니다.
  • 엔드포인트 함수는 "나는 이 값이 필요해"라고 선언만 하고, FastAPI가 대신 값을 넣어 줍니다.

이 세 줄만 기억하면 바로 다음 섹션 코드를 읽을 수 있습니다.

이번 글에서 새로 나오는 용어

  1. 의존성 주입(DI): 함수가 필요한 자원을 직접 생성하는 대신 외부에서 받아 쓰는 설계 방식으로, 인증·DB 연결 같은 공통 코드를 반복하지 않게 해 줍니다.
  2. Depends: FastAPI가 특정 함수를 실행해 반환값을 엔드포인트 인자로 넣어 주는 문법으로, DI를 간단히 표현하는 핵심 키워드입니다.
  3. 요청 스코프: HTTP 요청 한 번이 시작되고 끝날 때까지 이어지는 수명 개념으로, DB 세션처럼 yield 패턴에서 열고 닫는 자원이 여기에 묶입니다.
  4. Bearer 토큰: Authorization: Bearer <token> 헤더에 담긴 문자열로, 글에서 토큰 파싱 의존성을 만들 때 다루는 인증 방식입니다.
  5. dependency_overrides: 테스트나 특정 상황에서 등록된 의존성을 가짜 함수로 교체할 수 있게 해 주는 FastAPI 설정으로, 실습 마지막 단계에서 사용합니다.

실습 카드

  • 예상 소요 시간: 45분
  • 사전 준비: 7편 구조 예제, Python 함수와 컨텍스트 매니저 기본
  • 실습 목표: Depends로 설정/DB/인증 의존성을 주입하고 테스트에서 교체한다

최소 예제로 구조 이해하기

from fastapi import Depends, FastAPI

app = FastAPI()

def get_settings():
    return {"service": "todo", "version": "0.1.0"}

@app.get("/info")
def read_info(settings = Depends(get_settings)):
    return settings

Depends(get_settings)는 함수가 실행될 때마다 설정을 계산해 settings 인자로 전달합니다. 인자에 타입 힌트를 주면 FastAPI가 문서화와 검증도 함께 처리합니다.

요청 스코프 자원 관리

의존성 함수 안에서 yield를 사용하면 컨텍스트 매니저처럼 정리 코드를 작성할 수 있습니다. 예를 들어 요청마다 DB 세션을 열고 닫는 경우입니다. 여기서 DB는 "데이터를 담아 둔 상자" 정도로 이해하면 충분합니다.

from contextlib import asynccontextmanager
from sqlmodel import Session

@asynccontextmanager
async def get_session():
    session = Session(engine)
    try:
        yield session
    finally:
        session.close()

@router.get("/tasks")
def list_tasks(session: Session = Depends(get_session)):
    return session.exec(select(Task)).all()

FastAPI는 yield 이후의 블록을 항상 실행하므로 예외가 발생해도 세션이 닫힙니다.

(선택) 중첩 의존성과 재사용

의존성에서 다시 의존성을 선언할 수 있습니다. 인증 토큰을 파싱한 뒤 사용자 객체를 조회하는 흐름을 예로 들 수 있습니다. 선택 섹션이니, 토큰 기초를 아직 배우지 않았다면 그냥 넘어가도 됩니다.

def get_token(auth_header: str = Header(alias="Authorization")) -> str:
    scheme, token = auth_header.split()
    if scheme.lower() != "bearer":
        raise HTTPException(status_code=401)
    return token

def get_current_user(
    token: str = Depends(get_token),
    session: Session = Depends(get_session),
):
    return session.exec(select(User).where(User.token == token)).first()

@router.get("/me")
def read_me(user: User = Depends(get_current_user)):
    return user

이렇게 연결하면 라우터마다 인증·세션 로직을 반복할 필요가 없습니다.

(선택) 의존성 체인 한눈에

사용자 HTTP 요청Authorization 헤더get_tokenget_sessionget_current_user/me Bearer token 추출token 전달DB 연결현재 사용자 객체JSON 응답

Depends 호출이 겹겹이 쌓이는 모습을 시각화하면, 어떤 함수가 어느 시점에 실행되고 어떤 값이 주입되는지 머릿속에서 순서도를 다시 그릴 필요가 없습니다. 테스트에서는 이 노드 중 하나만 교체해도 전체 흐름을 제어할 수 있다는 점이 더 명확해집니다. 다이어그램을 이해하기 어렵다면 코드를 먼저 몸으로 익힌 뒤 다시 돌아오세요.

(선택) 테스트와 의존성 오버라이드

테스트할 때는 app.dependency_overrides[get_current_user] = fake_user_factory처럼 원하는 함수로 교체하면 됩니다. 실 서비스에서는 실제 DB나 외부 API를 사용하고, 테스트에서는 인메모리 객체나 모킹을 주입하는 패턴이 자연스럽게 만들어집니다. 지금은 "테스트에서는 같은 자리에 가짜 함수를 꽂는다" 정도만 기억해도 충분합니다.

정리

  • Depends는 함수를 호출해 반환값을 인자로 넣어 주는 구문 설탕이지만, 타입·문서·라이프사이클을 함께 관리해 줍니다.
  • yield 패턴으로 요청 단위 자원을 안전하게 정리할 수 있습니다.
  • dependency_overrides를 이용하면 테스트에서 손쉽게 스텁을 넣을 수 있습니다.

실습

  • 따라 하기: get_settingsget_session 의존성을 만들고 라우터 인자에 Depends로 주입한다.
  • 확장하기: 토큰 파싱 → 사용자 조회로 이어지는 중첩 의존성을 작성해 /me 엔드포인트를 완성한다.
  • 디버깅: 테스트 코드에서 dependency_overrides로 가짜 사용자/세션을 주입해 응답이 바뀌는지 확인한다.
  • 완료 기준: 실제 서버와 테스트 모두에서 의존성 체인이 원하는 객체를 전달한다.

마무리

Depends 패턴을 이해하면 인증, 설정, DB 연결을 반복해서 작성할 필요가 없습니다. 공통 로직을 함수로 분리하고 주입하는 습관을 들이면, 테스트와 확장 모두가 훨씬 쉬워집니다. 다음 글에서는 이러한 의존성을 실제 데이터베이스로 연결해 Session과 모델을 다루는 첫 단계를 밟아 보겠습니다.

💬 댓글

이 글에 대한 의견을 남겨주세요