2012年12月18日火曜日

Node.jsが面白い件⑦ MongoDBで遊ぶ 〜Mongoose編その④:Middleware〜

今回はMongooseの便利な機能の1つ、middlewareを使ってみます。middlewareにはpreとpostがあり、initやsave、validate、removeの前後の処理をSchemaレベルで定義することができます。

4. pre

まずは前処理のpreから記述してみます。今まで使ってきたuserSchemaを使いますが、わかりやすくするためにcreatedキーを付けます。

    // サンプルスキーマ
    var userSchema  = mongoose.Schema({
        uid : { type: String, index: {unique:true} },
        name: { first: String, last: String },
        age : { type: Number, min: 1 },
        gender : { type: String, enum: genders },
        created : Date
    });

    // preの設定
    userSchema.pre('save', function(next) {
      this.created = new Date;
      next();
    });

この例では、preを使ってsaveされる直前に現在時刻をcreatedに設定する処理を記述しています。それではsaveしてみます。

    // インスタンス(document)作成。
    var user = new User();
    user.uid = 'kat';
    user.name.first = 'KAT';
    user.name.last = 'INTINK';
    user.age = 100;
    user.gender = 'male';

    user.save(function(err) {(以下、省略)

以下、結果です。

[ { created: Tue Dec 18 2012 22:45:22 GMT+0900 (JST),
    gender: 'male',
    age: 100,
    uid: 'kat',
    _id: 50d07372ff75973d29000001,
    __v: 0,
    name: { last: 'INTINK', first: 'KAT' } } ]

createdは事前に設定していませんが、きちんと入っていることがわかります。preで定義した関数の中でnext()が呼ばれていますが、next()が呼ばれることで次の処理が実行されます。複数のpre定義があった場合、next()が呼ばれるたびに次の処理が実行されることになります。

上の例では順次実行でしたが、処理によっては複数の処理を並列で処理したい場合もあります。この場合は以下のようにnext()/done()を使います。

    var checkStr = "a";

    // preの設定
    // 処理①
    userSchema.pre('save', true, function(next, done) {
      console.log(new Date);
      next();    // ->処理②の開始。
      setTimeout(function() {
        checkStr += 'b';
        done();  // ->処理①の終了。
      }, 1000);
    });

    // 処理②
    userSchema.pre('save', true, function(next, done) {
      console.log(new Date);
      next();    // ->処理③の開始。
      setTimeout(function() {
        checkStr += 'c';
        done();  // ->処理②の終了。
      }, 2000);
    });

    // 処理③
    userSchema.pre('save', true, function(next, done) {
      console.log(new Date);
      next();    // ->終了次第、実際の処理(save)
      setTimeout(function() {
        checkStr += 'd';
        done();  // ->処理③の終了。
      }, 3000);
    });

実際の処理ではこのような書き方をしないと思いますが、動作がわかりやすいように各preの定義でsetTimeoutによって処理を実行しています。各関数の中にはnext()とdone()がそれぞれ記述されているのがわかります。next()が呼ばれると前述のように次の処理が実行されますが、それぞれの処理は終了時にdone()を実行しています。この場合、全てのdone()が実行されないとsave処理を実施しませんが、それぞれは並列で実行されるため効率よく処理が終了できます。また、並列で実行する際にはpreの第二引数にtrueの指定を忘れないようにしましょう。念のため、結果は以下のようになります。

Tue Dec 18 2012 22:58:15 GMT+0900 (JST) // -> 処理①開始
Tue Dec 18 2012 22:58:15 GMT+0900 (JST) // -> 処理②開始
Tue Dec 18 2012 22:58:15 GMT+0900 (JST) // -> 処理③開始
Tue Dec 18 2012 22:58:18 GMT+0900 (JST) // -> save開始
checkStr : abcd

上手く使えばいろいろと効率よく処理ができそうですね。ドキュメントには複雑なバリデーションや依存関係のあるドキュメントの削除などが例として記載されています。

4. post

事前処理があれば事後処理も、ということでpostが用意されています。postはよりシンプルで、以下のように定義できます。

    // postの設定
    userSchema.post('save', function(doc) {
      console.log('User: %s was created.', doc.uid);
    });

実行結果は以下のようになります。

User: kat was created.

pre/postを使えばRDBMSで言うトリガーのような処理が実現できます。あまり複雑な処理は重くなるため好ましくないですが、ODMの機能を効果的に使うことでそこそこの実装コスト削減が期待できそうですね。

0 件のコメント:

コメントを投稿