FastAPI 의존성 주입 — Depends 이해하기
똑같은 코드를 엔드포인트마다 베껴 쓰고 있다면 그건 신호다. 그 반복을 함수 하나로 묶어 "받아 쓰게" 하는 장치, Depends.
4편까지 오면서 우리는 경로·쿼리 매개변수로 입력을 받고, Pydantic 모델로 요청 본문을 검증하는 법을 익혔다. 엔드포인트가 두세 개일 때는 별 문제가 없다. 그런데 라우트가 열 개, 스무 개로 늘어나기 시작하면 묘하게 거슬리는 장면이 보인다 — 똑같은 코드 토막이 함수마다 복사되어 붙어 있다.
5편의 주제 의존성 주입(Dependency Injection) 은 바로 이 반복을 없애는 도구다. 이름이 거창하지만 개념은 단순하고, FastAPI 에서는 Depends 라는 함수 하나로 거의 다 끝난다. 이 편을 마치면 공통 파라미터·인증·DB 세션 같은 것을 한 곳에 정의해 두고, 필요한 라우트에서 "받아 쓰는" 구조를 짤 수 있게 된다.
1. 같은 코드가 반복될 때 — 무엇이 문제인가
예를 들어 목록을 돌려주는 엔드포인트가 여러 개 있다고 하자. 상품 목록, 주문 목록, 사용자 목록… 이들은 대개 똑같은 페이지네이션 옵션을 받는다. 검색어 q, 건너뛸 개수 skip, 가져올 최대 개수 limit. 그래서 코드는 자연스럽게 이렇게 쌓인다.
같은 세 줄짜리 매개변수 묶음이 세 군데에 똑같이 들어가 있다. 지금이야 별것 아니지만, 어느 날 limit 의 기본값을 10 에서 20 으로 바꾸기로 했다고 해 보자. 함수 세 곳을 전부 찾아 고쳐야 한다. 하나라도 빠뜨리면, 어떤 목록만 다르게 동작하는 미묘한 버그가 생긴다. 인증 검사나 DB 연결 코드라면 더 심각하다.
여기서 발상을 살짝 뒤집어 보자. 지금까지 각 함수는 자기에게 필요한 재료(파라미터)를 직접 구하고 있었다. 의존성 주입은 그 방향을 바꾼다. 함수는 "나는 이런 재료가 필요해" 라고 선언만 하고, 그 재료를 실제로 마련해서 손에 쥐여 주는 일은 프레임워크에 맡긴다.
2. Depends 첫 사용 — 공통 파라미터를 함수로 빼기
위의 중복된 세 줄을 별도 함수로 뽑아내 보자. 이 함수가 바로 의존성(dependency) 이다. 이름 그대로, 엔드포인트가 "의존하는" 코드다.
common_params 는 평범한 함수처럼 보이지만, 매개변수에 적힌 타입힌트와 기본값은 우리가 4편에서 본 쿼리 매개변수 규칙 을 그대로 따른다. 이제 이 함수를 엔드포인트에 "주입" 한다. 방법은 인자 자리에 Depends() 를 적는 것뿐이다.
이제 read_items 함수 본문에는 더 이상 q, skip, limit 가 보이지 않는다. 대신 commons 라는 변수 하나로 묶여 들어온다. 동작 순서는 이렇다.
누군가 /items?q=phone&limit=5 로 요청하면, FastAPI 는 먼저 의존성 함수 common_params 를 실행한다. 이때 URL 의 쿼리값들이 그 함수의 매개변수로 들어가 검증·변환을 거친다. 함수가 돌려준 딕셔너리가 곧 commons 의 값이 되어, 그제야 read_items 본문이 실행된다. 즉 FastAPI 가 의존성을 먼저 실행하고, 그 결과를 인자로 끼워 넣은 다음, 엔드포인트를 호출하는 것이다.
덤으로 따라오는 이득이 하나 더 있다. common_params 의 매개변수들도 결국 쿼리 매개변수이기 때문에, /docs 의 자동 문서에 q·skip·limit 가 그대로 노출된다. 의존성으로 빼냈다고 해서 문서에서 사라지지 않는다. 검증과 문서화 능력은 고스란히 유지된다.
3. 재사용과 클래스 의존성
의존성 주입의 진짜 값어치는 재사용 에서 나온다. 이제 페이지네이션 규칙을 바꾸고 싶으면 common_params 함수 한 곳만 고치면 된다. 그 함수를 Depends 로 받아 쓰는 모든 라우트가 동시에 새 규칙을 따른다. 라우트가 50 개여도 수정 지점은 하나다.
딕셔너리보다 명확한 클래스 의존성
의존성으로 함수만 쓸 수 있는 건 아니다. 클래스 도 의존성이 될 수 있다. 파이썬에서 클래스를 호출하면(Pagination()) __init__ 이 실행되니, FastAPI 입장에서는 클래스도 "호출 가능한 무언가" 일 뿐이다. 그래서 클래스 자체를 그대로 Depends 에 넘긴다.
__init__ 의 매개변수가 그대로 쿼리 매개변수가 된다. 결과로 page 에는 Pagination 객체가 들어오고, page.skip 처럼 점(.) 으로 또렷하게 꺼내 쓸 수 있다. 딕셔너리의 commons["skip"] 보다 오타에 강하고, 에디터의 자동완성도 받는다. 데이터가 단순하면 함수로, 구조가 좀 있으면 클래스로 — 정도로 기억하면 충분하다.
page: Pagination = Depends(Pagination) 처럼 타입과 Depends 인자가 같은 클래스로 겹칠 때는, FastAPI 가 알아서 추론하도록 page: Pagination = Depends() 로 인자를 비워 둘 수 있다. 클래스를 의존성으로 쓸 때만 통하는 편의 문법이다.
4. yield 의존성 — 정리까지 책임지기
지금까지의 의존성은 값을 return 하고 끝났다. 그런데 어떤 자원은 다 쓰고 나서 반드시 닫아 줘야 한다. 대표적인 게 데이터베이스 세션이다. 열어서 쓰고, 끝나면 닫아야 연결이 새지 않는다. 이럴 때 의존성 함수에서 return 대신 yield 를 쓴다.
흐름을 따라가 보자. 요청이 들어오면 FastAPI 가 get_db 를 실행하다가 yield db 에서 잠시 멈추고, 그 db 를 엔드포인트에 넘긴다. read_articles 가 응답을 다 만들고 나면, FastAPI 가 멈춰 뒀던 get_db 로 돌아와 yield 이후 코드, 즉 finally 블록을 실행한다. 그래서 db.close() 가 항상 호출된다. 중간에 에러가 나도 finally 라 빠짐없이 정리된다.
한마디로 yield 의존성은 "쓰기 전 준비 → 엔드포인트 실행 → 쓴 뒤 정리" 를 한 함수 안에 담는다. yield 윗부분은 준비, 아랫부분은 뒷정리다. 7편에서 SQLAlchemy 로 실제 DB 를 붙일 때 이 get_db 패턴을 거의 그대로 다시 만나게 되니, 지금은 모양에만 익숙해지면 된다.
중첩 의존성 — 의존성이 또 다른 의존성을 가질 때
의존성 함수 역시 평범한 함수라서, 그 안에서 또 다른 의존성을 받을 수 있다. 예컨대 토큰에서 사용자를 꺼내는 get_current_user 의존성이 DB 세션 get_db 에 의존하고, 어떤 엔드포인트는 그 get_current_user 에 의존하는 식이다. FastAPI 는 이 의존성 사슬을 끝까지 따라가 가장 안쪽부터 차례로 해결한 뒤 결과를 위로 전달한다. 우리는 사슬의 맨 끝에서 필요한 것만 Depends 로 받으면, 그 뒤에 줄줄이 매달린 준비 과정은 프레임워크가 알아서 처리한다. 인증처럼 단계가 여러 개인 로직을 깔끔하게 쌓을 수 있는 이유다.
Depends(..., use_cache=True) 가 기본값). 다만 요청이 바뀌면 다시 실행되므로, 무거운 초기화(예: 거대한 모델 로딩)를 의존성에 넣으면 요청마다 반복될 수 있다. 그런 자원은 앱 시작 시 한 번만 준비해 두고, 의존성에서는 그 준비된 것을 꺼내 주기만 하는 게 정석이다.
5. 정리 — Depends 한 줄이 바꾸는 것
이번 편의 큰 그림을 다시 모아 본다.
| 패턴 | 언제 쓰나 | 핵심 형태 |
|---|---|---|
| 함수 의존성 | 공통 쿼리·간단한 공유 로직 | Depends(common_params) |
| 클래스 의존성 | 구조 있는 값을 점(.)으로 꺼낼 때 | Depends(Pagination) |
| yield 의존성 | DB 세션 등 정리가 필요한 자원 | try / yield / finally |
| 중첩 의존성 | 인증처럼 단계가 여러 개일 때 | 의존성 안에서 또 Depends |
요약
의존성 주입은 "필요한 재료를 직접 구하지 말고 받아 쓴다" 는 한 문장으로 압축된다. FastAPI 에서는 인자 자리에 Depends() 를 적으면, 프레임워크가 의존성 함수를 먼저 실행해 그 결과를 끼워 넣어 준다. 덕분에 공통 파라미터·인증·DB 세션 같은 코드를 한 곳에 정의하고 어디서든 재사용할 수 있고, 규칙이 바뀌어도 수정 지점은 하나로 줄어든다. 단순하면 함수로, 구조가 있으면 클래스로, 정리가 필요하면 yield 로 — 이 세 가지 형태와, 의존성이 또 다른 의존성을 부르는 중첩 구조까지가 5편의 전부다. 다음 편에서는 늘어난 라우트와 의존성을 파일·폴더 단위로 쪼개 관리하는 법을 다룬다.
다음 편 예고 — APIRouter 로 프로젝트 구조화
한 파일에 다 몰아넣던 엔드포인트를 APIRouter 로 기능별로 나누고, 폴더 구조와 공통 의존성을 라우터 단위로 묶는 법을 배운다.