Golang周辺のツールをいろいろ使いながらGin Web FrameworkでAPIを作る
Golangで開発する上でライブラリの依存関係解決どうするとか、Object Relational Mapping(ORM)どうするとか、hot deployしながら開発するのどうするとかあると思うんですがその辺りを軽く書きながらAPI作っていきたいと思います。
Docker使って開発するあたりの解説は以前のブログを参照されたい。
できること
- Golangで依存ライブラリの管理(と現場言っていいものかは微妙)
- ORMを使ったDBアクセス
- Mac上でソースコードを変更した際、Dockerコンテナに修正が反映されてフレームワークがリロードされる
- Webフレームワークを利用したAPI開発
ツールと環境
- Mac OSX 10.9.5 Mavericks
- Vagrant 1.7.2
- Fresh (c4a501e4002c78dfd6bd519001a580e59a9db4b6)
hot deployのためのツール。
pilu/fresh · GitHub - Godep (58d90f262c13357d3203e67a33c6f7a9382f9223)
依存関係管理ツール() - APIで使用している各種ライブラリのバージョン
以下参照
golang-gin-api/Godeps.json at master · takasing/golang-gin-api · GitHub
Frameworkの選定
昨今ではGolangのWebフレームワークいくつかありますね。
- Revel
- Martini
- Beego
- Goji
フルスタックフレームワークは学習コスト高いのでやめました。
Martiniはパフォーマンスがあまりよくないみたいなので、Martiniライクでパフォーマンス改善されたgojiかginが選択肢になりました。
gojiの売りはgraceful restartだったりするのですが、Docker環境だとBlue-Green deploymentすると思うのであまり重要ではないと思います。
かつ、gojiよりginのほうがEndpointに差し込む関数インターフェースが簡潔なので、今回はGinを使用することにします。
依存ライブラリの管理について
ここがまだGolangが未開拓というかまだまだだなというところなんですが、Goの思想として
最初からあるGoの目標は、MakefileやMakefileに変わる様々な現代的な構成ファイルを書く必要をなくし、 ソースコード自体にある情報のみを使ってGoのコードをビルドできるようにすることでした。
とか
新しいGoコマンドの目標は、Goプログラムは必要なインポート文を書くということ以上に開発者側で構成ファイルや追加の作業をせずにコンパイルできるようにする必要がある。という、私たちの理想へ回帰しています。
Goコマンドについて - The Go Programming Language
という考えがあります。
つまり設定ファイル(゚⊿゚)イラネってことですね。
これがどういうことかというと、go get
するだけでライブラリ落としてこれるのですが、一方で.goファイルには
import "github.com/gin-gonic/gin"
としか書かれないので、ライブラリのバージョン指定ができないわけですね。。。
外部ライブラリ使ったことある人はわかると思うんですけど、ライブラリは普通にバグ仕込まれてくるので、バージョン指定しないと、毎回go get
して開発メンバー間で異なるバージョンで開発をするという危ういスタイルを余儀なくされるだけでなく、作成したアプリケーション自体がライブラリのバグによって動作しなくなるという可能性があります(テストをちゃんと書きましょう。あっはいスミマセンデシタ)。
一応Golangが公式で推奨しているスタイルがあるものの、そこまで確立していないのが現状です。
PackageManagementTools · golang/go Wiki · GitHub
生暖かい目で見守りましょう。
今回はいろんなライブラリとかツールで使われているGodepを使用します。
Godepのスタイルとしては、ライブラリのソースコードを自分のアプリケーションのリポジトリにコミットして使用するというなかなかワイルドな感じになっていてあまり気に入らなかったりするのですが、誰かイケてるやつ作ってくださいませんかねw
冒頭で「依存ライブラリの管理(と現場言っていいものかは微妙) 」と書いたのはそういうことです。
アプリケーションの解説
エンドポイントの作成
GinではSinatraなどと同様に、一つのエンドポイントに関数を渡して処理を行います。
この際のメソッドのインターフェースはgithub.com/gin-gonic/gin/gin.go
でシンプルに定義されています。
type ( HandlerFunc func(*Context) ...
なので、エンドポイントに対する関数を定義して、その関数に対する参照をルーターに渡します。
routes/article.go
func GetArticle(c *gin.Context) { id := c.Params.ByName("id") v, err := c.Get("dbMap") if err != nil { log.Printf("error: %s", err.Error()) c.JSON(http.StatusBadRequest, gin.H{"status": "bad request"}) return } dbMap, ok := v.(*gorp.DbMap) if !ok { c.JSON(http.StatusBadRequest, gin.H{"status": "bad request"}) return } art, err := dbMap.Get(model.Article{}, id) if err != nil { log.Printf("error: %s", err.Error()) c.JSON(http.StatusInternalServerError, gin.H{"status": "db error"}) return } if art == nil { c.JSON(http.StatusNotFound, gin.H{"status": "not found"}) return } c.JSON(http.StatusOK, gin.H{"status": "ok", "article": art}) }
routes/router.go
func Init(app *app.App) *gin.Engine { router := gin.Default() router.Use(app.AppendEnv) v1 := router.Group("/v1") { v1.Use(filter.Auth) v1.GET("/article/:id", GetArticle) v1.POST("/article", PostArticle) } return router }
これで/v1/article/1
などとリクエストが来た場合はGetArticleメソッドが実行されるわけです。
filter処理
上記のようにエンドポイントを設定するより前の部分でUseメソッドを使ってメソッドを渡しておくことで、filterのような処理をすることができます。
ここではDbMap(後述)をContextに追加する処理と、Cookieによるユーザー認証を行っています。
この場合もGinでは同じ関数のインターフェースなのでとてもシンプルであることがわかります。
DBアクセスとORM
今回はPostgreSQLのDBにアクセスするようにしています。
db/postgres.go
package db import ( _ "github.com/lib/pq" // for sql.Open "github.com/go-gorp/gorp" "database/sql" "golang-gin-api/model" ) func InitDb() *gorp.DbMap { db, err := sql.Open("postgres", "host=db user=takasing dbname=toyo password=takapass sslmode=disable") if err != nil { // handle error } dbmap := &gorp.DbMap{Db: db, Dialect: gorp.PostgresDialect{}} dbmap.AddTableWithName(model.Article{}, "articles").SetKeys(true, "Id") dbmap.AddTableWithName(model.User{}, "users").SetKeys(true, "Id") return dbmap }
database/sql
パッケージを使用してDbへの接続情報を生成しています。
ここで注意したいのがimport文のところ。
_ "github.com/lib/pq"
Golangでは使用していないimport文がソースコードに残っていたらコンパイルが通らないのですが、今回のDBのドライバようにソースコード上で使用されていないけど必要なものに関してはアンダースコアをつけてimport文を書くことでコンパイルエラーが回避され、無事DBへの接続が生成されます。
(これに気づかず結構ハマった(^q^))
また、Golangではパッケージ内の型のフィールドやメソッドを外部に開く時は、先頭の文字を大文字にすることでそれを実現しているんですが、この場合DBのカラム名と異なることがあります。
そのままではうまくマッピングしてくれないので、Golangのタグの機能を使って関連付けてやります。
model/article.go
type Article struct { Id int `json:"id"` Title string `json:"title" validate:"min=4,max=64,nonzero"` Content string `json:"content"` CreatedAt time.Time `json:"created_at" db:"created_at"` }
タグはバリデーションとかjsonのマッピングとかにも使われます。
実行
$ vagrant up $ vagrant ssh core@core-01 ~ $ ./share/container.sh -pgx core@core-01 ~ $ docker exec -it postgres bash root@6b18b5a5b43c:/# cd /project root@6b18b5a5b43c:/# sh database.sh root@6b18b5a5b43c:/# exit core@core-01 ~ $ docker logs -f golang ... app log here ...
ブラウザに172.17.8.102
のドメインにuid
でvalueを1にしてCookie焼きます。
そしてAdvanced REST API ClientとかでAPIを叩いてみてください。
詳しくはソースコードを御覧くださいw
所感
- 僕は会社入ってからプログラム書き始めたクチなので、JavaとRubyしかまともに書いてないゆとりプログラマなんですが、Golangはまだまだデバッグとかむずいっすね。
ポインタ(^q^) - Ginはまだ新しいフレームワークなので、ドキュメントがあまりないのですが、Martini、ひいてはSinatraインスパイアというところに着目してドキュメント探せばある程度参考になるのではないでしょうか。
フレームワーク自体はシンプルなので、Golangの言語仕様さえある程度頭に入れば簡単にAPIは書けそうですね。 - Golang的にしかたのないことなんですけど、複数の返り値をつかってerrorを制御するので、うまいこと書かないとコードは長くなりますね。。。たるい。
TODO
- Dockerまわりの設定はDocker ComposeとかDocker Machineとかあるので書き換えたい。