Tech Sketch Bucket of Technical Chips by TIS Inc.

Sailsを使ってPubSubしてみる〜基礎編 お約束のチャットを作ってみる〜

Pocket

Node.jsを日常的に利用している方はSailsというフレームワークの名前を聞いたことあるという人も多いと思います。今回はそのSailsを使って簡単なデモを作ってみたいと思います。


そもそもSailsとは

Sailsは米国オースティンにある Balderdash社Mike McNeilさん が作成したRailsライクな開発スタイルが取れるMVCフレームワークです。これまでにもRailsライクを主張するフレームワークはいくつも登場していますが、Sailsはこれまでのものと違ってMVCのベースを保ちながらも、最近のMobileを含めたWeb開発に向けた明確ないくつかの特徴を持っています。

その主な特徴としては、以下の3点が挙げられます。

  • WebAPIを簡単に作成できる
  • リアルタイムWebなアプリケーションが簡単に作成できる
  • Single page siteやMobileなどの多様なフロントエンドに柔軟に対応できる

WebAPIについてはJSONベースのRESTfulなAPIが簡単に作れるようになっています(Sails API blueprints)。特にAPIを作るという意識をせずとも、generatorによって単純なCRUDならすぐにAPIとして公開することができます。ルーティングについても、この手のフレームワークではすでに標準的ですが、RESTを意識したルーティングがデフォルトで採用されています。

リアルタイム対応については、始めからSocket.ioとWebSocketが利用できるようにサポートされており、便利なビルトインのメソッドも用意されています。

Single Page SiteやPhoneGAP、Chrome ExtensionのようなWebAPIベースで作成されるサイトやアプリを作成する場合にも、buildスクリプトやCORS(Cross Origine Resource Sharing)対応、CSRFなどのセキュリティ対策が始めから組込まれてサポートされています。

また、ミドルウェアにはExpressが採用されていますので、すでにあるExpressで構築されたWebアプリとの互換性も比較的容易に確保することができます。

Sailsを使う準備

Node.jsを使える環境がすでに整っていれば、npmでインストールしてすぐに使い始めることができます。

Nodeの環境がなければ、単体でインストールするかNodeのバージョンマネージャツールであるnodebrewやnvmを入れてNodeをインストールします。それぞれツールなどについては、検索サイトで検索していただければすぐに使い方が見つかるので、ここでは説明を割愛します。

今回アプリを作ってみた環境はそれぞれ

  • Node.js: v0.10.15
  • Sails: v0.9.4

のバージョンを利用しています。

簡単なアプリを作ってみる

それでは簡単なアプリを実際に実装してみたいと思います。タイトルにもあるように、Sailsの組込みのリアルタイム対応機能のPubSubを使って、お約束の簡易チャットを作ってみたいと思います。

プロジェクトを作る

Railsや他のNode上で利用できるフレームワークを使ったことがある方ならお馴染みですが、まずはアプリのひな形を作成します。コマンドもまたお馴染みな感じで以下の通りです。

sailsコマンドにnewオプションを付けてアプリ名を指定します。ここではアプリ名をchatとしています。ここまでの指定による実行でひな形を作成することはできます。今回は追加で--linkerオプションをつけています。これはSailsに組込まれているassetマネージメントの機能を利用することを宣言しています。コマンドの実行が終了するとひな形が出来上がります。ディレクトリ構成は以下の通りです。

apiディレクトリ以下の各ディレクトリがアプリを作成する際の軸になる部分です。後ほど実行してみますが、generatorにより生成されるcontroller、model、独自で作成したDBとのadapterなどが配置されます。

assetディレクトリ以下には画像ファイルやjsのライブラリ、スタイルシートなどが配置されます。今回はひな形作成時に--linkerオプションを付けて作成しましたので、アセットの自動マネージメント機能が有効になっています。linkerディレクトリ以下の対応する各ディレクトリにjsのライブラリやスタイルシートなどを配置することによって、それらへのリンクを自動的にviewへ埋め込むことができます。

configディレクトリはその名の通り、アプリの動作全体に関連する設定を行うための各ファイルが配置されています。セッションやリアルタイム系の機能を実行するためのsocketの設定、DBへ接続するadapterの設定、セキュリティ系の設定が含まれます。node_modulesはデフォルトで利用するnodeのmoduleの配置ディレクトリです。

viewsも同様に、viewを配置するディレクトリになります。デフォルトではhomeディレクトリのみ作成されていますが、generatorで作成したcontroller名と対応するディレクトリをviewsディレクトリ以下に作成することで、ルート上のviewの対応付けが行われます。

チャット機能を実装していく

入り口の作成

