Goの「wire」と「sqlboiler」でレイヤードアーキテクチャを実装してみる



## 0. はじめに
[wire](https://github.com/google/wire)を勉強中なので、レイヤードアーキテクチャーでの使い方を記事として書こうと思います。
今回の記事では、goのバックエンドコンテナ、mysql,adminerを利用しており、docker-composeファイルで管理していました。
また`wire`と`sqlboiler`の基本的なセットアップについてもすでに行った状態で記事をはじめます。

ディレクトリ構造
```
|   .gitignore
|   docker-compose.yml
|   README.md
|        
+---backend
|   |   .air.toml
|   |   Dockerfile
|   |   go.mod
|   |   go.sum
|   |   main.go
|   |   sqlboiler.toml
|   |       
|   +---domain
|   |   +---entity
|   |   |       book.go
|   |   |       
|   |   \---repository
|   |           book.go
|   |           
|   +---infrastructure
|   |   \---repositoryImpl
|   |           book.go
|   |           
|   +---interface
|   |   \---handler
|   |           book.go
|   |           router.go
|   |           
|   +---mysql
|   |       db.go
|   |       
|   +---sqlboiler (auto generated)
|   |       boil_queries.go
|   |       boil_table_names.go
|   |       boil_types.go
|   |       books.go
|   |       mysql_upsert.go
|   |       
|   |       
|   +---usecase
|   |       book_repository.go
|   |       
|   \---wire
|           wire.go
|           wire_gen.go
|           
\---initdb
        init.sql
```

## 1. initdb/init.sql
docker-compose.yamlのdocker-entrypoint-initdb.dで初期化のsqlを自動で読み込まれるように設定しました。
```sql
CREATE TABLE books (
    id INT PRIMARY KEY,
    name VARCHAR(255) NOT NULL
);
```
## 2. sqlboilerファイルの生成

`sqlboiler mysql`を実行します
`sqlboiler.toml`で設定した場所に生成されます

## 3. NewDBの定義
インフラ層で呼ばれるデータベースとの接続用の関数を定義します。
`/mysql/db.go`
```go
package mysql

import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
)

type DBConfig struct {
	User     string
	Password string
	Host     string
	Port     int
	DBName   string
}

func NewDB() (*sql.DB, error) {
	cfg := DBConfig{
		User:     "sample",
		Password: "sample",
		Host:     "sample",
		Port:     3306,
		DBName:   "sample",
	}

	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DBName)
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return nil, err
	}

	if err := db.Ping(); err != nil {
		return nil, err
	}

	return db, nil
}

```
## 4. ドメイン層、ユースケース層、インフラ層、インターフェイス層の実装
### /domain/entity/book.go
```go
package entity

type Book struct {
	Id int
	Name string
}


```
### /domain/repository/book.go

```go
package repository

import "main/domain/entity"

type BookRepository interface {
	Save(book *entity.Book) error
}

```

### /usecase/book.go

```go
package usecase

import (
	"main/domain/entity"
	"main/domain/repository"
)

type BookUsecase interface {
	Save(book *entity.Book) error
}

type bookUsecaseImpl struct {
	bookRepository repository.BookRepository
}

func NewBookUsecaseImpl(br repository.BookRepository) BookUsecase {
	return &bookUsecaseImpl{bookRepository: br}
}
func (bu *bookUsecaseImpl) Save(book *entity.Book) error {
	if err := bu.bookRepository.Save(book); err != nil {
		return err
	}
	return nil
}

```

### /infrastructure/repositoryImpl/book.go

```go
package repositoryImpl

import (
	"context"
	"database/sql"
	"main/domain/entity"
	"main/domain/repository"
	"main/sqlboiler"

	"github.com/volatiletech/sqlboiler/v4/boil"
)

type bookRepositoryImpl struct {
	db *sql.DB
}

func NewBookRepositoryImpl(db *sql.DB) repository.BookRepository {
	return &bookRepositoryImpl{db: db}
}

func (br *bookRepositoryImpl) Save(book *entity.Book) error {
	bookModel := &sqlboiler.Book{
		ID:   book.Id,
		Name: book.Name,
	}
	err := bookModel.Insert(context.Background(), br.db, boil.Infer())
	if err != nil {
		return err
	}
	return nil
}

```

### /interface/handler/book.go
`/book`で分けることのできるAPIはこのハンドラーで管理します。

```go
package handler

import (
	"main/domain/entity"
	"main/usecase"
	"net/http"

	"github.com/labstack/echo/v4"
)

type BookHandler struct {
	bookUsecase usecase.BookUsecase
}

func (bh *BookHandler) RegisterRoutes(e *echo.Echo) {
	e.POST("/book", bh.SaveBook)
}

func NewBookHandler(bu usecase.BookUsecase) *BookHandler {
	return &BookHandler{bookUsecase: bu}
}

func (bh *BookHandler) SaveBook(c echo.Context) error {

	book := new(entity.Book)
	if err := c.Bind(book); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
	}
	if err := bh.bookUsecase.Save(book); err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}
	return c.JSON(http.StatusOK, book)
}

```
### /interface/handler/router.go

複数APIで使う場合もあるため、それぞれのハンドラーをまとめる`router.go`も作成します。


```go
package handler

import (
	"github.com/labstack/echo/v4"
)

func RegisterRoutes(e *echo.Echo, bookHandler *BookHandler) {
	bookHandler.RegisterRoutes(e)
}

func NewEchoInstance(bookHandler *BookHandler) *echo.Echo {
	e := echo.New()
	RegisterRoutes(e, bookHandler)
	return e
}

```


## 5. wire.goの実装
DIを管理する`wire.go`を実装します。 `router.go`でechoのインスタンスまで返しているので`wire.go`ではここまで含めます。
`//go:build wireinject`は最終的なビルド時に含まれないように書いておくそうです。

```go
//go:build wireinject

package wire

import (
	"main/infrastructure/repositoryImpl"
	"main/interface/handler"
	"main/mysql"
	"main/usecase"

	"github.com/google/wire"
	"github.com/labstack/echo/v4"
)

func InitializeEcho() (*echo.Echo, error) {
	wire.Build(
		mysql.NewDB,
		repositoryImpl.NewBookRepositoryImpl,
		usecase.NewBookUsecaseImpl,
		handler.NewBookHandler,
		handler.NewEchoInstance,
	)
	return nil, nil
}

```


## 6.　wire_gen.goの生成
`wire_gen.go` は`wire.go`から自動生成されるファイルです。

`wire` を`/wire`で実行してください。

`wire_gen.go`が生成されます。

```go
// Code generated by Wire. DO NOT EDIT.

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

package wire

import (
	"github.com/labstack/echo/v4"
	"main/infrastructure/repositoryImpl"
	"main/interface/handler"
	"main/mysql"
	"main/usecase"
)

// Injectors from wire.go:

func InitializeEcho() (*echo.Echo, error) {
	db, err := mysql.NewDB()
	if err != nil {
		return nil, err
	}
	bookRepository := repositoryImpl.NewBookRepositoryImpl(db)
	bookUsecase := usecase.NewBookUsecaseImpl(bookRepository)
	bookHandler := handler.NewBookHandler(bookUsecase)
	echoEcho := handler.NewEchoInstance(bookHandler)
	return echoEcho, nil
}

```

## 7. main.go
main.goではwire_gen.goのInitializeEcho()を呼び出すだけです。
```go
package main

import (
	"log"
	"main/wire"
)

func main() {
	e, err := wire.InitializeEcho()
	if err != nil {
		log.Fatal(err)
	}

	e.Logger.Fatal(e.Start(":8000"))
}

```
## 8. 確認

APIにリクエストを送り、データベースに保存できたことを確認しました。


![apireq1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1430891/033b30d8-5cdc-ced4-6c5e-59d4cd5a6510.png)
![adminer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1430891/51ae91df-96b0-c712-e049-0cc881971223.png)



## 9. 最後に
読んでいただきありがとうございます。この記事では、レイヤードアーキテクチャの中で、WireとSQLBoilerを使ってシンプルなAPIを作成しました。Wireを使用することで、依存関係が複雑になっても管理を簡単にできることを学びました。もし間違いありましたら、ぜひお知らせください。