リズムのじかん

javascript、typescriptなど中心に書きます。

mongoose + typescript

MVCのmodel

早速話は脱線します。
以下は個人的に違和感があります。あくまでも個人的な思いです。

  • railsの(scaffoldで作成される)model
  • mongooseのmodel
  • .netのEntityFrameworkをmodelと呼ぶこと

個人的に、論理モデルを"model"と呼ぶとしっくりきます。関数従属図の各要素が"model"のイメージです。
上に書いたものは、物理モデルを"model"と呼んでおり、個人的にしっくりきません。

関数従属図のサンプル
f:id:chords:20141008220703p:plain

ある業務をシステム化する場合、
データベースの正規化の違いにより物理モデルは変わりますが、論理モデルは変わりません。

物理モデルの部分は、DAOとかdbとかdataとかdocumentとか呼んで欲しいです。
.netならそのままEntityFrameworkとか呼んで欲しいです。

mongoose + typescript

概要

本題です。以下やりたいこと。

  • mongodbにAuthorコレクションが存在
  • mongodbのODMであるmongooseを用いてデータを操作する
  • データ操作用の独自のメソッドを追加する
  • Authorモデルを定義する

mongooseは物理モデルを"model"と読んでるので(個人的に)しっくりきません。
しかしながら、typescriptのmongooseの型定義では「Model」となっているので、
それを無理やりDAOとか呼ぶのも気が引けます。
仕方なく以下のサンプルでは、物理モデルをDocumentModelとし、論理モデルをmodelとしました。

フォルダ構造

modelのAuthorに業務上必要なものを実装します。
db配下にはデータベースの操作に必要なものを実装します。

├── db
│   ├── AuthorDocumentModel.ts
│   ├── IAuthorDocument.ts
│   ├── IAuthorDocumentModel.ts
│   └── db.ts
└── model
    └── Author.ts

Author.ts(論理モデル)

/// <reference path="../typings/tsd.d.ts" />
 
import db = require('../db/db');
import IAuthorDocument = require('../db/IAuthorDocument');
 
class Author {
  private _author:IAuthorDocument;
 
  constructor(author:IAuthorDocument) {
    this._author = author;
  }
 
  get name():string {
    return this._author.name;
  }
 
  get isValid():boolean {
    return !!this._author;
  }
 
  public static createNewAuthor = (name:string, email:string, callback:(err:any, author:Author)=>void) => {
    db.Author.createNewAuthor(name, email, (err:any, author:IAuthorDocument)=> {
      callback(err, new Author(author));
    });
  };
 
  public static getById = (authorId:string, callback:(err:any, author?:IAuthorDocument)=>void) => {
    db.Author.findById(authorId, (err:any, author:IAuthorDocument) => {
      if(err) {
        return callback(err);
      }
      if(!author) {
        return callback(new Error('not found.'), author);
      }
      callback(null, author);
    });
  };
}
 
export = Author;

AuthorDocumentModel.ts(物理モデル)

/// <reference path="../typings/tsd.d.ts" />
 
import mongoose = require('mongoose');
import IAuthorDocument = require('IAuthorDocument');
import IAuthorDocumentModel = require('IAuthorDocumentModel');
 
var AuthorSchema:mongoose.Schema = new mongoose.Schema({
  email: String,
  name: {type: String, required: true, index: {unique: true}},
  created: { type: Date, default: Date.now },
  updated: { type: Date, default: Date.now }
});
 
AuthorSchema.static('createNewAuthor',
  (name:string, email:string, callback:(err:any, result:IAuthorDocument)=>void) => {
    AuthorDocumentModel.findByName(name, (err:any, author:IAuthorDocument) => {
      if(err) {
        return callback(err, null);
      }
      if(author) {
        return callback(new Error('already exists.'), author);
      }
      author = <IAuthorDocument>new AuthorDocumentModel({name: name, email: email});
      author.save(callback);
    });
  });
 
AuthorSchema.static('findByName', (name:string, callback:(err:any, result:IAuthorDocument)=>void)=> {
  AuthorDocumentModel.findOne({name: name}, callback);
});
 
var AuthorDocumentModel:IAuthorDocumentModel = <IAuthorDocumentModel>mongoose.model('Author', AuthorSchema);
 
export = AuthorDocumentModel;

IAuthorDocument.ts(インタフェース)

/// <reference path="../typings/tsd.d.ts" />
 
import mongoose = require('mongoose');
 
interface IAuthorDocument extends mongoose.Document {
  email: string;
  name: string;
  created: Date;
  updated: Date;
}
 
export = IAuthorDocument;

IAuthorDocumentModel.ts(インタフェース)

/// <reference path="../typings/tsd.d.ts" />
 
import mongoose = require('mongoose');
import IAuthorDocument = require('IAuthorDocument');
 
interface IAuthorDocumentModel extends mongoose.Model<IAuthorDocument> {
  findByName:(name:string, callback:(err:any, author:IAuthorDocument)=>void)=>void;
  createNewAuthor:(name:string, email:string, callback:(err:any, result:IAuthorDocument)=>void)=>void;
}
 
export = IAuthorDocumentModel;

db.ts

/// <reference path="../typings/tsd.d.ts" />
 
import mongoose = require('mongoose');
import AuthorDocumentModel = require('./AuthorDocumentModel');
import IAuthorDocumentModel = require('./IAuthorDocumentModel');
 
class db {
 
  public static connect(db:string) {
    mongoose.connect('mongodb://' + db);
  }
 
  public static debug(debug:any) {
    mongoose.set('debug', debug);
  }
 
  static get Author():IAuthorDocumentModel {
    return <IAuthorDocumentModel>AuthorDocumentModel;
  }
}
 
export = db;