AWS Advent Calendar 12/13 -Go言語でのAWS、特にDynamoDBの利用-
AWSのアドベントカレンダーの12月13日として、私が興味あるGo言語からAWSをどうやって利用するかを述べようかなと思います。
ここ最近Goは暇な時間を見つけては好きに書いているだけなので、大して書けるわけではないです。そこんとこご了承ください。
Goって?
Googleが開発しているオープンソースのプログラミング言語で、非常にシンプルなのにも関わらず必要十分な機能があります。
詳しくは http://golang.org/ まで。はい、おしまい。
シンプルなコードで簡単なWebアプリ書くならこんな感じになります。これはGo言語のチュートリアルかなんかに従って作ったWebアプリです。
package main import ( "fmt" "io/ioutil" "net/http" "html/template" "regexp" "errors" "log" "os" ) /* * Struct prestenting wiki page */ type Page struct { Title string Body []byte } /* * Template caching for some templates which heavily used. */ var templates = template.Must(template.ParseFiles("edit.html", "view.html")) /* * valid path regex * - m1: edit, view, save * - m2: [a-zA-Z0-9]+ */ var validPath = regexp.MustCompile("^/(edit|view|save)/([a-zA-Z0-9]+)$") /* * Simple logger from golang logging feature. */ var logger = log.New(os.Stdout, "", log.Ldate|log.Lmicroseconds|log.Lshortfile) /* * Make http.HandlerFunc for editing, viewing, saving by title given by URL */ func MakeHandler(fn func(w http.ResponseWriter, r *http.Request, title string)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { m := validPath.FindStringSubmatch(r.URL.Path) if m == nil { http.NotFound(w, r) return } else { fn(w, r, m[2]) } } } /* * View handler */ func ViewHandler(w http.ResponseWriter, r *http.Request, title string) { p, err := LoadPage(title) if err != nil { http.Redirect(w, r, "/edit/" + title, http.StatusFound) return } else { RenderTemplate(w, "view", p) } } /* * Edit Handler */ func EditHandler_ugly(w http.ResponseWriter, r *http.Request, title string) { p, err := LoadPage(title) if err != nil { p = &Page{Title:title} } fmt.Println("ugly", p.Body) } /* * Edit Handler */ func EditHandler(w http.ResponseWriter, r *http.Request, title string) { p, err := LoadPage(title) if err != nil { p = &Page{Title:title} } RenderTemplate(w, "edit", p) } /* * Save Handler */ func SaveHandler(w http.ResponseWriter, r *http.Request, title string) { body := r.FormValue("body") page := &Page{Title:title, Body:[]byte(body)} err := page.Save() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { http.Redirect(w, r, "/view/" + title, http.StatusFound) } } /* * save page */ func (p *Page) Save() error { filename := p.Title + ".txt" return ioutil.WriteFile(filename, p.Body, 0600) } /* * load page as Page struct */ func LoadPage(title string) (*Page, error) { filename := title + ".txt" body, error := ioutil.ReadFile(filename) if error != nil { return nil, error } return &Page{Title:title, Body:body}, nil } /* * Render using template loaded from name. */ func RenderTemplate(w http.ResponseWriter, tmpl string, p *Page) { err := templates.ExecuteTemplate(w, tmpl + ".html", p) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } /* * bootstrap */ func main() { logger.Println("start web server...") logger.Println("register http.HandlerFunc.") http.HandleFunc("/view/", MakeHandler(ViewHandler)) http.HandleFunc("/edit/", MakeHandler(EditHandler)) http.HandleFunc("/save/", MakeHandler(SaveHandler)) logger.Println("bootstrap web server...") http.ListenAndServe(":8080", nil) }
GoからのAWS利用方法
現状残念ながらAWSからの正式SDKは出ていませんが、比較的アクティブに動いているプロジェクトは幾つかあります。
例えばGoamz(https://launchpad.net/goamz)とかですね。その中で特にDynamoDBを利用するには、今回紹介するGodynamoがなんかよさげにみえます。
Godynamoの前に
今回のre:InventでAWSのヘビーユーザであるSmugmugはMySQLからDynamoDBへ移行した話をしてくれました。
Smugmugは大きく分けて3つ今回新しいオープンソースを出しています。
- Godynamo : DynamoDBをGoから使うライブラリ
- bbpd : DynamoDBをHTTPプロキシしてくれるライブラリというかサーバ?
- goawsroles : GoからIAM Roleをシームレスに使うためのライブラリ。
時間が出来ればbbpdは試してみたいですねー!bbpdがあると
curl -X POST -d '{"TableName":"mytable","Key":{"Date":{"N":"20131001"},"UserID":{"N":"1"}}}' "http://localhost:12333/GetItem?indent=1&compact=1"
こんな感じでDynamoのデータを参照できます。便利!
Godynamo
GodynamoはAWSのヘビーユーザであるSmugmugが実際に使っているGo言語用のDynamoDBライブラリになります。
実際にMySQLからの移行から使われているので実績は既にあるものになります。
Godynamoを使うには3つのことをする必要があります。
- Goのインストール
- 必要なライブラリのインストール
- クレデンシャル情報の設定
クレデンシャル情報の設定はjsonファイルにて行います。
大きく分けて、アクセスキーとシークレットキーを使う方法と、IAM Roleを使う方法があります。
設定ファイル自体は~/.aws-config.jsonとしておきます。
設定ファイルはこんな感じ。
{ "extends":[], "services": { "default_settings":{ "params":{ "access_key_id":"your_access_key", "secret_access_key":"your_secret_key", "use_sys_log":true } }, "dynamo_db": { "host":"dynamodb.ap-northeast-1.amazonaws.com", "zone":"ap-northeast-1", "iam": { "use_iam":false, "role_provider":"file", "access_key":"role_access_key", "secret_key":"role_secret_key", "token":"role_token", "base_dir":"/dir/where/you/update/role_files", "watch":true } } } }
本来はIAM Roleを使うべきですが、今回は割愛します。本番ではIAM Roleを使いましょう!
コードは非常にシンプルに作ることが出来ます。私が気に入ってるのは、
- 直感的でわかりやすい
- レスポンスが生のJSONで帰ってくるので、どう扱うかは利用者側にゆだねられている
- 早い
のあたりです。特に生のJSONファイルなのでとりまわしは正直楽で、かつパースは後からやればいいので高速です。このあたりのシンプルさが好きですね。
例えばQueryのコードはこんな感じになります。ハッシュキーを指定してレンジキーを1万件取得している感じです。
package main import ( "fmt" "encoding/json" ep "github.com/smugmug/godynamo/endpoint" query "github.com/smugmug/godynamo/endpoints/query" "github.com/smugmug/godynamo/conf" "github.com/smugmug/godynamo/conf_file" ) func main() { conf_file.Read() if conf.Vals.Initialized == false { panic("the conf.Vals global conf struct has not been initialized") } tn := "revaluate" q := query.NewQuery() { q.TableName = tn q.Select = ep.SELECT_ALL } k_v1 := "209963" var kc query.KeyCondition { kc.AttributeValueList = make([]ep.AttributeValue,1) kc.AttributeValueList[0] = ep.AttributeValue{N:k_v1} kc.ComparisonOperator = query.OP_EQ } q.Limit = 10000 q.KeyConditions["id"] = kc json,_ := json.MarshalIndent(q, "", "") fmt.Printf("JSON:%s\n",string(json)) body,code,err := q.EndpointReq() fmt.Printf("%v\n%v\n%v\n",body,code,err) }
レスポンスはJSONで帰ってくるので必要な部分だけをパースすればよいと思います。
2013/12/13 17:03:38 read conf from: /Users/shot6/.aws-config.json JSON:{ "AttributesToGet": null, "ConsistentRead": false, "ExclusiveStartKey": null, "IndexName": null, "KeyConditions": { "id": { "AttributeValueList": [ { "N": "209963" } ], "ComparisonOperator": "EQ" } }, "Limit": 10000, "ReturnConsumedCapacity": "NONE", "ScanIndexForward": true, "TableName": "revaluate", "Select": "ALL_ATTRIBUTES" } {"Count":17, "Items":[{"id":{"N":"209963"},"date":{"N":"1366807742203"},"user":{"S":"209963:231131"}},......]} 200 <nil>
まとめ
いかがでしたでしょうか?非常に簡単に使える感覚がわかってもらえたら嬉しいです。
私自身も試し始めたばかりですが、そこそこ使えているので、ぜひ試しにやってみてください。
また、DynamoDB自身もいよいよ待望のグローバルセカンダリインデックスが対応されましたので、こちらも試してみてください!
http://aws.typepad.com/aws/2013/12/now-available-global-secondary-indexes-for-amazon-dynamodb.html