読者です 読者をやめる 読者になる 読者になる

ぺい

大阪の専門学生の落書き。主にエンジニア寄りの話。

GoでAPIから取得したJSONを5分でパースする

微妙に面倒なアレ

GoはAPIの用途で、私は結構使うのですが、そのAPIを構築する上で、外部のAPIを使ってデータを集めたりすることもよくあります。そして避けて通れないのが、JSON解析です。自力でやると地味に面倒です。
今回はその作業は5分で終わらせる方法を紹介します。

はよソース

github.com

便利なツール達

Let’s try!!!

目的のJSONを取得する

connpass.com 今回はConnpassのAPIから返ってくるJSONを解析します。

{
    "results_returned": 1,
    "events": [
        {
            "event_url": "https://kikaigakushuu.connpass.com/event/56040/",
            "event_type": "participation",
            "owner_nickname": "keiichirou_miyamoto",
            "series": {
                "url": "https://kikaigakushuu.connpass.com/",
                "id": 2589,
                "title": "人工知能勉強会"
            },
            "updated_at": "2017-04-26T02:47:01+09:00",
            "lat": "35.696575200000",
            "started_at": "2017-05-09T20:00:00+09:00",
            "hash_tag": "geek",
            "title": "強くなるロボティック・ゲームプレイヤーの作り方勉強会「4章 強化学習 前半」",
            "event_id": 56040,
            "lon": "139.771830100000",
            "waiting": 0,
            "limit": 30,
            "owner_id": 10866,
            "owner_display_name": "keiichirou_miyamoto",
            "description": "説明",
            "address": "東京都千代田区神田須田町2丁目19−23  (野村第3ビル4階)",
            "catch": "#人工知能,#機械学習,#深層学習,#ニューラルネット,#ディープラーニング,#初心者",
            "accepted": 17,
            "ended_at": "2017-05-09T22:00:00+09:00",
            "place": "コワーキングスペース秋葉原 Weeyble(ウィーブル)"
        }
    ],
    "results_start": 1,
    "results_available": 1744
}

https://connpass.com/api/v1/event/?keyword=python&count=1
リクエストをブラウザで実行します。

https://mholt.github.io/json-to-go/JSON-to-Go: Convert JSON to Go instantly
コピーして、JSON整形します。 整形したJSONをコピーして、goのソースに貼り付けます。

jsonStrという変数に以下のような感じで代入してください。

var jsonStr = `{
    "results_returned": 1,
    "events": [
        {
            "event_url": "https://kikaigakushuu.connpass.com/event/56040/",
            "event_type": "participation",
            "owner_nickname": "keiichirou_miyamoto",
            "series": {
                "url": "https://kikaigakushuu.connpass.com/",
                "id": 2589,
                "title": "人工知能勉強会"
            },
            "updated_at": "2017-04-26T02:47:01+09:00",
            "lat": "35.696575200000",
            "started_at": "2017-05-09T20:00:00+09:00",
            "hash_tag": "geek",
            "title": "強くなるロボティック・ゲームプレイヤーの作り方勉強会「4章 強化学習 前半」",
            "event_id": 56040,
            "lon": "139.771830100000",
            "waiting": 0,
            "limit": 30,
            "owner_id": 10866,
            "owner_display_name": "keiichirou_miyamoto",
            "description": "説明",
            "address": "東京都千代田区神田須田町2丁目19−23  (野村第3ビル4階)",
            "catch": "#人工知能,#機械学習,#深層学習,#ニューラルネット,#ディープラーニング,#初心者",
            "accepted": 17,
            "ended_at": "2017-05-09T22:00:00+09:00",
            "place": "コワーキングスペース秋葉原 Weeyble(ウィーブル)"
        }
    ],
    "results_start": 1,
    "results_available": 1744
}`

JSONからstructを作成する

JSON Pretty Linter - JSONの整形と構文チェック
jsonを食わせて、strcutを生成する。

f:id:tikasan0804:20170426103310p:plain 右に出てるstrcutをコピーしてgoのソースに貼り付ける

type AutoGenerated struct {
    ResultsReturned int `json:"results_returned"`
    Events          []struct {
        EventURL      string `json:"event_url"`
        EventType     string `json:"event_type"`
        OwnerNickname string `json:"owner_nickname"`
        Series        struct {
            URL   string `json:"url"`
            ID    int    `json:"id"`
            Title string `json:"title"`
        } `json:"series"`
        UpdatedAt        time.Time `json:"updated_at"`
        Lat              string    `json:"lat"`
        StartedAt        time.Time `json:"started_at"`
        HashTag          string    `json:"hash_tag"`
        Title            string    `json:"title"`
        EventID          int       `json:"event_id"`
        Lon              string    `json:"lon"`
        Waiting          int       `json:"waiting"`
        Limit            int       `json:"limit"`
        OwnerID          int       `json:"owner_id"`
        OwnerDisplayName string    `json:"owner_display_name"`
        Description      string    `json:"description"`
        Address          string    `json:"address"`
        Catch            string    `json:"catch"`
        Accepted         int       `json:"accepted"`
        EndedAt          time.Time `json:"ended_at"`
        Place            string    `json:"place"`
    } `json:"events"`
    ResultsStart     int `json:"results_start"`
    ResultsAvailable int `json:"results_available"`
}

