Goに入門して、ついでにクリーンアーキテクチャに入門した　ーその2

## はじめに
前回のつづきです。

https://qiita.com/shimamoooon/items/04b2feed39876674ba8d

その1ではGoのインターフェースについて学んだので、今回はそれを踏まえて実際にクリーンアーキテクチャに則りシンプルなTodoアプリの処理を書いてみます。

また、ところどころGoの基礎知識や、Goのフレームワーク**Gin**などについても触れながら進めていきたいと思います。

:::note warn
思考整理のためのアウトプットですので、情報の正確性などは保証できません！ご容赦ください:cop_tone2:
:::

## ディレクトリ構成
今回作成するtodoアプリのディレクトリ構成はこんな感じです。

```
todo-app/
├── cmd/
│   └── main.go
├── domain/
│   └── todo.go
├── handler/
│   └── todo_handler.go
├── repository
│   └── todo_repository.go
├── usecase/
    └── todo_usecase.go
```
MVCしか書いたことがない身からすると、「modelやcontrollerはどこへ行った？？」という感じですが、似たような概念も出ていきますので、ちょこちょこ比較しながら実装していきたいと思います。

また、各ディレクトリ（クリーンアーキテクチャではレイヤー（層）といいます）でなにを担当するかという責任を決めて、それに基づいたコードを書いていきます。

| No | ディレクトリ | 責務 |
|:-:|:-:|:-:|
| 1 | domain | アプリの中核となるルールや概念（エンティティ）の定義  |
| 2 | usecase |  アプリでやりたい操作（ユースケース）の実装 |
| 3 | repository |  データの保存・取得方法の実装（DBやメモリへのアクセス） |
| 4 | handler  |  リクエストの受け取りと処理の振り分け（APIの入口） |
| 5 |  cmd | アプリの起動処理（エントリーポイント）  |


## Domain
```go:todo.go
package domain

type Todo struct {
	ID    int    `json:"id"`
	Title string `json:"title"`
	Done  bool   `json:"done"`
}

type TodoRepository interface {
	Create(todo *Todo) error
	FindAll() ([]*Todo, error)
}
```
Domain層ではまずTodoの構造体を定義しています。

フィールドはシンプルにID・タイトル・実行済みのフラグのみです。

<br>

その下にさっそくTodoRepositoryというインターフェースが登場していますが、ここが最初のポイントになります。

クリーンアーキテクチャでは、内側の層（domain・エンティティ）にインターフェースを定義して、**その実装をするのは外側の層**というのが望ましいです。

そのため、ここではCreate・FindAllという2つのメソッドを準備するだけに留めておきます。


## Usecase
```go:todo_usecase.go
package usecase

import (
	"todo-app/domain"
)

type TodoUsecase struct {
	Repo domain.TodoRepository
}

func NewTodoUsecase(repo domain.TodoRepository) *TodoUsecase {
	return &TodoUsecase{Repo: repo}
}

func (u *TodoUsecase) Create(todo *domain.Todo) error {
	return u.Repo.Create(todo)
}

func (u *TodoUsecase) FindAll() ([]*domain.Todo, error) {
	return u.Repo.FindAll()
}
```

Usecase層でやることは「Todoの作成と取得」です。

ただし、Todoの作成・取得するメソッドを定義しますが、**実際にTodoを作成して保存したり、保存されているTodoを取得するのはrepositoryの役割**です。

そのため、具体的な処理についてはRepositoryから渡してもらう必要があります。

