目次


多忙な Java 開発者のための Sails.js ガイド

モデルとブループリント

Sails でデータ・アクセス層をプロトタイプ化する

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: 多忙な Java 開発者のための Sails.js ガイド

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:多忙な Java 開発者のための Sails.js ガイド

このシリーズの続きに乞うご期待。

Sails 開発者の皆さん、お帰りなさい!第 1 回では Sails.js 入門として、皆さんの開発環境のローカルに Salis.js をインストールして、基本的な Sails.js アプリケーションを作成し、IBM Bluemix 上でこのアプリケーションを起動しました。基本的なアプリケーションを作成する過程では、Sails.js でのプログラミングの基礎も紹介しました。今回は、「Hello World」アプリを作成して単純に楽しむ次の段階として、より現実に即したサンプル・アプリケーションに取り組みます。

前回作成したサンプル・アプリケーションは、従来型の REST アーキテクチャーのすべての要素ではなく、その一部だけを統合したブログ API です。このブログ・アプリケーションは、ブログ・エントリーを保持して公開し、ブログ関連の一般的な操作 (コメントの追跡、RSS フィードの生成と表示など) を行います。HTTP API の設計では、ビューについてはモバイル・クライアント、デスクトップ・クライアント、または Web クライアントに任せて、バックエンドを安定したスケーラブルなものにすることができます。

このブログ API を使用できるケースは (少なくとも始めは) 比較的限られていますが、そのうち、ユーザーが任意の数のブログ・エントリーを作成できるようになります。各エントリーにはタイトル、本文、タグ、作成者を含めるだけでなく、エントリーに作成日時のタイム・スタンプと、更新日時のタイム・スタンプも表示されるようにします。

ゆくゆくは、このアプリケーションをリファクタリングして他にも機能を追加することになりますが、今のところはシンプルなものにとどめておきましょう。

アプリケーションをモデル化する

MVC アプリケーションをゼロから開発するときは、モデルの要素とコントローラーの要素のどちらから取り掛かるべきかについて意見が分かれることでしょう。判断に困った場合は、コントローラーから開発するだけでなく、コントローラーからアプリケーション全体を構築することも可能です。つまり、アプリケーションの機能ごとにコントローラーを定義して、データベースとの間でデータを出し入れするという方法です。この方法では、後からいつでもアプリケーションをリファクタリングして、コントローラーのアーキテクチャーからモデルを抽出することができます。

Sails.js では、先にモデルを用意しておくほうが効率的です。Sails のモデルを設計するのは簡単であることに加え、Sails ではさまざまなモデル・タイプに自動的に適用される一連のコントローラーのルート (「ブループリント」と呼ばれます) を提供しているからです。Sails でモデルを定義するだけで、そのモデルに不可欠なデータを保管したり取得したりできるよう、Sails が自動的に API ルートと機能をセットアップしてくれます。

Sails のモデル・ファーストのアプローチは、HTTP API アプリケーションの RESTful 設計でも功を奏します。REST ではリソースを公開しなければなりませんが、これらのリソースはモデルの一部としてあらかじめ定義されることになるからです。

ブログ API をモデル化するのは至って簡単な作業です。アプリケーションに必要なモデル・タイプを定義するだけで、Sails がデフォルトで、定義されたモデル・タイプに応じたブループリントを適用します。モデルとブループリントが用意できたら、それらの間の関係を定義して、アプリケーション全体での動作を扱うスタンドアロンのコントローラーをいくつか定義します。このブログ API には、コメント数が最も多いブログ・エントリーを検出するスタンドアロンのコントローラーと、ブログ・エントリーの RSS フィードを提供するコントローラーが必要となります。

一般化されたモデル化

最初の Sails モデルを開発する前に、データをモデル化する方法を決める必要があります。つまり、リレーショナル・エンティティーを使用するのか、ドキュメント・エンティティーを使用するのか、それとも他のエンティティーを使用するのかを決定します。Sails での答えは、これらすべてのエンティティーを使用するか、1 つも使用しないかですが、その答えは場合によって異なります。

