GAE/GoでAppStoreのレビューをSlackに通知してくれるやつ作った


smart newsのレビューをslackに通知したサンプル
![Slack.png](https://qiita-image-store.s3.amazonaws.com/0/45686/311975f3-8d0f-5d55-2e2a-0ad80e85afc6.png "Slack.png")

まだ、実際のAppStoreのレビューと見比べるとちゃんと全部拾えてないとか変なところありますが、そこそこできたので晒します。

# リポジトリ
使い方とかはこちらで解説。アプリ3つ分ぐらいの処理であれば、GAEの無料分で十分運用できます。
https://github.com/yosukesuzuki/goisky-tools

# フレームワークの選定
go言語の強者達はフレームワークなんていりません、net/httpでいいんじゃんて方が多いようなのですが、そんなに強者じゃないので、フレームワークがほしいです。

これまで、[Martini](https://github.com/go-martini/martini)と[Gorilla/Mux](http://www.gorillatoolkit.org/pkg/mux)を使ったりしてましたが、Martiniは作者があまりやる気なくしていたり、Gorilla/Muxはフレームワークではなくtoolkitだと言っている通りフレームワークとしてのタガがゆるいので、自分のようなgo弱な人が使うとけっこうコードがぐじゃぐじゃになってしまいました。

## Beegaeを採用
今回使ったのは、Beegoという上海在住の中国人？エンジニアが作っているフレームワークのGAE版＝Beegaeです。割とわかりやすいMVC構造を持っています。

main.go <- ルーティング
┗ models/ <- モデル
┗ views/ <- ビュー／テンプレート
┗ controllers/ <- コントローラー

日本語のドキュメントはないですが、英語のドキュメントは整備されています。ユーザーコミュニティとしては中国人が多いようですが、githubでの会話は英語でできました。

その他、機能はいろいろ整備されていて、サービスをサッと作るにはなかなかいいです。

今回使ってないですが、GAEに特化したセッション機能とかも使えるようです。

ただ、ちょこちょこ、この機能なんで用意されてないの？みたいのがない気がします。ドキュメント全部把握してないだけかもしれないですが、JSONでレスポンス返すときにステータス・コード指定できないとか（JSONじゃなくてHTMLでエラーを返すなら簡単に指定できますが）。

# MVCの構造
## main.go
ルーティングの部分です。

```go
func init() {
	beegae.TemplateLeft = "{{{"
	beegae.TemplateRight = "}}}"
	beegae.Router("/admin/api/v1/blobstoreimage/handler", &controllers.BlobStoreImageController{}, "*:Handler")
	beegae.Router("/admin/api/v1/blobstoreimage/uploadurl", &controllers.BlobStoreImageController{}, "*:UploadURL")
	beegae.Router("/admin/api/v1/blobstoreimage/:key_name", &controllers.BlobStoreImageController{}, "get:GetEntity;patch:UpdateEntity;delete:DeleteEntity")
	beegae.Router("/admin/api/v1/blobstoreimage", &controllers.BlobStoreImageController{})
	beegae.Router("/admin/task/iosapp/getappreview/:key_name", &controllers.IOSAppController{}, "*:GetAppReview")
	beegae.Router("/admin/task/iosapp/getreviews", &controllers.IOSAppController{}, "*:GetReviews")
	beegae.Router("/admin/api/v1/iosapp/:key_name", &controllers.IOSAppController{}, "get:GetEntity;patch:UpdateEntity;delete:DeleteEntity")
	beegae.Router("/admin/api/v1/iosapp", &controllers.IOSAppController{})
	beegae.Router("/admin/form", &controllers.AdminFormController{})
	beegae.Router("/admin/", &controllers.AdminController{})
	beegae.Router("/", &controllers.MainController{})
	beegae.Run()
}
```

REST APIでcontrollerのほうにGet()やPost()を実装していれば、何も書く必要ないです。メソッドごとに受けるfunctionを決めたい場合は第3引数で、

```
"get:GetEntity;patch:UpdateEntity;delete:DeleteEntity"
```
のように指定できます。


## models
modelsには対応するDatastoreのKindごとにファイルを作ってます。annotationでjsonとdatastore上での名前を付けます。この部分の記述が長くなるのがイマイチなので、なんか勝手に命名規則にしたがって変えてくれるやつがほしい。というか知っていたら教えて下さい。

```go
type IOSApp struct {
	KeyName    string    `json:"key_name" datastore:"KeyName"`
	AppID      string    `json:"app_id" datastore:"AppID"`
	Title      string    `json:"title" datastore:"Tite"`
	WebhookURL string    `json:"webhook_url" datastore:"WebhookURL"`
	IconURL    string    `json:"icon_url" datastore:"IconURL"`
	Content    string    `json:"content" datastore:"Content,noindex"`
	Region     string    `json:"region" datastore:"Region"`
	UpdatedAt  time.Time `json:"updated_at" datastore:"UpdatedAt"`
	CreatedAt  time.Time `json:"created_at" datastore:"CreatedAt"`
}
```

## controllers
こちらも上記のmodelsに対応する形でファイルを作り、ルーティングされて来た処理を裁きます。

まず、下のように処理を書いて、this.Data["json"]のところにデータを渡すと最終的に、RenderのところでJSONにして返してくれます。このときにステータスコード渡したいのですが、そういう機能がないような。

```go
func (this *IOSAppController) Get() {
	iosapps := []models.IOSApp{}
	_, err := datastore.NewQuery("IOSApp").Order("-UpdatedAt").GetAll(this.AppEngineCtx, &iosapps)
	if err != nil {
		this.Data["json"] = err
		return
	}
	listDataSet := map[string]interface{}{"items": iosapps}
	this.Data["json"] = listDataSet
}

func (this *IOSAppController) Render() error {
	if _, ok := this.Data["json"].(error); ok {
		this.AppEngineCtx.Errorf("iosapp error: %v", this.Data["json"])
	}
	this.ServeJson()
	return nil
}

```

また、JSONのREST APIで受ける処理が多いですが、一部HTMLを返すページ処理もあるので、こちらはその分ファイルが増えています。

ただ単に静的なHTMLを返す場合は以下のようにするだけです。

```go
func (this *MainController) Get() {
	this.Layout = "layout.html"
	this.TplNames = "index.html"
}
```

appreviewについては、直接的にやり取りする必要がないので作ってません。

## views
HTMLテンプレートが入っています。layout.htmlが外側の共通の枠になっていて、その中に各テンプレートが埋め込まれます。controllerで上のように指定します。

テンプレートの中で変数を使うにはgoでは通常 {{ variable }}のように記述しますが、今回使ったvuejsの標準のテンプレート表記とバッティングします。そこでgo側は{{{ variable }}}のように３つ括弧を続ける方式にしました。main.goのところで指定してます。

```go
	beegae.TemplateLeft = "{{{"
	beegae.TemplateRight = "}}}"
```

# レビューの取得処理
さて、レビューの取得処理をどうしているかというと、https://github.com/grych/AppStoreReviews というpython製のプログラムを発見してこちらの処理を参考にgoに移しました。

itunesアプリが使っていると思われるxmlを取得して、それをgoqueryというライブラリで処理しました。スクレイピングよりはだいぶいいですが、すごい処理しにくいXML構造で、ここの処理のコードはぶっちゃけだいぶ汚いです。標準のxml処理ライブラリとか使いたかったのですが、うまくいかなかったです。ベターな方法あれば書き直したいです。

# GAEの特殊対応
## travis ciとの連携
細かいことは前に[GAE/GoのWebアプリをTravisCIで自動テスト&自動デプロイする](http://qiita.com/yosukesuzuki/items/667dbef5ff16f7b73aba)で書きましたの参照してください。

今回新たにやったのは２つです。

### 最新のSDKを取得
GAEのSDKはどんどんバージョンアップしつつ、最新2つ分ぐらいしかダウンロードできなくなります。古いSDKは使わせたくないみたいですね。CIの環境づくりの時に1.9.17とかベタッとバージョン書いていると、SDKの取得処理で失敗してCIの実行が止まったりしてだるいです。これだけ直してコミットするのもだるいです。

いろいろ調べていたら、以下のURLから最新のSDKファイルのありかが、わかることがわかったのでそれを取得するスクリプト（ここはpython）を書きました。
https://www.googleapis.com/storage/v1/b/appengine-sdks/o?prefix=featured

### go getを別スクリプトに
go get を.travis.ymlに書いて、依存ライブラリの取得が可能ですが、beegaeが依存するappengineライブラリはgo getではとれないのでエラーが出て、実行が止まってしまいます。それはappengineまわりは別途取得するのでエラー出ても無視したい。そこでgo getするシェルスクリプトを作って、実行しています。

```bash
#!/bin/sh
echo "running go get to fetch dependencies..."
go get github.com/PuerkitoBio/goquery
go get github.com/yosukesuzuki/beegae
echo "dependencies fetched."
exit 0
```	


## blobstore 対応
slack通知とは直接的に関係しないですが、blobstoreに画像をアップできる機能をつけています。blobstoreに画像アップしたうえで、picasaインフラで配信するURLを取得すると使い勝手がいいです。

Slack通知の際のアイコンをカスタマイズできる機能を用意してますが、その際に使いたい画像をアップロードして、picasaインフラで配信するURLの後ろに「=s57-c」とつけると５７ｘ５７ピクセルの正方形画像に成形できます。（sのあとの数字は任意に変更できる）

例えば以下のとおりです。
https://lh4.ggpht.com/hwT-2OO2D1481EgmkATIR5WzfGlmh2KUkEs9ph6hBA1I9maQ7cDo7tVo9TeTppQMsBfD8e_Uezt_W0ExGaNE70CBTbTm=s57-c

元画像
![元画像](https://lh4.ggpht.com/hwT-2OO2D1481EgmkATIR5WzfGlmh2KUkEs9ph6hBA1I9maQ7cDo7tVo9TeTppQMsBfD8e_Uezt_W0ExGaNE70CBTbTm
)

=s57-cパラメータ付き
![57x57](https://lh4.ggpht.com/hwT-2OO2D1481EgmkATIR5WzfGlmh2KUkEs9ph6hBA1I9maQ7cDo7tVo9TeTppQMsBfD8e_Uezt_W0ExGaNE70CBTbTm=s57-c
)

で、この機能が便利なのでblobstoreに画像をアップロードしたあとのhandler処理で、Datastoreへの登録とかしようと思っていたのですが、標準のbeegaeでは動きません。router.goのところで、ちょこちょこrequestのデータをいじくっているらしくて、それが悪さしているようでした。

他のユーザーも困っていて、pull-requestもあがってますが、取り込まれていません。
https://github.com/astaxie/beegae/pull/16

しかたないのでforkして、このpull-requestを取り込んだバージョンを自分で用意しました。

https://github.com/yosukesuzuki/beegae

ちなみにgofmtを使うとimportのところを一気に書きなおすことができます。

```bash
$ gofmt -r '"github.com/astaxie/beegae" -> "github.com/yosukesuzuki/beegae"' -w ./
```

ただし、go界の巨人、mattnさんによるとgofmtとよりgorenameがいいようです。
http://mattn.kaoriya.net/software/lang/go/20150113141338.htm

## local unit testがうまくいかない
この問題は、クリティカルなのですが、いったん放置しています。

```bash
$ goapp test
main.go:4:2: cannot find package "controllers" in any of:
```

goapp serveを実行した時はちゃんとbuildできているのに、goapp testでできないってどういうことなの？MLとかで情報探していますが、解決方法がまだわかりません。

かわりにcasperjsでのe2eテストを実行しています。

# フロントエンド
GAE/Goとは関係ないですが、フロントエンドについても書いておきます。

## Vuejs
angularも、jqueryも選択肢から外しつつ、reactjsは学習コスト的にきつそうだったのでvuejsで記述。goでAPIだけ用意してCRUDの部分はフロントでやっています。

## Coffee Script
サービス用のコードをcoffeeで書くのは初めてでしたが、mizchiさんの解説読んだらうまくコンパイル環境ができました。chromeのdeveloper toolでもブレークポイントをcoffeeのまま付けられるのでデバッグも問題無いです。

http://qiita.com/mizchi/items/10a8e2b3e6c2c3235e61

![Goisky_Tools_on_GAE_Go.png](https://qiita-image-store.s3.amazonaws.com/0/45686/af634a2b-48f7-02bc-f9f9-d61914320755.png "Goisky_Tools_on_GAE_Go.png")


# Todo
- unit testできるようにする
- golintとするといろいろ怒られるので修正する

# おまけというか経緯
自分個人で何か作ろうと思ったら基本的に、Google App Engineです。今回は下の記事を見て、この機能欲しいと思い作ってみました。

http://qiita.com/mach/items/a1477ff0c66aa65c6ac4

> デフォルトの連携機能は用意されていませんが、App Store / Google Play でのレビューも Slack に流して皆で日々チェックしています。ちょっと前まで自前でストアのレビューをスクレイピングして流していましたが、メンテが大変なので、今は AppFigures と Zapier を組み合わせて実現しています。

AppFigures見て登録しようと思ったのですが、自分のアカウントをAppFiguresにあげるような動きが必要になるとか、有料だとかで断念しました。