# Runtime

Dependency

Injection in Go

Fukuoka.go #11 by Yuichi Watanabe @nulab

## Overview

Golang DI ライブラリ vvatanabe/shot を書きました

  • 所謂 DIコンテナ
     
  • inspired by google/guice
     
  • 実行時に動的にDI (Runtime DI) する
     
  • 設定がコードベース
     
  • オブジェクトの依存解決方法や生成タイミング等が指定できる
    • Constructor Function、Struct Field、Object
    • Singleton、Eager Singleton
object App {
    def main(args: Array[String]) {
        val injector = Guice.createInjector(new Module() {
            override def configure(binder: Binder): Unit = {
                binder.bind(classOf[UserRepository]).to(classOf[UserRepositoryOnDB])
                binder.bind(classOf[GroupRepository]).to(classOf[GroupRepositoryOnDB])
            }
        })
        val userRepository = injector.getInstance(classOf[UserRepository])
        val groupRepository = injector.getInstance(classOf[GroupRepository])
    }
}
func main() {
        injector, err := CreateInjector(func(binder Binder) {
                binder.Bind(new(UserRepository)).To(new(UserRepositoryOnDB))
                binder.Bind(new(GroupRepository)).To(new(GroupRepositoryOnDB))
        })
        userRepository := injector.Get(new(UserRepository))
        groupRepository := injector.Get(new(GroupRepository))
}

Scala:

Golang:

Like This

## Motivation

  • DI自体は、依存しているオブジェクトを外から指定できるように書けばいいだけなので、汎用化されたライブラリを使わなくてもできる。

 

  • しかし、その依存しているオブジェクトを外から指定するコードは結構地道な作業でちょっと気が乗りにくい。自動化したい。

 

  • Golang のコードベースで DI の設定を書けるようにしとけば、型の定義と設定がずれにくくなる。IDEでリファクタリングしたときなど自動で置き換わってくれる。

 

  • 普段の業務でScalaやJavaを書くなか、google/guice を使っていて慣れていたので、同じ様なインターフェースで Golang でも書いてみたい。

## Usage

package main

import (
	"github.com/vvatanabe/shot/shot"
)

func main() {

        injector, err := shot.CreateInjector(func(binder shot.Binder) {

                binder.Bind(new(UserRepository)).To(new(UserRepositoryOnDB))
                binder.Bind(new(GroupRepository)).To(new(GroupRepositoryOnDB))

        })

}

### Basis

Configure 関数にオブジェクトの生成ルールを記述すると、依存関係を解決してオブジェクトを生成する。

Constructor 関数で依存するオブジェクトをバインドする

injector, err := shot.CreateInjector(func(binder shot.Binder) {
        binder.Bind(new(GroupRepository)).ToConstructor(NewGroupRepositoryOnDB)
})

### Constructor Injection

### Field Injection

Struct の Field へ直接依存するオブジェクトをバインドする

type ProjectService struct {
        UserRepository    UserRepository   `inject:""`
        GroupRepository GroupRepository `inject:""`
}

injector, err := shot.CreateInjector(func(binder shot.Binder) {
        binder.Bind(new(ProjectService)).To(new(ProjectService))
})

生成済のオブジェクトを直接バインドする

injector, err := shot.CreateInjector(func(binder shot.Binder) {
        binder.Bind(new(Config)). ToInstance(&Config{})
})

### Instance Injection

シングルトンとしてオブジェクトを生成する

injector, err := shot.CreateInjector(func(binder shot.Binder) {
        binder.Bind(new(UserRepository)).
            To(new(UserRepositoryOnDB)).In(shot.SingletonInstance)
        binder.Bind(new(GroupRepository)).
            To(new(GroupRepositoryOnDB)).AsEagerSingleton()
})

### Singleton

コンテナからオブジェクトを取得する

injector, err := shot.CreateInjector(func(binder shot.Binder) {
        binder.Bind(new(UserRepository)).To(new(UserRepositoryOnDB))
        binder.Bind(new(GroupRepository)).To(new(GroupRepositoryOnDB))
})

userRepository := injector.Get(new(UserRepository)).(UserRepository)
groupRepository := injector.Get(new(GroupRepository)).(GroupRepository)

### Get Object

userRepository, err := injector.SafeGet(new(UserRepository))
groupRepository, err := injector.SafeGet(new(GroupRepository))

