uber-go/dig を使う
GoのDIコンテナ実装である uber-go/dig を使ってみました。
基本的な使い方はGoDocに書いてあります。あえて3行にまとめると以下になります。
c := dig.New() c.Provide(func (/* 生成に必要な型... */) /* 生成される型 */ { /* 生成処理 */ }) c.Invoke(func (/* 実行に必要な型 */) { /* 実行処理 */ })
例えば、以下のように Provide
メソッドを記述すると、 interface GetUserUsecase
を実装するオブジェクトを生成するには interface UserRepository
が必要であると宣言できます。
c := dig.New() c.Provide(func (r UserRepository) GetUserUsecase { return &GetUserUsecaseImpl{UserRepository: r} })
Springでいうconfiguration classの @Bean
と同じです。
// これはJava/Springの例 @Bean public GetUserUsecase(UserRepository r) GetUserUsecase { return new UserRepositoryImpl(r) }
依存関係に基づいて実際に値を取得するには Invoke
メソッドを実行します。例えば、以下のように記述すると interface GetUserUsecase
を実装するオブジェクトを取得して実行できます。
c.Invoke(func (u GetUserUsecase) {
u.Do()
})
Provide
で宣言した依存関係は再帰的に解決されます。例えば、以下のように記述すると interface GetUserUsecase
→ interface UserRepository
→ interface UserAPIClient
の依存関係を宣言できます。
c := dig.New() c.Provide(func (r UserRepository) GetUserUsecase { return &GetUserUsecaseImpl{UserRepository: r} }) c.Provide(func (c UserAPIClient) UserRepository { return &UserRepositoryImpl{UserAPIClient: c} })
実践的な使い方としては、依存関係を別のパッケージに切り出すとよいでしょう。
package di func New() (*dig.Container, error) { c := dig.New() if err := c.Provide(func (r UserRepository) GetUserUsecase { return &GetUserUsecaseImpl{UserRepository: r} }); err != nil { return nil, errors.WithStack(err) } if err := c.Provide(func (c UserAPIClient) UserRepository { return &UserRepositoryImpl{UserAPIClient: c} }); err != nil { return nil, errors.WithStack(err) } return c, nil }
宣言した依存関係が正しく解決できるかテストを書いておくとよいでしょう。
package di_test func TestInvoke(t *testing.T) { c, err := di.New() if err != nil { t.Fatalf("error while di.New: %+v", err) } if err := c.Invoke(func(u GetUserUsecase) { t.Logf("%+v", u) }); err != nil { t.Fatalf("error while c.Invoke: %+v", err) } }
構造体に dig.In
を埋め込むと構造体のメンバに定義されている型を解決して代入してくれます。この方法を使うと Provide
に渡す関数を簡潔に書けます。
type GetUserUsecaseImpl struct { dig.In UserRepository UserRepository FooRepository FooRepository BarRepository BarRepository }
c.Provide(func (i GetUserUsecaseImpl) GetUserUsecase { return &i })
Springでいう @Autowired
に近いですね。
// これはJava/Springの例 public class GetUserUsecaseImpl { @Autowired private UserRepository userRepository @Autowired private FooRepository fooRepository @Autowired private BarRepository barRepository }
なお、小規模なアプリケーションの場合は、digを使わずに手作業で依存関係を解決する方法もあります。例えば、以下のようにmainにすべての依存関係を書いてしまいます。
func main() { u := &GetUserUsecaseImpl{ UserRepository: &UserRepositoryImpl{ UserAPIClient: &UserAPIClientImpl{ Client: http.DefaultClient, }, }, } if err := u.Do(context.Background()); err != nil { log.Fatalf("Error: %s", err) } }
依存関係が単純な場合は手作業による解決でも十分ですが、複雑になってきた場合はdigのようなDIコンテナの導入を検討するとよいでしょう。