【学習】Go/Ginで何か作ってみたりごちゃごちゃ作業メモ

VSCodeでGo環境整備
仕事ではIntelliJ系を使うことが多いけど、VSCode入門も兼ねて。
- Version: 1.99.3
- Mac
Goのインストール
ちゃんとした開発はDokcerコンテナ内でやるかもしれないけど、開発環境構築手順を学ぶためにローカルで環境を整えてみる。
brew install go
go version
go version go1.24.2 darwin/arm64
拡張機能をインストール
Go拡張機能をインストール
ついでに依存ツールを更新
はろわ
作業用ディレクトリ作成して、プロジェクト作成
mkdir golang-hello-world
cd golang-hello-world
go mod init golang-hello-world
go: creating new go.mod: module golang-hello-world
ls
go.mod
vscodeで開いてmain.go
を作成し実行
package main
import "fmt"
func main() {
fmt.Printf("Hello world\n")
}
Starting: /Users/uni/go/bin/dlv dap --listen=127.0.0.1:51337 --log-dest=3 from /Users/uni/github/golang-hello-world
DAP server listening at: 127.0.0.1:51337
Type 'dlv help' for list of commands.
Hello world
Process 57221 has exited with status 0
Detaching

Goのパッケージ管理について
参考にさせて頂きました
go.modの中身を見てみる
// 自身のモジュール名
module golang-hello-world
// goのバージョン
go 1.24.2
// 以下に依存関係のあるモジュールが続く
- Deprecatedなどもこのファイルで設定できる
- Pythonと違ってモジュール(パッケージ)を公開・読み込みする仕組み自体が言語に含まれている
- 公開する場合はgithubのパスとモジュール名を対応させる必要がある
- github.com/{ユーザー名}/{プロジェクト名}

Docker環境作る
Go + mysql + Redisで軽いwebアプリを作ってみようと思う。
ディレクトリ構成
- docker-compose.yml
- docker
- go/Dockerfile
- app
- mysql

ハローワールドなハンドラ関数を読む
router := gin.Default()
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Hello, World!",
})
-
gin.Default()
- ルーターオブジェクト
-
func(c *gin.Context)
- ginフレームワークのリクエスト・レスポンス情報を持つコンテキストをポインタとして受け取っている
-
gin.Contextについて
-
// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example. -
TODO: goにおけるcontextについて学ぶ
-

logライブラリ
exitの効果を持つログ出力!
log.Fatalf("redis error: %v", err)
リンターで弾いてコーディングルールレベルで使用禁止にされることもあるとのこと。