一般に、MEAN スタックなどといった他のソフトウェア・スタックを扱う場合は、データ・ストレージ・エンジンがスタックの明示的な構成部分となります。従って、そのスタックを使用するアプリケーション用に定義するエンティティーは、データ・ストレージ・エンジンに固有の先入観に忠実に従ってモデル化することになります。例えば Rails を使用する場合はリレーショナル・エンティティーに密接に関連付けられたモデルを作成し、MEAN を使用する場合はドキュメント指向のモデルを作成するといった具合です。

他のスタックとは異なり、Sails はデータ・ストレージ・エンジンに対してデータ操作を抽象化します。そのため、MySQL や PostgreSQL などのリレーショナル・データベース、MongoDB などのドキュメント・データベース、Redis などのキー・バリュー型ストアを選択できるだけでなく、インメモリー・フォーマットまたはシンプルなカスタム・ディスク・ストレージ・フォーマットでも選択することができます。この抽象化によってデータ・ストレージ・エンジンの選択肢の幅は大幅に広がるものの、選択できるオプションはかなり一般的なものになります (これが特長であるか制約であるかは、開発者が決めることです)。

Waterline: Sails ORM

デフォルトの状態の Sails が抽象化するデータ層の中心には、Waterline と呼ばれる組み込み ORM があります。データ・アクセス層としての Waterline は、リレーショナル・データベースと非リレーショナル・データベース (NoSQL) を含め、事実上、あらゆるデータベースをサポートします。Waterline はどのようなタイプのデータ・ストレージでもサポートするため、ベンダー・ロックインから免れることができますが、このような一般化により、特定のタイプのデータ層に対して最適化する機会もなくなってしまいます。

幸い、Sails で Waterline を使用しなければならないわけではありません。MongoDB を対象としたデータのモデル化に Waterline が適していないと判断した場合には、代わりに Mongoose を導入して、Sails アプリケーション・コードをMongoDB と Mongoose に直接的に結び付けることができます。

この注意事項を踏まえ、とりあえず、一般化したデータ層のモデル化を行うことを前提としましょう。

ディスク・ベースの永続化

開発サイクルにおけるこの早い段階では、永続化層についても、MongoDB による一般化された緩い型付けの層についてもあまり考えたくないはずです。そうなると、Waterline を堅持するだけでなく、Sails のデフォルトの永続化層を使用することになります。この完全にローカルのシステムでは、Sails が「sails-disk」データベースを呼び出し、このデータベースが、ディスクに書き込めるようになるまでオブジェクトをメモリーに保管します。実際のストレージ・フォーマットはかなりシンプルなものですが、このストレージはプロジェクトのプロトタイプ化、独自の実験、または (例えば) 記事の中で Sails をデモする目的のためだけに使用するので、フォーマットはほとんど関係ありません。デフォルトのシステムを使用すれば、早いうちから複雑な要素を構築する際の詳細にとらわれることなく、モデルの最終的な形態について独創的に考える余地が与えられます。スキーマはないので、準備ができた時点でリファクタリングするのは簡単な作業です。

Sails モデルを作成するプロセスは、オブジェクトを扱うプロセスと似たようなものとは言え、Sails モデルは皆さんが慣れ親しんでいるような「リッチ・ドメイン・モデル」とは言えません。オブジェクト・ベースのプログラミングで一般的な機能の一部は、Sails モデルにはありません。ただし、他のフレームワークでオブジェクト・リレーショナル・マッピングを行ったことがあるとしたら、Sails はそのときの感覚に匹敵すると感じるはずです。オブジェクト間の関係を構築し始めると、トレードオフが見えてきます。とりあえず、基本的なデータ型を定義するところから始めましょう。

ブログ・エントリーをモデル化する

少し時間をとって、ブログ・エントリーにはどのような機能を期待するのか考えてみてください。すべてのコンポーネントを直ちに計画して実装する必要はありませんが、コーディングに取り掛かる前に、基本的な計画を練っておくのは賢明なことです。モデルとしての BlogEntry には、少なくともタイトルと本文が必要となります。ブログ・エントリーの編集および取得は頻繁に行われるため、エントリーが最初に作成されるときのために created フィールドを追加し、最後に編集されるときのために updated フィールドを追加するのが得策です。