では早速チャットができるようにしていこうと思います。今回は基礎編ということで、簡単にするためにログインやユーザの登録などははぶきます。このあたりは次回以降に付け足してみたいと思います。とは言え、チャットする同士が何かしら識別できた方がよいので、入り口画面を作り、その入り口画面表示時に乱数を発生させてユーザID代わりにし、リンク先へパラメータとして付加することにします。入り口画面を作らずに、直接リンク先を表示するのでも同じなのですが、次回以降(あるのか?)ログイン画面を作るので、その準備的に作っておきます。

入り口のためのcontrollerとviewを作成します。

ここではmodelを使わないため、generatorでcontrollerのみを作成しています。sails generateコマンドにcontollerオプションを付けて実行します。controller名はentranceとしています。entranceに続けてdoorが渡されていますが、これはアクションメソッド名(パス名)になります。実行後にapiディレクトリ下のcontrollerディレクトリに対応するcontrollerのjsファイルが作成されているはずです。今回の場合はEntranceController.jsになります。

viewはgeneratorで自動生成されないので、自分でviews以下に対応するディレクトリとviewファイルを作成します。今回の場合はentranceディレクトリとその下にdoor.ejsを作成します。Sailsのデフォルトのテンプレートエンジンはejsなため、viewファイルのサフィックスはejsとします。他にも設定を変えることでJadeなどが利用できますが、今回はこのままejsを利用します。諸々生成・作成したら、中身を記述していきます。
 
door.ejs

 
EntranceController.js

door.ejsを表示したタイミングでMathオブジェクトのrandomを利用してアンカーのリンク先URLに追加しています。Sailsのデフォルトのルーティングルールでは/:controller/:action/:idとなっているため、ここで追加したidは遷移先ではrequestオブジェクトからパラメータidとして値を取得することができます。door.ejsの表示に対応したEntranceControllerのinメソッドでは、viewメソッドの呼出しのみを行なっています。responseオブジェクトのviewメソッドを呼び出した場合、アクション名と対応した同じ名称のviewが呼び出されます。door.ejsのリンク先はroomになります。IDはユーザのIDにあたるものを渡してしまっているので、本来的にはREST的にややおかしいのですが、今回は簡単にするために仮にこのようにしておきます。ご容赦ください。

チャットルームの作成

次に遷移先のroomを見ていきます。entranceと同様にまずはcontrollerのみを作成しておきます。ゆくゆく一つの部屋内で複数のtalkingspotに分けたり、room自体を複数にするなどすると使い方が広がりそうで、その場合モデルが必要となりますが、まずはただ一つしかないものとしてモデルは作りません。generateの仕方はentranceの場合と同じ要領です。部屋の中のおしゃべりをする場所ということでtalkingspotというviewとそれを表示するメソッドを用意しておきます。viewはentranceのときと同様にviews以下にroomディレクトリとtalkingspot.ejsファイルを作成してください。

ここまではcontrollerとviewのみ扱ってきましたが、pubsubの実装を行う為に、RoomControllerを生成するのと合わせてUserとChatを扱うためのmodelとcontrollerを生成しておきます。対応するmodelとcontrollerを同時に生成する場合は、次のようにgenerateコマンドにオプションなしでモデル名を渡して実行します。

実行すると空のmodelとcontrollerが生成されます。今回は明示的に利用しませんが、Sailsでは、冒頭で特徴として説明したSails API blueprintsの仕組みによって、controllerは空の状態でもモデルのCRUDがHTTPの標準メソッドであるget、post、put、deleteによってできるようになっています。ルーティングルールとしてget、put、deleteは/:controller/:idで、postは/:controllerでそれぞれCRUDの実行が可能です。

入室通知機能の追加

生成したところでPubSubの機能を使う実装をしてみたいと思います。まず新しいユーザが入室した時点で通知を行うようにします。ユーザが入室すると、そのユーザのIDをアラートダイアログで通知します。今回は先程書いた通り、エントランスからリンクで移動する際、IDを乱数で割り振っていますので、そのIDが表示されることになります。

User.js

 
RoomController.js

 
Userモデルはとりあえず属性としてIDしか持たせていません。モデルの属性はattributes内に上記のように記述します。属性に利用できる型については 公式ドキュメント を参考にしてください。RoomControllerにはtalkingspotメソッドとcheckinメソッドを用意しています。talkingspotメソッドはtalkingspotのviewに先ほどのentranceのリンクから取得したIDをそのまま渡す形にしてあります。checkinメソッドはユーザの入室を示すためのメソッドです。talkingspotを表示した際にアクセスするようにします。ここでUserクラスのsubscribeメソッドとpublishCreateメソッドを呼んでいます。Sailsでは、モデルクラスの生成や更新を自動的にpubsub方式で通知する機能が組込まれています。通知の範囲として、そのクラス全体についての通知(class room)と各クラスの特定インスタンスの変更等を対象とした通知(instance room)が可能です。仕組みとしては、socket.ioを利用したもので、subscribeメソッドを通してリクエスト元クライアントのsocketを登録しておくと、publish系のメソッドが呼ばれた時に登録したsocketを通して各クライアントに通知のメッセージが送られるというものです。今回は単純にクラス全体の通知を利用するため、Userのpublishメソッドにはreqから取り出したsocketのみを渡しています。これでsocketがclass roomに登録されます。インスタンスレベルで通知をしたい場合はsocketと合わせてクライアントに対して、その内容が変更されたり削除されるなどされた場合に通知したい特定のインスタンスの配列かIDの配列を渡すことでsocketがinstance roomに登録されます。