UmarshalでJSONをパースする

jsonパッケージのUnmarshalでパースして終了。

jsonBytes := ([]byte)(jsonStr)
data := new(AutoGenerated)

if err := json.Unmarshal(jsonBytes, data); err != nil {
    fmt.Println("JSON Unmarshal error:", err)
    return
}
fmt.Println(data.Events[0])

$ go run main.go

以上で、完了です!!!簡単すぎる!!!!

全ソース

package main

import (
    "encoding/json"
    "fmt"
    "time"
)

type AutoGenerated struct {
    ResultsReturned int `json:"results_returned"`
    Events          []struct {
        EventURL      string `json:"event_url"`
        EventType     string `json:"event_type"`
        OwnerNickname string `json:"owner_nickname"`
        Series        struct {
            URL   string `json:"url"`
            ID    int    `json:"id"`
            Title string `json:"title"`
        } `json:"series"`
        UpdatedAt        time.Time `json:"updated_at"`
        Lat              string    `json:"lat"`
        StartedAt        time.Time `json:"started_at"`
        HashTag          string    `json:"hash_tag"`
        Title            string    `json:"title"`
        EventID          int       `json:"event_id"`
        Lon              string    `json:"lon"`
        Waiting          int       `json:"waiting"`
        Limit            int       `json:"limit"`
        OwnerID          int       `json:"owner_id"`
        OwnerDisplayName string    `json:"owner_display_name"`
        Description      string    `json:"description"`
        Address          string    `json:"address"`
        Catch            string    `json:"catch"`
        Accepted         int       `json:"accepted"`
        EndedAt          time.Time `json:"ended_at"`
        Place            string    `json:"place"`
    } `json:"events"`
    ResultsStart     int `json:"results_start"`
    ResultsAvailable int `json:"results_available"`
}

func main() {
    var jsonStr = `{
    "results_returned": 1,
    "events": [
        {
            "event_url": "https://kikaigakushuu.connpass.com/event/56040/",
            "event_type": "participation",
            "owner_nickname": "keiichirou_miyamoto",
            "series": {
                "url": "https://kikaigakushuu.connpass.com/",
                "id": 2589,
                "title": "人工知能勉強会"
            },
            "updated_at": "2017-04-26T02:47:01+09:00",
            "lat": "35.696575200000",
            "started_at": "2017-05-09T20:00:00+09:00",
            "hash_tag": "geek",
            "title": "強くなるロボティック・ゲームプレイヤーの作り方勉強会「4章 強化学習 前半」",
            "event_id": 56040,
            "lon": "139.771830100000",
            "waiting": 0,
            "limit": 30,
            "owner_id": 10866,
            "owner_display_name": "keiichirou_miyamoto",
            "description": "説明",
            "address": "東京都千代田区神田須田町2丁目19−23  (野村第3ビル4階)",
            "catch": "#人工知能,#機械学習,#深層学習,#ニューラルネット,#ディープラーニング,#初心者",
            "accepted": 17,
            "ended_at": "2017-05-09T22:00:00+09:00",
            "place": "コワーキングスペース秋葉原 Weeyble(ウィーブル)"
        }
    ],
    "results_start": 1,
    "results_available": 1744
}`

    jsonBytes := ([]byte)(jsonStr)
    data := new(AutoGenerated)

    if err := json.Unmarshal(jsonBytes, data); err != nil {
        fmt.Println("JSON Unmarshal error:", err)
        return
    }
    fmt.Println(data.Events[0])
}

実際にAPIにリクエストする

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "time"
)

type AutoGenerated struct {
    ResultsReturned int `json:"results_returned"`
    Events          []struct {
        EventURL      string `json:"event_url"`
        EventType     string `json:"event_type"`
        OwnerNickname string `json:"owner_nickname"`
        Series        struct {
            URL   string `json:"url"`
            ID    int    `json:"id"`
            Title string `json:"title"`
        } `json:"series"`
        UpdatedAt        time.Time `json:"updated_at"`
        Lat              string    `json:"lat"`
        StartedAt        time.Time `json:"started_at"`
        HashTag          string    `json:"hash_tag"`
        Title            string    `json:"title"`
        EventID          int       `json:"event_id"`
        Lon              string    `json:"lon"`
        Waiting          int       `json:"waiting"`
        Limit            int       `json:"limit"`
        OwnerID          int       `json:"owner_id"`
        OwnerDisplayName string    `json:"owner_display_name"`
        Description      string    `json:"description"`
        Address          string    `json:"address"`
        Catch            string    `json:"catch"`
        Accepted         int       `json:"accepted"`
        EndedAt          time.Time `json:"ended_at"`
        Place            string    `json:"place"`
    } `json:"events"`
    ResultsStart     int `json:"results_start"`
    ResultsAvailable int `json:"results_available"`
}

