DRYな備忘録

Don't Repeat Yourself.

【GCP】AppEngine GoからCloudSQLをつかう

前回までのあらすじ

AppEngineでWebサーバをうごかして、メールを送ったり、CloudStorage(AWS S3みたいなやつ)使ったりできたので、今回はCloudSQL(AWS RDSみたいなやつ)使うのやります。

成果物

f:id:otiai10:20160403043305p:plain

参考

ドキュメント読んだ感じ

なんてことはあらへん、ただのMySQLインスタンスで、sql.Openするときにcloudsqlっていうalias使うだけっぽいやん?

まずダッシュボードからCloudSQLインスタンスを作成

デフォではCloudSQLのセクションが隠れてるような気がするんで、カスタマイズで

f:id:otiai10:20160403044006p:plain

このセクションを見えるようにして

CloudSQL MySQLインスタンス作成

f:id:otiai10:20160403044041p:plain

※ このとき、AppEngineからCloudSQLへのアクセスは、2nd Generationではできないみたいな記述を見たので、1st Generationの一番安いやつにしました。

インスタンスができたら、今度はデータベースを作成する

f:id:otiai10:20160403044109p:plain

準備はオッケー

サンプルを書く

GolangのORMっぽいもののgormが使いやすいと思ってるので、gorm使います。

package cloudsqlx

import (
    "fmt"
    "net/http"
    "os"
    "strconv"

    _ "github.com/go-sql-driver/mysql"

    // _ "google.golang.org/appengine/cloudsql"
    // 別にいらないぞこれ... なんぞ
    // 薄いし https://github.com/golang/appengine/blob/master/cloudsql/cloudsql.go

    "github.com/jinzhu/gorm"
    "github.com/otiai10/marmoset"
)

// User represents model in users table
type User struct {
    ID   int
    Name string `json:"name" gorm:"NOT NULL;UNIQUE"`
    Age  int    `json:"age"  gorm:"TYPE:int;NOT NULL"`
}

func init() {

    projectID := os.Getenv("PROJECT_ID")
    instanceName := os.Getenv("INSTANCE_NAME")
    databaseName := os.Getenv("DATABASE_NAME")

    db, err := gorm.Open("mysql", fmt.Sprintf(
        "root@cloudsql(%s:%s)/%s", projectID, instanceName, databaseName,
    ))
    if err != nil {
        panic(err)
    }

    if !db.HasTable(&User{}) {
        if err := db.CreateTable(&User{}).Error; err != nil {
            panic(err)
        }
    }

    marmoset.LoadViews("./views")
    router := marmoset.NewRouter()

    router.GET("/", func(w http.ResponseWriter, r *http.Request) {

        users := []User{}
        if err := db.Find(&users).Error; err != nil {
            marmoset.Render(w).HTML("index", map[string]interface{}{"error": err.Error()})
            return
        }

        marmoset.Render(w).HTML("index", map[string]interface{}{
            "users": users,
        })
    })

    router.POST("/", func(w http.ResponseWriter, r *http.Request) {

        user := &User{}
        user.Name = r.FormValue("name")
        user.Age, err = strconv.Atoi(r.FormValue("age"))
        if err != nil {
            marmoset.Render(w).HTML("index", map[string]interface{}{"error": err.Error()})
            return
        }

        if err := db.Save(user).Error; err != nil {
            marmoset.Render(w).HTML("index", map[string]interface{}{"error": err.Error()})
            return
        }

        users := []User{}
        if err := db.Find(&users).Error; err != nil {
            marmoset.Render(w).HTML("index", map[string]interface{}{"error": err.Error()})
            return
        }

        marmoset.Render(w).HTML("index", map[string]interface{}{
            "users": users,
        })

    })

    http.Handle("/", router)
}

で、良い感じにうごいた

f:id:otiai10:20160403043305p:plain

ソースコード

github.com

アクセス制限

どこ見てもuserとpasswordを設定する項目が無い… が、AppEngineアプリケーション単位やネットワーク的に制限しているし、ネットワーク的にカスタマイズできるようだ。

f:id:otiai10:20160403050627p:plain

ローカル開発はどうすべきか

ローカルで書いてgoapp serveで動かしてみたら以下のエラーを得た

f:id:otiai10:20160403045630p:plain

アクセス制限が基本ネットワーク単位なので、localhostからアクセスできないのはうなずける。じゃあどうすっかというと、

DatabaseのURIをまるまる環境変数にしてしまえば、あとは標準のsql driverの挙動となんら変わらないので、いけるはず、

と思ったんだけど、本番は動くがローカルで動かない。なぜなら、ローカルのDev Serverには環境変数を直接渡せないようだ。

ということで、けっきょくappengine.IsDevAppServerを使わざるをえなかった(暫定)

Fix Database URI · otiai10/gcpx@93ff4f0 · GitHub ← そのコミット

雑感

  • ローカル開発、もうちょっとうまいことできないだろうか

DRYな備忘録

プログラミング Google App Engine

プログラミング Google App Engine

はじめてのGoogle App Engine Go言語編 (I・O BOOKS)

はじめてのGoogle App Engine Go言語編 (I・O BOOKS)