💉
Dependency Injectorについて(FastAPI)
1. FastAPI + Dependency Injector
- FastAPI: Python で人気の Web フレームワーク。
- DI(依存性注入)の仕組みとして Depends を提供。
- Dependency Injector: Python の高度な依存性注入ライブラリ。
- ライフサイクルの管理(シングルトン、ファクトリなど)が強力。
この2つを組み合わせると、「複雑な依存関係」 を 「明確に管理」 できる。
2. ざっくりした流れ
- サービスクラス を作る(実際の処理ロジックを担当)。
- DI コンテナ を定義する(サービスをどんなスコープ・設定値で作るか管理)。
- FastAPI アプリ を立ち上げる(コンテナを初期化し、API エンドポイントと紐づけ)。
- エンドポイント でコンテナから依存を取り出し、処理を実行。
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()}
主なポイント
- 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 にコンテナをアタッチ(あとで参照するかもしれないため)。
- @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. 実行イメージ
- アプリ起動
- FastAPI が startup_event を呼び出す。
- Container を作って config.message に値を設定。
- 以後、Provide[Container.service] を呼び出すたびに、コンテナが Service を作る。
- GET / へのリクエスト
- index 関数が呼ばれる。
- Depends(Provide[Container.service]) により、Service のインスタンスが自動生成され、注入される。
- service.process() が呼ばれ、結果を JSON で返す。
5. なぜ @inject が必要か?
- @inject は、Dependency Injector が関数の依存性注入を補助するデコレータ。
- これを付けることで、関数引数で Provide[...] を使えるようになり、コンテナが自動的に解決してくれる。
6. まとめ
- DI コンテナ にどのクラスをどう生成するかを登録(config.message など設定も管理)。
- FastAPI の startup_event でコンテナを初期化し、wire でモジュールと紐づけ。
- エンドポイント で @inject と Depends(Provide[Container.XXX]) を使うことで、
- 必要なオブジェクト (Service など) が自動生成され、
- メソッド内で簡単に使える。
Discussion