tags フィールドや author フィールドなど、その他のフィールドは、後から追加します。まずは、最初の 4 つのフィールドに絞って取り掛かりましょう。

Sails: 生成する!

これまでのところで、基本的なスキーマを決めて、Sails にモデルをディスクに保管させることにしました。次は、Sails に対し、データ・モデルとそれに関連付けられたコントローラーの土台を生成するよう指示します。それにはまず、コマンド sails generate api BlogEntry を呼び出します。このコマンドによって、2 つのファイルが作成されます。1 つは /api/models ディレクトリー内の BlogEntry.js、もう 1 つは /api/controllers ディレクトリー内の BlogEntryController.js です。

BlogEntryController については後で説明するとして、ここでは BlogEntry モデルについて見ていきます。

リスト 1. 空のモデル
/**
 * BlogEntry.js
 *
 * @description :: TODO: ...
 * @docs        :: http://sailsjs.org/#!documentation/models
 */

module.exports = {

  attributes: {
  }

};

明らかに、このモデルには属性が必要です。デフォルトの sails-disk データベースを使用して、まずは BlogEntry のタイプを定義します。created 属性と updated 属性は Sails が自動的に追加するため、この 2 つの属性を追加する必要はありません。

リスト 2. モデルにフィールドを追加する
/**
 * BlogEntry.js
 *
 * @description :: A blog entry stored in the CMS.
 * @docs        :: http://sailsjs.org/#!documentation/models
 */

module.exports = {

  attributes: {
    title: {
      type: 'string'
    },
    body: {
      type: 'string'
    }
  }

};

次に、Sails が BlogEntry モデルに基づいて BlogEntry クラス (結局のところ、これは JavaScript なので、実際にはクラスのようなもの) を定義します。モデルの各インスタンスには、モデルに対して定義した初期のブログ・エントリーの属性が設定されます。

モデルに属性を追加する

Sails がサポートする属性は string 型だけではありません。'string''text''integer''float''date''datetime''boolean''binary''array''json' などの便利で標準的な型を含め、多数の属性をサポートしています。これらの属性の多くは、必要に応じて (例えば、検証や一意性を目的に) カスタマイズすることもできます。

リスト 2 のモデルには、重要なものが欠けています。2 つのブログ投稿に同じタイトルが付けられているとしたら何が起こるでしょう?この当然起こり得るシナリオには、主キー・フィールドをモデルに追加すれば簡単に対処することができます。この主キー・フィールドには 'integer' 属性を設定するのが妥当でしょう (以下を参照)。

リスト 3. モデルに主キーを追加する
/**
 * BlogEntry.js
 *
 * @description :: A blog entry stored in the CMS.
 * @docs        :: http://sailsjs.org/#!documentation/models
 */

module.exports = {

  attributes: {
    id: {
      type: 'integer'
    },
    // ... the rest
  }

};

上記コードでは、さまざまなデータ・ストアを管理する Waterline の手法は興味深いものになります。Waterline は、データ・モデルに含まれるあらゆる属性を記述してデータベースに示すことができなければならないからです。例えば、Waterline は ID が主キーとして使用されるのかどうかを把握する必要があります。ID が主キーとして使用される場合 (大抵は、そうなります)、Waterline は RDBMS 内の id 列を「PRIMARY KEY」としてマークし、id 属性を MongoDB 内の _id に対応させるなどの処理を行います。

Sails では、以下のように JavaScript の単純なオブジェクト・リテラル構文を使用してメタ属性を指定することができます。

リスト 4. モデルにメタ属性を追加する
/**
 * BlogEntry.js
 *
 * @description :: A blog entry stored in the CMS.
 * @docs        :: http://sailsjs.org/#!documentation/models
 */

module.exports = {

  attributes: {
    id: {
      type: 'integer',
      primaryKey: true
    },
    title: {
      type: 'string',
      required: true,
      defaultsTo: ''
    },
    body: {
      type: 'string',
      required: true,
      defaultsTo: ''
    }
  }
};

このコードはかなり直感的な記述フォーマットです。上記コードで表現しているのは、人間の言葉に翻訳すると、「id は integer 型の属性であり、primaryKey メタ属性は true に設定される」ということです。

