1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DIコンテナの使い所

Last updated at Posted at 2025-10-02

DIコンテナを整備するメリットについて、Dependency Injection(依存性の注入)の観点から、具体的なPythonコードを交えて解説します。
DIコンテナ導入のメリット:なぜ依存関係を管理するのか?
DIコンテナは、一言で言うと**「クラス間の依存関係を自動的に解決し、オブジェクトの生成と注入(Injection)を一元管理してくれるツール」です。
これを導入する最大のメリットは、
「疎結合(So-Ketsugou)」**な設計を容易に実現できることにあります。疎結合とは、各コンポーネント(クラス)が互いに独立しており、変更やテストがしやすい状態を指します。
以下で、DIの基本からDIコンテナの具体的なメリットまでをコードと共に見ていきましょう。

  1. DI(Dependency Injection)の基本:問題点の理解
    まず、DIがない場合に何が問題になるのかを見てみましょう。
    DIがないコード例
    UserServiceが、内部でUserRepositoryを直接生成しています。
# user_repository.py
class UserRepository:
    def get_user(self, user_id: int) -> str:
        # 本来はデータベースにアクセスする
        print(f"DBからID:{user_id}のユーザーを取得しました。")
        return f"User {user_id}"

# user_service.py
from user_repository import UserRepository

class UserService:
    def __init__(self):
        # ❌ 問題点: UserServiceがUserRepositoryの実装に直接依存している(密結合)
        self.user_repository = UserRepository()

    def get_user_info(self, user_id: int) -> str:
        return self.user_repository.get_user(user_id)

# main.py
from user_service import UserService

def main():
    user_service = UserService()
    user_info = user_service.get_user_info(1)
    print(user_info)

if __name__ == "__main__":
    main()

このコードの問題点は、UserServiceがUserRepositoryという具体的なクラスを直接知ってしまっている(依存している)点です。もしUserRepositoryをテスト用のMockUserRepositoryに差し替えたい場合、UserServiceのコード自体を書き換える必要があり、非常に手間がかかります。これが**「密結合」**な状態です。
DIを適用したコード例(手動DI)
次に、DIの考え方を使ってこの問題を解決します。依存するオブジェクトを外部から注入(渡してあげる)ように変更します。

# user_service_di.py
# (UserRepositoryは上記と同じ)
from user_repository import UserRepository

class UserService:
    def __init__(self, user_repository: UserRepository):
        # ✅ 改善点: 依存オブジェクトを外部から受け取る
        self.user_repository = user_repository

    def get_user_info(self, user_id: int) -> str:
        return self.user_repository.get_user(user_id)

# main_di.py
from user_repository import UserRepository
from user_service_di import UserService

def main():
    # 依存関係の解決を外部(main)で行う
    repo = UserRepository()
    service = UserService(user_repository=repo) # ⬅️ ここで注入!
    
    user_info = service.get_user_info(1)
    print(user_info)

if __name__ == "__main__":
    main()

これで、UserServiceはUserRepositoryの具体的な実装から切り離されました。しかし、アプリケーションが大きくなり、A→B→C→Dのように依存関係が複雑になると、mainでのオブジェクト生成と注入のコードがどんどん肥大化し、管理が大変になります。
2. DIコンテナによるメリット
そこで登場するのがDIコンテナです。ここでは人気のライブラリdependency-injectorを使います。
メリット1: 依存関係の解決を一元管理できる
DIコンテナを使うと、どこで何が生成され、どこに注入されるのかという設定を1箇所に集約できます。

# まずはライブラリをインストールします
# pip install dependency-injector
# containers.py
from dependency_injector import containers, providers
from user_repository import UserRepository
from user_service_di import UserService # DI適用済みのUserService

class Container(containers.DeclarativeContainer):
    
    # configは外部ファイル(config.ini)などから読み込むことも可能
    config = providers.Configuration()

    # UserRepositoryのインスタンス生成方法を定義
    user_repository = providers.Factory(
        UserRepository
    )
    
    # UserServiceのインスタンス生成方法を定義
    # 依存するuser_repositoryには、上で定義したものを注入するよう指定
    user_service = providers.Factory(
        UserService,
        user_repository=user_repository
    )

# main_container.py
from containers import Container

def main():
    # コンテナを初期化
    container = Container()
    
    # コンテナからUserServiceのインスタンスを取得
    # 依存関係はコンテナが自動で解決してくれる!
    user_service = container.user_service()
    
    user_info = user_service.get_user_info(1)
    print(user_info)

if __name__ == "__main__":
    main()

main関数が非常にスッキリしました。container.user_service()を呼び出すだけで、DIコンテナが裏側でUserRepositoryを生成し、それをUserServiceに注入して、完成したインスタンスを返してくれます。依存関係の定義はすべてContainerクラスに集約されているため、見通しが良くなります。
メリット2: テストが圧倒的に簡単になる
DIコンテナの最も強力なメリットの一つが、**テスト時の依存性の差し替え(オーバーライド)**です。
テスト用のMockUserRepositoryを用意します。

# mock_repository.py
class MockUserRepository:
    def get_user(self, user_id: int) -> str:
        print("Mockからテスト用ユーザー情報を返します。")
        return f"Mock User {user_id}"

このモックをテストコードで本物のUserRepositoryの代わりに注入します。

# test_user_service.py
import unittest
from containers import Container
from mock_repository import MockUserRepository
from user_service_di import UserService

class TestUserService(unittest.TestCase):
    def test_get_user_info_with_mock(self):
        # DIコンテナを初期化
        container = Container()
        
        # 💡 ここが重要!
        # user_repositoryの提供元をMockUserRepositoryに上書きする
        with container.user_repository.override(MockUserRepository()):
            
            # コンテナからUserServiceを取得すると、
            # 内部のUserRepositoryはMockに差し替わっている
            user_service = container.user_service()
            
            # テスト実行
            result = user_service.get_user_info(99)
            
            # 検証
            self.assertEqual(result, "Mock User 99")

if __name__ == "__main__":
    unittest.main()

container.user_repository.override()を使うことで、アプリケーションの他のコード(UserServiceなど)を一切変更することなく、依存先をテスト用のモックに差し替えることができました。これにより、データベースなどに接続しない、独立した高速なユニットテストが簡単に実現できます。
まとめ
DIコンテナを整備するメリットは以下の通りです。

メリット 解説
依存関係の一元管理 オブジェクトの生成と注入のロジックがコンテナに集約され、コードの見通しが良くなる。
疎結合の促進 各クラスが具体的な実装に依存しなくなるため、変更に強い柔軟なシステムを構築できる。
テスト容易性の向上 override機能などにより、テスト用のモックへの差し替えが極めて簡単になり、品質の高いテストを効率的に書ける。
再利用性の向上 一度コンテナに登録したコンポーネントは、アプリケーションの様々な場所で簡単に再利用できる。
アプリケーションが小規模なうちは手動DIでも問題ありませんが、規模が大きくなるにつれてDIコンテナの恩恵は計り知れないものになります。保守性・拡張性の高いアプリケーション開発のための、非常に強力なツールです。
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?