21
11

Go言語とwireで始めるDI戦略

Last updated at Posted at 2023-12-17

はじめに

もう年末ですね!皆さんいかがお過ごしでしょうか?僕は就活がひと段落ついたので家でずっとダラダラしています...笑
今年は人生の中で最も濃かった一年だったかもしれません笑

というわけで今回IDEAのアドベントカレンダーの初日を担当します!
物事は最初が肝心と言いますが,なぜそんな大役を自分が担っているのでしょうか...?
これがメリー苦しみますというやつでしょうか...

というわけで記念すべき初日の記事は自分の方からGo言語のwireというライブラリの紹介をしたいと思います!!
クリーンアーキテクチャやオニオンアーキテクチャと言ったようにさまざまなアーキテクチャで実装を行うときに必ずと言っていいほど,DIを用いて開発を行います

そんなDIをを簡単にしてくれるGo言語が持つDIコンテナのライブラリ,それがwireです!!
丁寧に解説するので少し長くなってしまうかもしれませんが,ご了承ください...

事前知識

今回扱うwireというライブラリはいわゆるDIコンテナと呼ばれるものにあたります,そのためまずはサラッとDI周りに関して解説をしたいと思います
そんなこと知ってるわい!!という方はwireの説明までスキップしてください

また今回newから始まるインスタンスを生成する関数のことをコンストラクタと呼んでいます
以下の例でNewMyStructがそれに当たります

// 構造体の定義
type MyStruct struct {
    Field1 int
    Field2 string
}

// MyStructのための「コンストラクタ」関数
// 新しいMyStructインスタンスを生成して初期化する
func NewMyStruct(field1 int, field2 string) *MyStruct {
    return &MyStruct{
        Field1: field1,
        Field2: field2,
    }
}

これはPythonとかのコンストラクタと少し毛色が異なるので違和感を覚えるかもしれませんがご了承ください(ファクトリ関数とかの方がいいのかな...?)

それでは始めて行きます!

DI

DIとは依存性注入と言う意味の英語(Dependency Injection)の先頭のアルファベット2文字を使って表されます

簡単に言うと依存するオブジェクトはclassの中で直接作るのではなく,外側から注入しよう!という考えです

日常生活に例えると

業務委託サービスを想像してみてください
会社がある業務を行いたいと思ったときに,その業務に特化した会社や専門家に外注すると思います
このように自社で扱う専門領域を超える場合は自社で用意するのではなく,より専門的な別の会社や専門家から取り込むと言う考えに似ています

具体的なDIの実装例

ではGoでDIを使った場合と使わなかった場合でどういったコードになるのでしょうか?みていきましょう!

今回はcsvファイルからデータを取ってきて,その合計値を出力する例で見てみましょう

処理の流れ
使うデータ
商品A(円) 商品B(円) 商品C(円)
120 200 80

DIを使わない場合と使う場合の比較

今回はわかりやすいように1つのファイルに全ての処理を書いています(本当は分けないとダメだよ...)

DIを使わなかった場合

全体コード
package main

import (
	"encoding/csv"
	"fmt"
	"os"
	"strconv"
)

// TotalValueImpl TotalValueの実態
type TotalValueImpl struct{}

// ProductPriceImpl ProductPriceの実態
type ProductPriceImpl struct{}

// SumValue TotalValueが持つメソッドの実装
func (t TotalValueImpl) SumValue() int {
	pp := ProductPriceImpl{} // ProductPriceImplのインスタンスを直接生成
	record, err := pp.AllData()
	if err != nil {
		return 0
	}
	// 合計を計算してその値を返す
	var sumValue int
	for _, row := range record {
		for _, value := range row {
			intValue, err := strconv.Atoi(value)
			if err != nil {
				continue
			}
			sumValue += intValue
		}
	}
	return sumValue
}

