FastAPI 의존성 주입

예찬예찬·2024년 7월 2일

FastAPI

목록 보기
9/10
post-thumbnail

의존성 주입 (Dependency Injection)

  • 소프트웨어 디자인 패턴 중 하나
  • 객체 간의 의존 관계를 외부에서 설정하고 관리하는 방식
  • 즉, 객체가 자신이 필요로 하는 다른 객체를 직접 생성하거나 찾는 대신, 외부에서 주입받는 것을
    의미함.
class Engine:
	def start(self):
			print("엔진이 시작됩니다.")
			
class Car:
	def __init__(self, engine):
			self.engine = engine
	def start(self):
			self.engine.start()

# 의존성 주입
engine = Engine()
car = Car(engine)
car.start()			

왜 쓸까?

  • 코드의 재사용성 증가
  • 단위 테스트 및 통합 테스트의 용이성 증가
  • 코드간 느슨한 결합 → 유지보수성과 확장성 향상!

단점?

  • 의존성이 많이 있거나 프로젝트가 큰 경우 구성이 복잡해지고 이해가 어려울 수 있음
  • 구현이 늘어나면서 오버헤드가 생길 수 있
  • 학습 곡선이 높고 디버깅도 어려움

FastAPI에선?

코드의 재사용성

  • 반복되는 코드가 줄어든다
  • 유지보수가 쉬워짐

테스트가 용이함

  • 의존성을 Mock Object로 교체하기가 쉬워짐

느슨한 결합 (Loose Coupling)

  • 컴포넌트 간 의존성이 확 줄어듬

Depends를 사용

  • 특정 기능을 수행한은 사용자 정의 함수를 만듦
  • 의존성 주입

의존성 오버라이딩

# 의존성 오버라이딩
def dependency():
    return {"key": "original"}

@app.get("/")
def main(dep=Depends(dependency)):
    return dep

def override_dependency():
    return {"key": "overridden"}

app.dependency_overrides[dependency] = override_dependency
  • Depends 함수를 다른 함수로 대체하여 특정 엔드포인트에서 의존성을 변경할 수 있음.

클래스 기반 의존성

# 클래스 기반 의존성
class DatabaseConnection:
    def __init__(self, db_url: str):
        self.db_url = db_url
class Repo:
    def __init__(self, conn: DatabaseConnection = Depends()):
        self.conn = conn

@app.get("/repo_items/")
def read_items(repo: Repo = Depends()):
    return {"db_url": repo.conn.db_url}
  • 초기화 과정에서 추가적인 설정이나 상태 관리가 용이함
  • 클래스 인스턴스는 call 매직 메서드를 구현해서 호출 가능하게 만들 수 있고 이 메서드 내에서 필요한 작업을 수행 할 수 있음

사용자 정의 종속성

# 사용자 정의 종속성을 반환하는 복잡한 의존성
def get_query():
    return {"q": "fastapi"}

def get_db():
    return{"db": "test_db"}

def complex_dependency(query=Depends(get_query), db=Depends(get_db)):
    return {"query": query, "db": db}

@app.get("/complex")
def read_complex_data(data=Depends(complex_dependency)):
    return data
  • 복잡한 의존성 관리를 가능하게 해줌
  • 여러 의존성을 하나의 의존성으로 묶을 수 있음

스코프, 생명 주기

# 스코프, 생명주기
class ScopedConnection:
    def __init__(self, requests: Request):
        self.request = requests

@app.get("/scoped_items/")
async def read_scoped_items(conn: ScopedConnection = Depends(ScopedConnection)):
    return {"status": "new connection for each request"}
  • Request 스코프는 요청시 생성 끝나면 해제되는 의존성을 정의함
  • Application 스코프는 애플리케이션이 시작 시 생성 종료시 해제됨

의존성 캐시

# 데이터베이스 연결을 시뮬레이션하는 클래스
class DBConnection:
    def __init__(self):
        self.conn = "Database Connection"

    async def __call__(self):
        # 실제 환경에서는 여기에서 데이터베이스 연결 로직을 실행
        return self.conn

# 의존성 인스턴스를 생성
db_connection = DBConnection()

# 의존성 함수
async def get_db_connection():
    return await db_connection()

# FastAPI 경로 작업
@app.get("/items/")
async def read_items(conn: str = Depends(get_db_connection, use_cashe=True)):
    # 첫 번째 호출에서 의존성을 계산하고, 같은 요청 동안의 후속 호출에서 캐시된 값을 사용.
    return {'database_connection': conn}

@app.get("/more-data/")
async def read_more_data(conn: str = Depends(get_db_connection, use_cache=True)):
    # 같은 요청에서 이전에 캐시된 데이터베이스 연결을 재사용합니다.
    return {"database_connection": conn}
  • 같은 요청 내에서 여러 번 의존성이 호출될 때 매번 의존성을 다시 실행하지 않고 캐시된 결과를 사용할 수 있게 해줌

비동기 지원

# 의존성의 비동기 지원
async def get_remote_data():
    async with httpx.AsyncClient() as client:
        response = await client.get("https://api.example.com/data")
        return response.json()
    
@app.get("/external")
async def external_data(data: dict = Depends(get_remote_data)):
    return data
  • 의존성 함수가 비동기로 정의되어 있으면 FastAPI는 자동적으로 비동기로 사용하게 됨
  • 빠르고 효율적인 API 응답이 가능
profile
나는 오예찬

0개의 댓글