GoでDI (依存性注入) を実装する

# はじめに

Go でいつものオブジェクト指向言語のノリでサービス層をリポジトリの抽象に依存させようとした場面があったのですが、言語の機能として`implements`がなく永遠に頭を抱えてしまったので実装法を記します

# DI とは

あるオブジェクトにおける依存関係をオブジェクトのソースコード内に記述せず全く違うオブジェクトから注入させるシステム開発におけるデザインパターンの一つです

![スクリーンショット 2024-04-02 19.34.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2839531/07ac9c26-0839-2a7e-b203-22544863386c.png)

## メリット

DI を行う最大のメリットはテストが容易になることです

例えば、`DB登録を含む処理`をテストしたい時、外部に依存している`DB登録処理`は一旦テストから除外したいとします。そんなとき、`DB登録処理`はある引数を渡したら定数が返ってくるようにする（モック化）と外部サービスに一切依存することがないため、`DB登録を含む処理`のテストに集中できそうです

そこで、DI を用いて DB 登録処理の抽象に対し、モック化した`DB登録処理`の関数を外部から引数として渡すことで目的通りのテストができるというわけです

![スクリーンショット 2024-04-02 19.32.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2839531/c865027c-4762-f544-fe83-2b2bba09067a.png)

### 今回の使ったソースコード

https://github.com/karasuneo/go-di

# ソースコードのディレクトリ

今回はレイヤードアーキテクチャを参考した構成になってます

```bash
.
├── application
│   └── services
│       └── create_user_service.go
├── config
│   └── env.go
├── domain
│   ├── models
│   │   └── user
│   │       ├── user.go
│   │       └── user_id.go
│   └── repository_impl
│       └── user_repository_impl.go
├── go.mod
├── go.sum
├── infrastructure
│   ├── connection.go
│   └── repository
│       └── user_repository.go
├── main.go
├── presentation
│   └── handlers
│       └── create_user_handler.go
└── utils
    ├── ulid.go
    └── validate.go
```

![スクリーンショット 2024-04-02 18.15.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2839531/5bc26aa7-a494-f49e-21cf-6a7178c294af.png)

# やること

今回は application 層に対して DI（抽象に依存）を行います

## リポジトリの抽象を作成する

`src/domain/repository_impl`

```go:user_repository_impl.go
package repository_impl

import (
	"database/sql"

	user_models_domain "github.com/karasuneo/go-di/src/domain/models/user"
)

type UserRepositoryImpl interface {
	Save(
		db *sql.DB,
		u *user_models_domain.User,
	) (*user_models_domain.User, error)
}

```

## リポジトリ具象を抽象に依存させるようにする

go には interface を参照することを示す`implements`が存在しないため自身で`interface`が返り値の型となり、返り値が具象となるコンストラクタを作成します

型合ってないからコンパイルエラーが出るのでは？と思う方もいるかもしれませんが、`interface`として定義したメソッドが構造体のメソッドとして実装してあり、引数と返り値を正しければちゃんと通るようになってます

`src/infrastructure/repository`

```go:user_repository.go
package repository

import (
	"database/sql"
	"time"

	user_models_domain "github.com/karasuneo/go-di/src/domain/models/user"
	"github.com/karasuneo/go-di/src/domain/repository_impl"
)

type UserRepository struct{}

func NewUserRepository() repository_impl.UserRepositoryImpl {
	return &UserRepository{}
}

func (ur *UserRepository) Save(
	db *sql.DB,
	u *user_models_domain.User,
) (*user_models_domain.User, error) {
	var id string
	var name string
	var mail string
	var createdAt, updatedAt time.Time
	err := db.QueryRow(`
        INSERT INTO users (id, name, mail)
        VALUES ($1, $2, $3)
        RETURNING id, name, mail, created_at, updated_at
	`, u.GetId(), u.GetName(), u.GetMail()).Scan(&id, &name, &mail, &createdAt, &updatedAt)
	if err != nil {
		panic(err)
	}

	resUser, err := user_models_domain.NewUser(
		&id,
		name,
		mail,
	)
	if err != nil {
		return nil, err
	}

	return resUser, nil
}
```

## リポジトリを使う処理を含む記述する

```go
type CreateUserService struct {
	userRepo repository_impl.UserRepositoryImpl
}

func NewCreateUserService(
	userRepo repository_impl.UserRepositoryImpl,
) *CreateUserService {
	return &CreateUserService{
		userRepo: userRepo,
	}
}
```

以上の部分でリポジトリの抽象に依存させます
コンストラクタで`repository_impl.UserRepositoryImpl`を引数としている部分です

`src/application/services`

```go:create_user_service.go
package services

import (
	user_models_domain "github.com/karasuneo/go-di/src/domain/models/user"
	"github.com/karasuneo/go-di/src/domain/repository_impl"
	"github.com/karasuneo/go-di/src/infrastructure"
)

type CreateUserService struct {
	userRepo repository_impl.UserRepositoryImpl
}

func NewCreateUserService(
	userRepo repository_impl.UserRepositoryImpl,
) *CreateUserService {
	return &CreateUserService{
		userRepo: userRepo,
	}
}

func (cus *CreateUserService) Run(
	u *user_models_domain.User,
) (
	*user_models_domain.User,
	error,
) {
	dbConnection := infrastructure.DBConnection{}
	conn, err := dbConnection.Connect()
	if err != nil {
		return nil, err
	}

	resUser, err := cus.userRepo.Save(conn, u)
	if err != nil {
		return nil, err
	}

	return resUser, nil
}
```

## 外部からアプリケーション層に依存性（リポジトリ）を注入

今回はリポジトリの具象を注入しています

```go:create_user_service.go
func CreateUserHandler(r *gin.Engine) {
	r.POST("api/user/create", func(c *gin.Context) {
		var req CreateUserRequest
		// リクエストのバリデーション
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		cus := services.NewCreateUserService(
			repository.NewUserRepository(),
		)

		user, err := user_models_domain.NewUser(
			nil,
			req.Name,
			req.Mail,
		)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}

		resUser, err := cus.Run(user)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		res := CreateUserResponse{
			Id:   resUser.GetId(),
			Name: resUser.GetName(),
			Mail: resUser.GetMail(),
		}

		// レスポンスを返却
		c.JSON(http.StatusCreated, res)
	})
}

```

# 参考資料

https://www.creationline.com/tech-blog/60259#:~:text=%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E3%83%89%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3%E3%83%BC%E3%81%A8%E3%81%AF%E3%80%81%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E3%81%84%E3%81%8F%E3%81%A4%E3%81%8B%E3%81%AE,%E3%81%99%E3%82%8B%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3%E3%83%BC%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB%E3%81%A7%E3%81%99%E3%80%82&text=%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9%E3%81%AE%E5%AE%9F%E7%8F%BE%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B,%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9%E3%82%92%E6%8F%90%E4%BE%9B%E3%81%97%E3%81%BE%E3%81%99%E3%80%82