func main() {
    url := "https://connpass.com/api/v1/event/?keyword=python&count=1"
    resp, _ := http.Get(url)
    defer resp.Body.Close()
    byteArray, _ := ioutil.ReadAll(resp.Body)

    jsonBytes := ([]byte)(byteArray)
    data := new(AutoGenerated)

    if err := json.Unmarshal(jsonBytes, data); err != nil {
        fmt.Println("JSON Unmarshal error:", err)
        return
    }
    fmt.Println(data.Events[0])
}

以上で終了です。Go最高っすね。

Go合宿でビール駆動開発してきた@土善旅館

乾杯!乾杯!乾杯!

connpass.com
Go言語の合宿参加してきました。
一言で言うと、アルコール+Gopherって感じでした。

会場について

土善旅館[弓道合宿・開発合宿]

開発向けのプランがあるというイケてる旅館でした。
以下に気になってる人向けに良かった点をまとめておきます。

  • 高機能な電源タップと延長コードが大量にある。
  • 大きなスクリーンがある。
  • WiFi完備
  • スイッチングハブがあったり。。。(本当によくわからないくらいある)
  • TCP/IPマスタリング本やけものフレンズなど、偏った本のチョイス。
  • コーヒーが飲み放題。
  • 温泉がある。(24H)
  • 宴会場が60人くらい入れる。
  • 人が駄目になるクッションがある。
  • ディスプレイがある。(デュアルディスプレイとか出来る。)
  • 飯がうまい。

誰でも参加できるイベント?

Go言語に興味がある人やGo言語が好きな人、Go知らないけど楽しいコトしたい!という方なら、誰でも参加出来るイベントです。
参加者の実力も様々で、一切やったことない人から、有名なコミッターの方まで、私のような学生でも問題なく楽しめます。少しでも興味があれば気軽に申し込みをすると良いと思います。

他の参加者の記事

qiita.com medium.com

本題

JR日暮里駅で集合して、そこから笹川駅へ!

f:id:tikasan0804:20170423220627j:plain 正直遠かったです。夜行バスからのこの移動は結構体力もってかれましたw
お金に余裕があるなら、新幹線か前日入りまたは飛行機で成田から合流とかした方が良いかもです。

f:id:tikasan0804:20170423224725j:plain 笹川のこのいい感じの田舎感。本当にIT系とはかけ離れてる場所でしたw

f:id:tikasan0804:20170423221819j:plain 合宿所到着!大きなスクリーン!この時点でかなりテンションがあがる。

f:id:tikasan0804:20170423221828j:plain 開発に適した環境が用意されていて、参加者びっくり。

f:id:tikasan0804:20170423221831j:plain Go合宿スタート!!!(初乾杯)

f:id:tikasan0804:20170423222641j:plain 開発部屋風景。(乾杯)

f:id:tikasan0804:20170423222803j:plain 開発にはビールは必須。当然ですが昼間からビールを飲みます。もちろん開発もしてました。(そして、乾杯)

合宿の形式

基本的にもくもく形式で、個々でテーマを決めて何かしらの成果を出します。そして、2日目にあるLT大会で発表する。参加者の中には初心者もいらっしゃったので、@tenntennさんよるレクチャータイムもありました。
LT大会にはGo華景品もあり、どうせなら頑張っちゃおうって感じで、みなさん頑張ってました。

f:id:tikasan0804:20170423223344j:plain そして、飯がうまい!!!(既に4回くらい乾杯してる)

飯を食ってからが本番。

晩御飯を食べている時に、参加者のみなさん口を揃えて、「開発?ってなんですか」「これは一度、オフトゥンの様子を確認した方が良さそうです!」とか、「風呂入って酔いを覚まして・・・そこから乾杯するか」など、全くやらないオーラすごかったですが・・・
部屋に戻ると、みなさん真面目に開発していました。そして、何人かの参加者が「ビール入れた方が捗るんだが」と目覚めてる人もちらほら居ましたw

f:id:tikasan0804:20170423224205j:plain いざ、LT大会!!!(乾杯)

f:id:tikasan0804:20170423224321j:plain 私もLTしました。goaの話に触れつつ、goを好きになる方法について話しました。
以下に資料を貼ったので、興味あればどうぞ。

www.slideshare.net

結果発表

Goを一番愛しているで賞?というものに受賞しました!!!
景品は、@tenntennさんがアメリカでGopherグッズを買ってきてくれるそうで、かなり嬉しいw
景品提供ありがとうございました!!!

