@kyanny's blog

My thoughts, my life. Views/opinions are my own.

Single Page Application ではない場合 JavaScript コードのエントリポイントはどこにあるべきか?

仕事で中規模程度の Rails アプリケーションのコードベースをいじっている。このアプリはもともと app/assets/javascripts 以下に必要に応じて JavaScript ファイルを置き、適当なテンプレートファイルから直接 JavaScript の関数を呼び出したりしていた。ごく普通の Rails アプリである。

このアプリは CMS で、いわゆる「ブログの管理画面」みたいな用途で使われている。一部の機能はそれなりに込み入った UI 操作を必要としページ遷移なしに操作できる必要があるが、旧来のやり方では JavaScript コードの管理が間に合わなくなってきたので部分的に Backbone.js を導入し始めている。

最近悩んでいるのが、 Backbone.js なコードのエントリポイントをどのように呼び出すべきなのか?ということ。そもそも自分が Backbone.js なり JavaScript なりの流儀に疎いということもあるが、現状では旧来のやり方を引きずってテンプレート内で Backbone.View を継承した自前の View クラスを new したりしている。が、コードが追いづらくてイマイチな気がしている。

気になったので Backbone.js の導入事例紹介ページ にあるもののうち名前を知ってるサービスのページを見てみたが、調査力不足でよくわからなかった。

  • DocumentCloud

    • index.html の一番下、 $.ready の中で dc.app.workspace = new dc.controllers.Workspace; を呼び出している
    • dc.controllers.Workspace は Backbone.Router を extend しており、こいつの initialize のなかで Backbone.hisotry.start を呼び出している
  • Hulu

    • Hulu.Utils. 的なのの中から Backbone.hisotry.start を呼んでいる形跡がある
    • が、具体的に html のどこで導火線に火をつけているのかはよくわからず
  • GILT

    • どこで発火してるかさっぱりわからず
  • Wordpress.com

    • Notification のページで使っているらしいがログインしてないのでわからず
  • Delicious

    • Chaplin.js を使ってると素の Backbone.js とはだいぶ事情が違うよなぁ、と思い調べず
  • Foursquare

    • underscore.js は使ってるようだが backbone.js をどこで利用してるかすらわからず
  • Khan Academy

    • index.html の下の方で Homepage.init というのを呼び出しておりこいつがエントリポイントっぽい
    • Homepage.init の中からいろいろ芋づる式にコードが呼び出されているが Backbone.history.start を呼んでるかどうかはわからず
  • Basecamp

    • Backbone.history を使っていそうだが、内部でいろいろやってて具体的にどこから発火してるのかわからず

さすがに DocumentCloud は Backbone.js を作っただけあってお手本通りな使い方をしているだろう、と考えると、 Backbone.history.start を呼ぶことで(編集済み:Backbone.history の役割について勘違いしていた) Router からいろいろ発火していく、という風にするのがきれいなのかな、という気がする。しかし、 SPA ではないふつうのアプリの場合、ページ遷移はそこかしこで発生するので Router はあんまり役に立たないような気もする。

「実質ルーティングが発生しないとしても、 Backbone.js アプリでは必ず Router から一連のコードが実行されるように統一すべき」とも思えるし、しかし「ルーターとしての用をなさないのに余分なものを導入してはかえって見通しが悪くなるので html から直接 View を new するなりすべき」と言われたら反論できる気もしない。今のところ 7:3 くらいで、必ず Router を通す、を試してみたい気はしている。

みなさんどうやっているんでしょうか。

例えば OSFA な API をやめる

OSFA == one-size-fits-all 単一の API で全てをカバーするのをやめたらどうか、ということ。

APIのバージョニングは限局分岐でやるのが良い - Hidden in Plain Sight

この話のポイントとはちょっとずれてる && Podcast 聴いてないのですが。

