Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】If you're looking for what comes after wire, take a look at kanna-di

2026年10月31日に「Dev.to」が公開したITニュース「If you're looking for what comes after wire, take a look at kanna-di」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

Go言語の新しいDIツール「kanna-di」が登場した。構造体に印を付けるだけで、プログラムに必要な部品(依存)を自動で探し、組み立てる。手動での設定が減り、テスト時の部品交換も容易になるため、効率的に開発を進められる。

ITニュース解説

Go言語で大規模なアプリケーションを開発する際、プログラムの部品(コンポーネント)間の依存関係を管理することは非常に重要だ。依存性注入(Dependency Injection、DI)という設計パターンは、この問題を解決するための強力な手法として知られている。DIは、コンポーネントが必要とする別のコンポーネントを、そのコンポーネント自身が作るのではなく、外部から与える(注入する)ことで、各コンポーネントが特定の別のコンポーネントに直接結びつく(結合する)度合いを低くする。これにより、コードの再利用性が高まり、テストがしやすくなり、アプリケーション全体の保守性も向上する。

Go言語の世界では、長らく「google/wire」がコンパイル時にDIを行うための主要なツールとして使われてきた。しかし、wireは現在、新しい機能の追加を停止している。そのため、wireに代わる、より効率的で開発者の負担を軽減するツールが求められていた。そこで登場したのが「kanna-di」というツールだ。

kanna-diは、wireが抱えていたいくつかの課題を解決することを目指している。wireを使う場合、開発者はいくつかの手作業が必要だった。例えば、wire.goという特殊なファイルを作成し、インジェクター(依存性を注入する役割を果たすもの)の関数署名を自分で記述する必要があった。また、wire.NewSetという関数を使って、アプリケーション内で使われるすべてのプロバイダ(特定の型を提供する関数)をリストアップし、集める必要があった。インターフェースを使う場合は、そのインターフェースに対してどの具体的な実装を使うかをwire.Bindを使って一つ一つ指定しなければならなかった。新しいコンストラクタ(型を生成する関数)を追加するたびに、それらをwireのセットに手動で追加する手間も発生した。つまり、wireでは、どの関数を依存性として使うかをアプリケーションのビジネスロジックとは別の場所で繰り返し指定する必要があり、これが煩雑さを生んでいた。コンストラクタは通常、それらが生成する型のすぐ近くに定義されているにもかかわらず、wireのために別の場所で再度リストアップするという手間が発生していたのだ。

kanna-diは、この課題に対して異なるアプローチを取る。開発者が行うのは、依存性をまとめる「コンテナ」となる構造体(アプリケーション全体の依存関係を集約する場所)に//kanna:containerという特別なコメントを付けることだけだ。kanna-diは、このコンテナ構造体のフィールドの型を自動的に調べ、その型を生成するコンストラクタ関数を型の近くから自動的に探し出す。さらに、見つかったコンストラクタが持つ引数(別の依存性)を再帰的にたどり、すべての依存性を適切な順序で呼び出して解決するコードを自動的に生成する。生成されるコードは、純粋なGo言語のコードであり、実行時に特別なライブラリをインポートしないため、実行時のオーバーヘッドがないというメリットもwireと同様に持っている。

具体的な例で見てみよう。もしあなたのアプリケーションが、ユーザー登録のユースケース、永続化のためのリポジトリ、ウェルカムメール送信のためのメール送信機能など、複数の層に分かれているとする。それぞれの層で、コンストラクタは通常通り、その型と一緒に定義されている。例えば、NewRegisterUserという関数がユーザー登録のユースケースを生成し、user.Repositoryとmail.Senderというインターフェースを必要とするとする。postgres.NewUserRepositoryがuser.Repositoryの実装を提供し、sendgrid.NewSenderがmail.Senderの実装を提供するとしよう。

この時、アプリケーションの起動時に必要なすべての依存性を保持するメインのserver構造体があると仮定する。このserver構造体に//kanna:containerとマークする。そして、user.Repositoryやmail.Senderといったインターフェース型のフィールドに、di:"with=postgres.NewUserRepository"やdi:"with=sendgrid.NewSender"のようにタグを付けることで、どの具体的な実装を使うかを指定する。また、openDBのような、コンテナと同じパッケージ内にあるデータベース接続関数や、config.Loadといった設定ロード関数なども、kanna-diが自動的に認識して利用する。

この状態でgo generate ./...コマンドを実行すると、kanna-diはdi_gen.goというファイルを自動生成する。このファイルには、newServer()のような関数が定義されており、その中でconfig.Load()を呼び出して設定を読み込み、openDB()でデータベースを初期化し、postgres.NewUserRepositoryとsendgrid.NewSenderを呼び出してリポジトリとメール送信サービスを生成し、それらをNewRegisterUserやNewGetUserといったユースケースのコンストラクタに渡して初期化し、最終的にすべての依存性が解決されたserver構造体を組み立てる処理が記述される。開発者は、依存性を一つ一つ手動で繋ぎ合わせるコードを書く必要がなく、//kanna:containerといくつかのタグを付けるだけで、これらの初期化処理が自動で生成されるのだ。コンストラクタの引数が変更されても、次のgo generateを実行すれば、生成されるコードも自動的にそれに合わせて更新される。