合宿終了

運営の素晴らしい進行により、無事大成功で合宿は終了しました。
とても、楽しかったです!ありがとうございました!

f:id:tikasan0804:20170423224607j:plain どこかで見たことがある集合写真を撮影し、解散しました。

qiita.com

OSSにコントリビュートしたら楽しかった

OSS楽しい!

僕みたいな学生エンジニアに限らず、お世話になることが多い世界中のOSSですが、つい先日ようやくコントリビュート童貞卒業したぺいです。
結構ハードルを感じている人が居ますが、もっと気軽にやってもらいたいと考え、今回は魅力について書いてみたいと思います。

何にコントリビュートしたか?

趣味で作っているアプリのバックエンドに使っているGoのWAFのgoaです。主にバグの修正やGAE対応です。

github.com github.com github.com github.com

説明努力をすれば、周りも助けてくれるかも?

goaの関係者が優しいというのもありますが、どのPRにしても色んな人の助けられてマージに至っています。「直そうと思ったけど、こういう問題があって難しいです」と正直に相手に伝えてみるといいと思います。場合によってはヒントを頂けます。そういったコミュニケーションを繰り返し、改善をしていけばマージ出来るコードになります。なので、実力に自信がなくてコミット出来てない人は、完璧でないといけないと気負いすることをまずやめたら良いと思います。

メリットがたくさん

何かの問題を解決することは、全体の構成を知るためにコードをたくさん読む必要があったり、そもそも言語の問題などが絡んでることもあります。なので、強制的に勉強になることが多く、自分の知識の穴埋めに丁度良いです。また、テクニックなども盗み放題なので、自分のものにすることが出来るのも魅力です。
解決するものによってはかなり大変なものもあります。(バージョンの問題や下位互換など)ですが、そういった問題解決はとても良い経験になり、個人でやっている開発にも応用できます。また、単純に達成感もありすごく楽しいです。なので、興味が少しでもあればチャレンジしてみると良いと思います。

小さいことでも貢献になる

初心者からするとハードルを感じてしまうPRやISSUEなど、「こんなしょうもないこと書いたら馬鹿にされないかな」とか、「間違ってたらどうしよう」とか、色々思うことはあると思いますけど、正直あまり気にしなくてもいいと思います。以下のようなものでも十分な貢献になります。

  • READMEやコメントなどのtypo修正
  • closeしていないコネクションcloseするだけ
  • 処理結果変わらないけど、若干無駄が減る
  • バグか分からないけど、怪しい挙動の報告

人気なOSS程、製作者は暇でないことが多いので、気づかないような小さいミスは見過ごしがちなので、結構PRチャンスはあるらしい?また、相手が知らないかもしれないバグを報告することも有益な情報です。特に自分が使っているOSSなら、自分のためにもなるので積極的にコミットすることをおすすめします。

中身のないISSUEはやめよう

Githubは誰でもアカウントが取れるので、気軽にOSS活動が出来ますが、全く利益を生まないようなISSUEを投げるのはやめましょうw f:id:tikasan0804:20170409223134j:plain 製作者側からしたら読む気が失せるような内容の報告が結構散見されます。相手も人間なので一定のマナーを守ること大事だと思います。

PRによってはテストが必要

コードの修正によって起きる変更に合わせたテストも加えることも必要です。どういうテストが必要なのかどこに書いたらいいかなどは、他のテストを参考にしたり、どうしても分からない時は聞くなりすると良いと思います。

今後も出来る限りコミットを継続し、色んなOSSに携わりたい!

テストコードが生む生産性について

テストコード書いてますか?!!!

最近になって、テストコードをちゃんと書くようになったぺいです。
今まで、どうしても以下のような理由でなかなか書けていませんでした。

  • どうやって書けばいいのか分からん。
  • 正直めんどい。

何故そんな私が、テストコード書くようになったのかについて、実経験を元にまとめてみました。

テスト書かないと起きる問題

自分はまだ学生ですが、アルバイトなどでプロダクトコードを書く機会があります。そこでテストを書いていないことによって起きた問題がありました。
それは、大きな変更が怖くて出来ない事と、動作チェックを手作業で行うので、チェック漏れはもちろん時間もかかるという2点です。

大きな変更が怖くて出来ない

例えば、様々な箇所から参照されているプロパティやメソッドの変更があったとします。その変更をすることは容易ですが、その変更から生じる影響範囲が広ければ広いほど、絶対に大丈夫という担保が難しくなります。そのため、問題っちゃ問題だけど、思い切って直すまでもないなというものは放置してしまう。
しかし、これが絶対に必要な変更だった場合が最悪です。かなり危険な上に、精神衛生上よろしくない開発がスタートします。もし、テストをちゃんと書いていれば、変更によって生じる問題が分かるので、変更に対して億劫にならずに済み、改善もスムーズに出来るので、最終的にサービスの寿命も生産性も上がると感じました。

