FastAPI 의존성 주입(Dependency Injection, DI) 이란?

박병현·2025년 2월 9일

🔎 FastAPI 의존성 주입(Dependency Injection, DI) 이란?

의존성 주입(DI, Dependency Injection)은 객체가 직접 다른 객체를 생성하지 않고, 외부에서 생성된 객체를 주입받아 사용하는 개념이다.
이를 통해 코드의 결합도를 낮추고, 테스트 용이성을 높이며, 유지보수를 쉽게 할 수 있다.

또한, FastAPI와 같은 프레임워크에서는 레이어드 아키텍처(Layered Architecture)와 함께 사용하면 더욱 효과적으로 관리할 수 있다.

추가로, FastAPI는 내장된 DI 시스템(Depends)을 제공하여, 객체 간의 의존성을 쉽게 관리할 수 있다.


1️⃣ FastAPI의 레이어드 아키텍처와 DI

FastAPI에서 레이어드 아키텍처(Layered Architecture)는 코드의 유지보수성, 확장성 및 테스트 용이성을 높이는 중요한 구조이다.
레이어드 아키텍처는 일반적으로 3계층 구조를 사용하며, 각 계층의 역할을 분리한다. 이를 통해 코드의 명확한 책임 분담과 독립성을 보장한다.

계층역할
Presentation Layer (routers/book.py)클라이언트 요청을 받아 Service Layer로 전달
Service Layer (src/book/service.py)비즈니스 로직을 처리하고 Repository Layer와 통신
Persistence Layer (src/book/repository.py)데이터베이스와 직접 연결하여 데이터 저장 및 조회

이 아키텍처에서 의존성 주입(DI)은 중요한 역할을 한다. DI를 사용하면 각 계층 간의 의존성을 외부에서 관리할 수 있기 때문에 계층 간 결합도를 낮추고 각 계층을 독립적으로 테스트하고 유지보수할 수 있다.

다음은 FastAPI의 레이어드 아키텍처에서 의존성 주입(DI)이 이루어지는 흐름을 시각적으로 표현한 Mermaid 다이어그램입니다.

설명

  1. Presentation Layer (라우터): 클라이언트 요청을 받아 Depends(BookService)를 통해 Service Layer에 요청을 전달한다.
  2. Service Layer: 비즈니스 로직을 처리하며, Depends(BookRepository)를 사용하여 Repository Layer에 데이터 요청을 보낸다.
  3. Repository Layer: 실제 데이터베이스와 연결하여 데이터를 저장 및 조회하며, 주입받은 DB 세션(Session)을 활용한다.

💡 위 다이어그램을 기반으로, 의존성 주입이 어떻게 이루어져야 하는지, 한눈에 쉽게 파악할 수 있다!

의존성 주입(DI)과 레이어드 아키텍처의 관계

  • Presentation Layer는 클라이언트의 요청을 받으며, 요청에 따라 Service Layer를 호출한다.
  • Service Layer는 비즈니스 로직을 처리하며, 데이터베이스와의 상호작용은 Repository Layer에 맡긴다.
  • Repository Layer는 데이터베이스와의 연결을 담당하고, 데이터를 저장하거나 조회하는 작업을 수행한다.

의존성 주입Service LayerRepository Layer 간의 의존성을 관리하는 중요한 도구가 된다.
예를 들어, Service LayerRepository Layer를 직접 생성하지 않고 외부에서 주입받아 사용한다. 이는 각 계층의 독립성을 높이고, 테스트 시 Mock 객체를 사용하기 용이하게 만든다.


2️⃣ "의존성(Dependency)"이란?

의존성이란 하나의 객체가 다른 객체를 필요로 하는 관계를 의미한다.
예를 들어, 서비스(Service)리포지토리(Repository)에 의존한다고 가정해보자.

📌 의존성이 있는 코드 예제 (문제점)

from sqlalchemy.orm import Session
from src.book.models import Book

class BookRepository:
    def __init__(self):
        self.db = Session()  # ❌ 여기서 직접 Session 객체를 생성 (잘못된 예시)

    def list_books(self):
        return self.db.query(Book).all()

