https://ye-seul0-0.tistory.com/2
[FastAPI - DI 마스터 01부] 복잡성 증가를 해결하는 의존성 주입(DI) 개념과 필요성
FastAPI 의존성 주입(Dependency Injection) 심층 분석: 왜, 그리고 어떻게 사용하는가? 이번 포스팅에서는 FastAPI를 빠르고 견고하게 만들어주는 핵심 원리, 의존성 주입(Dependency Injection, DI) 시스템에 대
ye-seul0-0.tistory.com
https://ye-seul0-0.tistory.com/6
[FastAPI - DI 마스터 2부] Depends 사용법 기초부터 yield를 이용한 DB 연동까지 실전 정복
FastAPI 의존성 주입(DI) 마스터 시리즈 (2부)안녕하세요! 지난 1부에서는 우리가 API를 개발하며 겪는 문제점들과, 이를 해결하기 위한 의존성 주입(DI) 의 개념에 대해 알아보았습니다. 이번 2부에서
ye-seul0-0.tistory.com
FastAPI 의존성 주입(DI) 마스터 시리즈 (3부 / 완결)
안녕하세요! FastAPI DI 시리즈의 마지막 편입니다.. 생각 없이 쓰던걸 정리해보니 새롭고, 더 효율적인 코드를 짤 수 있겠다는 생각이 듭니다.
지난 2부에서 우리는 yield를 사용해 DB 세션을 안전하게 관리하고, 인증 시스템을 깔끔하게 구현하는 법을 배웠습니다. 하지만 항상 확인하는 마음가짐을 가지게 되는 거 같습니다.
"이거... 정말 잘 돌아가는 거 맞아? 나중에 코드를 고쳐도 안 망가질까?"
보통 이 불안함을 없애기 위해 테스트 코드를 짭니다. 하지만 전통적인 방식의 API 테스트는 고통 그 자체였습니다.
테스트할 때마다 실제 DB를 켜야 하고, 데이터를 넣었다 지웠다 해야 했으니까요. 느리고, 깨지기 쉽고, 귀찮죠.
오늘 '의존성 오버라이드(Dependency Override)' 기능을 소개하려 합니다.