// AllData ProductPriceが持つメソッドの実装
func (p ProductPriceImpl) AllData() ([][]string, error) {
	// ファイルを読み込んで、データを返す
	file, err := os.Open("sample_data.csv")
	if err != nil {
		return nil, err
	}
	defer file.Close()

	reader := csv.NewReader(file)
	records, err := reader.ReadAll()
	if err != nil {
		return nil, err
	}
	return records, nil
}

func main() {
	tv := TotalValueImpl{} // TotalValueImplのインスタンスを直接生成

	// 合計値を計算
	sum := tv.SumValue()
	fmt.Println("Total Value:", sum)
}


DIを使った場合

全体コード
package main

import (
	"encoding/csv"
	"fmt"
	"os"
	"strconv"
)

// TotalValue 商品の合計値を計算するinterface
type TotalValue interface {
	SumValue() int
}

// ProductPrice 商品の全てのデータを取得するinterface
type ProductPrice interface {
	AllData() ([][]string, error)
}

// TotalValueImpl TotalValueの実態(interfaceに書かれているメソッド(sumValueByName)を実装する必要がある)
type TotalValueImpl struct {
	pp ProductPrice
}

// ProductPriceImpl ProductPriceの実態(interfaceに書かれているメソッド(getAllData)を実装する必要がある)
type ProductPriceImpl struct {
}

// NewTotalValue TotalValueImplをインスタンス化するためのコンストラクタ
func NewTotalValue(pp ProductPrice) TotalValue {
	return &TotalValueImpl{pp: pp}
}

// NewProductPrice ProductPriceImplをインスタンス化するためのコンストラクタ
func NewProductPrice() ProductPrice {
	return ProductPriceImpl{}
}

// SumValue TotalValueが持つsumValueByNameの実装
func (t TotalValueImpl) SumValue() int {
	record, err := t.pp.AllData()
	if err != nil {
		return 0
	}
	// 合計をしてその値を返す
	var sumValue int
	for _, row := range record {
		for _, value := range row {
			intValue, err := strconv.Atoi(value)
			if err != nil {
				continue
			}
			sumValue += intValue
		}
	}

	return sumValue
}

// AllData ProductPriceが持つAllDataの実装
func (p ProductPriceImpl) AllData() ([][]string, error) {
	// ファイルを読み込んで,返すだけの処理
	file, err := os.Open("sample_data.csv")
	if err != nil {
		return nil, err
	}
	defer file.Close()

	reader := csv.NewReader(file)
	records, err := reader.ReadAll()
	if err != nil {
		return nil, err
	}
	fmt.Println(records)

	return records, nil
}

func main() {
	// ProductPriceのインスタンスを生成
	pp := NewProductPrice()

	// TotalValueのインスタンスを生成し、ProductPriceを注入
	tv := NewTotalValue(pp)

	// 合計値を計算
	sum := tv.SumValue()
	fmt.Println("Total Value:", sum)
}

この2つのコード具体的に何が違うのでしょうか?見ていきましょう

解説

ここで簡単にコードの解説をすると,この2つのコードには大きく2つの処理が書かれたコードがあります

1. csvファイルを読み取りreturnするメソッド(関数)(AllData)
// AllData ProductPriceが持つAllDataの実装
func (p ProductPriceImpl) AllData() ([][]string, error) {
	// ファイルを読み込んで,返すだけの処理
	file, err := os.Open("sample_data.csv")
	if err != nil {
		return nil, err
	}
	defer file.Close()

	reader := csv.NewReader(file)
	records, err := reader.ReadAll()
	if err != nil {
		return nil, err
	}
	fmt.Println(records)

	return records, nil
}
2. 配列の値を合計して出力するメソッド(関数)(SumValue)
// SumValue TotalValueが持つsumValueByNameの実装
func (t TotalValueImpl) SumValue() int {
	record, err := t.pp.AllData()
	if err != nil {
		return 0
	}
	// 合計をしてその値を返す
	var sumValue int
	for _, row := range record {
		for _, value := range row {
			intValue, err := strconv.Atoi(value)
			if err != nil {
				continue
			}
			sumValue += intValue
		}
	}

	return sumValue
}