以下に、Sails で一般的に使用されるメタ属性のクイック・リファレンスを記載します。

  • email: データ要素が e-メール・アドレスであることが検証されます。
  • defaultsTo: 値が指定されていない場合に使用する値を設定します。
  • autoIncrement: integer 型の属性でのみ使用できます。
  • unique: この属性値がすべてのインスタンスの間で一意であることが検証されます。
  • primaryKey: この属性がモデルで主キーとして使用されます。
  • enum: このメタ属性の値は、指定された属性に許容される値のみで構成される配列となります。つまり、enum: ['Fred', 'Barney', 'Wilma', 'Betty'] は、これら 4 つの値だけが許容されることを意味します。
  • size: データベースに渡せるサイズの制限 (通常は、文字列長の制限) を設定します。

Sails のすべてのメタ属性を網羅したリストについては、Sails または Waterline のドキュメントを参照してください。

基本的なブログ・エンジンには、上記の単純なモデルを使用するので十分です。BlogEntry.js ファイルを保存してから、「sails lift」と入力して、このファイルをローカルで実行します。次のステップに進む前に、このコマンドを必ず実行してください。

アボート、再試行、無視

sails-lift を実行すると、ちょっとしたショックを受けるかもしれません。Sails は静かにきびきびと処理を実行するのではなく、すぐに以下のメッセージをコンソールに送信してくるからです。

