💉

Dependency Injectorについて(FastAPI)

2025/01/19に公開

公式ドキュメント


1. FastAPI + Dependency Injector

  • FastAPI: Python で人気の Web フレームワーク。
    • DI(依存性注入)の仕組みとして Depends を提供。
  • Dependency Injector: Python の高度な依存性注入ライブラリ。
    • ライフサイクルの管理(シングルトン、ファクトリなど)が強力。

この2つを組み合わせると、「複雑な依存関係」 を 「明確に管理」 できる。


2. ざっくりした流れ

  1. サービスクラス を作る(実際の処理ロジックを担当)。
  2. DI コンテナ を定義する(サービスをどんなスコープ・設定値で作るか管理)。
  3. FastAPI アプリ を立ち上げる(コンテナを初期化し、API エンドポイントと紐づけ)。
  4. エンドポイント でコンテナから依存を取り出し、処理を実行。

3. 段階的に解説

(1) サービスの定義

# service.py
class Service:
    def __init__(self, message: str):
        self._message = message

    def process(self) -> str:
        return f"Processed {self._message}"
  • Service クラス: message を受け取って何か処理をするクラス。
  • コンストラクタで message を保持し、process で結果を返すシンプルな例。

(2) DI コンテナの定義

# containers.py
from dependency_injector import containers, providers
from .service import Service

class Container(containers.DeclarativeContainer):
    config = providers.Configuration()
    service = providers.Factory(Service, message=config.message)
  • Container クラス: containers.DeclarativeContainer を継承して、依存性を登録する。
  • config = providers.Configuration():
    • アプリ設定(例: 環境変数やファイルの設定)を扱うプロバイダ。
  • service = providers.Factory(...):
    • Service クラスを ファクトリモード で生成する設定を行う。
    • message=config.message により、config に登録された値を引数にして Service が作られる。

Factory とは?

  • Factory: 呼び出されるたびに 新しい Service インスタンス を作る設定。
  • 他にも Singleton など様々なプロバイダがある。

(3) FastAPI アプリとの統合

# main.py
from fastapi import FastAPI, Depends
from dependency_injector.wiring import inject, Provide

from .containers import Container
from .service import Service

app = FastAPI()

@app.on_event("startup")
def startup_event():
    container = Container()
    container.config.message.from_env("MESSAGE", default="Hello, world!")
    container.wire(modules=[__name__])

    # アプリ内でコンテナを保持(必要に応じて使えるようにする)
    app.container = container

@inject
@app.get("/")
def index(service: Service = Depends(Provide[Container.service])):
    return {"result": service.process()}

主なポイント

  1. startup_event でコンテナ初期化
  • container = Container() でコンテナを作る。
  • container.config.message.from_env("MESSAGE", default="Hello, world!")
    • 環境変数 MESSAGE があればそれを使い、なければデフォルト文字列を使う。
  • container.wire(modules=[_name_])
    • @inject や Provide[...] を使うモジュールを指定して紐づける。
  • app.container = container
    • FastAPI の app にコンテナをアタッチ(あとで参照するかもしれないため)。
  1. @inject デコレータ と Depends(Provide[...]) の組み合わせ
@inject
@app.get("/")
def index(service: Service = Depends(Provide[Container.service])):
    return {"result": service.process()}
  • @inject: この関数内の依存性注入を有効にする。
  • service: Service = Depends(Provide[Container.service])
    • Container.service が service = providers.Factory(Service, message=config.message) で定義されているので、そのインスタンスを取得。
    • 注入された service オブジェクトを使い service.process() を呼び出し、レスポンスを返す。

4. 実行イメージ

  1. アプリ起動
  • FastAPI が startup_event を呼び出す。
  • Container を作って config.message に値を設定。
  • 以後、Provide[Container.service] を呼び出すたびに、コンテナが Service を作る。
  1. GET / へのリクエスト
  • index 関数が呼ばれる。
  • Depends(Provide[Container.service]) により、Service のインスタンスが自動生成され、注入される。
  • service.process() が呼ばれ、結果を JSON で返す。

5. なぜ @inject が必要か?

  • @inject は、Dependency Injector が関数の依存性注入を補助するデコレータ。
  • これを付けることで、関数引数で Provide[...] を使えるようになり、コンテナが自動的に解決してくれる。

6. まとめ

  1. DI コンテナ にどのクラスをどう生成するかを登録(config.message など設定も管理)。
  2. FastAPI の startup_event でコンテナを初期化し、wire でモジュールと紐づけ。
  3. エンドポイント で @inject と Depends(Provide[Container.XXX]) を使うことで、
    • 必要なオブジェクト (Service など) が自動生成され、
    • メソッド内で簡単に使える。

Discussion