DI

依存性の注入(Dependency Injection)とは、オブジェクトが必要とするオブジェクト(依存)を、自身でnewして構築するのではなく、外部から提供することです。依存を構築するにはそのまた依存が必要、という繰り返しの結果、アプリケーションは相互に接続されたオブジェクトの網(オブジェクトグラフ)になります。このグラフを手作業で構築すると、労力がかかり、ミスが発生しやすく、テストが困難になります。BEAR.Sundayでは、その代わりにDIフレームワークのRay.Diがオブジェクトグラフを構築します。

書くコード

クラスは、必要なものをコンストラクタで宣言するだけです。

class Index extends ResourceObject
{
    public function __construct(
        private readonly UserRepositoryInterface $repository
    ) {}

    public function onGet(string $id): static
    {
        $this->body = $this->repository->get($id);

        return $this;
    }
}

IndexUserRepositoryInterfaceの実装が何であるか、それがどう作られるかを知りません。実装を決めるのは利用するクラスではなく、モジュールに記述する束縛です。

$this->bind(UserRepositoryInterface::class)->to(UserRepository::class);

インジェクターはこの束縛に従い、依存の依存まで含めてグラフ全体を再帰的に組み立てます。オブジェクトの「利用」と「構築」が分離されているので、実装の差し替えが利用側のコードに波及せず、テストではモックを束縛するだけで済みます。

束縛が決める

束縛には目的に応じた種類があります。

  • リンク束縛 to() — インターフェイスに実装クラスを対応させる、最も基本の束縛
  • プロバイダー束縛 toProvider() — 生成にロジックが必要なとき、ファクトリーを束縛
  • インスタンス束縛 toInstance() — 生成済みの値やインスタンスを直接束縛(接続文字列・設定値・作成済みオブジェクトなど)
  • 名前付き束縛 annotatedWith() — 同じ型の依存を名前で区別

構文の一覧はモジュールを、それぞれの詳細はRay.Diマニュアルを参照してください。メソッド横断の関心事(ログ、認証、トランザクションなど)は、依存の束縛とは別に、AOPbindInterceptor()によるメソッドインターセプション)で扱います。

コンテキストでグラフが変わる

束縛のセットを切り替えれば、コードは同じままアプリケーションの振る舞いが変わります。BEAR.Sundayではこの束縛のセットをコンテキストと呼び、prod-hal-api-appのように組み合わせて指定します。開発用・プロダクション用・API用で異なるオブジェクトグラフが構築されるため、設定ファイルや実行モードによる条件分岐は必要ありません。詳しくはアプリケーションを参照してください。

Ray.DiはGoogle Guiceに大きく影響を受けたDIフレームワークです。