Node.js および MongoDB を使用して単純な通知サービスを作成する

2014年 8月 21日
PDF (348 KB)
 

Build a simple notification service with Node.js and MongoDB

02:14  |  Transcript

Kevin Williams

Cloud, Automation, and Test Architect for IBM Software

 

IBM Bluemix™ にサインアップ
無償のサービス、ランタイム、インフラを含むクラウド・プラットフォームが、新たなモバイルやウェブ・アプリのクイックな構築とデプロイを実現します。

増え続ける異種のツールの動作を調整して、さらに大きなシステムを構築しようとしたことがありますか?私が携わっているケースでは、開発チームが継続的デリバリー・パイプラインを組み立て、それらの工程を順番に並べる必要があります。そのための 1 つの方法は、イベントの作成、イベントに対するシグナリング、イベントへの登録 (イベントのサブスクリプション) をサポートする通知サービスを採用することです。

このようなサービスをビルド・システムに適用すれば、新しいビルドが使用可能であると知らせることができます。すると、パイプラインの下流コンポーネントは通知を受け取り、新しいビルドがあることに基づいてアクションを行うことができます。このアクションには、新たなテスト・システムを事前に用意して、リグレッション・スイートを実行することを含めることもできます。

便利な通知サービスが必ずしも心躍るようなものであるとは限りません。

私は、REST ライクな API を使用した HTTP サーバーの迅速な開発をサポートするために Node.js ランタイムを使用して、この単純な通知サービスを開発しました。また、バックエンドには MongoDB を採用しました。MongoDB のドキュメント指向は迅速なプロトタイピングに最適なようであり、ACID 特性 (Atomicity (アトミック性)、Consistency (一貫性)、Isolation (独立性)、Durability (永続性)) を厳密にサポートする必要がありませんでした。

アプリケーションのアーキテクチャー

稼働中の通知サービスを参照するには、「アプリを実行する」をクリックし、通知サービスが処理した最新の 5 つのイベント・シグナルのログを取得します。ログに含まれるシグナルのほとんどは、製品ビルド・システムによって生成されたもので、JSON 形式になっています。

類似のアプリを作成するために必要となるもの

 

読む:「Node.js: 基本を越えて」

Node.js と MongoDB をインストールすると、Node.js のパッケージ・マネージャー (npm) を使用して必要な依存関係をロードできるようになります。このアプリケーションを有効にするためのモジュールおよびフレームワークの該当バージョンに関しては、DevOps Services からダウンロードしたコード内にある package.json ファイルを参照してください。

それでは、はじめましょう。

ステップ 1. API の作成

 

この API は、固有の URL に HTTP 動詞 (GET、PUT、POST、および DELETE) を適用することによって、リソースにアクセスしたり、変更を加えたりするという点で、REST ライクです。HTTP メッセージング機能を持つアプリケーションであれば、どのアプリケーションでもこのサービスにアクセスすることができます。この方法によって、今後のブラウザー・ベースのツールにも対応する簡潔なインターフェースが提供されます。

サービスが管理する主要なリソースはイベント (/events) とサブスクリプション (/subscriptions) です。作成、読み取り、更新、削除という処理のそれぞれに API が用意されています。

私はこの API を Express フレームワークをベースにしました。それは、Web アプリケーション開発を目的とした堅牢な機能セットを含んでいること、そして規約に従えば、使用するのがとても簡単であることが理由にあります。イベント (/events) 宛てで受信されるリクエストのルーティングをセットアップするアプリケーションのメイン・モジュール (server.js) のコードを見てみましょう。

console.log ('registering event routes with express');
app.get('/events', event.findAll);
app.get('/events/:id', event.findById);
app.post('/events', event.addEvent);
app.put('/events/:id', event.updateEvent);
app.delete('/events/:id', event.deleteEvent);

これと同じ基本パターンはサブスクリプション (/subscriptions) に関しても繰り返されます。

console.log ('registering subscription routes with express');
app.get('/subscriptions', sub.findAll);
app.get('/subscriptions/:id', sub.findById);
app.post('/subscriptions', sub.addSubscription);
app.put('/subscriptions/:id', sub.updateSubscription);
app.delete('/subscriptions/:id', sub.deleteSubscription);

