mmagスマートフォン用の表示で見るmmagハマったことメモなど2017-02-23ニポポタマスを支える技術こんにちは。わたくし、勤め先の有志と社内向けの日報投稿Webサービス、ニポポタマス(通称ニポタマ)をやっています。元々は会社の開発合宿でつくりはじめたものですが、会社のみんなが日々の出来事、書きたいことを書ける場として、また僕ら開発者がやりたいことをいろいろ試せる場として、9月半ばから、ざっくり5ヶ月くらい開発・運用してきました。使っている言語やフレームワークについて、どんな感じに使っているのか、また思うことなど書いてみます。PageなWebアプリです。ElixirとPhoenixってなんぞや、という方にはこの動画がおすすめです。基本的には単にJSONを返すAPIなので、至って普通のPhoenixアプリケーションです。--no-brunchから初めています。データベースもデフォルトのPostgresです。このAPIサーバはbodyというコードネームで開発しているので、以降bodyと表記します。Elixirを採用したのは、僕が使いたかったからです。deployでラップしています。VMではプロセスをspawnすることで簡単に非同期処理を書くことができます。ニポタマでは、ログイン時の認証にSlackを使っており、ニポタマにおけるアイコンはSlackアイコンと同じものです。アイコン画像を保存しているわけではなく、URLを保持しているのですが、Slackのアイコンを変えたときにリンク切れになってしまうという問題があります。暫定対応として、ログイン時にその人のSlackアイコンURLを保存しなおしています。アイコンURLはOAuthのフローの中で取得できるので改めて取り直すことはしませんし、保存はUPDATEクエリ1回だけなので、とくに時間がかかるわけではありません。ユーザへのレスポンスはすでに返せる状態にあるのに、こちらの都合でわずかな時間でも待たせるのはどうなんだという建前と、ちょっとかっこいいよねという本音から、非同期に保存をするようにしています。コードとしては以下のように、コントローラの中でTask.start/1を使っています。滅多にないと思いますが仮に失敗してもクリティカルな問題にはならないので、クラッシュしても何もしていません。VMで動くアプリケーションをつくるときのライブラリやフレームワークやデザインパターン集みたいなものです。VMで動くOTPアプリケーションたちのWebインターフェースとして捉えるべきかなと思っています。ここで言いたいのは、Phoenixの後ろ側に機能を追加することで、Webアプリとしても幅が広がるんですよ、ということです。OTP自体の説明が難しかったりあくまでフレームワークなので「こんなことができるようになります」というのも難しいのですが、例えば先ほど書いたSlackアイコンのURLを更新する実装では、アイコンを変えた後にログインしてくれないと、いつまで経ってもリンク切れのままという問題があります。定期的に任意の処理をするサーバを書けば解決するので、あとでやりたいなと思っています。quantumなどのジョブスケジューラを使ったりしてもいいかな、という気持ちですが、結局quantumも中身を見るとGenServerをつかったOTPアプリケーションです。他には、昨年末に以下のような集計をして、2016年のまとめのようなページを作ったことがあります。2016年に投稿された日報の数2016年に最も「otsu」されたひと(otsuはlikeみたいなものです)2016年に最も「otsu」したひと日報投稿から最速で付けられた「otsu」などなどこれらをJSONで返すコントローラはEctoを経由してSQLで集計をしているわけですが、アクセスがある度に集計するのは無駄なので、キャッシュしようと考えるのが自然です。そんなときに、redisやmemcachedを立てなくても、キャッシュしてくれるOTPアプリケーションを導入すれば問題を解決できます。ニポタマではcon_cacheを使いました。一部省略したコードを以下に示します。endendアプリケーションサーバが1台であったり永続化したいときはそれほど旨味を感じませんが、Erlangクラスタを組んだときなどは複数ホストでキャッシュを共有できる(はず)ので便利かと思います。Vue.jsユーザが実際に触れる部分は、Vue.jsで実装されたSPAです。faceというコードネームで呼んでいるので、以降faceと書きます。Vue.jsは、scriptタグでちょいっとCDNから読むだけでも使えて、かつ、ある程度の規模のSPAも実装できるフレームワークと言われています。開発メンバーの@hypermktさんがぜひVue.jsでSPAを試したいということで採用が決まりました。2.0のリリースから約1週間後には、v2にバージョンアップしていました。SPAとして動かすため、vue-routerやvue-resourceなどのライブラリも使用しています。vue-resourceは、そろそろaxiosに替える予定です。学習コストという言葉はあまり好きではありませんが、つくれるものの規模を考えると、学習のコスパは良いと思います。僕が見えていない部分もあると思いますが、それほどフレームワークとして巨大でなく、日本語版のドキュメントもあるので、未経験でもわりとすぐに開発に入れた印象があります。vuexは使っていませんが、ニポタマくらいの規模のアプリケーションでも導入してもいいんじゃないかと思っています。公式のドキュメントには中規模から大規模なアプリケーションで導入を検討するとよいということが書いてありますが、APIからデータを取ってきて見せるくらいのSPAで使っても、特に都合の悪い点は無いと思います。ニポタマくらいの規模と言っても伝わらないと思うので、参考までにsrcディレクトリ以下の*.js、*.vueの行数は合計約2000行、ルーティングは以下の通りです。]});開発中にちょっと気をつけたほうが良いなと思った点として、VueのMixinはあまり使い過ぎないことをおすすめします。複数のMixinをコンポーネントに入れていくと、どこで定義されたのかパッと分からないメソッドが増えていってしまいます。テストロジックの複雑なクラスに絞って、ランナーはKarma、フレームワークはMocha、アサーションはChaiを使ってテストを書いています。コンポーネントのテストについては@hypermktさんによるブログエントリが詳しいです。E2Eテストは一切やっていません。E2Eテスト自体をメンテナンスするコストと、E2Eで得られるであろう恩恵を比べての判断です。Go/Echoニポタマでは、家からも読み書きできて、かつ社外の人には日報を読めないようにするために、ユーザ認証にSlackのOAuthを使っています。OAuthにはstateパラメータというものがあり、これを使って、認証を要求した人と認証できたよと言っている人が本当に同じなのか、ということをチェックしています。tokenを得るという感じです。これをサボるとCSRFなどのリスクが生まれるのですが、開発初期はサボっていました。後になって対応しようとなったときに、APIサーバにこの役割を追加するか、別の言語で小さなサーバを書くか検討して、楽しそうな方を選びました。Toolkitのsessionsパッケージを使っています。役割上faceのCSSを使うことがあるので、faceと同じリポジトリに入れてしまっています。役割このサーバがどんな仕事をしているのか説明します。認証に関することをしているのでauthと呼んでいましたが実際よく見るとgatewayなどが適当かな、と思うので、gatewayサーバと呼んでいきます。リクエストを受けるnginxは、/loginはgatewayサーバへプロキシし、/ではfaceをserveするように設定してあります。Slackボタンに埋め込みます。Slackログインしたユーザがリダイレクトされてくると、セッションに入れた文字列とstateパラメータを比較して、違っていたらエラーを表示します。両者が一致したら正当なログインなので、codeをbodyに送ります。bodyはSlackとやり取りし、以下の3通りの応答をしてきます。ニポタマユーザとして登録済みである。ユーザ情報とJWTを与えようニポタマにユーザ登録する資格がある。サインアップへ進むのだニポタマにユーザ登録する資格がない。採用サイトへ飛ばせこれらの内いずれかのレスポンスを受けたgatewayはfaceの適切なURLへリダイレクトします。faceはbodyとやり取りする中で認証に関するエラーを受けると/loginへリダイレクトし、認証やり直しとなります。teamのメンバーとしてログインしてるか、などを見ています。所感ポン置きとはこのことか、というくらい、デプロイの簡単さに驚きでした。ビルドしてrsyncしておしまい。デプロイ後の起動はsystemdでやっています。np-app:~/auth_serverGoで動くものを書いたのはほぼ初めてだったので、動いてるっぽいけどこんな書き方でいいのだろうか、という手探りな状態でした。あとで有識者に見てもらおうかと思います。WebSocketニポタマをつくる上での哲学として、リアルタイム性に重きを置いており、誰かのアクションが他のユーザの画面にすぐに反映されるようにしています。具体的には、日報につけられたotsuや、日報が書かれている途中の様子は、画面を見ている他のユーザにWebSocketで配信されるようにしています。これは開発画面で2つウィンドウ開いて、片方で日報を書いているところです。日報が書かれている様子が見えています。保存していない段階では、あえて誰が書いているのか見せないようにしているのですが、これが意外と楽しかったりします。実装としては、サーバ側はPhoenixのChannel、クライアント側はそのクライアントを使っています。Pollingにフォールバックしてくれます。簡単にコードを示すと、クライアントはtextareaのkeyupイベントで日報の内容が変化していたらnippo:updateイベントと共に日報の内容をサーバに送ります。それを受けたサーバは、送られてきた内容をその他のクライアントにbroadcastしています。接続はVueのmountedライフサイクルで行っています。endend所感WebSocketを使うとサーバからのpushができるようになるので、表現の幅がグッと広がるなと感じています。アプリケーションとしてできることも増えますし、提供できる体験も変わってくると思います。ユーザの環境によっては接続が切れやすかったりするので、その点は留意したほうがよいでしょう。今後やりたいこととしては、Phoenix.Presenceで接続しているユーザの一覧が取れるので、何かおもしろい表現ができないかなと考えています。APIニポタマには、投稿された日報を読み上げる機能があります。APIを使っています。以下のようなmixinを用意しています。`${d.user.nickname}さんの日報です。${d.readable_content}`).join('。'みなさんおつかれさまでした。