문제점
1. BookRepository가 데이터베이스 세션(Session)에 직접 의존하고 있음 (self.db = Session())

  • 만약 Session의 구현이 변경되면, BookRepository도 함께 수정해야 한다.
  • 예를 들어, 동기식 SQLAlchemy에서 비동기 세션(AsyncSession)으로 바꾸려면 BookRepository를 전면 수정해야 한다.
  1. 테스트하기 어려움
    • BookRepository를 테스트할 때, 항상 실제 데이터베이스가 필요하다.
    • 만약 Session이 네트워크를 사용하거나 실제 데이터베이스를 필요로 하면, 빠른 단위 테스트(Unit Test)를 수행하기 어려워진다.

3️⃣ 의존성 주입(DI) 적용 후 개선된 코드

의존성 주입을 사용하면 BookRepository가 직접 Session을 생성하지 않고, 외부에서 주입받도록 개선할 수 있다.

📌 의존성 주입을 적용한 코드

from sqlalchemy.orm import Session
from src.book.models import Book

class BookRepository:
    def __init__(self, db: Session):  # ✅ Session을 외부에서 주입받음
        self.db = db

    def list_books(self):
        return self.db.query(Book).all()

Repository는 더 이상 Session을 직접 생성하지 않음
db: Session을 외부에서 주입받아 관리
Session이 변경되더라도 BookRepository를 수정할 필요 없음

💡 즉, BookRepositorySession에 직접 의존하지 않고, 외부에서 주입받아 사용하도록 개선되었다.


4️⃣ FastAPI에서 Depends()를 활용한 DI 적용

FastAPI에서는 Depends()를 활용하여 의존성을 자동으로 주입할 수 있다.
이를 통해 객체 생성을 직접 관리하지 않아도 되고, 유지보수가 쉬워진다.

📌 FastAPI에서 Depends()를 활용한 DI 적용 예시

from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session

class Database:
    def query(self):
        return "데이터 조회 완료"

class BookRepository:
    def __init__(self, db: Database):
        self.db = db

    def list_books(self):
        return self.db.query()

def get_database() -> Database:
    return Database()

def get_repository(db: Database = Depends(get_database)) -> BookRepository:
    return BookRepository(db)

app = FastAPI()

@app.get("/")
def read_root(repository: BookRepository = Depends(get_repository)):
    return {"data": repository.list_books()}

Depends(get_repository)를 사용하여 Repository 객체를 자동으로 주입
Depends(get_database)를 사용하여 Database 객체를 자동으로 주입
FastAPI가 의존성을 자동으로 관리하여 코드의 유지보수성을 높임

💡 즉, FastAPI의 Depends()를 활용하면 객체 생성을 직접 관리하지 않아도 되고, 테스트 시에도 유연하게 Mock 객체를 주입할 수 있다.


5️⃣ FastAPI에서 DI를 적용한 레이어드 아키텍처 예제

from fastapi import APIRouter, Depends
from src.book.service import BookService
from dependencies.dependency import dependency_book_service

router = APIRouter(prefix="/books", tags=["Books"])

@router.get("/")
async def list_books(book_service: BookService = Depends(dependency_book_service)):
    return await book_service.list_books()

라우터에서는 Depends(dependency_book_service)를 활용하여 서비스만 주입받음
Service는 Repository를, Repository는 Database를 주입받아 레이어 간 결합도를 낮춤
FastAPI가 의존성을 관리하므로, 객체 생성 및 관리를 직접 할 필요가 없음


6️⃣ 결론: 의존성 주입(DI)의 핵심

기존 방식DI 방식
객체가 직접 다른 객체를 생성객체가 외부에서 제공받음
코드가 강하게 결합됨코드의 결합도가 낮아짐 (유지보수 쉬움)
테스트하기 어려움가짜(Mock) 객체를 쉽게 주입 가능

DI를 사용하면 코드가 더 유연해지고, 유지보수가 쉬워지며, 테스트가 용이해진다. 🚀

Python 및 FastAPI를 활용하는 프로젝트에서 DI를 적극적으로 활용하면 더 효율적이고 유지보수하기 쉬운 코드를 작성할 수 있다.

profile
AI Application Engineer

0개의 댓글