AirbnbEng

Follow

Nov 11, 2013 – 10 min read

By Spike Brehm

This post has been crossposted on VentureBeat.

Airbnb では、過去数年間、リッチな Web エクスペリエンスを構築しながら、多くのことを学びました。 2011 年にモバイル Web サイトでシングル ページ アプリの世界に飛び込んで以来、Wish Lists や新しくデザインされた検索ページなどを立ち上げてきました。 このアプローチは今日では一般的であり、Backbone.js、Ember.js、および Angular.js などのライブラリによって、開発者はこれらのリッチな JavaScript アプリを簡単に構築できるようになりました。 しかし、この種のアプリにはいくつかの重大な制限があることが分かっています。 Web の黎明期以来、ブラウジング エクスペリエンスは次のように機能してきました。Web ブラウザが特定のページ (たとえば、「http://www.geocities.com/」) を要求すると、インターネット上のどこかのサーバーが HTML ページを生成し、それをワイヤーで送り返すというものでした。 これは、ブラウザがそれほど強力でなかったことと、HTMLページがほとんど静的で自己完結した文書であったことから、うまく機能したのです。 JavaScript は、Web ページをよりダイナミックにするために作成されましたが、画像のスライドショーや日付選択ウィジェット以上のことはできませんでした。

パーソナル コンピューティングの長年にわたる進歩の後、創造的なテクノロジストが Web をその限界まで押し上げ、Web ブラウザはそれに追いつこうと進化してきました。 高速な JavaScript ランタイムと HTML5 標準により、開発者は、以前はネイティブ プラットフォームでのみ可能だったリッチなアプリケーションを作成することができるようになりました。 シングル ページ アプリの典型例である Gmail のようなアプリは、ユーザー インタラクションに即座に応答でき、新しいページをレンダリングするためだけにサーバーに往復する必要がなくなりました。

Backbone.js, Ember.js, Angular.js といったライブラリは、しばしばクライアントサイド MVC (Model-View-Controller) または MVVM (Model-View-ViewModel) ライブラリと呼ばれています。 典型的なクライアントサイド MVC アーキテクチャは次のようなものです。

アプリケーション ロジック(ビュー、テンプレート、コントローラー、モデル、国際化など)の大部分はクライアント内にあり、データのために API に話しかけます。 サーバーは Ruby、Python、または Java などの任意の言語で書くことができ、主に HTML の最初の基本的なページを提供することを処理します。 JavaScript ファイルがブラウザによってダウンロードされると、それらが評価され、クライアント側アプリが初期化され、API からデータを取得し、残りの HTML ページをレンダリングします。

これは開発者にとっても素晴らしいことです。なぜなら、理想的な単一ページ アプリは、クライアントとサーバーの間の懸念事項を明確に分離し、優れた開発ワークフローを促進し、異なる言語で書かれていることが多い両者間で多くのロジックを共有する必要性を防止するからです。

しかしながら、実際には、このアプローチにはいくつかの致命的な欠陥があり、多くのユースケースに適しているとは言えません。

SEO

クライアント側でのみ実行できるアプリケーションは、クローラーに HTML を提供できないので、デフォルトで SEO 対策が不十分なものになります。 Web クローラーは、Web サーバーにリクエストしてその結果を解釈することで機能しますが、サーバーから白紙のページが返されたのでは、あまり意味がありません。

Performance

同様に、サーバーが HTML の全ページをレンダリングせず、クライアント側の JavaScript がレンダリングするのを待つ場合、ユーザーはページのコンテンツを見る前に数秒間のブランク ページまたはロード スピナーを経験することになります。 遅いサイトがユーザー、ひいては収益に与える劇的な影響を示す研究はたくさんあります。 Amazonは、ページのロード時間が100ミリ秒短縮されるごとに、収益が1%増加すると主張しています。 Twitter では、1 年と 40 人のエンジニアを費やして、クライアントではなくサーバーでレンダリングするようにサイトを再構築し、認識される読み込み時間が 5 倍に改善されたと主張しています。 よくある例は、日付や通貨のフォーマット、フォームの検証、ルーティングロジックなどです。 このため、特に複雑なアプリでは、メンテナンスが悪夢のようになります。

私を含め、一部の開発者はこのアプローチに食傷気味です。

この目的のために、Airbnb では、クライアント側とサーバー側の両方で実行できる JavaScript アプリケーションである「アイソモーフィック JavaScript」アプリを実験しています。

アイソモーフィック アプリは、ここでは「クライアント-サーバー MVC」と呼ばれる、次のようなものです。 パフォーマンスの最適化、より良い保守性、デフォルトでの SEO、よりステートフルな Web アプリなどです。

Node.js は高速で安定したサーバーサイド JavaScript ランタイムですが、この夢を現実にすることができます。 適切な抽象化を作成することにより、サーバーとクライアントの両方で動作するようなアプリケーション ロジックを書くことができます – これが isomorphic JavaScript の定義です。 オープンソースのアイソモーフィック フレームワークとしては、Mojito が初めて報道されました。 これは、高度でフルスタックの Node.js ベースのフレームワークですが、YUI への依存と Yahoo!特有の癖があるため、2012 年 4 月にオープンソース化されて以来、JavaScript コミュニティではあまり人気がありません。 Meteor はリアルタイム アプリケーションをサポートするためにゼロから構築され、チームはそのパッケージ マネージャーとデプロイ ツールを中心にエコシステム全体を構築しています。 Mojitoと同じく、大規模で意見の多いNode.jsフレームワークですが、JavaScriptコミュニティとの連携はよりうまくいっており、待望の1.0リリースが目前に迫ってきています。 Meteor は、オールスターのチームを擁し、Andreessen Horowitz から1,120万ドルを調達した、オープンソース製品のリリースに完全にフォーカスした企業としては前代未聞のプロジェクトです。 Moskovitz 氏が世界で最も若い億万長者であることを考えると、Asana は資金に困ることはなく、クローズドソースの Luna フレームワークの開発に何年も R&D で時間を費やしました。 Luna は、もともと Node.js が存在する前の時代に v8cgi をベースに構築され、ユーザー セッションごとにサーバー上でアプリの完全なコピーを実行できるようにしたものです。 各ユーザーに対して個別のサーバー プロセスを実行し、クライアントで実行されているのと同じ JavaScript アプリケーション コードをサーバーで実行し、堅牢なオフライン サポートや迅速なリアルタイム更新など、あらゆる種類の高度な最適化を可能にします。 Rendr と呼ばれるこのライブラリにより、サーバー側で完全にレンダリングできる Backbone.js + Handlebars.js のシングル ページ アプリケーションを構築することができます。 Rendr は、Airbnb モバイル ウェブ アプリを再構築してページロード時間を劇的に改善した経験から生まれたもので、遅延の大きいモバイル接続のユーザーにとって特に重要なものです。 そのため、Mojito や Meteor と比べて問題をあまり解決しませんが、変更と拡張は簡単です。

Abstraction, Abstraction, Abstraction

これらのプロジェクトが大規模でフルスタックの Web フレームワークになる傾向があるということが、問題の難しさを物語っています。 クライアントとサーバーは非常に異なる環境であるため、基礎となる実装からアプリケーション ロジックを切り離す一連の抽象化を作成する必要があり、アプリケーション開発者に単一の API を公開できます。 ルート ハンドラは、HTTP ヘッダー、クッキー、および URI 情報にアクセスでき、window.location (ブラウザ) または req と res (Node.js) に直接アクセスせずにリダイレクトを指定できる必要があります。

データのフェッチと持続

フェッチのメカニズムから独立して特定のページまたはコンポーネントを表示するのに必要なリソースを記述したいのです。 リソース記述子は、JSON エンドポイントを指す単純な URI であるか、または大規模なアプリケーションでは、リソースをモデルおよびコレクションにカプセル化して、モデル クラスおよび主キーを指定することが有用であり、ある時点で URI に変換されることになります。 アプリケーションのニーズに応じて、サーバーまたはクライアントのいずれかで任意のビューをレンダリングできる必要があります。

構築とパッケージング

Isomorphic アプリケーション コードを書くことは、戦いの半分に過ぎないことが分かりました。 Grunt や Browserify のようなツールは、実際にアプリケーションを起動して実行するためのワークフローの重要な部分です。 テンプレートのコンパイル、クライアント側の依存関係、変換の適用、最小化など、多くの構築手順があります。 単純なケースでは、すべてのアプリケーションコード、ビュー、テンプレートを1つのバンドルにまとめますが、大規模なアプリの場合、ダウンロードに数百キロバイトを要することになります。 より高度なアプローチとしては、動的なバンドルを作成し、アセットのレイジーローディングを導入することですが、これはすぐに複雑になってしまいます。 Esprima のような静的解析ツールにより、野心的な開発者は高度な最適化とメタプログラミングを試み、定型的なコードを削減することができます。 しかし、これでは、大きくて扱いにくいフレームワークになってしまい、既存のアプリケーションに採用したり統合したりするのが難しくなります。 より多くの開発者がこの問題に取り組むようになれば、一緒に統合して同型のアプリを構築することができる、小さくて再利用可能なモジュールが爆発的に増えることでしょう。 たとえば、Underscore、Backbone.js、Handlebars.js、Moment、および jQuery などの人気のあるライブラリは、サーバー上で使用できます。

この点を示すために、私は isomorphic-tutorial というサンプル アプリを作成し、GitHub でチェックアウトできます。 いくつかのモジュールを組み合わせて、それぞれがアイソモーフィックに使用できるようにすることで、わずか数百行のコードでシンプルなアイソモーフィック アプリを簡単に作成することができます。 サーバーとブラウザーベースのルーティングに Director、HTTP リクエストに Superagent、テンプレートに Handlebars.js を使用し、すべて基本的な Express.js アプリの上に構築されています。 もちろん、アプリが複雑になればなるほど、より多くの抽象化レイヤーを導入しなければなりませんが、より多くの開発者がこれを試しているうちに、新しいライブラリや標準が出現することを期待しています。

The View From Here

Node.js を快適に運用する組織が増えるにつれ、クライアントとサーバー コード間でますます多くの Web アプリがコードを共有し始めるのは必然です。 同型の JavaScript は、テンプレートの共有から始まり、アプリケーションのビュー層全体、さらにはアプリケーションのビジネス ロジックの大部分まで、さまざまなものがあることを覚えておくことが重要です。 正確に、どのように JavaScript コードが環境間で共有されるかは、構築されるアプリケーションとそのユニークな制約のセットに完全に依存します。 アプリは、isomorphic JavaScript を使用するために、バックエンドを切り取って Node.js に置き換える必要はなく、本質的に風呂の水と赤ちゃんを捨てるようなものです。 その代わり、賢明な API と RESTful リソースを作成することにより、従来のバックエンドは Node.js レイヤーと共存できます。

Airbnb では、クライアント側の構築プロセスを、Grunt や Browserify といった Node.js ベースのツールを使用するようにすでに再設計しはじめました。 私たちのメインの Rails アプリが Node.js アプリに完全に取って代わられることはないかもしれませんが、これらのツールを採用することにより、特定の JavaScript のビットやテンプレートを環境間で共有することがこれまで以上に容易になります。

Learn More

このアイデアに興奮された方は、11 月 12 日の火曜日、サンフランシスコの DevBeat、または 11 月 21 日の木曜日の General Assembly で私が教える Isomorphic JavaScript ワークショップをチェックしてみてください。 私が作成した Node.js のアイソモーフィック チュートリアル アプリのサンプルで一緒にハックし、アイソモーフィック アプリを書き始めることがいかに簡単であるかを実証します。

コメントを残す

メールアドレスが公開されることはありません。