- Before Override - 실제 배포 환경이라 생각하시면 될 거 같습니다. create_user 경로 함수가 get_db 의존성을 통해 무거운 실제 프로덕션 데이터베이스(예: PostgreSQL)와 연결되어있기 때문에, 테스트를 돌리면 실제 DB에 데이터가 쌓이게 됩니다. (근데 만약, 실 사용 디비와 테스트용 디비를 따로 분기해뒀다면,,, 큰 상관은 없겠죠. )
- After Override 테스트 실행 중(app.dependency_overrides 적용)의 모습입니다. FastAPI가 get_db로 가는 연결을 차단(X)하고, 대신 우리가 준비한 가벼운 가짜 의존성(override_get_db) 으로 연결 방향을 바꿉니다. 이 가짜 의존성은 메모리 상의 가짜 DB(Mock Database) 를 사용하므로 빠르고 안전합니다.
여기서 이제 오른쪽을 어떻게 구현하는지 알아보겠습니다.
1. app.dependency_overrides
우리가 2부에서 만든 create_user 함수를 기억하시나요? 이 함수는 get_db라는 의존성을 통해 실제 데이터베이스와 연결되어 있습니다.
# main.py (복습)
@app.post("/users/")
def create_user(user: UserSchema, db: Session = Depends(get_db)):
# 실제 DB에 저장하는 로직
...
이 함수를 테스트하려고 pytest를 돌리면 실제로 운영 DB(혹은 개발 DB)에 데이터가 쌓입니다. 끔찍하죠.
FastAPI는 "테스트 실행 중에만 get_db를 다른 함수로 바꿔치기" 할 수 있는 기능을 제공합니다.
1.1. 테스트용 가짜(Mock) 의존성 만들기
먼저, 실제 DB 대신 메모리(RAM) 에만 데이터를 저장했다가 휘발시키는 가볍고 빠른 SQLite DB를 사용하는 함수를 만듭니다.
# test_main.py
from sqlalchemy import create_engine, StaticPool
from sqlalchemy.orm import sessionmaker
from main import app, get_db
from fastapi.testclient import TestClient
# 1. 테스트 전용 인메모리(In-Memory) DB 설정
# 파일이 아닌 메모리에 저장하므로 속도가 엄청나게 빠릅니다.
TEST_DATABASE_URL = "sqlite:///:memory:"
test_engine = create_engine(
TEST_DATABASE_URL,
connect_args={"check_same_thread": False},
poolclass=StaticPool, # 메모리 DB를 위한 설정
)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=test_engine)
# 2. 실제 get_db를 대체할 테스트용 의존성 함수
def override_get_db():
db = TestingSessionLocal()
try:
# DB 테이블 생성 (테스트 시작 전 준비)
Base.metadata.create_all(bind=test_engine)
yield db
finally:
db.close()
# 메모리 DB라 굳이 drop은 필요 없지만, 명시적인 정리를 위해
Base.metadata.drop_all(bind=test_engine)
1.2. 바꿔치기(Override) 마법 부리기
이제 테스트 코드에서 "야, FastAPI! 지금부터 get_db 부르지 말고, 내가 만든 override_get_db 써!" 라고 명령만 내리면 됩니다.
client = TestClient(app)
def test_create_user():
# 앱 전체에서 get_db가 호출될 때마다 override_get_db가 대신 실행됨.
app.dependency_overrides[get_db] = override_get_db
# 요청 보내기
response = client.post(
"/users/",
json={"username": "testuser", "email": "test@example.com"}
)
# 검증 (실제 DB가 아닌, 메모리 DB에서 수행됨)
assert response.status_code == 200
assert response.json()["username"] == "testuser"
# 테스트가 끝나면 오버라이드를 해제하여 원래 상태로 되돌림!
app.dependency_overrides = {}
이 방식이 왜 혁신적일까요?
- 실제 네트워크를 타거나 무거운 DB를 띄우지 않아 테스트가 수백 배 빨라짐. (제대로된 성능 테스트는 추후에...)
- 실수로 운영 DB 데이터를 날려 먹을 일이 절대 없음!
- 복잡한 Mocking 라이브러리를 배우지 않아도, 딕셔너리(
{}) 값만 바꾸면 끝
2. 클래스 기반 의존성 (Class-based Dependencies)
지금까지는 함수(def)로만 의존성을 만들었습니다. 하지만 의존성에 설정값이나 상태를 담고 싶을 땐 어떻게 할까요?
예를 들어, 특정 쿼리 파라미터가 포함되어 있는지 검사하는 로직 이 필요한데, 어떤 엔드포인트에서는 "item"을 검사하고, 다른 곳에서는 "user"를 검사해야 한다면요?
함수를 여러 개 만드는 대신, 클래스를 사용하면 훨씬 우아해집니다.
2.1. __call__을 이용한 클래스 의존성
파이썬의 클래스 인스턴스를 함수처럼 호출할 수 있게 해주는 __call__ 메서드를 활용합니다.
from fastapi import FastAPI, Depends
app = FastAPI()
# 설정을 저장할 수 있는 의존성 클래스
class FixedContentQueryChecker:
def __init__(self, fixed_content: str):
# 초기화 시점에 '무엇을 검사할지' 설정값을 저장합니다.
self.fixed_content = fixed_content
# 의존성으로 실행될 때 호출되는 부분
def __call__(self, q: str = ""):
if q:
return self.fixed_content in q
return False
# 사용 예시: 인스턴스를 생성하면서 설정을 다르게 줍니다.
checker_item = FixedContentQueryChecker("item")
checker_user = FixedContentQueryChecker("user")
@app.get("/query-check/item")
def check_query_item(has_item: bool = Depends(checker_item)):
return {"has_item": has_item}
@app.get("/query-check/user")
def check_query_user(has_user: bool = Depends(checker_user)):
return {"has_user": has_user}
이렇게 하면 로직은 하나(FixedContentQueryChecker)로 유지하면서, 필요한 설정만 바꿔가며 무한히 재사용할 수 있습니다.
3. 의존성의 의존성 (Sub-dependencies)
의존성 주입의 또 다른 매력은 계층 구조를 가질 수 있다는 점입니다. 이를 "서브 의존성(Sub-dependencies)"이라고 부릅니다.
서브 의존성의 개념은 다른 장에서 설명하겠습니다.
예를 들어볼까요?
get_token_header: 헤더에서 토큰 문자열을 가져오는 의존성.get_current_user: 위 의존성을 사용하여, 가져온 토큰으로 사용자를 찾는 의존성.get_active_user: 위 의존성을 사용하여, 사용자가 활성 상태인지 확인하는 의존성.
def get_token_header(x_token: list[str] = Header()):
if not x_token:
raise HTTPException(...)
return x_token[0]
def get_current_user(token: str = Depends(get_token_header)):
# get_token_header의 결과가 token 변수로 들어옵니다.
return fake_decode_user(token)
def get_active_user(current_user: User = Depends(get_current_user)):
# get_current_user의 결과가 current_user로 들어옵니다.
if not current_user.active:
raise HTTPException(...)
return current_user
@app.get("/users/me")
def read_users_me(user: User = Depends(get_active_user)):
return user
FastAPI가 하는 일
개발자는 경로 함수에 Depends(get_active_user)만 적으면 됩니다. 그러면 FastAPI가 알아서
get_token_header 실행 → 결과 전달 → get_current_user 실행 → 결과 전달 → get_active_user 실행 → 결과 전달 → 최종 경로 함수 실행
이 복잡한 과정을 자동으로 조립해 줍니다. 우리는 로직을 조립하기만 하면 되는 것!
시리즈를 마치며
3부작 시리즈를 통해 다음과 같은 것들을 배웠습니다.
- 1부: 스파게티 코드를 해결하기 위해 왜 의존성 주입(DI)이 필요한지, 그리고
Depends의 개념. - 2부:
yield를 사용해 DB 연결을 안전하게 관리하고, 보안 인증을 간결하게 구현하는 실전 기술. - 3부:
dependency_overrides를 통해 테스트 코드를 획기적으로 개선하고, 클래스와 서브 의존성으로 구조를 확장하는 법.
그동안은 프로젝트 마감에 쫓겨, 혹은 당장 기능이 돌아가는 것이 중요해서 의존성 주입의 진짜 가치를 모른 채 '마구 써대기만' 했던 적도 많았습니다. "그냥 남들이 이렇게 쓰니까", "공식 문서에 예제가 있으니까" 복사해서 붙여넣기 바빴죠.
하지만 이번에 개념 하나하나를 풀어서 설명하다 보니, "아, 이래서 FastAPI가 빠르다고 하는구나", "이래서 테스트가 쉬워지는구나" 를 새삼 다시 깨닫게 되었습니다. 머리로만 알던 것을 글로 정리하는 과정 자체가 저에게는 가장 큰 공부가 되었습니다.
다음 목표: 테스트 자동화 라이브러리 개발?
특히 3부에서 다룬 테스트와 서브 의존성(Sub-dependencies) 부분은 저에게 새로운 자극을 주었습니다.
솔직히 고백하자면, 테스트 코드를 짤 때 서브 의존성까지 꼼꼼하게 설계해서 사용하는 빈도가 아주 높지는 않았습니다. 의존성이 깊어질수록 override 설정이 귀찮아지기도 하고, "에이, 이 정도는 그냥 넘어가자"라고 타협할 때도 있었거든요.
그래서 문득 든 생각이 있습니다. "이 귀찮은 의존성 오버라이드 과정을 자동으로 처리해 주는 라이브러리가 있다면 어떨까?"
복잡한 계층 구조의 의존성들을 분석해서, 테스트 환경에 맞게 자동으로 Mocking 해주고 주입해 주는 도구를 직접 만들어보고 싶다는 욕심이 생겼습니다. 만약 이 프로젝트가 구체화된다면, 그때는 "FastAPI 테스트 자동화 라이브러리 개발기" 라는 새로운 시리즈로 여러분을 찾아뵙겠습니다.
'ServerDev > FastAPI' 카테고리의 다른 글
| [FastAPI - CORS 마스터 2부] 쿠키 인증(Credential) 이슈 해결과 보안을 위한 정교한 허용 전략 (0) | 2025.12.11 |
|---|---|
| FastAPI - CORS 마스터 1부] 프론트엔드 연동 첫걸음, CORS 에러 (0) | 2025.12.10 |
| [FastAPI] 웹 프레임워크가 아니라 'AI 모델 서빙기'로 사용하기 (feat. Spring Boot) (0) | 2025.12.09 |
| [FastAPI - DI 마스터 2부] Depends 사용법 기초부터 yield를 이용한 DB 연동까지 실전 정복 (0) | 2025.12.06 |
| [FastAPI - DI 마스터 01부] 복잡성 증가를 해결하는 의존성 주입(DI) 개념과 필요성 (0) | 2025.12.04 |