context.Context
context.Context型とは何か
パッケージcontextはContext型を定義する。Context型は、APIの境界を越えて、プロセス間で、期限、キャンセルシグナル、その他のリクエストスコープの値を運ぶ。
サーバーへの着信リクエストはContextを生成する必要があり、サーバーへの発信コールはContextを受け入れる必要がある。その間の関数呼び出しの連鎖はContextを伝播し、オプションでWithCancel、WithDeadline、WithTimeout、またはWithValueを使用して作成された派生Contextに置き換える必要がある。
Contextをキャンセルすることで、そのContextのために行われた作業を中止させることができる。 期限付きの Context は期限が過ぎるとキャンセルされる。Contextがキャンセルされると、そこから派生したすべてのContextもキャンセルされる。
WithCancel関数、WithDeadline関数、WithTimeout関数は、Context(親)を受け取り、派生Context(子)とCancelFuncを返す。CancelFunc を直接呼び出すと、子とその子がキャンセルされ、親の子への参照が削除され、関連するタイマーが停止します。CancelFunc の呼び出しに失敗すると、親がキャンセルされるまで、子とその子はリークされます。go vetツールは、すべてのコントロール・フロー・パスでCancelFuncが使用されていることをチェックする。WithCancelCause 関数、WithDeadlineCause 関数、および WithTimeoutCause 関数は CancelCauseFunc を返し、この関数がエラーを受け取り、それをキャンセルの原因として記録します。キャンセルされたコンテキストまたはその子でCauseを呼び出すと、その原因が取得されます。原因が指定されていない場合、Cause(ctx)はctx.Err()と同じ値を返します。
Contextsを使用するプログラムは、パッケージ間でインターフェースの一貫性を保ち、静的解析ツールでコンテキストの伝播をチェックできるようにするために、以下の規則に従う必要があります:
Contextを構造体型の内部に格納しないでください。代わりに、Contextを必要とする各関数に明示的にContextを渡してください。 これについてはhttps://go.dev/blog/context-and-structs。
Contextは最初のパラメータとし、通常はctxという名前にする:
関数が許可している場合でも、nilのContextを渡してはならない。使用するContextがわからない場合は、context.TODOを渡してください。
コンテキストの値は、プロセスやAPIを通過するリクエストスコープのデータにのみ使用してください。
コンテキストは複数のゴルーチンで同時に使用しても安全です。
Contextsは複数のゴルーチンが同時に使用しても安全です。Contextsを使用するサーバーのコード例は https://go.dev/blog/context を参照してください。
要約メモ
-
context.Context 型は、API間・プロセス間でのリクエストスコープな情報(期限、キャンセル信号、値など)を伝播する
-
リクエスト単位で生成される(べき?)、関数間で明示的に渡す(べき?)
-
派生Contextの作成(子Context)
- WithCancel / WithDeadline / WithTimeout / WithValue などを使って、元のContextから新しいContextを派生させる(子Context)
- WithCancel などは、ContextとCancelFunc(キャンセル関数)を返す
- 子Context
- 親がキャンセルされたら、自動で一緒にキャンセルされる(伝播される)
-
CancelFuncについて
- CancelFunc を呼ぶと起きること
- そのContextおよび派生された全てのContextがキャンセルされる。
- タイマーなどの関連リソースがクリーンアップされる。
- 呼び出しを忘れると、リソースリークの原因になる
- go vet によって、すべてのコードパスでCancelFuncが呼ばれているかチェックできる。
- CancelFunc を呼ぶと起きること
-
CancelCauseFunc にエラーを渡すと、キャンセルの原因(Cause)を記録できる
-
使い方など
- Contextは関数引数で明示的に渡す。構造体のフィールドに持たせてはいけない。
- Contextは常に関数の第1引数として渡し、変数名はctxが慣例。
- nilのContextを渡すのはNG。 必要に応じて context.TODO() を使う。
- Context は 複数のゴルーチンから同時に安全に使用可能(スレッドセーフ)。

context.Background()
- 空のコンテキスト
- 元となるルートコンテキストを作成する時に使われる

Ginモデルバインディング
参考:https://gin-gonic.com/ja/docs/examples/binding-and-validation/
フロントから受け取るpixcelの更新データを構造体で定義してみたい。
必要な情報はX軸とY軸とピクセルの色なので以下のように定義した。
type Pixel struct {
X int `json:"x" binding:"required"`
Y int `json:"y" binding:"required"`
Color string `json:"color" binding:"required"`
}
バインディングを定義することでリクエストパラメータのバリデーションを行うことができる。
便利〜

mysqlもdocker環境だけ用意して永続化まではできていないけど、go学習の本筋から外れるのでそのうちやります。

Googleから出ている命名規則や命名スタイル。
- 定数名
- キャメルケース
- 大文字から→グローバル
- 小文字から→プライベート
- 値自体ではなく、役割に基づいて名前をつける- これはgo関係なく重要
- get~は使わない
- 生まれがPHPなので無意識にget~にしてしまう。きをつける

Interfaceとポリモーフィズム
- goのinterfaceは暗黙的
- メソッドのリストを定義し、それらを満たせば継承していることになる
type Input interface {
ReadInput() string
}
type CLIInput struct {
scanner *bufio.Scanner
}
// コンストラクタ的なやつ
func NewCLIInput() *CLIInput {
return &CLIInput{
scanner: bufio.NewScanner(os.Stdin),
}
}
// 構造体に紐づいたメソッドの定義
// これが定義されることでinterface
func (cli *CLIInput) ReadInput() string {
cli.scanner.Scan()
return cli.scanner.Text()
}
型指定して呼び出す
func RunGame(input Input) {
}

Interfaceに対する疑問
Q. 何を実装するのかが明示的じゃないと構造体側のコード書いている時点ではIDEの補完とか使えなくな〜い?
利用される時にしか、どんなインターフェースの実装か分からないのって不便じゃないの〜?
A. 明示的にもできる。
以下のように定義することでコンパイル時にインターフェースの実装を満たすか確認できる。
var _ Input = (*CLIInput)(nil)