Quipper プラットフォームで内部的に利用されている API も、 /v1 というパスの下にはえててごく一部のエンドポイントだけ /v2 がある、みたいなよくある状態だったんだけど、ここ二三ヶ月くらいで

  • クライアントアプリケーションに特化した一連の API を必要に応じて実装する
  • それらの API は「UI べったり」に最適化されている

という風に(たぶん @miyagawa さんが言っているのと似た形に)変わってきた。どういうことかというと。

Quipper は GAKUMO とか Quipper School とか昨日リリースしたばかりの Quipper Training とか、複数のサービスを展開している。ソースコードレベルで同じアプリケーションをブランドだけかえて使い回していることもあれば、完全に独自のアプリケーションを実装して使っていることもある。それらのアプリケーションはすべて単一の API を参照しているんだけど、一種類の、一般的な API だけではどうしてもクライアント側の実装に無理がでてしまう。

そこで、もう割り切ってクライアントごとにネームスペースを切り、それぞれ独自の API を実装したほうが素直な実装になるし、コンフリクトも無くなるし、開発スピードも上がるのではないか?と思って試してみたところ、うまくいった。具体的には、

  • Boofy というアプリケーションは /boofy/v1 以下に
  • Soozy というアプリケーションは /soozy/v1 以下に

必要な API を実装する。開発拠点がロンドンと東京に分散しており、それぞれ別サービスを開発しているが API は共有している、という状況下においてこの隔離戦略はうまくフィットした。

こうして Backbone.js や Chaplin.js をベースに実装されたクライアント向けに最適化された API を提供できるようになり、クライアント側の実装をシンプルに保てたり、 HTTP リクエストの回数を減らしてパフォーマンスを向上させることができた。例えば、

  • ダッシュボード画面を描画するために必要な全てのデータを返す /boofy/v1/dashboard API とか
  • ユーザーが学習できるコンテンツを全てまとめて返す /soozy/v1/contents API とか

そういう「UI べったり」な API を実装している。なお /v1 がついてるのは過去の慣習に引きずられている側面が大きく、バージョニングよりも上位の部分で分離してしまおうという考え方なので、実は /v1 は無くてもいいと思っている。

API と聞いて多くの人がイメージする RESTful でジェネリックな API とは真逆で、パッと見だといかにもまずそうなパターンに見えるが、 Quipper ではどのエンジニアも JavaScript/CoffeeScript でクライアントを書きつつ Ruby で API サーバも書くので、互換性やら調整やらでブレーキがかからなくて済むぶんだけ開発サイクルははやくなった。コードレビューもサービスごとの担当チーム内でうまく回せている。

このアーキテクチャ、実は Netflix の API デザインを参考にしている。

The Netflix Tech Blog: Embracing the Differences : Inside the Netflix API Redesign

タブレット、スマートフォン、 AppleTV のようなセットトップボックス、さらには PlayStation3 のような据え置きゲーム機など、さまざまなクライアントからアクセスされる Netflix の API は、各クライアントごとに特化したインタフェースを提供するアダプタ層と、その裏側にあるジェネリックな API との二層構造になっている。

Quipper API は、このデザインのうちクライアントに特化したアダプタ層を作るという点に注目して、そこを抜き出して適用した状態といえる。MongoDB の O/R マッパを利用したモデル層が独立したライブラリになっていて API サーバのインスタンス内にロードされているので、このモデル層を MongoDB に対する API とみなせば二層構造になっていると言えなくもない。

このやり方が正解だ、と言うつもりはない。というか、 API デザインに正解なんて無いのだと思う。どんなに美しくデザインされた API であっても、クライアントから利用しづらければ有用だとは言えない。大事なことは常に進化していくことで、変更に耐えうるデザインをいかに維持するのか、そのためのアプローチは複数あっていい。バージョニングを工夫するのも一案だと思う。