手作業チェックはテストコード作成より時間がかかる

テストコードを書かない場合、コードの安全性は手作業によって行われます。なので、当然のように抜けもあります。しかも、チェックがその時の一回しか行われないので、後からの仕様変更でバグが発生しても、気がつかないことがほとんどです。結局そのバグのせいで余計な業務が発生して、テスト書く時間以上の時間がかかっていたりして、かなり悲惨な状態になっていたりする。
また、出来上がったコードのチェックがそもそも面倒なので、コードチェックは後回しになることが多く永遠チェックされないまま放置されたりしていることもある。

結論

テストは書かなくてもプロダクトは作れますが、長く運用する程、問題が発生します。結局テストコードを余裕で書ける時間以上の時間を食われるので、無理してでも書いた方が良いと結論づき、書くようになりましたとさ。

github.com 余談になりますが、BDDというテスト手法がわりと自分の中で良いなと感じいます。

Goのgoaを使ったAPIデザインまででイケてるところ

goa goa goaでAPIデザインまでしてみた

GoのWAFであるgoaの最大の特徴であるAPIデザインまでやって、イケてるなと感じた点を挙げてみました。

結論

goaにはDSLというものがあって、それを覚えるのが「最初はだるい」ですが、覚えてしまえば、すぐに追加や修正が出来るのと、最初の時点で設計をしないと開発に入れないので、今まで以上にどういう設計にすべきかを考えれるので、僕のようなすぐに手を動かしちゃう人には良い。

現時点でイケてるなと感じた点

現在、goaは趣味で開発しているアプリのバックグラウンドに使う目的で開発をしています。

セキュリティ周りのロジックが簡単に適用できる

端末の識別に使うtokenなどをHTTPのヘッダーに含めて、それが正しいものをかを毎回チェックしたりするのですが、そのチェックのロジックを全てのエンドポイントで行うわけではなく、必要な時とそうじゃない時の2パターンがありました。
goaでは、そのチェックロジックgあ以下のように簡単に記述することが出来ました。

goa :: Design-first API Generation
examples/security at master · goadesign/examples · GitHub

ヘッダーチェックロジックの定義(標準で用意されている)

var UserAuth = APIKeySecurity("key", func() {
    Description("ユーザートークン")
    Header("X-Authorization")
})

適用する

var _ = Resource("users", func() {
    BasePath("/users")
    Security(UserAuth) <-----/user以下のエンドポイントでヘッダーのチェックをする

        // このエンドポイントは特殊でチェックしたくない。
    Action("tmp account create", func() {
        Routing(
            POST("/tmp"),
        )
        ~~~~~~
        NoSecurity()  <------/user/tmpではチェックしない
        Response(OK, Token)
        Response(BadRequest, ErrorMedia)
    })

        // ▼チェックされる▼
    Action("account create", func() {
        Routing(
            POST("/new"),
        )
        ~~~~~~
        Response(OK, Token)
        Response(Unauthorized)
        Response(BadRequest, ErrorMedia)
    })
    Action("list", func() {
        Routing(
            POST("/"),
        )
        ~~~~~~
        Response(OK, User)
        Response(Unauthorized)
        Response(BadRequest, ErrorMedia)
    })
        // ▲チェックされる▲
})

まあ、便利!wという感じでしたね。他にもデフォルトでチェックのロジックはgoaで用意されており、ごく普通なアプリを開発するには困らなさそうでした。

パラメーターのバリデーションが行える

地味にかったるいバリデーション。ですが、やらない訳にもいかない。しかし、だるい。
goaはとても簡単にこの問題を解決してくれます。

Param("email", String, "メールアドレス", func() {
    // メールのフォーマットになっているかチェック
    Format(goa.FormatEmail)
})
Param("client_version", String, "アプリのバージョン")
Param("platform", String, "OSとバージョン")
Param("identifier", String, "識別子(android:Android_ID, ios:IDFV)", func() {
    // 正規表現
    Pattern("(^[a-z0-9]{16}$|^[a-z0-9\\-]{32}$)")
})
Required("email", "client_version", "platform", "identifier")

他にも文字数チェックやEnum形式で特定の文字列だけを許可したりなど、いい感じにバリデーションしてくれます。 goa :: Design-first API Generation

Swagger食わせるだけでAPIドキュメントが出来上がる

実際に作成したDSLをSwaggerのフォーマットに合ったjsonyamlファイルを作成してくれます。これに関してはすごいの一言でしたね。。。
見たほうが早いので、以下に自分の作成したものの実例を貼っておきます。
Build, Collaborate & Integrate APIs | SwaggerHub
作らないといけないけど、正直面倒なドキュメント。goaは強制的にドキュメント作成されるので、共同開発する上で最高です。

ビジネスロジックに集中が出来る