ではまず,DIを使わなかった場合のコードですが

// SumValue TotalValueが持つメソッドの実装
func (t TotalValueImpl) SumValue() int {
	pp := ProductPriceImpl{} // ProductPriceImplのインスタンスを直接生成
	record, err := pp.AllData()
	if err != nil {
		return 0
	}
	// 合計を計算してその値を返す
                .
                .
                .

とこのように関数の中でインスタンスを生成してそのメソッドを呼び出しています

逆にDIを用いたコードは

func main() {
	// ProductPriceのインスタンスを生成
	pp := NewProductPrice()

	// TotalValueのインスタンスを生成し、ProductPriceを注入
	tv := NewTotalValue(pp)

	// 合計値を計算
	sum := tv.SumValue()
	fmt.Println("Total Value:", sum)
}

と言ったように全体の呼び出しを行うmain関数の中でインスタンス化を行い,インスタンスの引数に含めています

つまり2つの大きな違いは
メソッドの中でインスタンス化をするか,インスタンス化したものをインスタンスの引数に含めるか
の違いなのです

また他にもDIを持ちたコードにはinterfaceの実装と,インスタンスを生成するためのコンストラクタ(Newがついたやつ)を作っています

これらを用いる事によりより疎結合なコードを実現しています

じゃあ疎結合って何がいいの?

今回DIを用いる,interfaceを用いる事により
実装を変更しても使用する側のコードは変更する必要がありません

もう少し詳しく解説します
例えば今回,データはcsvファイルからデータを取ってきてます
しかしこれが今後DBになった場合、コードを書き換える必要が出てきます

さらにはテスト用にmockを使いたい場合も出てくるでしょう

そんなときにわざわざ

func (t TotalValueImpl) SumValue() int {
	pp := ProductPriceImpl{} // ProductPriceImplのインスタンスを直接生成

ここで実装をしていたら,毎回毎回書き換えなくてはいけません

しかしinterfaceを用いて外側からDIをする事により,開発者はmain.goの中だけを書き換えるだけで良くなりますし,

これにより、コードの再利用性とテストのしやすさが向上します。

interfaceを使う理由は依存関係を逆転できるなど,他にもさまざまありますが今回は趣旨から少しズレるので割愛します以下に非常にわかりやすかった記事を載せておきます(サボってごめんちょ)

まとめるとDIはインスタンスをインスタンスにmain.goのような全体を管理するファイルから注入することを言うんだなぁ~
ってことがわかってもらえたら大丈夫です

依存関係とは

ここで後ほど記載するwireの具体的な実装を行う上で大事になってくるポイントをいくつか解説したいと思います

依存関係とは?

依存関係とは,簡単にいう関数,メソッド,コンストラクタといった要因が他の要因を必要とする関係性のことを言います
例えば関数Aの中で関数Bを呼び出す時,関数AはBが存在しないと実装が成り立ちません
試しに関数Bを消してみてください,関数Aでエラーが起きるはずです

// 関数B
func FunctionB() {
    fmt.Println("FunctionB is called")
}

// 関数A: 関数Bを呼び出す
func FunctionA() {
    fmt.Println("FunctionA is calling FunctionB")
    FunctionB() // ここで関数Bを呼び出す
}

func main() {
    FunctionA() // メイン関数から関数Aを呼び出す
}

逆に関数BはAに呼ばれていることを知る必要がありません
試しに関数Aを消してみてください,関数Bはエラーを吐きませんよね?

このようなケースを関数Aは関数Bに依存しているということができます

DIコンテナ

こちらは簡単に言うとDIを簡単に使えるようするツールのことです
なので今から説明するwireというライブラリを使うことで非常に簡単にDIをすることができるようになるというわけです

人によってはDIとDIコンテナを同じ意味で捉えている記事も見かけるので,そう言った場合は柔軟に読み進めてください

wireの説明

長かったですね~笑
ここからやっとwireの説明に移ります

wireはGoogle先生に作られたDIコンテナです
他にはUberが作ったdigなどが有名です

詳しくはこの記事を見てほしいです

このwireを使うことでコンストラクタへのインスタンスの注入を全て自動でやってくれます

wireの流れ

ではwireを実際に使ってみましょう!!

具体的なコードを見る前に簡単な手順を示します
手順は以下のようになっています

このように手順としては

  1. まずはDI用のwire.goファイルを用意する
  2. 1のファイルがある場所でwire_genを生成するコマンドを叩く
  3. 生成された関数をmain.goで呼び出す

となっています
先ほどのように

func main() {
	// ProductPriceのインスタンスを生成
	pp := NewProductPrice()

	// TotalValueのインスタンスを生成し、ProductPriceを注入
	tv := NewTotalValue(pp)

	// 合計値を計算
	sum := tv.SumValue()
	fmt.Println("Total Value:", sum)
}

あくまでコンストラクタが2つであれば,どのコンストラクタにどのインスタンスを注入するかがわからなくなることはないのですが,これがたくさんに増えてくると依存関係がわからなくなってきます

そんな時にwireを使うと簡単に依存関係を解決して解決されたファイル(wire_gen.go)を生成してくれるということです
なのでその生成されたwire_gen.goをmain.goなどで呼び出せばいいということですね!
いやぁ便利だ...笑

とその前に

今回実装する具体的なコードの流れを説明します

今回は大きく3つの処理が書かれたコードを使用します

  1. repository
    これは主にDBとの接続処理を記載したコードになります
    なのでORMなどを使った処理はここに書く事になります(今回はgormというORMを使っています)

    コード例
    userRepository.go
    type ActivityRepositoryImpl struct {
    	db *gorm.DB
    }
    
    // インスタンスを生成するNew関数
    func NewActivityRepository(db *gorm.DB) repository.ActivityRepository {
    	return &ActivityRepositoryImpl{
    		db: db,
    	}
    }
    
    // PostActivity 勤怠の登録を行う関数
    func (a ActivityRepositoryImpl) PostActivity(attendance *model.Attendance) (*model.Attendance, error) {
    	entity := &orm_model.Attendance{
    		UserID:         attendance.UserID,
            AttendanceType: attendance.AttendanceType.ToInt(),
    		Time:           attendance.Time,
    	}
    
    	if err := a.db.Create(entity).Error; err != nil {
    		return nil, err
    	}
    
    	attendance.ID = entity.ID
    	return attendance, nil
    }
    
    
    
  2. usecase
    こちらは具体的なロジックを書いていく層(ディレクトリ)になります
    簡単にいうとDBから取得したものを使って色々な処理を書く層がここに当たります

    コード例
    userUsecase.go
    // AddStarWork 作業の開始を登録
    func (a ActivityUsecaseImpl) AddStarWork(work *request.ActivityRequestDTO) (*response.ActivityResponseDTO, error) {
    
    	var res *model.Attendance
    	var err error
    	userKey := work.UserKey
    
    	// userKeyからuserIDを指定
    	userID, err := a.ur.FindIDByUserKey(userKey)
    	if err != nil {
    		log.Println("usr_id not found")
    		return nil, err
    	}
    	// ユーザーの出勤を登録
    	updateUserStatus := &model.UserStatus{
    		UserId:   userID,
    		StatusId: model.Work,
    	}
    	userStatus, err := a.ur.PutUserStatus(updateUserStatus)
    	if err != nil {
    		return nil, err
    	}
    
    	// 作業の登録
    	attendance := &model.Attendance{
    		UserId:         userID,
    		AttendanceType: model.WorkStart,
    		Time:           work.Time,
    	}
    
    	res, err = a.ar.PostActivity(attendance)
    	if err != nil {
    		log.Printf("Failed to post start activity: %v", err)
    		return nil, fmt.Errorf("failed to post start activity: %w", err)
    	}
    
    	// DTOに詰め替え作業
    	responseDTO := &response.ActivityResponseDTO{
    		ID:             res.ID,
    		AttendanceType: "work_start",
    		Time:           res.Time,
    	}
    	return responseDTO, nil
    }
        
    
  3. controller
    ここは主にユーザーからのHTTPメソッドを適切に処理して受け取った値に応じて適切なusecaseを呼び出す層になっています

    コード例
    userController.go
    
    type ActivityController interface {
       AddWork() http.HandlerFunc
    }
    
    type ActivityControllerImpl struct {
       ActivityUsecase usecase.ActivityUsecase
    }
    
    func NewActivityController(au usecase.ActivityUsecase) ActivityController {
       return &ActivityControllerImpl{ActivityUsecase: au}
    }
    
    
    func (a ActivityControllerImpl) AddWork() http.HandlerFunc {
       return func(w http.ResponseWriter, r *http.Request) {
       	var activity request.ActivityRequestDTO
    
       	// bodyを取得
       	if err := json.NewDecoder(r.Body).Decode(&activity); err != nil {
       		log.Println("Can't get body")
       		http.Error(w, "Invalid request body", http.StatusBadRequest)
       		return
       	}
    
       	res, err := a.ActivityUsecase.AddStarWork(&activity)
       	if err != nil {
       		log.Println("Error in ActivityUsecase")
       		http.Error(w, `{"error":"作業開始は現在押せません"}`, http.StatusBadRequest)
       		return
       	}
    
       	w.Header().Set("Content-Type", "application/json")
       	if err := json.NewEncoder(w).Encode(res); err != nil {
       		log.Printf("Can't encode json: %v", err)
       		http.Error(w, "Failed to encode response", http.StatusInternalServerError)
       		return
       	}
    
       }
    }
    
    

とこのようにrepository,usecase,controllerにそれぞれの処理を分けたときのケースを考えます
今回はuserに関連するそれぞれの処理をまとめていますが,他にもrepositoryやusecase,controllerはたくさんあることを想定しています

また依存関係は

となっており,controllerからusecaseを読んで,usecaseからrepositoryを読んで,reositoryからDBの接続処理を呼びます

このような依存関係がある時にwireでどうやって実装するのかをみていきます

wireの具体的な使い方

では上で述べた手順の通りにwireを起動していきましょう!!
今回は特に実務を意識したコードを書いていきます

1. まずはwireをインストール

go install github.com/google/wire/cmd/wire@latest

2, 依存関係をwire.goに記載

どこでもいいのでwire.goというファイルを作成します

コード例
wire.go
//go:build wireinject
// +build wireinject

// この2つがないとパッケージ内で競合する

package di

import (
    "github.com/google/wire"
    "work-management-app/application/usecase"
    "work-management-app/infrastructure/database"
    "work-management-app/infrastructure/database/repository"
    "work-management-app/presentation/controller"
)

// infrastructure
var infrastructureSet = wire.NewSet(
    database.InitDB,
)

// repository
var repositorySet = wire.NewSet(
    repository.NewActivityRepository,
)

// usecase
var usecaseSet = wire.NewSet(
    usecase.NewActivityUsecase,
)

// controller
var controllerSet = wire.NewSet(
    controller.NewActivityController,
)

type ControllersSet struct {
    ActivityController controller.ActivityController
}

func InitializeControllers() (*ControllersSet, error) {
    wire.Build(
        infrastructureSet,
        repositorySet,
        usecaseSet,
        controllerSet,
        wire.Struct(new(ControllersSet), "*"),
    )
    return nil, nil
}


ちょっとコード解説

wireには大きくProviderとwireには大きくProviderとInjectorという2つのコードが重要になってきますという2つのコードが重要になってきます
これら2つのコードを準備することでwireは使えるようになります

Provide

こちらは上で定義したようなNewから始まるインスタンスを生成する関数です
これらの関数をwire.NewSet()の中に含めることで準備1は完了です

またwire.NewSet()はいくつでも作ることができます

今回は複数のwire.NewSetを使用して、異なるレイヤーのためのDIセットを定義しています

このようにロジックごとにwire.NesSetを用いることで異なるレイヤーのファイルを分けて管理することができます

こうすることで,後々新しいロジックが追加された時にどこに追加すればいいのかが明確になります

該当箇所のコード
// repository
var repositorySet = wire.NewSet(
    repository.NewActivityRepository,
)

Injector

InjectorはProvide同士を接続する関数です

先ほど作ったwire.NewSet()を全てwire.Buildの中にまとめます

ビルド時に与えられたプロバイダー関数やセットから依存関係のグラフを解析し、これらの関数を適切な順序で呼び出すコードを生成します

そうすることで依存関係を考えずにただnewSet()に打ち込むだけでいいというわけですね〜

該当箇所のコード
func InitializeControllers() (*ControllersSet, error) {
    wire.Build(
        infrastructureSet,
        repositorySet,
        usecaseSet,
        controllerSet,
        wire.Struct(new(ControllersSet), "*"),
    )
    return nil, nil
}

さらにwire.Struct()を使うと
特定の構造体のインスタンスを作成し、そのフィールドに自動的に依存関係を注入してくれます

該当箇所のコード
type ControllersSet struct {
	UserController     controller.UserController
	HistoryController  controller.HistoryController
	ActivityController controller.ActivityController
}

wire.Struct(new(ControllersSet), "*"),

このように構造体を定義して, "*"を第二引数に取ると,ControllersSetの中に含んである構造体全てのインスタンスを生成し,勝手に依存性を注入してくれます

これは複数にまたがる依存関係の繋がりの最初のインスタンスを生成するために使います

今回であれば,ControllerがUsecaseを読んで,Usecaseがrepositoryを呼ぶようにControllerが最初に来ているのでこのように最初にControllerをインスタンス化する必要があるということです

これらを全て色々実装した後のコードが以下のようなファイルです

最終的なwire.goファイル
wire.go
//go:build wireinject
// +build wireinject

// この2つがないとパッケージ内で競合する

package di

import (
	"github.com/google/wire"
	"work-management-app/application/usecase"
	"work-management-app/infrastructure/database"
	"work-management-app/infrastructure/database/repository"
	"work-management-app/presentation/controller"
)

// infrastructure
var infrastructureSet = wire.NewSet(
	database.InitDB,
)

// repository
var repositorySet = wire.NewSet(
	repository.NewActivityRepository,
	repository.NewHistoryRepository,
	repository.NewUserRepository,
)

// usecase
var usecaseSet = wire.NewSet(
	usecase.NewActivityUsecase,
	usecase.NewHistoryUsecase,
	usecase.NewUserUsecase,
)

// controller
var controllerSet = wire.NewSet(
	controller.NewActivityController,
	controller.NewHistoryController,
	controller.NewUserController,
)

type ControllersSet struct {
	UserController     controller.UserController
	HistoryController  controller.HistoryController
	ActivityController controller.ActivityController
}

func InitializeControllers() (*ControllersSet, error) {
	wire.Build(
		infrastructureSet,
		repositorySet,
		usecaseSet,
		controllerSet,
		wire.Struct(new(ControllersSet), "*"),
	)
	return nil, nil
}

先ほどに比べていくつかusecase,controllerが増えたと思います

3, 最後にコマンドを打ち込んでwire_gen.goを生成

wire gen

を打ち込んでください

最終的に以下のようなファイルができれば完成です

生成されたwire_gen.goのファイル
wire_gen.go
// Code generated by Wire. DO NOT EDIT.

//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject

package di

import (
	"github.com/google/wire"
	"work-management-app/application/usecase"
	"work-management-app/infrastructure/database"
	"work-management-app/infrastructure/database/repository"
	"work-management-app/presentation/controller"
)

// Injectors from wire.go:

func InitializeControllers() (*ControllersSet, error) {
	db, err := database.InitDB()
	if err != nil {
		return nil, err
	}
	userRepository := repository.NewUserRepository(db)
	activityRepository := repository.NewActivityRepository(db)
	userUsecase := usecase.NewUserUsecase(userRepository, activityRepository)
	userController := controller.NewUserController(userUsecase)
	historyRepository := repository.NewHistoryRepository(db)
	historyUsecase := usecase.NewHistoryUsecase(historyRepository, userRepository)
	historyController := controller.NewHistoryController(historyUsecase)
	activityUsecase := usecase.NewActivityUsecase(activityRepository, userRepository, historyRepository)
	activityController := controller.NewActivityController(activityUsecase)
	controllersSet := &ControllersSet{
		UserController:     userController,
		HistoryController:  historyController,
		ActivityController: activityController,
	}
	return controllersSet, nil
}

// wire.go:

// infrastructure
var infrastructureSet = wire.NewSet(database.InitDB)

// repository
var repositorySet = wire.NewSet(repository.NewActivityRepository, repository.NewHistoryRepository, repository.NewUserRepository)

// usecase
var usecaseSet = wire.NewSet(usecase.NewActivityUsecase, usecase.NewHistoryUsecase, usecase.NewUserUsecase)

// controller
var controllerSet = wire.NewSet(controller.NewActivityController, controller.NewHistoryController, controller.NewUserController)

type ControllersSet struct {
	UserController     controller.UserController
	HistoryController  controller.HistoryController
	ActivityController controller.ActivityController
}

一番重要なのは

func InitializeControllers() (*ControllersSet, error) {
	db, err := database.InitDB()
	if err != nil {
		return nil, err
	}
	userRepository := repository.NewUserRepository(db)
	activityRepository := repository.NewActivityRepository(db)
	userUsecase := usecase.NewUserUsecase(userRepository, activityRepository)
	userController := controller.NewUserController(userUsecase)
	historyRepository := repository.NewHistoryRepository(db)
	historyUsecase := usecase.NewHistoryUsecase(historyRepository, userRepository)
	historyController := controller.NewHistoryController(historyUsecase)
	activityUsecase := usecase.NewActivityUsecase(activityRepository, userRepository, historyRepository)
	activityController := controller.NewActivityController(activityUsecase)
	controllersSet := &ControllersSet{
		UserController:     userController,
		HistoryController:  historyController,
		ActivityController: activityController,
	}
	return controllersSet, nil
}

このコードになります,見てもらえればわかりますが,全ての依存関係の生成を自動でやってくれています
そのためこの関数を使いたいところで呼び出せば全ての実態を生成して,依存関係も注入できた事になるということです

wireを使わない場合はこのようなインスタンス化を全て自分でやらないといけなくなります

非常にめんどくさいですし正直見にくいです

特に複数のコンストラクタで呼ばれるようなファイルも存在するため,新しいコンストラクタが増えると管理が本当に難しくなってきます

また新しい処理を追加する場合でもwire.goのような構造だったら,どこに何を書けばいいのかがすぐわかるため保守的な観点においても非常に優れていると言えます

4. main.goなどで呼び出す

では最後に先ほど自動で生成されたwrie_gen.go InitializeControllers()を呼べば準備完了です

main.go

func main() {
	// DIコンテナ
	// コントローラの作成
	cr, err := di.InitializeControllers()
	if err != nil {
		log.Fatal("Failed to initialize controllers: ", err)
	}

とこのようにすればInitializeControllersを生成するだけで全ての依存関係を解決したControllerインスタンスが生成されるというわけです

まとめ

いかがだったでしょうか?
依存関係を自動で分析してコードを生成してくれるので,コードが増えて依存関係がわからなくなってくることをなくしてくれます
またファクトリ関数(New関数)を全て自分で書く必要がなくなるので,手作業によるエラーなども減らしてくれます

大規模な開発になればなるほど依存関係は複雑になっていきどうしても人の手だけでは管理がしきれなくなってくるケースが増えてくるので,wireのようなDIコンテナを用いて安全に効率的に開発を行っていきましょう!!

参考文献

21
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
11