IDを渡されたtalkingspotのviewをまずは以下の通りにしてみます。

talkingspot.ejs

表示と同時にsocket.getでcheckinにアクセスしています。続けて記述されているsocket.onでmessageイベントを拾ってメッセージを取り出してアラートダイアログを表示しています(この辺り、公式ドキュメントにはクライアント側でどうメッセージを検知するか等あまり記載がないのですが、コードを読んだところ、Socket.ioでのsendを実行しているようでしたので、messageイベントの指定を試してみたところ拾えました)。

scriptタグの上にという記述について、ここで合わせて少し説明しておきます。これはプロジェクトを作成する時にオプションで指定したアセットマネージメントの仕組みを利用して、linkerディレクトリ以下のJavaScriptファイルを組込む場合に記述するコメントです。この記述箇所にリンクが追加されます。ちなみにここではjQueryを追加するために記述しています。

ということで、実際にentranceのリンクからアクセスしてみると以下のようになります。

sails_id_dialog_2.png

左右でブラウザが異なるのは試しに変えてみているだけなので気にしないで下さい。左のID915が先に開いていた状態で右のID862が新たに入室しました。メッセージはアプリ内でブロードキャストされるので、先に開いていた915の画面に862の入室を通知するダイアログが表示されるのと同時に、新たに入室した862の画面にも自分自身の通知が表示されています。入室した本人には通知したくないといったことを行ないたい場合は、publishのメソッドに引数として通知除外したいsocketを指定することで可能ですが、ここでは行なっていません。

メッセージの送信

次にメッセージを送る実装を追加していきます。追加は以下の通りです。

RoomController.js

ChatController.js

Chat.js

talkingspot.ejs

RoomControllerのcheckinにアクセスしてきた際に、同じsocketでChatの更新があった際にも通知するようにsubscribeしています。ここでもクラス全体への通知としています。チャットメッセージ自体はモデルと同時に生成されたChatContollerにsayメソッドを追加して送信しています。Chatモデルにはメッセージを送った人のIDとメッセージを持たせています。今回特に関連を使っているわけではありませんが、実はSailsではまだアソシエーションの機能が追加されていないため、モデルの関連性についてはプログラムを組む時に作り込む必要があります。アソシエーションの機能はTODOリストには上がっているようですので、じきに実装されるはずです。

モデルと通知のためのメソッドが準備できたらviewに適宜処理を追加します。メッセージの送信はsendボタンの押下時にChatControllerのsayメソッドへアクセスするようにしています。メッセージの通知は先程の入室通知のアラートダイアログを出した処理に条件を追加して、メッセージがChatのものであれば表示領域のmessage_listに通知されてきたメッセージを追加するようにしています。

ちなみに先程からUserやChatといったモデルを作成してcreateを実行していますが、そこで持たせた属性値はどこに行ってしまうんだと思っていらっしゃるかたもいるかもしれません。Sailsでは Waterline という独自のデータ永続化エンジンを組込んでおり、adapterを変更することで永続化先をMySQLのようなRDB、ドキュメント指向のMongoDB、Key-ValueのRedisなどに組替えていくことができます。今回はDBなどは利用していないので、デフォルトのディスク上への永続化で動作しています。こちらはあくまで開発時にのみ利用するものですので、実際にアプリを公開する際は適宜永続化先を変えることになります。ディスク上の永続化では、アプリのディレクトリ下の.tmpディレクトリ下にdisk.dbというファイルが生成されてデータが格納されています(中を開いてみてみるとJSON形式のようです)。

ということで、実際に実行してみると以下のようになります。

sails_message_1.png

sails_message_2.png

sails_message_comet_log.png

縮小画像のままだと分かりにくいですが、クリックして画像を拡大していただくと「簡単ですね!!」というメッセージを915から送っています。送ると862側のviewにも表示されました。と静止画を並べても分かりにくいですが、最後の画像にブラウザのコンソールで実際のメッセージの様子も載せておきました。cometのメッセージを受け取っていることが分かります。

まとめ

Sails組込みのPubSub機能を利用した簡単なチャットの実装についてざっと説明してみました。割と簡単にリアルタイム系の実装ができそうな感じをつかめてもらえたでしょうか?次回の投稿ではSailsの持っている機能をもう少しいろいろと使ってみつつ、今回作成したチャットをもう少しそれらしくしてみようと思います。

エンジニア採用中!私たちと一緒に働いてみませんか?