goagenコマンドを実行して、フォルダ移動を多少必要としますが、ビジネスロジックが全てcontrollerパッケージにまとまります。
※controllerとserverフォルダはデフォルトではありませんが、私の好みで作成してます。

.
├── LICENSE
├── Makefile
├── README.md
├── app
├── client
├── controller <----------ビジネスロジックを書く(雛形は自動生成される)
│   ├── events.go
│   ├── genres.go
│   ├── prefs.go
│   └── users.go
├── design <----------APIでざいんを書く
│   ├── api_definition.go
│   ├── media_types.go
│   ├── resources.go
│   └── security.go
├── glide.lock
├── glide.yaml
├── server
│   ├── app.yaml <-------GAEの設定ファイル
│   └── main.go <-------ミドルウェアの定義など(雛形は自動生成される)
├── swagger
│   ├── swagger.json
│   └── swagger.yaml
├── tool
└── vendor

DSLで定義したセキュリティ周りやバリデーションなどは、自動的に生成されているので、組む必要がありません。そのため、コントローラがビジネスロジックだけを組めばいい状態になり、シンプルで目的がハッキリとしたコードにすることが出来ます。これはいつもごちゃごちゃになりがちな自分にとっては嬉しかったです。

goaは面倒なところもあるけど、結構良さそう

他にもイケてる点はありますが、とりあえず、印象的だった点を挙げてみました。これから実装に入るところなので、最終的な生産性に関して未知数ですが、微妙にだるい点などもいくつかあるので、うまく解消する方法などが見つかったら紹介していきたいと思います。

goaを使ったGAE/Go開発が辛かった話。(過去形)

goa x GAE/Go

この問題は解決されました!!!!!GAE/Goでgoaは使えます!!!!

結論

goaが使用しているパッケージの中にsyscallをインポートしているパッケージがあるので、forkしてコンパイル対象から外す記述を追加した上で、パッケージのimport先をVendor以下で書き換えてプッシュしたら行けた。(やばい)
プルリクがマージされれば一瞬で終わる問題です。

ikawahaさんによって、この問題は解決されました!!!!!圧倒的感謝!!!!!!
自分でも直してプルリク投げれるようになりたいなと思いました。。。
Comparing goadesign:master...ikawaha:fix/appengine/metrics_20170331 · goadesign/goa · GitHub

goaとは

github.com speakerdeck.com デザインベースで開発を行うことが出来るGoのフレームワークです。
個人的には、もっと人気出てもいいのになと感じているくらい良いOSSです。

GAE/Goとは

GoogleクラウドサービスのGCPのPaaSのことです。GoはGoogleは開発した言語とあって、とても親和性が高くおすすめの組み合わせです。(慣れるまで苦行ですw)

なぜgoaをGAE/Goで使おうとしたか?

趣味で開発しているアプリのバックエンドにGAEを使っていて、現在は自分のためだけに運用している状態で留まっていたので、ある程度使えるものにしようと考え、思いっきり変更をかけることになり、どうせなら、Echoベースで構築しているのをgoaに変えて作り直しちゃうかーwってノリです。
一応ちゃんとした選定理由があります。今回から本格的に友人との共同開発になるため、APIドキュメントツールのswaggerに使えるjsonファイルを作成してくれることや、ビジネスロジックに集中できるなどなど。

激しく辛かった理由

GAE Standard does not allow syscall imports and there is a syscall import here: github.com/armon/go-metrics/const_unix.go

上記のエラーが今回辛かった理由です。
goaを通常の環境で動かす分には全く問題なかったのですが、syscallというパッケージがgoaが使っているパッケージで一部使われており、それのせいで起動させることも、デプロイすることも出来ませんでした。

github.com この問題は、issueでも報告されていました。

これはGAE開発でよくあることで、単純な解決方法で解消が可能です。
// +build !appengineという記述を問題になっているコードに記述するとコンパイルされないので、回避することが出来ます。

// +build !windows
// +build !appengine <- こいつで解決できる

package metrics

import (
    "syscall"
)

const (
    // DefaultSignal is used with DefaultInmemSignal
    DefaultSignal = syscall.SIGUSR1
)

プルリクも既に投げられていました。

github.com

しかし、長期に渡ってマージされておらず、仕方ないのでforkしたリポジトリで修正を適用し、goaのインポート先のpackageを自前リポジトリに向けようと考えたのですが、ここで問題が発生しました。 goaはコードを自動生成をします。そのため、以下のようにimport先が文字列で指定がされています。

imports := []*codegen.ImportSpec{
        codegen.SimpleImport("io"),
        codegen.SimpleImport("github.com/goadesign/goa"), <----- これとか
        codegen.SimpleImport(imp),
        codegen.SimpleImport("golang.org/x/net/websocket"),
}