こうしたイベントやサブスクリプションの基本処理のほかに、2 つの重要な API があります。具体的には、以下の 2 つです。

  • イベントに対してシグナルを送信する API :
    app.post('/signals', signal.processSignal);
  • 最新シグナルのログを取得するための API :
    app.get('/signallog', signallog.findRecent);

ステップ 2. バックエンドとの連係

 

MongoDB によって、このサービスには永続ストアが提供されます。私は、オブジェクト・マッパーを使用せずに直接バックエンド・ドキュメント (コレクション) を使用します。Node.js のネイティブ・ドライバーは必要なすべての機能を実現します。

2 つの主要なリソースであるイベント (events) とサブスクリプション (subscriptions) は、データベースのコレクションに直接マッピングされます。これらのリソースへのアクセスは単純です。例えば、イベント・リソースに対する GET リクエストは、前述のコード部分で定義されているように event.findAll を呼び出すことになります。findAll 関数は events.js モジュールで定義されています。

exports.findAll = function(req, res) {
   mongo.Db.connect(mongoUri, function (err, db) {
      db.collection('events', function(er, collection) {
         collection.find().toArray(function(err, items) {
            res.send(items);
            db.close();
         });
      });
   });
}

findAll 関数は単に、データベースに接続し、イベント・コレクションへのハンドルを取得して、すべての要素を返した後、接続を切断します。API リクエストはすべて、これと同じようにとても直接的な方法で処理されます。次の例では、イベントの削除リクエストが events.js モジュールの deleteEvent 関数で処理されています。

exports.deleteEvent = function(req, res) {
   var id = req.params.id;
   mongo.Db.connect(mongoUri, function (err, db) {
      db.collection('events', function(err, collection) {
         collection.remove({'_id':new BSON.ObjectID(id)}, {safe:true}, function(err,
result) {
            res.send(req.body);
            db.close();
         });
      });
   });
}

前と同様に、接続を確立し、コレクション・ハンドルを取得します。次に、指定された ID に基づいてコレクション要素を削除し、接続を切断します。

ステップ 3. 通知の送信

 

管理されているリソースに対して作成、置換、更新、削除といった操作を実行するのに加え、このサービスでは、イベントにシグナルが送られると、適切なサブスクリプション・エンドポイントにメッセージを送信します。このサービスのこの最初のバージョンでは、e-メールで通知をしています。次に挙げるコード部分は、エンドツーエンドでプロセス全体をトレースしています。

シグナル・ルートを通じてシグナルが受信されます。server.js モジュールを見れば、このシグナル・モジュールの processSignal 関数を使用して、このルートの処理を行っていることがわかります。