個人的には、そもそも単一のシリーズの API だけで様々なクライアントからの要求を満たせると考えること自体が間違いだったんじゃないか、と思っている(これも先の Netflix のブログ冒頭で "Netflix has found substantial limitations in the traditional one-size-fits-all (OSFA) REST API approach." と言及されているが)

Learn Basics Seriously

 I decided to try to learn basics seriously in this year. 


English


I began to study audio lingual textbook and pronunciation DVD to gain my listening and speaking skills. As someone said, once learn pronunciation I couldn't speak even simple word like "father". Speaking training is a bit boring, but I can believe I'm going forward. 


Algorithm


As a software engineer, I have been ashamed that I have not enough knowledge of algorithm and data structure several years. I watched online course on Coursera, but it was difficult to understand because of very fast English speaking. I also read Japanese algorithm book, but it was too boring. I can't find out the way to learn algorithm good for me yet. 

You can review "bundle update" efficiently with Compare Linker

Usually Ruby projects use Bundler to manage dependent gems. You probably put a Gemfile.lock in version control system like Git. You will run bundle update to upgrade dependent gems.

If you are a member of software development team and your team is using GitHub, I guess that you'd like to open pull request and review the result of bundle update.

But a diff of Gemfile.lock isn't easy to review. Let's see below screenshots.

Problem

screenshot of Gemfile.lock

For example, byebug gem is upgraded from 2.5.0 to 2.6.0. But if you want to know what is changed by this upgrade, you have to find a changelog file by yourself.

Fortunately byebug has changelog, but it's not enough to review whole of changes between 2.5.0 and 2.6.0. There are 32 commits, 19 changed files with 449 additions and 356 deletions between 2.5.0 to 2.6.0 but only one line is added to changelog.

Unfortunately, some gems don't have changelog. You sometimes have to read each commit log to know what is changed.

screenshot of Gemfile.lock

If you are using gem from Git repository, it's more hard to investigate changes. In second screenshot, simplecov is upgraded from revision 5703690241a1df6525ce61bf3ec2eda813760237 to revision 5e2b5b39ef46be1187fde9e5b92f320be3f21934. It's totally useless information for human's eyes.

Solution

I was bothered by such a annoying diffs many times. One day, I noticed that I often make GitHub's compare view URL for each gems to read diffs with nice user interface. So I came up with to automate making compare URLs.

Compare Linker is a solution. This library analyses diff of Gemfile.lock, detects upgraded gems and makes compare URLs for each gems.

screenshot of compare linker

This library is designed to work well with GitHub's pull request, so I wrote tiny Rack application to receive webhook of pull request from GitHub. You can run this rack application on Heroku by a few steps setup, and will get a neat comment like above screenshot.

I'm going to launch a web service that provides this function for open source Ruby projects. This library is still under development. Any feedbacks are welcome.

Farewell, #shibuyarblunch

#shibuyarblunch のサイトをアーカイブ化しました。Lokka (CMS) で運営されていましたが、現在は静的サイトとして配信しており、今後は更新されない予定です。

2020/11/10 更新: heroku の bamboo stack (!) を使っていて動かなくなっていた && ドメインも変わってリンク切れになっていたのでアプリを作り直しました。

大江戸Ruby会議01 で僕と @1syo さんが意気投合し始まった #shibuyarblunch でしたが、発起人の二人が渋谷を離れてしまったことや、途中から運営を取り仕切ってくださった @tyabe さんの主催する渋谷.rbが「渋谷近郊のRubyistのための地域Rubyistコミュニティ」という役割を担うようになった等の事情により、定期的な活動が途絶えていました。

大量のスパムコメントが投稿されているなど好ましい状態では無かったため(僕がメンテナンスを怠っていたせいです)閉鎖することも考えたのですが、ランチに参加してくださった皆さんからのコメントを消したくなかったので、静的サイトという形で残すことにしました。

あらゆる形で #shibuyarblunch に関わってくれたすべての皆さんに感謝します。ありがとうございました。またどこかのRubyistコミュニティでお会いしましょう。