つまり、goaをforkして直してしまうと、codegen.SimpleImport("github.com/goadesign/goa"),のような記述がされている部分を自前のリポジトリに書き換える必要があります。これは記述箇所が多すぎるので、影響がどこまであるか予想がつかないので、変更を加えた自前リポジトリは使えないという状況になりました。(問題箇所だけ書き直したリポジトリを使っても出来そうな気もしますが、どこで止まるか想像つかないのでやめました。)
なので、仕方なくVendor以下のgoaのimport文を直接書き直し、一時的に動かせるようにしました。もちろんこれはリモートリポジトリでは加えられていない変更なので、Vendorプッシュをし、無理やり運用することになりました。

つまり

早くマージされてほしい。 github.com

参考資料

www.freegufo.com motemen.hatenablog.com

Realm World Tour 17 Mobile DatabaseとRealm Mobile Platformについて

そもそもRealmってどういうもの?からRealm Mobile Platformはどういった経緯で、開発がスタートしたのかが分かりやすかったので、記事にしました。
※聞き起こしに近いので、誤字脱字やニュアンスが違う可能性があります。

realm.io

The Realm Mobile Platform Experience

Realmは様々なプラットフォームに対応しています。
正式版がリリースされてから、多くの企業に使われています。恐らく世にあるアプリの一箇所以上は使われているのではないかと思います。

f:id:tikasan0804:20170326144848j:plain データベースのエンジンはモバイルに最適化するために、フルスクラッチで作成されています。そして、様々な便利な機能が盛り込まれています。
ライブオブジェクトであったり、リアクティブであることが特徴ですが、その中でもデータベースの特徴であるライブオブジェクトを使って、リアルタイム性のあるの良いものを作ろうと考えています。 例えばRealmのライブオブジェクトを使うと、自動的に要素が更新されますので、毎回フェッチする必要がなくなります。それをうまく使うことでスマートにリアルタイム性のあるアプリケーションを作ることが出来ます。

Realmの動作フロー

f:id:tikasan0804:20170326144904j:plain この図は、Realmの重要なフローを表しています。

f:id:tikasan0804:20170326144910j:plain 最初はオブジェクトとのAPIです。
スライドにあるように、まずはRealmのオブジェクトを作成します。使い方は簡単でクラスを作成するだけで使うことが出来ます。特にデータモデルを定義するための中間ファイルなどは必要ありません。またRealmのためのミドルウェアも必要がなく、シンプルに使えることがRealmの最大の特徴です。
このRealmのモデルには、自由にプロパティを設定する。メソッドを定義することが出来ます。またリレーションもプロパティを使うことで関連付けの表現をすることも出来ます。SQLのように高度な記述は必要なく、単純にポインタでの関連付けなので、シンプルにデータを取得することが出来ます。
作成したオブジェクトを使うには、クエリを使って取得する必要があります。クエリを使ってオブジェクトを取得すると、Resultを使って結果を取ってくることが出来ますし、関連しているデータも取得することが出来ます。
関連には逆方向関連も使うことが出来、それはプロパティを使うことで実現することが出来ます。Realmのクエリで自由なことは、クエリのために使ったメモリをコピーせずに使うことが出来るので、メモリ消費がほとんど発生しません。

f:id:tikasan0804:20170326144917j:plain これが実際に構築されたクエリです。見たらわかると思いますが、すごく簡単なことがわかります。この例ではageが2以下のものを検索していますが、もし、このageの内容が変わった場合は、自動的に更新されているので、検索結果を再読込することを気にする必要がありません。また、変更があったことは通知もできるので、アプリ側はそれに従って動けばいいだけなので、簡単に機能を実現することが出来ます。
さらに、Realmはできるだけメモリのコピーをしないようにしているので、例えばこのクエリの場合、全てのデータを取ってるように見えますが、アクセスされるまでメモリを確保されないので、全てのデータを取っているわけではなありません。そのため特別な処置を必要としません。

f:id:tikasan0804:20170326144930j:plain Realmのクエリの結果、または関連のオブジェクトに変更があった場合、通知を用いて画面の再描画をすれば良いです。
その通知の例がこちらのスライドになります。この通知では、何が変更された追加された削除されたという情報が細かく通知されます。これを使うことで部分更新やきれいなアニメーションを使った再描画が可能となっています。例えば、アプリケーションで犬専用のインスタグラムがあったとします。その場合、このスライドのようなクエリを書くことになります。そして、更新があったら通知を使って、データを更新するのですが、追加の通知を乱暴にすることなく、何が通知されたかを適切にユーザーに見せることが出来ます。

f:id:tikasan0804:20170326144937j:plain ネットワークからデータを取ってくるだけではなく、ユーザーによる並べ替えや更新などによって変更がされることもあります。それにはトランザクションを使います。
Realmはトランザクションによってまとめた変更や削除などをひとつの処理として扱うことが出来るので、データの整合性を確保することが出来るので、処理が完了していないデータなどが見えてしまうことがありません。
ここまでの流れがRealmの基本動作になります。Realmは高速でモバイルデータベースを提供しています。

