Google Cloud PlatformにはCloud DatastoreというNoSQLデータベースサービスがあります。
このサービスは無料枠がそこそこあるという特徴があります。
料金と割り当て | Cloud Datastore のドキュメント | Google Cloud
Google App Engineと組み合わせて使えばサーバ代をかけずにWebアプリケーションがインターネット上にデプロイできます。
目次
簡易メッセージボードの仕様
簡易メッセージボードは以下のような画面になります。
名前とメッセージを入れて書き込むと、書き込んだ時間とともに名前、メッセージが追加されていきます。
メッセージが存在しているときはメッセージを全て消すボタンが出現します。
ファイル構成
datasotre-messageboard/
|-app.yaml
|-main.go
|-dsmessage/dsmessage.go
|-templates/main.html
コード
main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
package main import ( "html/template" "log" "net/http" "time" MyMsg "github.com/kausui/datastore-messageboard/dsmessage" "google.golang.org/appengine" "google.golang.org/appengine/datastore" ) func index(w http.ResponseWriter, r *http.Request) { //テンプレートの取得 t, _ := template.ParseFiles("templates/main.html") //GAEのContextを作成する c := appengine.NewContext(r) //Data Storeから全てのメッセージの取得 var msgs []MyMsg.Message q := datastore.NewQuery("Message").Order("-CreatedOn") _, err := q.GetAll(c, &msgs) if err != nil { log.Fatal(err) } //デバッグ用表示 printMessages(msgs) //テンプレート実行 t.ExecuteTemplate(w, "main.html", msgs) } func printMessages(msgs []MyMsg.Message) { log.Printf("Msg count : %d", len(msgs)) if len(msgs) > 0 { for _, msg := range msgs { log.Printf("Msg : %s: %s", msg.Name, msg.Text) } } } func post(w http.ResponseWriter, r *http.Request) { //フォームをパース r.ParseForm() var msg MyMsg.Message //GAEのContextを作成する c := appengine.NewContext(r) //フォームに入力された文字列を取得 msg.Name = r.FormValue("name") msg.Text = r.FormValue("message") //現在日時を設定 msg.SetCurrentDate() //未記入項目を埋める msg.Fillout() //Data Storeへの保存処理 k := datastore.NewIncompleteKey(c, "Message", nil) k, err := datastore.Put(c, k, &msg) if err != nil { log.Fatal(err) } else { log.Printf("Message stored:\nkey: %s\nname: %s\ntext: %s\n\n", k, msg.Name, msg.Text) } //redirect前にsleepしてメッセージを確実に取得できるようにする time.Sleep(1 * time.Second) //indexページにリダイレクト http.Redirect(w, r, "/", http.StatusFound) } func clear(w http.ResponseWriter, r *http.Request) { //GAEのContextを作成する c := appengine.NewContext(r) //Data Storeから全てのメッセージの取得 var msg []MyMsg.Message q := datastore.NewQuery("Message") keys, err := q.GetAll(c, &msg) if err != nil { log.Fatal(err) } else { //全てのキーに対して for _, k := range keys { // 削除 err = datastore.Delete(c, k) } } //redirect前にsleepしてメッセージを確実に取得できるようにする time.Sleep(1 * time.Second) //indexページにリダイレクト http.Redirect(w, r, "/", http.StatusFound) } // GAE用init func init() { //Handlerへfuncの登録 http.HandleFunc("/", index) http.HandleFunc("/post", post) http.HandleFunc("/clear", clear) } |
datastoreではSQLのように一度に全てのレコードを削除するようなコマンドはないようなので、全てのキーを取得してから一つずつdatastore.Delete(c,k)で削除しています。
dsmessage.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package dsmessage import ( "time" ) type Message struct { CreatedOn time.Time Name string Text string } func (msg *Message) Fillout() { //Nameが未入力の場合 if len(msg.Name) == 0 { msg.Name = "No Name" } //Messageが未入力の場合 if len(msg.Text) == 0 { msg.Text = "No Message" } } func (msg *Message) SetCurrentDate() { msg.CreatedOn = time.Now() } |
dsmessage.goはメッセージの構造体を定義したファイルです。
メソッドとして未記入項目を埋めるFillout()、現在時刻を設定するSetCurrentDate()があります。
main.html(テンプレート)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<!DOCTYPE html> <html lang="ja"> <head> <title>Cloud DataStore Message Borad</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"> </head> <body> <div class="container"> <form method="post" action="/post"> <div class="form-group"> <label for="name">Name</label> <input class="form-control" name="name" id="name"> </div> <div class="form-group"> <label for="author">Message</label> <input class="form-control" name="message" id="message"> </div> <button class="btn btn-primary">Post</button> </form> <!-- ここから保存されているメッセージ --> {{if .}} {{range .}} <div class="row"> <div class="col-sm-4">{{.CreatedOn}}</div> <div class="price col-sm-2">{{ .Name}}</div> <div class="pointBox col-sm-6"> <p>{{.Text}}</p> </div> </div> {{end}} <form method="get" action="/clear"> <button class="btn btn-danger">Clear</button> </form> {{else}} <!-- メッセージがない場合 --> <p>保存されているメッセージはありません。</p> {{end}} </div> </body> </html> |
app.yaml
1 2 3 4 5 6 7 8 |
application: datastore-messageboard version: 1 runtime: go api_version: go1 handlers: - url: /.* script: _go_app secure: always |
ビルドと重複インポートエラー回避
GOPATH内でプロジェクトのディレクトリを作りソースコードを置いた状態で、そのプロジェクトのディレクトリで
goapp serve
もしくは
dev_appserver.py app.yaml
で実行すると以下のようなエラーが出て正常にビルドができません。
Failed parsing input: app file XXXXX.go conflicts with same file imported from GOPATH
これはGOPATH内でビルドをするとパッケージを二重に読み込む問題が発生するためです。
これを回避するには作成したプロジェクトのフォルダはGOPATH以外の場所に移動またはコピーさせ、そこから実行することで回避できます。
プロジェクト内の自分で作成したpackageのディレクトリは移動させる必要はありません。
datastoreの確認
http://localhost:8000 にアクセスするとdatastoreに保存されているエンティティの確認ができます。