GoでTodoアプリを作ってみた（Gin × GORM × Docker）


# はじめに
どうも、水無月せきなです。
Goの勉強のために、ローカルで動作するTodoアプリを作ってみました。
そのアプリについて、技術的な解説をする記事の第１回目です。

今回は、アプリの構成や使用技術、実装の概要など、全体的な開発の内容についてご紹介します。

## シリーズ一覧

1. GoでTodoアプリを作ってみた（Gin × GORM × Docker）
1. [クリーンアーキテクチャ風味でGo！なTodoアプリ開発](https://qiita.com/MinadukiSekina/items/ce3ce1a5f5266e37c174)
1. [GoでWebアプリのテストを書く（txdb × gomock × testify）](url)

## できたもの（動作イメージ）
※アップロード容量の制限内に収めるため、画質が荒くなっています。ご了承ください。
![for_Qiita.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1843472/43e321b2-34c7-4fc8-bd9a-ab7b38b3bf55.gif)

## リポジトリ
https://github.com/MinadukiSekina/todo-go-app

## 技術スタック
* 言語：Go
  * フロント側も、GoのTemplateで作成しています（少しだけJavaScriptあり）
* Webフレームワーク：Gin
* DB：MySQL
* ORM：GORM
* その他
    * テスト関連
        * gomock
        * txdb
        * testify
    * デバッグ関連
        * Air（ホットリロード）
        * Delve（デバッガ）
    * 開発ツール
        * Cursor
        * Docker

## 開発環境
Windows 10　22H2
WSL2
Docker Desktop 4.42.0
Cursor 1.1.3
Go 1.24.4
MySQL 8.0

# 開発の準備
元となる環境構築については、[こちらの記事](https://qiita.com/MinadukiSekina/items/9a4dd4d9c160ccb1baea)をご参照ください。

## ライブラリのインストール
### Gin
今回のメインの一つ。
```bash:インストールするコマンド
go get -u github.com/gin-gonic/gin
```

### GORM
今回はMySQLをDBとして採用したため、MySQLのドライバーも入れます。

```bash:インストールするコマンド
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
```

### gomock
モックを生成してくれるライブラリ。
Go公式のgomockがメンテナンスされていないので、代替であるUberのGomockを入れます。
※後述するtestifyにも、モックの機能があります

↓ Go公式のリポジトリから引用
> Update, June 2023: This repo and tool are no longer maintained. Please see go.uber.org/mock for a maintained fork instead.

https://github.com/uber-go/mock


```bash:インストールするコマンド
go install go.uber.org/mock/mockgen@latest
```


```bash:バージョン確認のコマンド
mockgen -version
```

```bash:モック生成のコマンド例
mockgen -source=app/domain/repository/todoRepository.go -destination=app/mock/repository/mockTodoRepository.go
```

### testify
テスト用のアサーションなど便利なライブラリ。
gomockを入れた後に、こちらにもモックの機能があることを知りました。

```bash:インストールするコマンド
go get github.com/stretchr/testify
```

### txdb
テストケースごとに独立したデータでの実施と、クリーンアップを容易にしてくれます。

```bash:インストールするコマンド
go get github.com/DATA-DOG/go-txdb 
```

### uuid
txdbでトランザクションを張る際に一意な名称が欲しかったので導入しました。
```bash:インストールするコマンド
go get github.com/google/uuid
```

# 実装の概要

## ディレクトリ構造

※テストファイルなどの一部ファイルやディレクトリは省いています。

```
/app
|-- app
|   |-- db
|   |   |-- db.go
|   |   `-- todoRepository.go
|   |-- domain
|   |   |-- interfaces
|   |   |   `-- closer.go
|   |   |-- models
|   |   |   `-- todo.go
|   |   `-- repository
|   |       `-- todoRepository.go
|   |-- handlers
|   |   `-- web
|   |       |-- flash.go
|   |       |-- mainHandler.go
|   |       |-- route
|   |       |   `-- route.go
|   |       `-- todoHandler.go
|   |-- injector
|   |   `-- injector.go
|   |-- mock
|   |   |-- repository
|   |   |   `-- mockTodoRepository.go
|   |   `-- usecase
|   |       `-- mockTodoUsecase.go
|   |-- static
|   |   |-- css
|   |   |   `-- style.css
|   |   `-- js
|   |       `-- todoItemClick.js
|   |-- templates
|   |   |-- error
|   |   |   `-- error.html
|   |   `-- todo
|   |       |-- index.html
|   |       `-- show.html
|   `-- usecases
|       `-- todoUsecase.go
|-- go.mod
|-- go.sum
`-- main.go
```


## アーキテクチャ
元々意図していたわけではありませんが、参考にした記事の多くがクリーンアーキテクチャで開発されたものが多かったため、そちらへ寄せたような形になっています。

後述するDIも含め、実装とテストにおいては下記の記事を特に参考に（というか依拠）させていただきました。

https://qiita.com/ogady/items/34aae1b2af3080e0fec4#didependency-injection%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E3%81%A8%E3%81%AF

## オブジェクト間の結合とDI
オブジェクト間は基本的にインターフェイス経由で繋がっており、`app/injector/injector.go`で定義した`injector`によってDI（依存性の注入）を行っています。

<details><summary>DI実装の抜粋</summary>

```go:app/handlers/web/route/route.go
// データベース接続を初期化し、SqlHandlerを返す
func InjectDB() db.SqlHandler {
	sqlhandler := db.GetSqlHandler()
	return sqlhandler
}

// sqlHandlerを使用してTodoRepositoryを生成する
func InjectTodoRepository() repository.TodoRepository {
	sqlHandler := InjectDB()
	return db.NewTodoRepository(sqlHandler)
}

// TodoRepositoryを使用してTodoUsecaseを生成する
func InjectTodoUsecase() usecases.TodoUsecase {
	TodoRepo := InjectTodoRepository()
	return usecases.NewTodoUsecase(TodoRepo)
}

// TodoUsecaseを使用してTodoHandlerを生成する
func InjectTodoHandler() handlers.TodoHandler {
	return handlers.NewTodoHandler(InjectTodoUsecase())
}

// アプリケーションのメインハンドラー（ルートパス用）を生成する
func InjectMainHandler() handlers.MainHandler {
	return handlers.NewMainHandler()
}
```

```go:app/handlers/web/route/route.go
func SetRouting() {

	…省略…

	// 各ハンドラーの初期化
	th := injector.InjectTodoHandler()
	mh := injector.InjectMainHandler()

	// ハンドラーの終了処理
	defer th.Close()

	// ルーティングの設定
	router.GET("/", mh.Index)

	router.GET("/todo", th.Index)
	router.POST("/todo", th.Create)
    
	…省略…
}
```

</details>

## ルーティング
| HTTPリクエスト | パス | 動作 |
|:-:|:-:|:-:|
| GET | / | /todoへリダイレクト |
| GET | /todo | 一覧表示 |
| POST | /todo | 新規作成 |
| GET | /todo/:id | 詳細表示 |
| POST | /todo/:id | 更新 |
| POST | /todo/:id/delete | 削除 |

# AIの利用について
今回Cursorでの開発を初めて行い、主に設計・実装における疑問やエラーの解決で支援を受けています。一人でやるよりも、明らかに捗ったと思います。

デザイン部分に関してはCursorにほぼ任せたのですが、意外にも一発でそれっぽいデザインを返してくれました。便利ですね（語彙力）。

上記以外であまりCursorの機能を使えていないのですが、Gitのコミットメッセージに関するルールについてちょうど良い記事が上がっていたので、そちらを元に設定していたりします。

https://zenn.dev/aki1990/articles/d3aff9b4d1b0a9

`.cursorrules`ではなくCursor自体のルールとして定義すると、ルールを踏まえたり踏まえなかったりとうまくいかない事象が発生したので、元記事の通り`.cursorrules`での定義に落ち着きました。
Cursorのルールに関しては今後調べていきたいと思います。

# おわりに
ここまでお読みいただきありがとうございました。

[次回](https://qiita.com/MinadukiSekina/items/ce3ce1a5f5266e37c174)は、実装の具体的な内容についてご説明する予定です。
良ければ合わせてお読みください。

この記事が参考になれば幸いです。

# 参考資料
* [Go / Gin で超簡単なWebアプリを作る](https://qiita.com/hyo_07/items/59c093dda143325b1859)
* [Go言語でTODOアプリwithクリーンアーキテクチャ](https://qiita.com/kikils/items/50be7c65664d867f3a1a)
* [【Go + レイヤードアーキテクチャ】DDDを意識してWeb APIを実装してみる](https://tech.yyh-gl.dev/blog/go_web_api/)
* [Clean ArchitectureでAPI Serverを構築してみる](https://qiita.com/hirotakan/items/698c1f5773a3cca6193e)
* [Go言語でClean Architectureを実現して、gomockでテストしてみた](https://qiita.com/ogady/items/34aae1b2af3080e0fec4)
* [GoにおけるDI](https://inukirom.hatenablog.com/entry/di-in-go)
* [Cursor AIでコミットメッセージを劇的に改善する方法](https://zenn.dev/aki1990/articles/d3aff9b4d1b0a9)