ember-data – Ember.js入門(14)

2014.01.03

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

渡辺です。

いよいよ禁断のember-dataについて解説する時が来たようです。

ember-dataは、2014年1月3日時点でbetaバージョンのプロダクトです。ご利用の際は充分に検証を行ってください。

まあ、仕様はだいぶ安定してきましたし、ぼちぼちbetaもとれそうと言われ続けているなので、よほど怪しい機能を使わない限りは大丈夫だと思います(笑)今回はEmber.jsとember-dataのコンセプトについて説明します。Ember.jsでアプリケーションを組むときは重要です。

Ember.jsの設計思想とember-data

どんなフレームワークであっても、そこには設計思想があります。設計思想に外れた使い方をしたならば、どれだけ良いフレームワークであっても使いにくく感じるのは当然です。特にEmber.jsは設計思想が明確であるため、アプリケーションの設計方針をEmber.jsの設計思想に合わせていかなければ、実装で大きな遠回りをすることが多々あります。そんな中でもこの傾向が強いのは、URL(Routing)とリソースとのマッピングです。

Ember.jsの大きな特徴としてあげられるのはパーマリンクです。Ember.jsではクライアントサイドでURLにより処理されるRouteが決まります(Routing)。そして、RouteによりModelが設定され、対応するControllerとViewにより画面が表示されます。このため、Eメールなどに記載されたリンクやブックマークされたリンクを表示した場合でも、アプリケーションの特定のページを表示できます。これはFlash(Flex)などで構築されたアプリケーションとは大きな違いです。

アプリケーションのRouteでModelを設定する時、サーバからデータ(Model)を取得します。この時、Ember.jsではREST APIでJSONを返す仕組みであることが強く推奨されています。他の仕組みでも実装できないことはないでしょうが、恐らくは心が折れるでしょう。また、この時、リソース(Model)に対するURLが一意となることが重要です。例えば、ブログアプリにおけるエントリー(entry)はidで管理されており、次のようなURLで取得できる、といった仕組みです。

http://api.deathmarch.jp/entry/1
http://api.deathmarch.jp/entry/2

この時、REST APIのリクエストを処理し、レスポンスとしてのJSONをEmber.jsで利用するのに最適なModelに変換する仕組みをになうのがember-dataです。また、ember-dataはModelの永続化全般を受け持つため、Modelの登録・更新・削除も行います。なお、ActiveRecordのようなORMに似ていますが、RDBとのインターフェイスではなく、あくまでREST APIとのインターフェイスであることに注意してください。柔軟なクエリを発行する出来るワケではなく、APIへのパラメータを設定することしかできません。

ember-data-2

ember-dataを使うことが最もEmber.jsを活用したアプリケーションを開発できることは言うまでもありません。ですが、ember-dataを使わずにAjaxリクエストを行いJSONデータを取得することも可能です。ただし、エラーハンドリングや非同期処理などを各自で考慮しなければならないでしょう。

ember-dataの準備

2014年1月3日時点でのember-dataの最新版はember-data 1.0.0-beata.4です。公式サイトのビルドページよりダウンロードしてください。以前は独立した別のライブラリも必要としていましたたが、現在は独立して利用できます。

ember-dataの主な機能

それではember-dataの主要機能を紹介します。それぞれの機能に関しては、今後紹介していきますので、まずは全体像を把握してください。

Model

emeber-dataはEmber.jsのModel層を構成します。Ember.jsでサーバとの通信を行わないスタンドアローンのアプリケーションを構築するならば、ember-dataを使わずにModel層を構成できますが、そうでないならばember-dataを利用するべきでしょう。

ember-dataのModelはDS.Modelクラスのサブクラスとして定義します。この時、ModelのプロパティはDS.attrメソッドで型を宣言します。次のサンプルはBlogアプリのEntry modelです。

App.Entry = DS.Model.extend({
  title: DS.attr('string'),
  postedAt: DS.attr('date'),
  content: DS.attr('string')
});

なお、ember-dataのModelではidプロパティが暗黙的に定義されます。

Store