コンテナからオブジェクトを安全に取得する

## Approach

  • 本家と同じく依存するオブジェクトを解決するためにリフレクションを多様。

 

  • ここではリフレクションでオブジェクトを注入する流れを、簡略化したコードベースで紹介。

### Constructor Injection

constructor 関数の reflect.Valuereflect.Type を取得する。

  • reflect.Value: 値を扱う
  • reflect.Type: 型を扱う
func Resolve(injector Injector, constructorFunc interface{}) interface{} {
        constructor := reflect.ValueOf(constructorFunc)
        constructorType := reflect.TypeOf(constructorFunc)
       ...








}

### Constructor Injection

constructor 関数自身の reflect.Type から、各引数の reflect.Type を取得する。

  • NumIn(): 関数の引数の数を返す
  • In(int): 関数の引数の reflect.Type を返す
func Resolve(injector Injector, constructorFunc interface{}) interface{} {
        constructor := reflect.ValueOf(constructorFunc)
        constructorType := reflect.TypeOf(constructorFunc)
        var constructorArgs []reflect.Value
        for i := 0; i < constructorType.NumIn(); i++ {
                argType := constructorType.In(i)
                ...





}

### Constructor Injection

各引数の reflect.Type を元に、解決済のオブジェクトリストから引数に突っ込む値を取得する。

func Resolve(injector Injector, constructorFunc interface{}) interface{} {
        constructor := reflect.ValueOf(constructorFunc)
        constructorType := reflect.TypeOf(constructorFunc)
        var constructorArgs []reflect.Value
        for i := 0; i < constructorType.NumIn(); i++ {
                argType := constructorType.In(i)
                value := injector.Get(argType)
                ...




}

### Constructor Injection

引数に突っ込む値を、reflect.Value に変換してコンストラクタ関数に渡す引数の配列に追加する。

func Resolve(injector Injector, constructorFunc interface{}) interface{} {
        constructor := reflect.ValueOf(constructorFunc)
        constructorType := reflect.TypeOf(constructorFunc)
        var constructorArgs []reflect.Value
        for i := 0; i < constructorType.NumIn(); i++ {
                argType := constructorType.In(i)
                value := injector.Get(argType)
                reflectValue := reflect.ValueOf(value)
                constructorArgs = append(constructorArgs, reflectValue)
        }
        ...

}

### Constructor Injection

コンストラクタ関数の reflect.Value が持つ Call 関数に、先程取得した引数の []reflect.Value を与えて実行する。

  • func (v Value) Call(in []Value) []Value : 関数を実行する関数
func Resolve(injector Injector, constructorFunc interface{}) interface{} {
        constructor := reflect.ValueOf(constructorFunc)
        constructorType := reflect.TypeOf(constructorFunc)
        var constructorArgs []reflect.Value
        for i := 0; i < constructorType.NumIn(); i++ {
                argType := constructorType.In(i)
                value := injector.Get(argType)
                reflectValue := reflect.ValueOf(value)
                constructorArgs = append(constructorArgs, reflectValue)
        }
        results := constructor.Call(constructorArgs)
        ...
}

### Constructor Injection

コンストラクタ関数は1つだけ値を返すとゆう制約のもと、結果の配列の頭にDI済のオブジェクトが格納されている。

func Resolve(injector Injector, constructorFunc interface{}) interface{} {
        constructor := reflect.ValueOf(constructorFunc)
        constructorType := reflect.TypeOf(constructorFunc)
        var constructorArgs []reflect.Value
        for i := 0; i < constructorType.NumIn(); i++ {
                argType := constructorType.In(i)
                value := injector.Get(argType)
                reflectValue := reflect.ValueOf(value)
                constructorArgs = append(constructorArgs, reflectValue)
        }
        results := constructor.Call(constructorArgs)
        return results[0].Interface()
}

### Constructor Injection

### Field Injection

### Field Injection

コンストラクタ関数は1つだけ値を返すとゆう制約のもと、結果の配列の頭にDI済のオブジェクトが格納されている。

func Resolve(injector Injector, structure interface{}) interface{} {
        structureType := reflect.ValueOf(structure)
        structureValue := reflect.TypeOf(structure)
...







}

### Field Injection

struct の reflect.Type 型 から NumField 関数 を 実行して フィールドの数を取得する。 Field 関数に indexを 渡して、フィールドの型情報 (reflect.StructField)フィールドの値情報 (reflect.Value) を取得する。

func Resolve(injector Injector, structure interface{}) interface{} {
        structureType := reflect.ValueOf(structure)
        structureValue := reflect.TypeOf(structure)
        for i := 0; i < structureType.NumField(); i++ {
                structField := structureType.Field(i)
                structFieldValue := structureValue.Field(i)
...




}

### Field Injection

それを元に解決済のオブジェクトリストからフィールドの値として実態を取得する。

func Resolve(injector Injector, structure interface{}) interface{} {
        structureType := reflect.ValueOf(structure)
        structureValue := reflect.TypeOf(structure)
        for i := 0; i < structureType.NumField(); i++ {
                structField := structureType.Field(i)
                structFieldValue := structureValue.Field(i)
                value := injector.Get(structField.Type)
...



}

### Field Injection

それを reflect.Value に変換して、Set 関数でフィールドにセットする。

func Resolve(injector Injector, structure interface{}) interface{} {
        structureType := reflect.ValueOf(structure)
        structureValue := reflect.TypeOf(structure)
        for i := 0; i < structureType.NumField(); i++ {
                structField := structureType.Field(i)
                structFieldValue := structureValue.Field(i)
                value := injector.Get(structField.Type)
                reflectValue := reflect.ValueOf(value)
                structFieldValue.Set(reflectValue)
...

}

### Field Injection

対象のオブジェクトは依存しているオブジェクトが解決された状態となる。

func Resolve(injector Injector, structure interface{}) interface{} {
        structureType := reflect.ValueOf(structure)
        structureValue := reflect.TypeOf(structure)
        for i := 0; i < structureType.NumField(); i++ {
                structField := structureType.Field(i)
                structFieldValue := structureValue.Field(i)
                value := injector.Get(structField.Type)
                reflectValue := reflect.ValueOf(value)
                structFieldValue.Set(reflectValue)
        }
        return structureValue.Interface()
}

## Impression & Issue

  • Golang でも、 割と少量のコードで DI できるようになった。
     
  • ユニットテスト時のモックの差し替えも vvatanabe/shot 経由でまとめてやれる。
    // TODO サンプル書く
     
  • 実行時に動的にDIしているので、必要なオブジェクトがバインドされていない時や、対象のinterfaceと突っ込むオブジェクトの型が合ってない等の設定ミスは、実行時にしかわからない。
     
  • できればコンパイル時にエラーの有無を知りたい。
  • 依存するオブジェクトを注入する処理を地道に書けば、もし漏れがある場合コンパイル時にエラーとなる。
     
  • 地道に書くのは面倒くさい。
     
  • パターンは決まっているのでコードジェネレーションできそう。
     
  • 次の感じで、所謂 コードジェネレーションを利用した、Compile time DI をやりたくなってきた。
package module

// @injector-gen-go
func GenerateInjector(binder Binder) {
        binder.Bind(new(UserRepository)).To(new(UserRepositoryOnDB))
        binder.Bind(new(GroupRepository)).To(new(GroupRepositoryOnDB))
        binder.Bind(new(ProjectService)).AsSingleton()
})

こんな感じでコードベースの設定をパースして

// Code generated by injector-gen-go. DO NOT EDIT.
// source: module/module.go

package module

type Injector interface {
        func GetUserRepository() UserRepository
        func GroupRepository() GroupRepository
        func ProjectService() *ProjectService
}

func NewInjector() Injector {
        injector := &injector{}
        return injector
}

type injector struct {
        projectService *ProjectService
        projectServiceOnce sync.Once
}

〜〜

こんなのを自動生成する

〜〜

func (i *injector) GetUserRepository() UserRepository {
        return &UserRepositoryOnDB{}
}

func(i *injector) GroupRepository() GroupRepositoryOnDB {
        return &GroupRepositoryOnDB{}
}

func(i *injector) GetProjectService() *ProjectService {
        i.projectServiceOnce.Do(func() {
                i.projectService = &ProjectService{
                        UserRepository: GetUserRepository(),
                        GroupRepository: GroupRepository(),
                }
        })
        return i.projectService
}

次の Fukuoka.go までにやってみたい

os.Exit(0) 

Made with Slides.com