これは旧eviry tech blogから移行した記事です。
tkです。
今回新しい試みとしてgoを使ったAPIサーバーを構築することになりました。
構成としてはcontroller/service/repositoryに分離し、テストも実装しながら進めていこうということになっています。
そうなると、モジュール間はできるだけ疎結合になってくれていると実装もしやすくテストも書きやすくなります。
なので、DI(依存性の注入: Dependency Injection)が実現できれば疎結合を保ったままモジュール間を連携できると考え、今回はgoogle/wire
を使用してDIを実現してみることにしました。
公式のページはこちらです。
GitHub - google/wire: Compile-time Dependency Injection for Go
ここでは、「Sampleモデルの情報をrepositoryからservice経由で取得して結果を返すcontrollerのメソッド」を実装してみます。
package model type Sample struct { Id int Name string }
package repository import "wire-sample/model" type SampleRepository struct {} func NewSampleRepository() SampleRepository { return SampleRepository{} } func (sr SampleRepository) GetSample() model.Sample { return model.Sample{Id: 1, Name: "sample"} }
package service import ( "wire-sample/model" "wire-sample/repository" ) type SampleService struct { sr repository.SampleRepository } func NewSampleService(sr repository.SampleRepository) SampleService { return SampleService{sr:sr} } func (ss SampleService) GetSample() model.Sample { return ss.sr.GetSample() }
package controller import ( "wire-sample/model" "wire-sample/service" ) type SampleController struct { ss service.SampleService } func NewSampleController(ss service.SampleService) SampleController { return SampleController{ss:ss} } func (sc SampleController) GetSample() model.Sample { return sc.ss.GetSample() }
それぞれ生成したインスタンスを返すメソッドを作成しています。
このメソッドにserviceやrepositoryを渡すことで、依存するものを外部から渡せるようにしています。
例えば、これを使ってcontrollerを作ろうとすると、
sampleRepository := repository.NewSampleRepository() sampleService := service.NewSampleService(sampleRepository) sampleController := controller.NewSampleController(sampleService)
という実装になると思いますが、google/wire
を使うとこれを自動生成することが可能で、独立したメソッドとして実装してくれます。
まずはgoogle/wire
をインストールします。
go get github.com/google/wire/cmd/wire
次に、wire.go
というファイルを作ります。
ここに、「どのインスタンスは何に依存しているか、それらを生成するためのメソッドは何か」を定義します。
//+build wireinject package main import ( “github.com/google/wire” “wire-sample/controller” “wire-sample/repository” “wire-sample/service” ) func InitializeSampleController() controller.SampleController { wire.Build(controller.NewSampleController, service.NewSampleService, repository.NewSampleRepository) return controller.SampleController{} }
あとはコマンドラインからwire
を実行します。
すると、wire_gen.go
というファイルが作られ、その中にwire.go
の定義を元にDIされたインスタンスを生成するためのメソッド(上記の場合、SampleControllerを返すInitializeSampleControllerメソッド)が実装されています。
// Code generated by Wire. DO NOT EDIT. //**go:generate** wire //**+build** !wireinject package main import ( “wire-sample/controller” “wire-sample/repository” “wire-sample/service” ) // Injectors from wire.go: func InitializeSampleController() controller.SampleController { sampleRepository := repository.NewSampleRepository() sampleService := service.NewSampleService(sampleRepository) sampleController := controller.NewSampleController(sampleService) return sampleController }
これを使えばmainメソッドの中でDIされたcontrollerを利用できます。
func main() { sc := InitializeSampleController() http.HandleFunc("/sample", func(w http.ResponseWriter, r *http.Request) { sample := sc.GetSample() body, _ := json.Marshal(sample) fmt.Fprintf(w, string(body)) }) http.ListenAndServe(":8080", nil) }
また、controllerやservice、repositoryは実装したものの中では直接インスタンスのやり取りをしていないので、疎結合が保たれてテストも書きやすくなっています。
これ以外にも、公式のページにあるように条件に合わせて生成物をコントロールすることもできるので、まずはそちらを確認してみてください。
以上になります。