ember-dataとREST APIによるモデルの永続化 – Ember.js入門(18)

2014.01.17

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

Ember.jsでデータの永続化を行う場合、ember-dataを利用してサーバとHTTP通信を行い、サーバサイドでRDB等に永続か処理を行います。この時、サーバはJSON形式でデータを送受信するREST APIとして構築する事が推奨されています。

サーバサイドを実装するプログラミング言語に制限はありません。今どきのWebアプリケーションフレームワークを使えば、REST APIを作成することは難しくないでしょう。弊社で開発しているアプリケーションではSpring Framework(Java)を利用していますが、Ruby on Rails *1やnode.js *2などは相性の良い選択肢でしょう。

今回はember-dataでREST APIを利用した永続化の概要を紹介します。

RESTAdapter

Ember.js自体はモデルの永続化をサポートしていません。モデルの永続化はember-dataを利用して行います。ember-dataは、2013年1月時点でベータバージョンですが、そろそろ安定してきた感があります。ぼちぼち学習して導入を検討しても大丈夫かと思います *3

ember-dataを導入する事でモデルの永続化を行えるようになります。永続化の方法はAdapterを差し替える事で切り替えることができます。AdapterのデフォルトでRESTAdapterで、REST APIを用いた永続化を行います。他にもFixtureAdapterなどが提供されており、必要に応じてカスタムアダプタを実装できるような設計となっています。

APIのURLと規約

RESTAdapterは規約からリクエストするAPIのURLを決定します。Modelに対する操作としては、find, findAll, update, create, deleteの5種類があり、規約に基づいたHTTPメソッド/URLにマッピングされます。

例えば、App.ItemというModelの場合、次のようマッピングとなります。

操作 メソッド URL 概要
find GET /items/1 IDを指定したモデルの取得
findAll GET /items 複数件のモデルを取得
update PUT /items/1 IDを指定したモデルの更新
create POST /items モデルの追加
delete DELETE /items/1 IDを指定したモデルの削除

このようにEmber.jsでは規約に基づいたURLのマッピングが行われます。もちろん、規約とは異なるURLをマッピングすることもできますが、新規に開発を行うのであれば規約に合わせておくことが無難です。

なお、サーバサイドでRuby on Railsを使うのであれば、ActiveModel::Serializersを利用すると、自然にember-dataと連携できるようです。ただし、サーバサイドはJSONを返すだけのシンプルな実装で済むため、Ruby on Railsのような重量級フレームワークを選択する必要があるかどうかは検討の必要があるでしょう。 詳しくはursmさんのブログを参照ください。

エンドポイント

RESTAdapterでアクセスするREST APIのエンドポイントは、デフォルトでEmberアプリケーション(HTMLファイル)のURLのホストとなります。すなわち、Emberアプリケーションが http://example.com/index.htmlであれば、http://example.comがエンドポイントとなります。

したがって、App.Itemのfindであれば、http://example.com/items/:id(GET)が完全なURLです。

ネームスペースのカスタマイズ

REST APIがEmberアプリケーションと同じドメインにあるがルートではない場合、ネームスペースを設定することでエンドポイントをカスタマイズできます。 例えば、Emberアプリケーションがhttp://example.com/index.htmlである時、APIのエンドポイントを http://example.com/api/1.1としたいのであれば、次のようにRESTAdapterのnamespaceプロパティを設定します。

App.ApplicationAdapter = DS.RESTAdapter.extend({
  namespace: 'api/1.1'
});

このように設定することで、App.Itemのfindであれば、http://example.com/api/1.1/items/:id(GET)が完全なURLとなります。

ホストのカスタマイズ

Emberアプリケーションと異なるドメインにREST APIを実行する場合、RESTAdapterのhostプロパティを設定します。

App.ApplicationAdapter = DS.RESTAdapter.extend({
  host: 'https://api.example.com'
});

このように設定することで、App.Itemのfindであれば、https://api.example.com/items/:id (GET)が完全なURLとなります。

なお、CORS(クロスドメイン通信)となるため、対応しているブラウザを利用することと、サーバサイドの対応も必要になります。

JSONのフォーマット

ember-dataのRESTAdapterではJSONフォーマットでデータを連携します。

find, update, createのレスポンスJSON

findでModelを取得する場合や、update/createのレスポンスとしてJSONを返す場合、Model名をroot要素としたJSONオブジェクトとします。

例えば、モデルとしてApp.Itemが定義されている場合、対応するJSONは次のようにitemをroot要素としたJSONオブジェクトとなります。

App.Item = DS.Model.extend({
  name: DS.attr('string'),
  price: DS.attr('number')
});

{
  'item': {
    id: 1,
    name: '羊羹',
    price: 780
  }
}

var item = this.store.find('item', 1);

この時、必ずidを含める必要があります。ember-dataでは全てのModelが一意に識別できるidプロパティががあることが暗黙の前提となっています。id以外の属性をIDとして利用することもできますが、各所でidが存在する前提となっているため可能な限りidプロパティを利用する方が良いでしょう。

updateやcreateを行った場合、更新後のModelをレスポンスとして返します。

findAllでのレスポンスJSON

findAllで複数件のModelを取得する場合、Model名の複数形をroot要素としたJSONオブジェクトの配列を返します。

{
  'items': [
    {
      id: 1,
      name: '羊羹',
      price: 780
    },
    {
      id: 2,
      name: 'カップ麺',
      price: 1200
    }
  ]
}

var items = this.store.find('item');

create, updateでのリクエストJSON

リクエストに含まれるJSONはクライアントサイドでは意識する必要はありませんが、サーバサイドで処理をする場合に考慮が必要です。

DS.Modelのsaveメソッドを呼び出すと、Modelの状態によってcreateまたはupdateが行われます。この時、リクエストボディにJSONが設定され、サーバに送信されます。この時のフォーマットは、find, create, updateのレスポンスJSONと同様です。

{
  'item': {
    id: 1,
    name: '羊羹',
    price: 780
  }
}

var item = this.store.createRecord('item', { name: '羊羹', price: 780});
item.save();

createの場合はidは送信されませんが、レスポンスJSONでは払い出されたidを含める必要があります。

まとめ

今回はember-dataのRESTAdapterを使ったモデルの永続化について基本事項をまとめました。ember-dataは規約が強いフレームワークなので、可能な限り規約に従ったサーバサイドの実装を行うことと良いでしょう。規約から外れた仕様でサーバサイドを実装すると、クライアントサイドでカスタマイズのコードが必要になるだけでなく、思いがけない挙動にハマるので注意してください。

脚注

  1. Ember.jsで求めるレスポンスのJSONがActiveModelの形式になっている
  2. クライアントサイドとサーバサイドでプログラミング言語が統一される
  3. 保証はしませんが…