Realm Mobile Platform

f:id:tikasan0804:20170326144943j:plain 今まではモバイルデータベースを提供してきましたが、もうひとつモバイル開発者が考えていることがあると思います。それはRDBMSなどのデータベースとの連携だと思います。その解決のために考案されたのが、Realm Mobile Platformになります。

f:id:tikasan0804:20170326144956j:plain 誰もがアプリ作成のためにデータの同期をサーバとやることがあると思います。その作業をシンプルにしたくても、思わぬトラブルや難しい問題に直面することがあります。
その問題を表現する時に我々はこの氷山を使った説明をします。

この図の上の部分は、データを更新して、UIへ反映してという単純な部分を表していますが、この処理だけでも色々な問題が介在していて、簡単ではありません。
サーバーからデータを取得して、保存が出来たとしても、別のユーザーの変更も考慮しなければいけなく、一筋縄ではいきません。

f:id:tikasan0804:20170326145002j:plain Realmを使ったデータ管理の場合とRestAPIの比較図になります。
従来のやり方では、JSONのパースなどの処理が必要でしたが、Realmの新しいプラットフォームでは、一切そういった操作は必要がありません。サーバーとクライアントで双方向の通信をしていて、それは全て自動で行われています。例えば、データの更新の衝突した場合でも、こちらで用意したルールに従って処理が行われますので、ケアをする必要はありませんし、CPUやメモリの消費も最小限に抑えられています。

f:id:tikasan0804:20170326145009j:plain なので、Realmを使う場合は、赤い枠の部分だけを気にすれば良いと言うことになります。先程までに詳解した自動同期などは全てRealmがカバーしてくれますので、今まで通りローカルのデータベースを使うようにやれば良いだけです。唯一違うことは、バックグラウンドスレッドで更新がされる場合と同じように、他のデバイスの変更の通知を知る術を用意する必要はあります。
本当に簡単になります。データの変換も必要ありませんし、今までは一回の処理のために複数回APIを叩くこともあったかもしれませんが、それが全て任せれるようになります。
ネットワークが繋がっていれば、いつでも使うことが出来ますし、データも差分だけなので最小限に済ませるようにしています。Push Notificationを使ったBackgorund Fetchも少ない時間で終わらせることが出来ます。

f:id:tikasan0804:20170326145017j:plain よくある通信のあるモバイルアプリはサーバーとの通信、JSONとのやり取りがあると思います。それはスパゲッティのように複雑になりがちです。普通にやるとメンテナンスコストがかかると思います。

f:id:tikasan0804:20170326145024j:plain これが実際にRealmで行った時の図になります。このようにものすごくシンプルになっていることが分かると思います。また、一部分だけRealmを使うなども出来るので、シンプルに導入することが出来ます。
サーバー側のコードもRealmを使うときと同じように使うことが出来ます。

f:id:tikasan0804:20170326145031j:plain これはサーバー側でRealmを使った時のコード例になります。いまのところはNodejsを使って構築することになっています。サーバーサイドのRealmでの変更はトランザクションを用いて更新します。

f:id:tikasan0804:20170326145038j:plain このスライドにあるように、同期に加えて他にも多くの機能を提供しています。ユーザーの認証だったり、エンタープライズのみになりますが、スケーリングなどもあります。これについては後でDemoを致します。
Realmはサーバーサイドとクライアントで同期はもちろんですが、双方向のやり取りや継続的なバックアップ機能も提供しています。

f:id:tikasan0804:20170326145050j:plain f:id:tikasan0804:20170326145057j:plain 岸川さんがマリウスさんに、漢字の書き方を教えるDemoの様子。
リアルタイム同期なので、書き方はもちろん書き順も教えることが出来ます。
以下に動画があるので、そちらで参照出来ます。
Realm Mobile Platform: リアルタイムの同期と、Coreのオープンソース化

Realm Mobile Platformを使ったソースも公式から公開されています。
Realm Demos · GitHub

f:id:tikasan0804:20170326145106j:plain Realmのウェブサイトから、demoを取得することが出来ますので、ほとんどの機能を試せるようになっています。

DeveloperエディションとPlatformエディションの2つがあり、platformエディションは月々$1,500がかかりますが、2ヶ月のトライアル期間があります。違いとしては、サーバーサイドから変更の通知や同期などが使えるかどうかです。クライアント側での同期だけなら、無料で使えます。
Realm Pricing

f:id:tikasan0804:20170326145114j:plain Realmは人材採用をしてます!
多くのエンジニアがリモートで働いています。もし、リモートワークに興味がある方がいましたら、是非チェックしてみてください。

当日のゲスト枠の発表

Realm Mobile Platformについての情報