Go(Gin)を用いてDBサーバからデータを取得する(Herokuデプロイまで)

この記事ではGoのginフレームワークを用いてデータベースサーバーからデータを取得し，JSON形式で表示することを目標とする．今回はdockerで環境構築をする．DBMSはPostgreSQLを用いる．
>goのバージョンは`1.22`を用いる

# Dockerで環境構築
まずプロジェクトディレクトリを作成する．
```
mkdir yourprojectname
cd yourprojectname
```
goプロジェクトを始めるために新しいGoモジュールを初期化する
```
go mod init example.com/yourprojectname
```
次にginフレームワークをインストールする．
```
go get -u github.com/gin-gonic/gin
```

DBサーバー(PostgreSQL)からデータを取ってくるための依存関係を追加する．
```
go get github.com/lib/pq
go get golang.org/x/net
```
:::note info
`go get github.com/lib/pq`
Go言語でPostgreSQLデータベースに接続するためのドライバ．GoプログラムからPostgreSQLデータベースに接続し，クエリを実行することができる．
`go get golang.org/x/net`
Goの標準ライブラリを補完するネットワーク関連のパッケージを提供する．以下のようなサブパッケージが含まれている

`net` 追加のネットワーク関連の機能を提供する．
`http2` HTTP/2プロトコルのサポートを提供する．
`websocket` WebSocketプロトコルのクライアントとサーバーの実装を提供する．
`proxy` プロキシ関連の機能を提供する．
:::


Dockerコンテナを作成する．
```
touch Dockerfile
```

Dockerfileの中身は以下のようにする．
```Dockerfile
FROM golang:1.22-alpine

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY . .

RUN go mod tidy

RUN go build -o main .

EXPOSE 8080

CMD ["./main"]
```
ついでにdocker-compose.ymlも作成する．
```
touch docker-compose.yml
```

>今回はコンテナは1つだけだし不要では...ある
```docker-compose.yml
version: '3'
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - PORT=8080
```
# main.go(それぞれのgoファイル実行用)の作成

次にそれぞれのgoファイルを実行する専用のgoファイル`main.go`を作成する．
```
touch main.go
```

```main.go
package main

import (
	//一旦空白で
)

func main() {
    //一旦空白で
}
```
# getdb.go(実際のロジック書くところ)の作成
次に実際にデータを受け取るロジックを書くgoファイル`getdb.go`を格納するディレクトリを作成する．
```
mkdir getdb
```
:::note info
各エンドポイントごとにディレクトリを作成すると今後規模が大きくなってきたときに探しやすくなる
:::
`getdb.go`を作成し，以下のように書く．

```
touch getdb/getdb.go
```
>ソースコードの細かい説明はコメントアウトしておきました．
```getdb/getdb.go
package getdb

import (
	"database/sql"
	"net/http"

	"github.com/gin-gonic/gin"
	_ "github.com/lib/pq"
)

// Run関数は，データベースとの接続を確立し、APIサーバーを起動する
func Run() {
	// sql.Open関数を使用して，指定されたデータベースサーバーへの接続を開く
	db, err := sql.Open("postgres", "取得したいDBサーバーのリンク")

	// エラーが発生した場合は，panic関数でプログラムを終了する
	if err != nil {
		panic(err)
	}
	// defer db.Close()により，関数の最後にデータベース接続を閉じるようにする
	defer db.Close()

	// Ginフレームワークを使用してルーターを作成する
	router := gin.Default()

	// router.GETメソッドを使用して，/getdbエンドポイントを定義する
	router.GET("getdb", func(c *gin.Context) {
		// db.Query関数を使用し，指定されたSQLクエリを実行し，結果を取得する
		rows, err := db.Query("SELECT * FROM \"テーブル名\";")
		// エラーが発生した場合は，ステータスコード500（Internal Server Error）とエラーメッセージをJSONレスポンスで返す
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		// defer rows.Close()により，関数の最後に結果セットを閉じるようにする
		defer rows.Close()

		// data変数を定義して，取得したデータを格納するためのスライスを初期化する
		var data []map[string]interface{}
		// rows.Columns()関数を使用して，結果セットのカラム名を取得する
		columns, _ := rows.Columns()
		// rows.Next()メソッドを使用して，結果セットの各行をループ処理する
		for rows.Next() {
			// 各行の値を格納するためのvaluesスライスとvaluePtrsスライスを作成する
			values := make([]interface{}, len(columns))
			valuePtrs := make([]interface{}, len(columns))
			for i := range columns {
				valuePtrs[i] = &values[i]
			}

			// rows.Scan関数を使用して，現在の行の値をスキャンし，valuesスライスに格納する
			err := rows.Scan(valuePtrs...)
			// エラーが発生した場合は，ステータスコード500（Internal Server Error）とエラーメッセージをJSONレスポンスで返す
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}

			// item変数を定義して，カラム名と値のマップを作成する
			item := make(map[string]interface{})
			for i, col := range columns {
				item[col] = values[i]
			}
			// dataスライスにitemを追加する
			data = append(data, item)
		}

		// c.JSON関数を使用して，ステータスコード200（OK）と取得したデータをJSONレスポンスで返す
		c.JSON(http.StatusOK, data)
	})

	// router.Runメソッドを使用して，指定されたポート番号でAPIサーバーを起動する
	// ポート番号は8080
	router.Run(":" + "8080")
}
```
# main.goにロジックが書かれたファイルへの依存関係を追加
ルートのmain.goにgetdb/getdb.goへの依存関係を追加し，Run関数を実行するようにする．
```main.go
package main

import (
	"example.com/yourprojectname/getdb"
)

func main() {
	getdb.Run()
}
```

# 実行
dockerコンテナを起動する
```
docker compose up
```
コンテナが立ち上がったら，以下のリンクにアクセスすればデータが返ってくるはずだ．
http://localhost:8080/getdb

# デプロイ
これをherokuにデプロイするには`go.sum`と`go.mod`がプロジェクトに存在する必要がある．`go.mod`はすでに存在すると思うので`go.sum`を初期化して生成する必要がある．`go.sum`はdockerコンテナ内で実行するようになってはいるが，go.modが適用されているか確認してほしい，できていないのであれば以下のコマンドを実行すること．

:::note info
`go.mod`ファイルは，プロジェクトのモジュール情報と依存関係を定義し，`go.sum`ファイルは，依存関係の正確なバージョンとチェックサムを記録する．
:::

>あればスキップしてね
```
go mod tidy
```
ここまで作成した全てのファイルをgitリポジトリにプッシュして，herokuでデプロイすればOK．
デプロイ方法は以下の記事から
https://qiita.com/tarakokko3233/items/0961933e2b9695cb561a

>この記事内ではPythonのDjangoを使っているが，そこに登場する`Procfile``runtime.txt`は不要である．
:::note info
今回は環境変数を用いなかったがherokuに環境変数を登録してソースコードには以下のように記述すれば環境変数が使用できる．
```golang
os.Getenv("KEY")
```
>環境変数については以下の記事を参考にすること
>https://qiita.com/tarakokko3233/items/bb63dbef9d91f44d3917
:::