ember-dataのStoreは、Modelのリポジトリとして機能し、後述するAdapter経由でModelのオブジェクトを取得する役割を持ちます。AdapterがREST Adapterであれば、バックエンドでAjaxリクエストがサーバに送信され、レスポンスとして返されるJSONを元にModelオブジェクトが生成されます。

Storeオブジェクトは、Routeなどにインジェクトされるため、次のようなコードでBlogのEntryを取得できます。

  App.IndexRoute = Ember.Route.extend({
  model: function() {
    this.store.find('blog', 1);
  }
};

findメソッドの第1引数はModelのlookup名で、第2引数がIDです。第2引数を指定しなければ全件取得(findAll)扱いとなります。その他、検索条件などをオプションパラメータとして追加する事もできます。

Adapter

Adapterはember-dataのStoreで実際に永続化を行うリポジトリとのアダプタとして機能します。言い換えれば、Ember.jsはAdapterが対応しているならば、Modelの永続化について柔軟に切り替えることができると言えます。

しかしながら、現実としてほとんどの場合はRESTAdapterを使用するでしょう。RESTAdapterはREST APIをWebサーバに送信し、レスポンスとしてJSONを期待するAdapterです。Ember.jsで推奨されるJSONのフォーマットがあるので、可能であれば合わせた実装にするとよいでしょう。他に利用するAdapterとしては、ローカル環境で開発やテストをする場合に便利なFixtureAdapterがあります。ただし、FixtureAdapterはデータの取得はできますが、データの保存はできません。

自動キャッシュ

ember-dataではIDでModelを一意に識別します。一度ロードされたModelオブジェクトはキャッシュされるため、同一のオブジェクトを検索する場合などには高速に処理されます。

しかし、IDとキャッシュが強力であるが故、集計データなどIDのないデータをModelとして扱う場合は注意が必要です。サーバサイドでなんらかの一意となるIDをJSONに乗せて返さなければ、クライアントサイドでキャッシュが利用されてしまうでしょう。

Promise

厳密にはember-dataの機能ではなくEmber.jsの機能ですが、ember-dataのfindなどの戻り値はPromise型であることは重要です。

先ほどのコードをもう一度みてください。

App.IndexRoute = Ember.Route.extend({
  model: function() {
    this.store.find('blog', 1);
  }
};

IndexRouteのmodelメソッドはStoreのfindメソッドの戻り値を返します。これはModelオブジェクトに他なりません。さて、findメソッドは同期メソッド処理でしょうか、それとも非同期メソッドでしょうか?

Ajaxを使ったクライアントサイドアプリケーションを開発した経験があれば、findメソッドは非同期メソッドであるかmodelメソッド自体が非同期メソッドであると考えるでしょう。しかし、Emberではmodelメソッドもfindメソッドも同期メソッドとして考えます。もし、findメソッドなどが同期メソッドであるならば、サーバへのリクエストが処理されるまで処理が返されないため、大きな問題となると考えるかもしれません。しかし、StoreのfindメソッドはPromiseオブジェクトは即時に返します。

Promiseオブジェクトは近い未来に実データが設定されることが約束された器です。とりあえず、なんらかのデータが入ると思われる器だけをフレームワークに渡すことで、同期処理としてコードを記述します。これは非同期処理の考慮が少なくなるため、コードの可読性に大きな影響を与えます。

勿論、Promiseオブジェクトは通信の結果エラーかもしれませんし、nullかもしれません。複数のModelオブジェクトを返すことが約束されているならばPromiseのArrayオブジェクトが返されます。

Ember.jsではRouteでmodelをController/Viewに対応づけます。ここでPromiseパターンが使われているため、非同期処理の考慮をほとんど不要でコードを書く事ができます。PromiseはObserveされ、Arrayのサイズが変更されたり、実際のModelオブジェクトがロードされた時、Viewにレンダリングされることになります。

まとめ

今回はざっくりとember-dataのコンセプトを紹介しました。ember-dataは一見単純なORMのようにみえますが、クライアントサイドMVCに最適化された仕組みが組み込まれています。特にPromiseパターンは、複雑になりがちなクライアントプログラムをシンプルに記述するために重要な仕組みです。