リスト 5. safe/alter/drop?
Excuse my interruption, but it looks like this app
 does not have a project-wide "migrate" setting configured yet.
 (perhaps this is the first time you're lifting it with models?)
 
 In short, this setting controls whether/how Sails will attempt to automatically
 rebuild the tables/collections/sets/etc. in your database schema.
 You can read more about the "migrate" setting here:
 http://sailsjs.org/#/documentation/concepts/ORM/model-settings.html?q=migrate

 In a production environment (NODE_ENV==="production") Sails always uses
 migrate:"safe" to protect inadvertent deletion of your data.
 However during development, you have a few other options for convenience:

 1. safe  - never auto-migrate my database(s). I will do it myself (by hand) 
 2. alter - auto-migrate, but attempt to keep my existing data (experimental)
 3. drop  - wipe/drop ALL my data and rebuild models every time I lift Sails

What would you like Sails to do?

おそらく最初に気付くと思いますが、Sails は前例がないほど読みやすいエラー・メッセージを出力します。上記のメッセージはユニークなものでも何でもありません。Sails が出力する他のエラー・メッセージも、他のプログラミング言語で受け取るメッセージと比べると尚更のこと、簡単に読んで理解することができます。

新しく作成した BlogEntry データ・モデルで初めて sails lift リクエストを実行していることから、上記のメッセージでは、Sails がこのモデルを保管するデータベース・システムのスキーマ (存在する場合) の処理方法を指定するよう求めています。

多くの開発者は、ここでいったん考えるはずです。他の ORM システムには、データベース・スキーマを手作業で編集して管理しなければならない基本的な動作がいくつかありますが、Sails はその作業を代わりに引き受けることを申し出ているのです。Sails は「古い」スキーマと「新しい」スキーマのセマンティックな比較ができないため、オプションは限られます。また、Sails を混乱させない程度に行えるリファクタリングにも限りがあります。このようなスキーマ・マイグレーションは、少なくとも自分のやり方の支障となったり、問題を起こしたりしない程度に環境に組み込んでおくと、なかなか便利なので、少し時間をとってオプションを検討してください。また、コードとデータをバックアップしておくことも重要です。

オプションを検討する間、コンソールに戻ると、Sails はモデルのリファクタリング時に行う処理も選択するよう求めてきます。選択肢としては以下のものがあります。

  • safe: 開発者がすべてのスキーマ・リファクタリングを管理することを前提とする
  • alter: データを一切破棄することなく (もちろん、データが破棄されると障害が発生するリスクがあります) 既存のスキーマを最適となるように変更する
  • drop: 新しく起動するたびにゼロに戻る

開発サイクルのこの初期段階では、モデル・タイプを繰り返し調整することになるため、起動するたびにモデルを再作成するのが最善の選択肢です。言うまでもなく、本番システムでこの選択肢を選ぶことは決してありません。アプリケーションを本番環境に移行した後は、おそらく 1 つ目の選択肢を選択する必要があるでしょう。ほとんどの組織は、データベース全体はもちろんのこと、テーブルが切り捨てられる可能性のある操作には非常に慎重なためです。けれども現在はまだ実験の段階であり、sails-disk ストレージ・システム (このシステムにはそもそもスキーマがありません) を使用しているので、事が複雑にならないよう、遠慮なく「drop」を選択してください。

「drop」を選択すると、コンソールにお馴染みのヨットが表示されます。Sails が起動されて実行中になったので、次はデータをモデルに取り込みます。しかし、それにはどうしたらよいでしょう?

Blueprint API

Sails には、GET、POST、PUT、および DELETE の基本 CRUD 操作をサポートするために事前定義された HTTP エンドポイントが用意されています。これら操作のそれぞれは、モデルの名前から生成された URL エンドポイントに接続されます。従って、ブログ API のスケルトンだけがセットアップされているとしても、GET リクエストを http://localhost:1337/BlogEntry に対して発行すると、正当な JSON レスポンスが返されます (以下を参照)。

図 1. GET /BlogEntry
空のブログ・エントリーのスクリーンショットを示す図
空のブログ・エントリーのスクリーンショットを示す図

上図に示されている JSON 配列は空ですが、この画面から、Sails のブループリントが実行されていることがわかります。この例では、ブループリントが HTTP GET エンドポイントに対して定義されたルートを実行しています。他の CRUD エンドポイントについても、理解するのは難しくありません。/BlogEntry に対して POST 送信を行うと、新しい BlogEntry オブジェクトが作成され、オブジェクトに挿入されるデータとして POST の本文が使用されます。PUT は既存の BlogEntry を変更し、DELETE は既存の BlogEntry を削除します (PUT と DELETE はどちらも、送信された HTTP リクエストに含まれる主キー属性から対象のオブジェクトを特定することに注意してください)。

一般的な Web 開発シナリオでは、この時点で curl コマンド・ライン・スクリプトの作成を開始します。ブラウザーはデフォルトでは PUT または DELETE を実行しないため、通常は POSTリクエストを起動するための HTML フォームが必要になるからです。この作業は手間がかかって面倒で、(Sails 開発者にとっては) まったく必要のないものです。基本的な 4 つの HTTP エンドポイントに加え、Sails が自動的にセットアップするブループリントのデフォルト設定のおかげで、ブラウザーから GET リクエストを使用してレコードをシステムに保管するために利用できる「ショートカット」ルートがいくつかあります。その好例として、ブラウザーに以下のアドレスを指定してください。

/BlogEntry/create?title=%27Fred%27&body=%27Hello world%27&id=1",

すると、Sails は以下のレスポンスを返します。

図 2. いくつかのデータ・パラメーターを設定した CREATE
コンソールに表示された CREATE 操作の結果を示す図
コンソールに表示された CREATE 操作の結果を示す図

ブラウザーに戻って GET /BlogEntry リクエストをもう一度実行すると、案の定、単一のインスタンスが表示されます。お気付きかもしれませんが、このセットアップはとりわけ RESTful らしくありませんが、それは GET 動詞を使用してデータベースを変更する操作を行っているためです。明らかに REST システムのルールに反していますが、開発サイクルのこの時点で利用するには、なかなか便利な方法です。

もちろん、この方法とその他すべての自動的に生成されるルートは、システムを本番に移行する前に無効にする必要があります。その理由は第 1 に、データをシステムに挿入するのに使用できる 2 つの方法があると、重複するコードを作成することになります (例えば、新しいブログ・エントリーをデータベースに挿入できるユーザーを制限する場合、2 つのエンドポイントを許可しなければなりません)。第 2 に、同じ操作を複数の方法で実行できるようにすると、システムを引き継ぐ開発者を混乱させる可能性があります。デモや初期のプロトタイプ作成には重宝しますが、初期段階を経た後はあまり役に立ちません。

ブループリント・ルート

包括的な Blueprint API のドキュメントには、Sails に構成されているすべてのブループリント・ルート (Blueprint ルート) の詳細が説明されています。作成/挿入するための (先ほど説明した) ショートカット・ルートに加え、自動的に生成される 2 つのブループリント・ルートについても説明しておく価値があります。

  • PUT /:model/:record: レコードを更新するためのデフォルト・ルートです。
  • GET /:model/:record/update: このルートでは、更新する属性をクエリー・パラメーターのペアとして指定することができます (例: GET /BlogEntry/1/update/title=New%20Title)。

PUT リクエストまたは GET「ショートカット」リクエストのどちらにもパラメーターを指定することができますが、この場合も、本番環境で使用する際にはショートカットを無効にする必要があります。

GET /:model には、GET リクエストにクエリー・パラメーターとして渡せるパラメーターが多数あります。例えば、limit には、返されるエントリーの最大数を指定することができます。また、skip には、エントリーを返す前にスキップするエントリー数を指定することができます。この 2 つのパラメーターを同時に使用することで、大規模なレコード・セットに対する簡単なページング・サポートを提供することができます。例えば、毎回 limit=50 と指定し、skip 0, 50, 100, 150 などと指定すると、ユーザーは 50 件のレコードのブロック間を移動できるようになります。

GET ブループリント・ルートは sort もサポートしています。このパラメーターを使用すると、ソート基準とする属性を指定し、要素を昇順 (ASC) または降順 (DESC) のどちらでソートするかを指定することができます (必ず、属性名とソート順との間にはスペースを入れてください。例: ?sort=lastName%20ASC)。

サポートされているパラメーターには、他にも JSON スタイルの MongoDB のような述部を受け入れる where (例: ?where={"name":{"contains":"theodore"}} など) があります。これは、Waterline の WHERE 標準オブジェクトであり、Waterline はこのオブジェクトを、リレーショナル・データベース・アダプター用に SQL に対応させる形にします。

特定の属性値を検索する場合 (おなじみの SELECT * FROM persons WHERE lastName='Flintstone' クエリーなど)、ルートはその属性値をクエリー・パラメーターとしてそのまま受け入れます (例: GET /BlogEntry?title=Greetings%20everybody)。

ここまでで約十数行のコードだけしか作成していないことを考えると、悪くはない機能揃えです。

まとめ

Sails.js でのモデル化については、他にもまだ学ばなければならないことがありますが、新しい分野を探るには、とりあえず十分な知識を得られたはずです。次回のチュートリアルでは、モデルと、モデル間の関係についてさらに詳しく説明します。具体的には、多くのデータ・システムで従来から使われている「1 対 1」、「1 対多」、「多対多」の関係です。関係をモデル化することで、ブログ API を拡張して、特にコメントなどを受け入れられるようになります。ひとまず、休憩の時間です。このフレームワークの名前にちなんで、「さようなら」とは言わずに「良いご旅行を」と言ってお別れしましょう。


ダウンロード可能なリソース


関連トピック

  • Bluemix クラウド・サービス
  • Sails.js のホームページにアクセスして、Sails.js のあらゆることについてさらに詳しく学んでください。
  • Waterline は Sails のデフォルト ORM です。
  • Waterline に組み込みのモデル・メソッドについて詳しく学んでください。
  • sails-disk アダプターである Sails.js にバンドルされているのは、本番前でのみ使用できる永続オブジェクト・ストアーです。
  • ORM について学ぶ必要があるなら、「リレーショナル・データベースへのオブジェクトのマップ」(Scott W. Ambler 著、developerWorks、2000年7月) を参照してください。
  • オブジェクト・リレーションのインピーダンス不整合についての Ted の考えについて詳しく知るには、「The Vietnam of computer science」(TedNeward.com、2006年6月) を参照してください。
  • Web development ゾーンには、開発者向けの多種多様なリソースが用意されています。
  • さまざまな IBM 製品や IT 業界のトピックに焦点を絞った developerWorks テクニカル・イベントで最新の技術情報を入手してください。
  • Twitter で developerWorks をフォローしてください。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Open source
ArticleID=1034453
ArticleTitle=多忙な Java 開発者のための Sails.js ガイド: モデルとブループリント
publish-date=06302016