オープンソースMBaaS「deployd」を使ってみる

## Deployd とは

[Deployd](http://deployd.com/) は、モバイルアプリケーションなどのバックエンドAPIを作成するためのオープンソースソフトウェアです。いわゆる「MBaaS (Mobile Backend as a Service)」とよばれるクラウドサービスが提供しているサービスの基盤の部分が実装されたものと考えてよいでしょう。

## 特徴

プラットフォームの動作環境としてNode.js、および永続システムとしてMongoDBを採用しています。REST形式のAPIを通してJSONメッセージで通信します。つまり隅から隅までJavaScriptベース、ということです。クライアントアプリがHTML5であればJavaScriptのクライアントライブラリも提供されています。もちろん、REST APIを直接利用するのでも構いません。

プラットフォームはイベントAPIを利用して拡張できるようになっており、妥当性検証や複数コレクションをまたがるクエリ、アクセス制御といったことがJavaScriptコードを追加することで簡単にできるようになっています。また、サーバプッシュといった仕組みも同様にイベントで統一的に利用できるようになっています。

## ソースコード

[https://github.com/deployd/deployd](https://github.com/deployd/deployd)

ライセンスは Apache License 2.0 で提供されています

## インストール

まず Node.js 0.8.x とMongoDB 2.0.x がローカル環境にセットアップされている必要があります。

Deployd自体のインストールには[WindowおよびMac OS X用のインストーラも配布されています](http://deployd.com/download.html)が、せっかくNode.jsベースのソフトウェアですので、ここはNode.js のパッケージ管理システムである npm を利用してインストールするのが一番かと思います。

```
$ npm install deployd -g
```

インストールが完了するとパス上に `dpd` というコマンドラインツールがインストールされているのが確認できます。以降このコマンドラインを利用してdeploydのAPIサーバを構築します。

## プロジェクトの作成とサービスの起動

まず最初にdeploydの雛形となるプロジェクトを作ります。続いて、カレントディレクトリを作成したプロジェクトにディレクトリに移り、deploydのサービスを起動します。

```
$ dpd create <project-name>
$ cd <project-name>
$ dpd -d
```

サービスが起動すると同時にWebブラウザが起動し、deploydサービスの管理ダッシュボードにアクセスできます。

## Collectionの作成

このダッシュボードからcollectionと呼ばれるデータをサーバ上に補完しておくための箱を定義することができます（MongoDBを利用されたことのある人にはおなじみです）。Deployd の collectionには通常のcollectionと「User Collection」という特殊なcollectionがありますが、User collectionについては後に触れるとして、ここでは通常のcollectionを適当な名前で作成してみます。

![deployd-dashboard.png](https://qiita-image-store.s3.amazonaws.com/0/6495/1056139d-d3ec-1cda-cfb1-fbb2d38c39e7.png)

CollectionにはJSONドキュメントのオブジェクトが格納されることになるのですが、あらかじめ簡単なスキーマを定義しておくことができます。Collection内での主キーとなる `id` プロパティがデフォルトで設定されていますが、任意の文字列、数値、真偽値などといったプリミティブ型、およびオブジェクト型や配列型もプロパティとしてスキーマ定義に追加できます。保存されるJSONドキュメントの中に、このcollectionに定義されていないプロパティが含まれている場合、そのプロパティは無視され破棄されることになります。

![property-def.png](https://qiita-image-store.s3.amazonaws.com/0/6495/fac834c9-9e86-b7c6-eaad-ce846bfffc19.png)

## APIからのアクセス

Collectionを作成すると同時に、REST APIのエンドポイントも作成されます。Deploydのダッシュボードにリストされているcollectionの中にある「API」の欄をクリックすると、それぞれのREST APIのHTTPエンドポイントを確認できます。

例えばtodosというcollectionを作成したとすると、HTTPのエンドポイントは

| Task | Method | Route | Accepts | Returns |
|---|---|---|---|---|
| Listing data | GET | /todos | Nothing | An array of objects |
| Creating an object | POST | /todos | A single object | The saved object (or errors) |
| Getting an object | GET | /todos/&lt;id&gt; | Nothing | A single object |
| Updating an object	 | PUT | /todos/&lt;id&gt; | A single object | The saved object (or errors) |
| Deleting an object | DELETE | /todos/&lt;id&gt; | A single object | Nothing |

のような形になります。

実際にcurlコマンドを利用してcollection内のデータにREST API経由でアクセスしてみます。

```
$ curl http://localhost:2403/todos   
[]

% curl -X POST http://localhost:2403/todos -d '{"title":"curl test #1"}' -H "content-type: application/json"
{"title":"curl test #1","id":"e0ff1f0dccb0db66"}

$ curl http://localhost:2403/todos/e0ff1f0dccb0db66
{"title":"curl test #1","id":"e0ff1f0dccb0db66"}

$ curl -X PUT http://localhost:2403/todos/e0ff1f0dccb0db66 -d '{"title":"curl test #1 (updated)"}' -H "content-type: application/json"
{"title":"curl test #1 (updated)","id":"e0ff1f0dccb0db66"}

$ curl http://localhost:2403/todos   
[{"title":"curl test #1 (updated)","id":"e0ff1f0dccb0db66"}]

$ curl -X DELETE http://localhost:2403/todos/e0ff1f0dccb0db66

```

## ユーザ認証

上記のREST APIには誰でもアクセスできてしまい、作成・更新・削除といった操作もまったく認証を行うことなくできてしまいますので、これではMongoDBをインターネットに晒しているのとほとんど変わりありません。適切にアクセス制御を行うためには、まず、リクエストを行っているユーザが誰なのか認証をする必要があります。

Deploydではユーザの管理のために特別なcollectionであるuser collectionを作成し、その中にユーザ情報を格納します。User collectionにはあらかじめ `username` と `password` プロパティが設定されており、認証はこちらを利用して行うことになります。その他のユーザ属性（メールアドレスや氏名など）の情報は通常のcollectionと同様に追加のプロパティで設定する形となります。

Deploydへのログインはcurlコマンドを用いた場合、以下のように行います（すでにstomitaユーザがパスワードpassword123で登録済みであるものとします）。

```
$ curl -X POST http://localhost:2403/users/login -d '{"username":"stomita","password":"password123"}' -H "content-type: application/json"
{"path":"/users","uid":"b431d8bd9ba3ba81","id":"f895394a23797832314ea9a88cbf472a91c2aac834dde5d913155c75704466b846fc361ddd411614afccaf51b42b76bce24093eab25b5345b85c6ccfd1edf87e"}
```

レスポンスにはログインしたユーザのidとしてuid、セッションIDとしてidをプロパティとして持つJSONドキュメントが返却されています。同時にSet-CookieヘッダもHTTPのレスポンスヘッダとして帰ってきますので、CookieをサポートするHTTPクライアントであれば次回以降のリクエストは認証済みの状態でAPIリクエストを行うことが可能になります。もちろん、返却されたセッションIDを明示的にCookieヘッダに設定してリクエストを行うことも可能です。

```
$ curl http://localhost:2403/users/me -H "Cookie: sid=f895394a23797832314ea9a88cbf472a91c2aac834dde5d913155c75704466b846fc361ddd411614afccaf51b42b76bce24093eab25b5345b85c6ccfd1edf87e" 
{"username":"stomita","id":"b431d8bd9ba3ba81"}
```

## イベントAPIとアクセス制御

多くのクラウドサービス型のMBaaSの場合、ユーザの属性やグループなどの情報を利用して、宣言的にコレクションやドキュメントレベルのアクセス制御を簡単に行える仕組みを提供していますが、deploydではそのような手段は現在のところ提供されていません。その代わり、Deloydではリクエストのアクセス制御を実現するプログラム的なアプローチとしてイベントAPIという仕組みを使います。

ダッシュボード画面から、アクセス制御を設定したいcollectionにアクセスし、EVENTSの欄をクリックすると、各HTTPアクセスについてイベントを設定することができます。

![events-api-setting.png](https://qiita-image-store.s3.amazonaws.com/0/6495/a670a3ef-58f2-7e7a-d404-1c5f5afcec04.png)

たとえば、以下のJavaScriptコードを`ON POST` / `ON PUT` / `ON DELETE` イベントに設定しておくことで、ログインしたユーザ以外に作成・更新・削除といった作業を禁止することができます。

```js
// ログインしていなければこのcollectionへの追加・更新・削除は禁止する
if (!me) {
  cancel("You are not authorized to do that", 401);
}
```

ここで使われている `me` という変数にはログイン済みのユーザの情報が格納されることになっていますので、空の場合には未ログインであると判断できることになります。アクセス不可の場合には `cancel()` 関数を利用してリクエストをエラーとして処理するように伝達します。

## 値の自動設定、入力値の妥当性検査

イベントAPIはアクセス制御以外にも利用可能です。例えばオブジェクトを追加する際に自動的に作成者と作成日時を記録したい場合には、`ON POST` に以下の記述をすることで自動的にオブジェクトに値を設定することができます。

```js
this.createdById = me.id; // createdByプロパティにはログインユーザのIDを設定する
this.createdAt = new Date.getTime(); // createdAt にはPOSTリクエストされた現在の日時を設定する
```

また、`ON VALIDATE` イベントを利用して入力値の妥当性検査を記述することもできます。

```js
// bodyプロパティの文字列長を制限する
if (this.body.length > 100) {
  error('body', "Cannot be more than 100 characters");
}
```

## 問い合わせ

REST APIでの問い合わせの記述は collection のURLエンドポイントに対してURLクエリパラメータでプロパティの値を設定するか、MongoDBと同じようなJSONによるクエリ条件をURLに渡す形になります。

* URLパラメータで指定する例:

```
GET /todos?category=pets
```

* JSONで指定する例:

```
GET /posts?{"likes": {"$gt": 10}}
```


## 複数collectionをまたがった問い合わせ

複数のcollectionのデータを結合して返却することが必要な場合、多くのNoSQLのストレージが多くそうであるように、DBではなくアプリケーションサーバ内でデータを結合する必要があります。この結合（リレーションシップの解決）の記述もイベントAPIで記述できます。

`ON GET` イベントに以下の記述をすることで複数collectionにまたがって値を取得するように設定することができます。

```js
// クエリに"loadComments"が設定されている場合は、投稿に関連しているコメントも同時に取得する
if (query.loadComments) {
  dpd.comments.get({ postId: this.id }, function(comments) {
    this.comments = comments;
  });
}
```

## サーバからの通知

いわゆるiOS/Androidネイティブのプッシュ通知とは異なりますが、Socket.IOを利用したクライアント（主にWebブラウザ）へのメッセージ配信が可能です。これもイベントの仕組みを活用します。

```js
// messages コレクションの ON POSTイベントに設定
// メッセージが作成されたことをクライアントに通知する
emit('messages:create', this);
```

WebブラウザではSocket.IOをそのまま利用することもできますが、deploydが提供するJavaScriptクライアントライブラリである`dpd.js` を利用して、以下のようにして通知を受け取ることができます。

```js
dpd.messages.on('create', function(message) {
  renderMessage(message);
});
```


## 配備

Deploydには作成したAPIサービスを開発テストとしてデプロイするクラウド環境も用意されています。

`dpd deploy [subdomain]` コマンドを利用することで、`[subdomain].deploydapp.com` というホスト上にdeploydのAPIのサービスをデプロイできます。


## まとめ

Deployd はモバイルアプリなどのバックエンドAPIを容易に構築することが可能なオープンソースソフトウェアです。Parse.com や Kinvey, Kii などのBackend as a Serviceと呼ばれるクラウドサービスとくらべて、モバイル開発に必要なピースがあらかじめ全部含まれているというわけではありませんが、npmおよびdeployd自身が提供するモジュールの仕組みを利用して自由に拡張できるというメリットもあります。

運用という点では、クラウドサービスであるMBaaSを利用する場合と比べ、アプリケーション開発後もサーバ運用をすべておまかせという訳にはいかないのは仕方ないところですが、HerokuやEngine YardなどのPaaSを活用することである程度補えると考えることもできるでしょう。

またスクラッチでPaaS上にAPIサービスを構築する場合と比較しても、主に開発スピードの効率化という点でメリットは多くありそうです。特にイベントAPIを利用した拡張の仕組みはそこそこ汎用性が高く、むしろフレームワークとして捉えたほうが良いのかもしれません。

あとは、ユーザ名・パスワードによるログインだけでなく、いわゆる「ソーシャルログイン」などと呼ばれるアイデンティティ連携の仕組みもサポートしていると嬉しいですね。今後のプロジェクトの活動を見守りたいところです。