app.post('/signals', signal.processSignal);
exports.processSignal = function(req, res) {
   var signal = req.body;
   console.log('Processing Signal: ' + JSON.stringify(signal));

  mongo.Db.connect(mongoUri, function (err, db) {
   db.collection('subscriptions', function(err, collection) {
     collection.find().toArray(function(err, items) {
      matches = _.filter(items, function(sub){return sub.eventTitle == signal.eventTitle});
      _.each(matches, function (sub) {processMatch(sub, signal)});
      res.send(matches);
    });
  });
});

前と同様に、データベースへの接続を確立し、関連するコレクションのハンドルを取得します。次にイベント・タイトルで絞り込んで、該当するイベント・タイトルのサブスクリプションをすべて取得します。その後、該当するサブスクリプションのサブセットの各要素が processMatch 関数で処理されます。

_.filter_.each の使い方に気づきましたか?この 2 つは両方とも、Underscore という非常に優れたライブラリーに含まれています。このライブラリーには、Ruby などのオブジェクト指向言語のユーザーにとても馴染みがある、有能なヘルパー関数が含まれています。

processMatch 関数は単に、e-メール・メッセージに適切な値を割り当て、mailer.sendMail 関数を呼び出します。

function processMatch(subscription, signal) {
   opts = {
      from: 'Simple Notification Service',
      to: subscription.alertEndpoint,
      subject: subscription.eventTitle + ' happened at: ' + new Date(),
      body: signal.instancedata
   }
   // Send alert
   mailer.sendMail(opts);
}

私は NodeMailer という別のライブラリーも使用するので、sendMail 関数も単純です。sendMail 関数がいくつかの NodeMailer 機能を使用するだけで、e-メール通知を送信するのがわかるかと思います。基本的には送信オブジェクトを認証値で初期化し、メッセージ・コンテンツ (件名、本文、アドレスなど) を指定して送信を開始します。

exports.sendMail = function (opts) {
   var mailOpts, smtpTransport;

   console.log ('Creating Transport');

   smtpTransport = nodemailer.createTransport('SMTP', {
      service: 'Gmail',
      auth: {
         user: config.email,
         pass: config.password
      }
});

// mailing options
mailOpts = {
   from: opts.from,
   replyTo: opts.from,
   to: opts.to,
   subject: opts.subject,
   html: opts.body
};

console.log('mailOpts: ', mailOpts);

console.log('Sending Mail');
// Send mail
smtpTransport.sendMail(mailOpts, function (error, response) {
   if (error) {
      console.log(error);
   }else {
      console.log('Message sent: ' + response.message);
   }
   console.log('Closing Transport');
   smtpTransport.close();
   });

}

ステップ 4. アプリケーションのテスト

 

通知サービスの主な機能については説明したので、次は自動テストについて簡単に説明します。

Node.js エコシステムにおけるもう 1 つの有能なフレームワークはテスト用の Mocha です。Mocha を、HTTP 関連の処理を対象とする supertest やアサーションを対象とする should などのアドオンと組み合わせることで、この API の機能テストが理解しやすい形で提供されます。以下に示すのは、readbyid イベント API を検証する eventtests.js モジュールのスニペットです。

it('should verify revised News flash 6 event', function(done) {
   request(url)
      .get('/events/'+ newsFlash6id)
      .end(function(err, res) {
         if (err) {
            throw err;
         }
         res.should.have.status(200);
         res.body.should.have.property('title');
         res.body.title.should.equal('News flash 6 - revised');
         done();
      });
});

この構文とフォーマットは非常に読みやすいため、保守をするのは比較的簡単だと思います。

自動化された機能リグレッション・テスト・スイートには、各 API のテストが含まれています。そこで私は、すべてのテストが含まれるテスト・フォルダーを作成しました。Mocha はこの規約を認識します。テスト・スイートを実行するには、プロジェクトのルートからコマンド mocha を実行します。

次の図で示すとおり、エラーがなければ、テスト・スイートからの出力は簡潔です。

テスト出力のスクリーン・ショット

私が現在行っているプロセスでは、変更を加えると (その都度) リグレッション・スイートをローカルで実行します。このリグレッション・スイートは、ソース管理に変更をプッシュする前に必ず実行します。このサービスのインスタンスは Bluemix プラットフォーム上で実行されているので、「アプリを実行する」をクリック済みで、このインスタンスを操作していた場合、最新のイベント・シグナルのログを見ることができます。

現在、私は基本的に 2 つの環境を使用しています。1 つは、変更を行ってテストを実行するローカルの開発システムであり、もう 1 つは Bluemix 上の本番システムです。今のところ、この組み合わせはうまくいっていますが、次のステップではホストされたステージング環境を Bluemix 上に作成します。新たなステージング環境では、これまでよりも本番に近い環境でリグレッション・スイートを実行することが可能になると同時に、ソース変更後に行う処理の選択肢が (自動テストやデプロイメントなど) 広がります。そのステップが実現したら、ブログに書くことをお約束します。

広範なツールのイベントを管理するために、この記事で紹介したのと同様の単純な通知サービスを作成することを検討してください。是非とも通知サービスを作成してみて、どのようになったかをお知らせください!


関連トピック:Node.jsMongoDBJazz

コメントの追加

注意: HTML コードは、コメント内ではサポートされません。


残り 1000 文字

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Open source, Cloud computing
ArticleID=981193
ArticleTitle=Node.js および MongoDB を使用して単純な通知サービスを作成する
publish-date=08212014