kanna-diがプロバイダ(依存性を提供する関数)を見つける方法には、明確なルールがある。ある型が必要になったとき、kanna-diは以下の優先順位でプロバイダを探す。まず、コンストラクタの引数としてdi:"arg"タグが指定されていればそれを使う。次に、コンテナのフィールドでdi:"with=..."タグによって指定されたプロバイダを探す。さらに、コンテナが埋め込んでいる、または_フィールドとして宣言している構造体のエクスポートされたフィールドも対象になる。もしこれらの指定がなければ、コンテナ自身のパッケージ内にある関数や、その型を宣言しているパッケージ内のエクスポートされた関数を検索する(ただし、これは自分のモジュールやgo.work内に限定される)。これらのルールに従ってプロバイダが見つからなかったり、複数の候補が見つかったりすると、生成は停止しエラーとなる。標準ライブラリや他のモジュールの関数が自動的に選ばれることはないよう、意図的に設計されている。

DIの設定において最も時間がかかるのは、依存性の解決に失敗した時だ。kanna-diは、この点でも開発者をサポートする。エラーが発生した場合、どのフィールドから問題が発生したのかを、依存関係の解決経路とともに分かりやすく表示する。例えば、インターフェースの実装指定を忘れた場合、「mail.Senderを提供するものが見つからない。usecase.NewRegisterUserが必要としている」というエラーメッセージとともに、問題の発生源であるserver.usersフィールドと、そこに至るまでのhandler.NewUser、usecase.NewRegisterUserという経路を教えてくれる。また、複数の候補がある場合も、どの関数が候補になっているかを示し、di:"with=..."タグを使ってどちらを選ぶべきかを具体的にアドバイスしてくれる。

テストにおけるDIの考え方も、kanna-diはwireと大きく異なる点だ。wireでは、テスト用に別のインジェクターとプロバイダセットを定義し直す必要があった。しかしkanna-diでは、テスト用の_test.goファイル内に新しいコンテナ構造体(例:testServer)を定義し、そこにプロダクション環境で使うserver構造体を埋め込む。さらに、テストで差し替えたい依存性、例えばデータベースリポジトリをインメモリのものに、メール送信機能を記録用のモックにしたい場合、それらをtestServer構造体のフィールドとしてdi:"arg"タグ付きで宣言する。これにより、kanna-diはtestServerを生成する際、データベース接続や実際のメール送信サービスを呼び出す代わりに、di:"arg"で指定された依存性を外部から引数として受け取るように生成コードを変更する。結果として、プロダクションの複雑な初期化ロジックは呼び出されず、テストで用意したシンプルなモックが注入されるようになる。これは、プロダクションのコンテナのフィールドを一つずつ再構築するような挙動となり、必要な部分だけを柔軟にオーバーライドできるため、非常に効果的だ。

実際のアプリケーションでは、kanna-diは複数の層にわたって利用できる。例えば、設定(Config)やインフラストラクチャ(Infra)のためのコンテナ、各ユースケース(Usecase)のためのコンテナ、そしてそれらを束ねるハンドラー(Handler)のコンテナといった具合に、階層的に依存性を管理できる。また、_フィールドを使って、特定の構造体自体はコンテナに保存せず、その構造体が提供する依存性のみを利用する、といった高度な使い方も可能だ。

プロバイダ関数がクリーンアップ関数(リソースを解放するための関数)を返す場合、kanna-diはそれらを自動的に収集し、依存性が構築された逆順で実行する。もし途中でプロバイダの初期化に失敗した場合でも、それまでに実行されたプロバイダのクリーンアップは適切に呼び出されてリソースが解放されるため、リソースリークの心配が少ない。

kanna-diはまだ開発中のツールであり、いくつかの制約もある。Go 1.26以降が必要であること、バージョン0のためAPIが今後変更される可能性があること、ジェネリック構造体をコンテナとして利用できないこと、特定のパッケージ構成においてテストコンテナの配置に制限があることなどが挙げられる。また、依存関係にあるモジュールのコンストラクタは自動的には検索されないという意図的な設計もある。しかし、これらの制約を理解した上で利用すれば、kanna-diはGo言語でのDIを大幅に簡素化し、開発効率を向上させる強力なツールとなるだろう。

kanna-diを試すには、まずgo get -tool github.com/go-kanna/di/cmd/kanna-diでツールをインストールし、コンテナとなる構造体に//kanna:containerとマークし、//go:generate go tool kanna-diというgo:generateディレクティブを追加して、go generate ./...を実行するだけだ。wireからの移行を考えている場合、wireのwire.NewSetでリストアップしていた関数は、ほとんどの場合すでに型と一緒に定義されているため、そのまま利用できる。wire.Bindは_ Iface \di:"with=pkg.NewImpl"``のようなフィールドタグに置き換えられ、インジェクター宣言の手間はなくなる。これはGo言語でよりクリーンで保守しやすいアプリケーションを構築するための、新しい一歩となるだろう。

関連コンテンツ

関連IT用語

関連ITニュース