はじめに
今年(2019年)の5月からチーム内の勉強会が週1〜2ペースで開催されるようになり、
モダンな言語を新たに習得したかったため積極的に参加して学習を進めてきました。
基本的なコードを読めるようになり、更なる理解を深めようと次のステップとして、
お決まりですがToDoアプリを作成したので手順などをまとめておきます。
今回GinとGORMを採用した理由は参考にできる記事や公式のドキュメントが充実しているからです。
勉強会について
半年で進めていた内容は以下の通りです。
- A Tour of Go
→読書会形式で皆で内容を読み進め、playgrond上でコードをし挙動を確認し進めました。
- Writing Web Applications
→ 講義形式(ハンズオン)で実装を進めました。
- GCPのチュートリアル
→講義形式(ハンズオン)で実装を進めました。
- AtCoder
→低難度の問題を繰り返し解き、基本構文(変数宣言、関数宣言、slice、for文など)の記述方法に慣れました。
今回の勉強会の参加者は全員Golang未経験だったため、ファシリテーター(進行役)が説明する講義・発表会形式ではなく、皆で資料の内容を読みながら疑問点を洗い出し、Googleで検索して疑問を解消するような流れが多かったです。
これは勉強会が講義・発表会形式だとファシリテーターが資料や教材の準備などが負担になり勉強会が開催されなくなるケースを防ぐためです。
とにかく一週間に一度はGoに触れようというコンセプトで今も勉強会を継続しています。
開発環境
- MacBook Pro
- go version : 1.12.9
$ go version
go version go1.12.9 darwin/amd64
事前情報
Webフレームワーク : Gin
ORM : GORM
DB : sqlite3
依存関係管理ツールdep : dep
├── Gopkg.lock
├── Gopkg.toml
├── README.md
├── controllers
│ └── task_controller.go
├── db
│ └── db.go
├── main.go
├── models
│ └── task.go
├── router
│ └── router.go
├── task.db
├── templates
│ ├── edit.html
│ └── index.html
└── vendor
ソースコードはGitHubにアップロードしています。
プロジェクトフォルダの作成
#$GOPATH + github.com + user名の配下にプロジェクトを作成
# ~/go/src/github.com/hanadaUG/go-gin-gorm-todo-app
$cd go/src/github.com/hanadaUG/
$mkdir go-gin-gorm-todo-app
$cd go-gin-gorm-todo-app
depで外部パッケージを管理する
# macにdepをインストール
$ brew install dep
#初期化
$ dep init
#外部パッケージをimportした時、vendor配下にインストールする
$ dep ensure
main.go
Webアプリの起動起点となるmain.goではDBの初期化+Open処理、
ルーティングの設定のみを行うシンプルな構造になっています。
packagemainimport("github.com/hanadaUG/go-gin-gorm-todo-app/db""github.com/hanadaUG/go-gin-gorm-todo-app/router")funcmain(){// DBのOpen & Close処理の遅延実行db.Initialize()deferdb.Close()// ルーティング設定router.Router()}db/db.go
DBのOpen/Close処理の制御はmain.goから行うことを想定しているため
publicな関数(関数名を大文字から始める)として定義しています。
Get関数はDBのインスタンスを外部から取得するために用意しました。
packagedbimport("github.com/hanadaUG/go-gin-gorm-todo-app/models""github.com/jinzhu/gorm"_"github.com/mattn/go-sqlite3")vardb*gorm.DBfuncInitialize(){// 宣言済みのグローバル変数dbをshort variable declaration(:=)で初期化しようとすると// ローカル変数dbを初期化することになるので注意する// DBのコネクションを接続するdb,_=gorm.Open("sqlite3","task.db")//if err != nil {// panic("failed to connect database\n")//}// ログを有効にするdb.LogMode(true)// Task構造体(Model)を元にマイグレーションを実行するdb.AutoMigrate(&models.Task{})}funcGet()*gorm.DB{returndb}// DBのコネクションを切断するfuncClose(){db.Close()}models/task.go
ToDoアプリに登録する1タスクの定義のみ記述しています。gorm.Model構造体を埋め込み(embedded)、
Textというフィールド変数を追加しています。
※ タスクの状態を保持するStateというフィールド変数を後日追加します。
packagemodelsimport"github.com/jinzhu/gorm"typeTaskstruct{gorm.ModelTextstring}controllers/task_controller.go
CRUDの挙動を記述しています。
templateからの入力を受け、出力内容を制御します。
packagecontrollersimport("github.com/gin-gonic/gin""github.com/hanadaUG/go-gin-gorm-todo-app/models""github.com/jinzhu/gorm""net/http")typeTaskHandlerstruct{Db*gorm.DB}// 一覧表示func(handler*TaskHandler)GetAll(c*gin.Context){vartasks[]models.Task// レコード一覧を格納するため、Task構造体のスライスを変数宣言handler.Db.Find(&tasks)// DBから全てのレコードを取得するc.HTML(http.StatusOK,"index.html",gin.H{"tasks":tasks})// index.htmlに全てのレコードを渡す}// 新規作成func(handler*TaskHandler)Create(c*gin.Context){text,_:=c.GetPostForm("text")// index.htmlからtextを取得handler.Db.Create(&models.Task{Text:text})// レコードを挿入するc.Redirect(http.StatusMovedPermanently,"/")}// 編集画面func(handler*TaskHandler)Edit(c*gin.Context){task:=models.Task{}// Task構造体の変数宣言id:=c.Param("id")// index.htmlからidを取得handler.Db.First(&task,id)// idに一致するレコードを取得するc.HTML(http.StatusOK,"edit.html",gin.H{"task":task})// edit.htmlに編集対象のレコードを渡す}// 更新func(handler*TaskHandler)Update(c*gin.Context){task:=models.Task{}// Task構造体の変数宣言id:=c.Param("id")// edit.htmlからidを取得text,_:=c.GetPostForm("text")// edit.htmlからtextを取得handler.Db.First(&task,id)// idに一致するレコードを取得するtask.Text=text// textを上書きするhandler.Db.Save(&task)// 指定のレコードを更新するc.Redirect(http.StatusMovedPermanently,"/")}// 削除func(handler*TaskHandler)Delete(c*gin.Context){task:=models.Task{}// Task構造体の変数宣言id:=c.Param("id")// index.htmlからidを取得handler.Db.First(&task,id)// idに一致するレコードを取得するhandler.Db.Delete(&task)// 指定のレコードを削除するc.Redirect(http.StatusMovedPermanently,"/")}router/router.go
ルーティングの設定を行います。
リクエストに対して、controllers/task_controller.goで定義した振る舞いを割り当てます。
packagerouterimport("github.com/gin-gonic/gin""github.com/hanadaUG/go-gin-gorm-todo-app/controllers""github.com/hanadaUG/go-gin-gorm-todo-app/db")funcRouter(){// gin内で定義されているEngine構造体インスタンスを取得// Router、HTML Rendererの機能を内包しているrouter:=gin.Default()// globパターンに一致するHTMLファイルをロードしHTML Rendererに関連付ける// 今回のケースではtemplatesディレクトリ配下のhtmlファイルを関連付けているrouter.LoadHTMLGlob("templates/*.html")// TaskHandler構造体に紐付けたCRUDメソッドを呼び出すhandler:=controllers.TaskHandler{db.Get(),}// 各パスにGET/POSTメソッドでリクエストされた時のレスポンスを登録する// 第一引数にパス、第二引数にHandlerを登録するrouter.GET("/",handler.GetAll)// 一覧表示router.POST("/",handler.Create)// 新規作成router.GET("/:id",handler.Edit)// 編集画面router.POST("/update/:id",handler.Update)// 更新router.POST("/delete/:id",handler.Delete)// 削除// Routerをhttp.Serverに接続し、HTTPリクエストのリスニングとサービスを開始するrouter.Run()}templates/index.html
<!DOCTYPE html><htmllang="ja"><head><metacharset="UTF-8"><title>TODOアプリ(Golang * gin * gorm)</title></head><body><formmethod="POST"action="/">
Task : <inputtype="text"name="text"value=""><inputtype="submit"value="登録"></form><br><ul>
{{ range .tasks }}
<li><formmethod="POST"action="/delete/{{.ID}}">
{{.ID}} : {{ .Text }} [<ahref="/{{.ID}}">編集</a>]
<inputtype="submit"value="削除"></form></li>
{{end}}
</ul></body></html>templates/edit.html
<!DOCTYPE html><htmllang="ja"><head><metacharset="UTF-8"><title>TODOアプリ(Golang * gin * gorm)</title></head><body><formaction="/update/{{.task.ID}}"method="POST"><inputtype="text"name="text"value="{{.task.Text}}"><br><inputtype="submit"value="更新"></form></body></html>動作確認
$ go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] Loaded HTML Templates (3):
- edit.html
- index.html
-
[GIN-debug] GET / --> github.com/hanadaUG/go-gin-gorm-todo-app/controllers.(*TaskHandler).GetAll-fm (3 handlers)[GIN-debug] POST / --> github.com/hanadaUG/go-gin-gorm-todo-app/controllers.(*TaskHandler).Create-fm (3 handlers)[GIN-debug] GET /:id --> github.com/hanadaUG/go-gin-gorm-todo-app/controllers.(*TaskHandler).Edit-fm (3 handlers)[GIN-debug] POST /update/:id --> github.com/hanadaUG/go-gin-gorm-todo-app/controllers.(*TaskHandler).Update-fm (3 handlers)[GIN-debug] POST /delete/:id --> github.com/hanadaUG/go-gin-gorm-todo-app/controllers.(*TaskHandler).Delete-fm (3 handlers)[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
http://localhost:8080にブラウザでアクセスして動作を確認できます。
ソースコードはGitHubにアップロードしています。
参考
下記サイト、記事を参考にさせていただきました。
- Go言語 GORM+GinでTODOリストを作ってみた
- Go / Gin で超簡単なWebアプリを作る
- GORM - The fantastic ORM library for Golang, aims to be developer friendly.
