Permalink
Find file
27c3f89 Dec 21, 2016
165 lines (136 sloc) 8.78 KB

我们使用 Mongolass 这个模块操作 mongodb 进行增删改查。在 myblog 下新建 lib 目录,在该目录下新建 mongo.js,添加如下代码:

lib/mongo.js

var config = require('config-lite');
var Mongolass = require('mongolass');
var mongolass = new Mongolass();
mongolass.connect(config.mongodb);

4.6.1 为什么使用 Mongolass

早期我使用官方的 mongodb(也叫 node-mongodb-native)库,后来也陆续尝试使用了许多其他 mongodb 的驱动库,Mongoose 是比较优秀的一个,使用 Mongoose 的时间也比较长。比较这两者,各有优缺点。

node-mongodb-native:

优点:

  1. 简单。参照文档即可上手,没有 Mongoose 的 Schema 那些对新手不友好的东西。
  2. 强大。毕竟是官方库,包含了所有且最新的 api,其他大部分的库都是在这个库的基础上改造的,包括 Mongoose。
  3. 文档健全。

缺点:

  1. 起初只支持 callback,会写出以下这种代码:
mongodb.open(function (err, db) {
  if (err) {
    return callback(err);
  }
  db.collection('users', function (err, collection) {
    if (err) {
      return callback(err);
    }
    collection.find({ name: 'xxx' }, function (err, users) {
      if (err) {
        return callback(err);
      }
    })
  ...

或者:

MongoClient.connect('mongodb://localhost:27017', function (err, mongodb) {
  if (err) {
    return callback(err);
  }
  mongodb.db('test').collection('users').find({ name: 'xxx' }, function (err, users) {
    if (err) {
      return callback(err);
    }
  })
  ...

现在支持 Promise 了,和 co 一起使用好很多。 2. 不支持文档校验。Mongoose 通过 Schema 支持文档校验,虽说 mongodb 是 no schema 的,但在生产环境中使用 Schema 有两点好处。一是对文档做校验,防止非正常情况下写入错误的数据到数据库,二是可以简化一些代码,如类型为 ObjectId 的字段查询或更新时可通过对应的字符串操作,不用每次包装成 ObjectId 对象。

Mongoose:

优点:

  1. 封装了数据库的操作,给人的感觉是同步的,其实内部是异步的。如 mongoose 与 MongoDB 建立连接:
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
var BlogModel = mongoose.model('Blog', { title: String, content: String });
BlogModel.find()
  1. 支持 Promise。这个也无需多说,Promise 是未来趋势,可结合 co 使用,也可结合 async/await 使用。
  2. 支持文档校验。如上所述。

缺点(个人观点):

  1. 功能多,复杂。Mongoose 很强大,有很多功能是比较鸡肋甚至可以去掉的,如果使用反而会影响代码的可读性。比如虚拟属性以及 schema 上定义 statics 方法和 instance 方法,可以把这些定义成外部方法,用到的时候调用即可。
  2. 较弱的 plugin 系统。如:schema.pre('save', function(next) {})schema.post('find', function(next) {}),只支持异步 next(),灵活性大打折扣。
  3. 其他:对新手来说难以理解的 Schema、Model、Entry 之间的关系;容易混淆的 toJSON 和 toObject,以及有带有虚拟属性的情况;用和不用 exec 的情况以及直接用 then 的情况;返回的结果是 Mongoose 包装后的对象,在此对象上修改结果却无效等等。

Mongolass

Mongolass 保持了与 mongodb 一样的 api,又借鉴了许多 Mongoose 的优点,同时又保持了精简。

优点:

  1. 支持 Promise。
  2. 简单。参考 Mongolass 的 readme 即可上手,比 Mongoose 精简的多,本身代码也不多。
  3. 可选的 Schema。Mongolass 中的 Schema (基于 another-json-schema)是可选的,并且只用来做文档校验。如果定义了 schema 并关联到某个 model,则插入、更新和覆盖等操作都会校验文档是否满足 schema,同时 schema 也会尝试格式化该字段,类似于 Mongoose,如定义了一个字段为 ObjectId 类型,也可以用 ObjectId 的字符串无缝使用一样。如果没有 schema,则用法跟原生 mongodb 库一样。
  4. 简单却强大的插件系统。可以定义全局插件(对所有 model 生效),也可以定义某个 model 上的插件(只对该 model 生效)。Mongolass 插件的设计思路借鉴了中间件的概念(类似于 Koa),通过定义 beforeXXXafterXXX (XXX为操作符首字母大写,如:afterFind)函数实现,函数返回 yieldable 的对象即可,所以每个插件内可以做一些其他的 IO 操作。不同的插件顺序会有不同的结果,而且每个插件的输入输出都是 plain object,而非 Mongolass 包装后的对象,没有虚拟属性,无需调用 toJSON 或 toObject。Mongolass 中的 .populate()就是一个内置的插件。
  5. 详细的错误信息。用过 Mongoose 的人一定遇到过这样的错: CastError: Cast to ObjectId failed for value "xxx" at path "_id" 只知道一个期望是 ObjectId 的字段传入了非期望的值,通常很难定位出错的代码,即使定位到也得不到错误现场。得益于 another-json-schema,使用 Mongolass 在查询或者更新时,某个字段不匹配它定义的 schema 时(还没落到 mongodb)会给出详细的错误信息,如下所示:
const Mongolass = require('mongolass');
const mongolass = new Mongolass('mongodb://localhost:27017/test');

const User = mongolass.model('User', {
  name: { type: 'string' },
  age: { type: 'number' }
});

User
  .insertOne({ name: 'nswbmw', age: 'wrong age' })
  .exec()
  .then(console.log)
  .catch(function (e) {
    console.error(e);
    console.error(e.stack);
  });
/*
{ [Error: ($.age: "wrong age") ✖ (type: number)]
  validator: 'type',
  actual: 'wrong age',
  expected: { type: 'number' },
  path: '$.age',
  schema: 'UserSchema',
  model: 'User',
  plugin: 'MongolassSchema',
  type: 'beforeInsertOne',
  args: [] }
Error: ($.age: "wrong age") ✖ (type: number)
    at throwError (/Users/nswbmw/Desktop/mongolass-demo/node_modules/another-json-schema/index.js:215:17)
    at validate (/Users/nswbmw/Desktop/mongolass-demo/node_modules/another-json-schema/index.js:184:7)
    at validateLeaf (/Users/nswbmw/Desktop/mongolass-demo/node_modules/another-json-schema/index.js:175:12)
    at iterator (/Users/nswbmw/Desktop/mongolass-demo/node_modules/another-json-schema/index.js:120:14)
    at iterator (/Users/nswbmw/Desktop/mongolass-demo/node_modules/another-json-schema/index.js:133:29)
    at _validateObject (/Users/nswbmw/Desktop/mongolass-demo/node_modules/another-json-schema/index.js:108:11)
    at _Schema.validate (/Users/nswbmw/Desktop/mongolass-demo/node_modules/another-json-schema/index.js:38:10)
    at formatCreate (/Users/nswbmw/Desktop/mongolass-demo/node_modules/mongolass/lib/schema.js:183:25)
    at Query.beforeInsertOne (/Users/nswbmw/Desktop/mongolass-demo/node_modules/mongolass/lib/schema.js:94:7)
    at /Users/nswbmw/Desktop/mongolass-demo/node_modules/mongolass/lib/query.js:146:44
    at next (native)
    at onFulfilled (/Users/nswbmw/Desktop/mongolass-demo/node_modules/co/index.js:65:19)
    at /Users/nswbmw/Desktop/mongolass-demo/node_modules/co/index.js:54:5
    at co (/Users/nswbmw/Desktop/mongolass-demo/node_modules/co/index.js:50:10)
    at Query.execBeforePlugins (/Users/nswbmw/Desktop/mongolass-demo/node_modules/mongolass/lib/query.js:142:10)
    at /Users/nswbmw/Desktop/mongolass-demo/node_modules/mongolass/lib/query.js:67:39
----- Mongolass error stack -----
Error
    at Model.insertOne (/Users/nswbmw/Desktop/mongolass-demo/node_modules/mongolass/lib/query.js:108:16)
    at Object.<anonymous> (/Users/nswbmw/Desktop/mongolass-demo/app.js:10:4)
    at Module._compile (module.js:409:26)
    at Object.Module._extensions..js (module.js:416:10)
    at Module.load (module.js:343:32)
    at Function.Module._load (module.js:300:12)
    at Function.Module.runMain (module.js:441:10)
    at startup (node.js:139:18)
    at node.js:974:3
 */

可以看出,错误的原因是在 insertOne 一条用户数据到用户表的时候,age 期望是一个 number 类型的值,而我们传入的字符串 wrong age,然后从错误栈中可以快速定位到是 app.js 第 10 行代码抛出的错。

缺点:

  1. schema 功能较弱,缺少如 required、default 功能。

扩展阅读

从零开始写一个 Node.js 的 MongoDB 驱动库

上一节:4.5 页面设计

下一节:4.7 注册