![画面収録 2025-03-30 15.35.54 (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783222/2a9d2841-fe82-4acf-b59d-971f4f9900f1.gif)

このように、必要なものを外部から引数として渡してもらうことを**依存性の注入（Dependency Injection）** といいます。

クリーンアーキテクチャでは特に大事なキーワードですね。

```go
func NewTodoUsecase(repo domain.TodoRepository) *TodoUsecase {
	return &TodoUsecase{Repo: repo}
}
```

そのため、Usecaseの初期化時にRepositoryを渡している、ということです。

<br>


そして、Usecaseの各メソッドはインターフェースを通じて具体的な処理を呼び出しているだけです。
その具体的な実装はこの後のRepository層に任せます。

```go
func (u *TodoUsecase) Create(todo *domain.Todo) error {
	return u.Repo.Create(todo)
}

func (u *TodoUsecase) FindAll() ([]*domain.Todo, error) {
	return u.Repo.FindAll()
}
```

:::note info
前回の記事で似たような話をしましたが、Usecase層の立場からすると、Todoを「DBに保存する」のかもっと具体的に「MySQLに保存する」のか、はたまた「メモリ上に保存する」のか、そんなことは"**知らなくてよい**"ということですね。
:::


## Repository
Usecaseで説明したとおり、Repositoryでは具体的なデータの保存・取得の処理内容を定義します。

```go:todo_repository.go
package repository

import (
	"go-clean-architecture/domain"
	"sync"
)

type InMemoryTodoRepository struct {
	todos []*domain.Todo
	mu    sync.Mutex
	idSeq int
}

func NewInMemoryTodoRepository() *InMemoryTodoRepository {
	return &InMemoryTodoRepository{}
}

func (r *InMemoryTodoRepository) Create(todo *domain.Todo) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	todo.ID = r.idSeq
	r.idSeq++
	r.todos = append(r.todos, todo)
	return nil
}

func (r *InMemoryTodoRepository) FindAll() ([]*domain.Todo, error) {
	r.mu.Lock()
	defer r.mu.Unlock()

	result := make([]*domain.Todo, len(r.todos))
	copy(result, r.todos)
	return result, nil
}
```

今回は簡単にDBではなくメモリ上でデータを処理するため、InMemoryと分かりやすく命名しています。

とはいえ、後からDB実装や、テスト用のモックを実装してもRepositoryの中で完結できて他の層に影響が出ないので、拡張性・テストしやすいというGoの特徴がよくわかりますね。

<br>

```go
import (
	"go-clean-architecture/domain"
	"sync"
)

type InMemoryTodoRepository struct {
	todos []*domain.Todo
	mu    sync.Mutex
	idSeq int
}
```

また、構造体にsync.Mutexという見慣れないフィールドを持たせていますが、これはGoの並行処理と関係するものです。

クリーンアーキテクチャとは直接関係ありませんので、↓で補足説明としておきます。

<details><summary>Mutexについての補足</summary>
Mutexは簡単にいうと複数の処理が同時にCreateを呼んだときに、データの不整合を防ぐための仕組みです。

[（公式ドキュメント）](https://pkg.go.dev/sync#Mutex)

Goでは**並行処理**を行うとき、複数の処理を同時に走らせることができます（ゴルーチン＝goroutnie）
例えば以下のようにCreateが**同時に**2回呼ばれたとします。
```go
go repo.Create(todo1)
go repo.Create(todo2)
```
todoアプリでは、CreateするたびにユニークIDを採番する必要がありますが、Createが同時に呼ばれるとIDが重複してしまうかもしれません。

そこで`r.mu.Lock()`で他の処理がCreateに入れないようにして、`defer r.mu.Unlock()`で処理の最後にロックを解除する、ということをしています。

<br>

さらっと書きましたが、並行処理はまだ全然理解できていないので、別途記事にできたらと思っています:runner_tone2:

また、このMutexは今回のようにメモリ上で処理を行うときに必要なことであって、DBで処理する際はDB側がうまいこと判断して、IDが重複しないようにしてくれるので、Mutexは不要なようです。
</details>

---

具体的なメソッドの内容については、`Create()`はインクリメントでIDの採番をしており、既存のtodosに新しくtodoを追加しているだけです。

```go
todo.ID = r.idSeq
r.idSeq++
r.todos = append(r.todos, todo)
```

`FindAll()`については、Go特有のスライスや組み込み関数がいくつか出てくるので、少し丁寧に説明します。
```go
result := make([]*domain.Todo, len(r.todos))
copy(result, r.todos)
return result, nil
```

まずmake関数でr.todosと同じ要素数を持つスライスを生成します。

（例としてメモリ上にTodoが3つ作成されているものとします）
```go
r.todos = [ &Todo{ID: 1}, &Todo{ID: 2}, &Todo{ID: 3} ]
```
![スクリーンショット 2025-03-30 20.36.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783222/63e286e2-1738-46ed-8620-8752d231ccbc.png)

そしてcopy関数を使って、makeで生成したresult（スライス）の要素に対して、r.todos（スライス）の要素をコピーしています。

![スクリーンショット 2025-03-30 20.36.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783222/53305550-94b3-47a3-93ac-55d97b412329.png)

（注意点として、resultとr.todosのそれぞれの要素（*Todo）は同じアドレスを指しています。）

一見回りくどく見えてしまいますが、ここでcopyを使う目的は「スライス構造自体の分離」です。

仮にcopyを行わず、`result := r.todos`としてしまったとします。

すると、FindAll()でTodo一覧を見ようとしたけど、その前にappendなどで要素が差し込まれた場合、見たかったTodo一覧とは別物がユーザーに表示されてしまいます。

それを防ぐためにr.todosをコピーして、resultに入れて返しているということですね。



## Handler
HandlerはMVCのコントローラーに近いイメージです。

また、ここからは**Gin**というWeb APIを作るためのフレームワークを使用しています。
ルーティングをURLに応じて直感的に書けたりします。

↓アイコンが好きです
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783222/fb3b3f10-7968-4c79-b806-7f59045a4a15.png" width="20%">

https://github.com/gin-gonic/gin?tab=readme-ov-file

<br>

```go:todo_handler.go
package handler

import (
	"go-clean-architecture/domain"
	"go-clean-architecture/usecase"
	"net/http"

	"github.com/gin-gonic/gin"
)

type TodoHandler struct {
	Usecase *usecase.TodoUsecase
}

func NewTodoHandler(u *usecase.TodoUsecase) *TodoHandler {
	return &TodoHandler{Usecase: u}
}

func (h *TodoHandler) CreateTodo(c *gin.Context) {
	var todo domain.Todo
	if err := c.ShouldBindJSON(&todo); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON"})
		return
	}

	if err := h.Usecase.Create(&todo); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create TODO"})
		return
	}

	c.JSON(http.StatusCreated, todo)
}

func (h *TodoHandler) GetTodos(c *gin.Context) {
	todos, err := h.Usecase.FindAll()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch TODOs"})
		return
	}

	c.JSON(http.StatusOK, todos)
}
```

まず、ginが使用されている箇所にフォーカスして見ていきましょう。
```go
func (h *TodoHandler) CreateTodo(c *gin.Context) {...}
func (h *TodoHandler) GetTodos(c *gin.Context) {...}
```

HandlerにはCreateTodo()とGetTodos()というメソッドを定義しており、どちらも`c *gin.Context`という引数をとりますが、Ginが内部で渡してくれるので、呼び出すときには特に意識しなくてOKです！

```go:main.go
r.POST("/todos", h.CreateTodo) // 引数不要
r.GET("/todos", h.GetTodos)    // 引数不要
```
（↑このようにURLに応じてハンドラー関数を呼び出す。）

<br>

例えばユーザーが`/todos`というURLを指定してPOSTリクエストを送信した場合、GinがPOSTのリクエスト情報をContextに詰めてハンドラに渡してくれます。

そして、CreateTodo（c *gin.Context）では、ShouldBindJSONを使ってリクエストボディ（JSON）をdomain.Todo（構造体）にマッピングします。

```go
func (h *TodoHandler) CreateTodo(c *gin.Context) {
	var todo domain.Todo
	if err := c.ShouldBindJSON(&todo); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON"})
		return
	}
    ...
}
```

例えば、リクエストにTodoのtitleがなかったりしてマッピングに失敗した場合は、ステータスコードと共にエラーメッセージを返すようにします。

```go
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON"})
```

このあたりもGinの便利な書き方ですね。

<br>

そして、マッピングが問題なければ、Usecaseから渡されたCreateメソッドを使ってTodoを作成します。

```go
if err := h.Usecase.Create(&todo); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create TODO"})
		return
	}

c.JSON(http.StatusCreated, todo)
```

クリーンアーキテクチャの観点では、usecaseのメソッドを次のように呼び出しています。
```go
h.Usecase.Create()
```
h はhandlerのインスタンスであり、そのフィールドにUsecaseを持っています。これはhandlerの初期化時に、usecaseのポインタを注入しているためです。

ただ、TodoUsecase は TodoRepository の インターフェースに依存していたのに対し、TodoHandler は TodoUsecase の構造体に直接依存しています。
しかし、これがNGというわけではありません。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783222/9c37a807-18c9-4e1d-bc59-4b4eebf96cd9.png" width="70%">

今回のアプリケーションでは**usecase→repositoryは実装が複数ありえます。（DBに保存、メモリに保存、モックでテストなど）**

そのため、インターフェースに依存させる必要がありましたが、handler→usecaseは**1つの実装で済むため、必ずしもインターフェースにする必要はない**ということです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783222/12b9a0c0-227b-4367-b6ce-25769fa0c7b1.png" width="70%">

（↑必要に応じてこのようにインターフェースを作成・依存させる）


## cmd
最後に、エントリーポイントとなるmain.goのコードです。

```go:main.go
package main

import (
	"go-clean-architecture/handler"
	"go-clean-architecture/repository"
	"go-clean-architecture/usecase"

	"github.com/gin-gonic/gin"
)

func main() {
	repo := repository.NewInMemoryTodoRepository()

	u := usecase.NewTodoUsecase(repo)

	h := handler.NewTodoHandler(u)

	r := gin.Default()

	r.POST("/todos", h.CreateTodo)
	r.GET("/todos", h.GetTodos)

	r.Run(":8080")
}
```
今までの集大成として、usecaseにrepositoryを、handlerにusecaseを注入して、依存関係を生成しています。

もう少しmain()の中身を詳しくみてみると...

```go
repo := repository.NewInMemoryTodoRepository()
```
TodoRepositoryインターフェースを満たす具体的な実装を生成します。
これをusecase層に渡して、データ操作の実際の処理として使用します。

```go
u := usecase.NewTodoUsecase(repo)
```
usecaseはrepositoryの中身を知りませんが、インターフェース経由でその機能を使います。

```go
h := handler.NewTodoHandler(u)
```

そして、handlerもusecaseの中身を知りませんが、提供された機能だけを使えるようにしておきます。

<br>

```go
r := gin.Default()

r.POST("/todos", h.CreateTodo)
r.GET("/todos", h.GetTodos)

r.Run(":8080")
```

あとはエンドポイントとhandlerメソッドを結びつけて、リクエストに応じてハンドラーが呼ばれます。

こういったシンプルな書き方ができるのもGinのおかげです。

<br>

最後にr.Run()でHTTPサーバーを起動しています。

---

では、Postmanでtodoの作成と取得が正しく動くか確認してみます。

POSTリクエストでtodo作成→OK
![スクリーンショット 2025-03-27 23.31.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783222/839deeeb-230b-4408-88f0-bbcce9e482c7.png)

GETリクエストでtodo取得→OK
![スクリーンショット 2025-03-27 23.32.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783222/d5a16b14-827a-417a-884e-a15fb925793d.png)

無事成功しました！！:clap:

## おわりに
Goの基礎知識＋クリーンアーキテクチャを詰め込んだので、けっこう長くなってしまいましたが、おかげでGoの設計思想など大枠を掴むことができました！

あとはひたすら書いてGoに慣れていきたいと思います。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783222/f4761a31-1b41-4aec-8580-7c2e8eaeee58.jpeg" width="60%">

> 引用: ほったゆみ (著)、小畑健 (作)、ヒカルの碁 (ジャンプコミックスDIGITAL)



## 参考
- [the-clean-architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
- [A Tour of Go | sync.Mutex](https://go-tour-jp.appspot.com/concurrency/9)
- [A Tour of Go | Creating a slice with make](https://go-tour-jp.appspot.com/moretypes/13)
- [[Golang]スライスの複製に便利な「copy」関数のサンプルと、直接代入した場合の罠](https://qiita.com/pon_maeda/items/06850524450d882e2bd5)
- [Go言語 スライス](https://recursionist.io/learn/languages/go/complex/slice)