👻
PythonでDIする(Dependency Injector)
dependency_injector は Python 向けの依存性注入ライブラリです。
基本的な使い方
Dependency Injector を利用して依存性注入を行う基本的な流れは、以下の手順で実現します。
- サービスの定義
まず、抽象クラス(インターフェース)を定義し、その実装となる具体的なサービスクラスを作成します。
これにより、サービスの実装が呼び出し元から独立し、後から簡単に入れ替えやテストが行える設計になります。 - コンテナの設定
次に、dependency_injector の containers.DeclarativeContainer を継承したコンテナクラス(例: ApplicationContainer)を定義します。
このコンテナ内で、設定値(config)やサービスを生成するプロバイダー(例: providers.Factory)を設定します。
ここで定義されたプロバイダーを通じて、各サービスのインスタンスが必要な依存性とともに生成されます。 - 依存性の注入
アプリケーションのエントリーポイント(例: main.py)では、コンテナを生成し、設定値を読み込みます。
その後、コンテナからサービスのインスタンスを取得して使用します。
これにより、main.py はサービスの具体的な実装に依存することなく、コンテナ経由で必要な機能を利用できるようになります。 - 自動依存性注入(@inject の利用)
さらに、@inject デコレータを用いることで、関数の引数に依存性を自動的に注入することが可能です。
このように、Dependency Injector を利用することで、依存性の管理がシンプルになり、アプリケーションの拡張性やテストのしやすさが向上します。以下のコード例を参考に、各ステップの実装方法を確認してください。
ディレクトリ構成
.
├── containers.py
├── main.py
└── services.py
Laravelではサービスプロバイダを用いて依存性を注入しますが、dependency_injectorではコンテナを通じて依存性注入を実現します。この例では、main.pyはExampleServiceの具体的な実装を直接参照することなく、ApplicationContainerからExampleServiceのインスタンスを取得しています。
1. サービスの定義
まずはサービスのインターフェースと実装を定義します。
from abc import ABC, abstractmethod
class IExampleService(ABC):
@abstractmethod
def do_something(self) -> str:
pass
class ExampleService(IExampleService):
def __init__(self, greeting: str):
self.greeting = greeting
def do_something(self) -> str:
return f"{self.greeting}, Dependency Injection!"
2. コンテナの定義
サービスを提供するためのコンテナを作成します。
from dependency_injector import containers, providers
from services import ExampleService
class ApplicationContainer(containers.DeclarativeContainer):
config = providers.Configuration()
example_service_provider = providers.Factory(
ExampleService,
greeting=config.greeting,
)
3. コンテナを使った依存性の注入
作成したコンテナを使用して依存性を注入します。
from containers import ApplicationContainer
from services import IExampleService
def main():
container = ApplicationContainer()
container.config.from_dict({"greeting": "Hello"})
example_service: IExampleService = container.example_service_provider()
print(example_service.do_something()) # "Hello, Dependency Injection!"
if __name__ == "__main__":
main()
4. @injectで依存性を注入する
dependency_injector は自動で依存性を注入する仕組みも提供しています。
from dependency_injector.wiring import inject, Provide
from containers import ApplicationContainer
from services import ExampleService
@inject
def run_service(
service: ExampleService = Provide[ApplicationContainer.example_service_provider]
):
print(service.do_something())
if __name__ == "__main__":
container = ApplicationContainer()
container.config.from_dict({"greeting": "Hello"})
container.wire(modules=[__name__])
run_service()
実行してみる
$ python main.py
Hello, Dependency Injection!
まとめ
fastapiの依存注入だと、依存性を注入するために関数の引数に依存性を渡す必要がありますが、dependency_injector を使うと、依存性を注入するためのコンテナを作成し、コンテナを使って依存性を注入することができます。
Discussion