この記事はGo Advent Calendar 2019 16日目の記事になります
実務で初めてGoを使うことに決めました。
Go自体は全3回のチュートリアル数年前に受け(当時はGoLandがまだ無料で使えた)、
それ以来全く触っていませんでした。
スライス
が大事と教わった気がします。
Goの仕様を思い出しながら実装していきます。
何かしらの情報を取得するAPIを実装しながら、思ったことを書いていこうと思います。
やりたい箇所はこんな感じ
- 開発環境構築
- ルーティング設定
- GETリクエストを受信
- ORMでDB接続(MySQL)、データ取得
- jsonでレスポンスを返す
「Go API」で検索
さて、「Go API」でとりあえずQiita検索
最初にこちらがヒットしました。
Go のAPI開発
なるほど、ORMやAPIフレームワーク、確認ツールの名前が書いてあります。
実装やフレームワーク選定したいのでもう少し情報を探します。
おお!惹かれるタイトルが出てきました!
Goで超簡単API
これ読めば、そのまま出来ちゃいますね!
ルーティングはgorilla/muxを使って設定する方法の様です。
とても使いやすそうですね。
Gorillaシリーズはtoolkitで色んな種類があるんですね。
使うタイミングで色々調べたい。宿題が一つ出来ました。
[gorilla/context] (http://www.gorillatoolkit.org/pkg/context)
gorilla/mux
[gorilla/reverse] (http://www.gorillatoolkit.org/pkg/reverse)
gorilla/rpc
gorilla/schema
[gorilla/securecookie] (http://www.gorillatoolkit.org/pkg/securecookie)
[gorilla/sessions] (http://www.gorillatoolkit.org/pkg/sessions)
[gorilla/websocket] (http://www.gorillatoolkit.org/pkg/websocket)
さて、もう少し他の記事も見てみます..。
GoでAPIをシュッと書く
おお、これも惹かれますね..!!
こちらはechoというWebフレームワークを使っているようです。
これも色々付いてて便利そうですね。
CORSとかCSRFとかの対策はフレームワークの機能で実装出来るならそうしたいですね。
APIで検索すると色んなフレームワークでの実装例が出てきますね...
フレームワークを使うならまずフレームワークを決めようと思います。
「go api framework」 で検索
Go言語Webフレームワークランキングが出てきました!
最高です!迷ったらランキングですよね。(安易)
使ってる人が多い=情報が多い
というのは一つの優位性でもあると思います。
Webフレームワーク
→ginというフレームワークが人気のようです。
APIのためにWEBフレームワークを入れるかどうかは作りたいアプリ次第という感じでしょうか。
Restフレームワーク
→go-json-restが一番人気。
これは期待するフレームワークのような気がしますね...!
router
→julienschmidt/httprouterが人気のようですね。gorilla/muxは2位でした。router提供のフレームワークは多いようです。
マイクロサービス
→go-kit/kitが人気のようです。Goといえばマイクロサービスというイメージがあります。唯一知っていたgoa
も3位にいました。このカテゴリに入るんですね。。
さて、人気ランキングを見た結果、今回は go-json-rest
で作ってみることにしました。
シンプルなAPIが作れそうです。
gin
も使ってみたいので、それはまた宿題です。。
ant0ine/go-json-restを使ってAPIの実装
恐らく、過去幾多の記事が上がっているであろうフレームワークでAPIを作るということをやっていきます。
みんな最初は通る道ですよね。そしてここからは公式ドキュメントを見ながら進めていきます。
公式ドキュメントをしっかり読みます。
開発環境構築
go get github.com/ant0ine/go-json-rest/rest
すんなり入りました。
goの環境は。以下のコマンドで確認した所、
期待した設定になっていたので、以前学習した環境が残っていたようです。
mac環境です。
$ go env GOROOT
結果
$ /usr/local/go
$ go env GOPATH
結果
$ /Users/(ユーザー名)/go
Hello world確認
公式ドキュメントにHello world確認のコードがあったので早速使います。
ようやくGoのコードが...
package main
import (
"github.com/ant0ine/go-json-rest/rest"
"log"
"net/http"
)
func main() {
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
api.SetApp(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) {
w.WriteJson(map[string]string{"Body": "Hello World!"})
}))
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
NewApi()
でobjectをまず作ると。まぁ、そうですよね。
api.Use(rest.DefaultDevStack...)
DefaultDevStackはmiddlewareを定義しているようです。
...
は可変引数ですかね。
api.SetApp
で受信待ち状態になるようです!
リクエストを受けるとjsonで返してくれそうです。
go run main.go
これで待ち状態になりました。
別ターミナルでcurlを叩きます。
curl -i http://127.0.0.1:8080/
レスポンスが返ってきました!Hello World!です。
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: Mon, 16 Dec 2019 11:02:56 GMT
Content-Length: 28
{
"Body": "Hello World!"
}
go run している方も受信ログが出ています。
16/Dec/2019:20:02:56 +0900 200 80μs "GET / HTTP/1.1" - "curl/7.64.1"
とても簡単に動きました。
ルーティング設定
さて、次に必要なのはルーティング。
MakeRouterを使うと良さそうです!
import (
"log"
"net/http"
"github.com/ant0ine/go-json-rest/rest"
)
func main() {
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
router, err := rest.MakeRouter(
rest.Get("/hello", func(w rest.ResponseWriter, r *rest.Request) {
w.WriteJson(map[string]string{"Body": "Hello World!"})
}))
if err != nil {
log.Fatal(err)
}
api.SetApp(router)
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
これで/hello
の時にHello Worldが返るはずです。
$ curl -i http://127.0.0.1:8080/hello
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: Mon, 16 Dec 2019 12:13:36 GMT
Content-Length: 28
{
"Body": "Hello World!"
}
さて、仕組みは出来たので、今度はORMも使ってDB接続の処理も追加していきます。
ORMはjinzhu/gormを使ったやり方がドキュメントに載っています。
早速使ってみます。
mysqlのドライバとGORMをインストールします!
go get github.com/go-sql-driver/mysql
go get github.com/jinzhu/gorm
ORMでDB接続(MySQL)、データ取得
最後、一気に書きます!
package main
import (
"log"
"net/http"
"time"
"github.com/ant0ine/go-json-rest/rest"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
func main() {
i := Impl{}
i.InitDB()
i.InitSchema()
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
router, err := rest.MakeRouter(
rest.Get("/messages", i.GetAllUserMessages),
rest.Post("/messages", i.PostUserMessages),
)
if err != nil {
log.Fatal(err)
}
api.SetApp(router)
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
type UserMessage struct {
Id int64 `json:"id"`
Message string `sql:"size:1024" json:"message"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt time.Time `json:"-"`
}
type Impl struct {
DB *gorm.DB
}
func (i *Impl) InitDB() {
var err error
i.DB, err = gorm.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?parseTime=true")
if err != nil {
log.Fatalf("Got error when connect database, the error is '%v'", err)
}
i.DB.LogMode(true)
}
func (i *Impl) InitSchema() {
i.DB.AutoMigrate(&UserMessage{})
}
func (i *Impl) GetAllUserMessages(w rest.ResponseWriter, r *rest.Request) {
userMessages := []UserMessage{}
i.DB.Find(&userMessages)
w.WriteJson(&userMessages)
}
func (i *Impl) PostUserMessages(w rest.ResponseWriter, r *rest.Request) {
userMessages := UserMessage{}
if err := r.DecodeJsonPayload(&userMessages); err != nil {
rest.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := i.DB.Save(&userMessages).Error; err != nil {
rest.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteJson(&userMessages)
}
上記処理の説明。
まずは、初期処理でDBに接続を行います。
i.InitDB()
i.InitSchema()
以下でMySQLの設定を書いています。
下記の場合rootユーザーでパスなし、test
というDB名で接続している。
gorm.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?parseTime=true")
以下で、DBにtableがなければmigrationで作成する処理を呼んでいます。
定義は構造体がベースになります(びっくり)
i.DB.AutoMigrate(&UserMessage{})
// 構造体
type UserMessage struct {
Id int64 `json:"id"`
Message string `sql:"size:1024" json:"message"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt time.Time `json:"-"`
}
以下でmessages
のGET
とPOST
そこに紐づくメソッドを定義しました。
router, err := rest.MakeRouter(
rest.Get("/messages", i.GetAllUserMessages),
rest.Post("/messages", i.PostUserMessages),
)
GETの時は、DBから取得した値を書き、POSTの際はRequestの内容をDBに設定します。
func (i *Impl) GetAllUserMessages(w rest.ResponseWriter, r *rest.Request) {
userMessages := []UserMessage{}
i.DB.Find(&userMessages)
w.WriteJson(&userMessages)
}
func (i *Impl) PostUserMessages(w rest.ResponseWriter, r *rest.Request) {
userMessages := UserMessage{}
if err := r.DecodeJsonPayload(&userMessages); err != nil {
rest.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := i.DB.Save(&userMessages).Error; err != nil {
rest.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteJson(&userMessages)
}
以下で、POSTしてレコードセットします。
$ curl -i -H 'Content-Type: application/json' -d '{"Message":"this is a test"}' http:/127.0.0.1:8080/messages
以下で、GETしてDBからレコード取得してjsonでresponseを返します。
$ curl -i http://127.0.0.1:8080/messages
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: Mon, 16 Dec 2019 13:42:32 GMT
Content-Length: 138
[
{
"id": 1,
"message": "this is a test",
"createdAt": "2019-12-16T00:00:00Z",
"updatedAt": "2019-12-16T00:00:00Z"
}
]
これで目的は達成できました!
- 開発環境構築
- ルーティング設定
- GETリクエストを受信
- ORMでDB接続(MySQL)、データ取得
- jsonでレスポンスを返す
まとめ
Goのリハビリとして、凄く簡単なことを長々と書いてしまいましたが、
Goの情報はかなり豊富にネット上にあるため、学習コストは低く感じます。
さらに内容は今回のような初歩的な内容から深いコアな内容まで幅広くあると思うので、
今回の内容ももっと実務的な内容やコードに寄せて行けるように、
定期的に続編を書いていければと思います。