在 Express 项目中使用 Waterline
    
      撰写于 2015年10月26日
      
        修改于 2017年6月27日
      
      
      分类
      
      
        
        
        编程杂记
      
      
      
      
      
      标签
      
      
        
        
        Node.js
      
        
          /
        
        
        Waterline
      
      
      
    
    
    在上一篇Node.js ORM 数据操作中间件 Waterline 中,我们介绍了 Waterline 的功能与特点,这篇文章中我们将用一个实例 waterline-sample (Github、Coding.NET),来看看在 Express 项目中如何使用 Waterline。
要在项目中使用 Waterline ,无非是解决如何配置,在什么时机初始化,怎样组织所有的数据集合,以及在控制器中怎么调用 Waterline 中的数据集合这几个问题。
项目的功能与结构
这个示例项目中,将会实现两个关于 Post 的 API ,添加和获取列表的接口,功能比较简单,但代码的组织,依然遵循模块化和 MVC 的设计原则,主要的文件和目录如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
 | |-- app |   |-- controllers |   |   `-- post.server.controller.js |   |-- models |   |   `-- post.server.model.js |   `-- routes |       `-- post.server.routes.js |-- config |   |-- config.js |   |-- env |   |   `-- development.js |   |-- express.js |   `-- waterline.js |-- app.js |-- bin |   `-- www `-- package.json
 | 
其中, config 目录存储基本配置、 Express 配置和 Waterline 的配置文件,其中的 env 目录存储根据环境而不同的基本配置。 app 目录下的三个文件夹,分别按 MVC 的结构组织 Waterline 的 Collections ,Expresss 的控制器以及路由文件。根目录下的 app.js 将使用 config/express.js 中的配置来生成 Express 的实例,而入口文件 bin/www 则完成 Waterline 的初始化和启动 Express 实例的端口监听。
数据集合的组织
所有的数据集合,全部组织在 app/models 目录中,直接使用 module.exports 来将 Waterline.Collections 实例导出,以便在 Waterline 的配置文件中调用。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 | var Waterline = require('waterline'); module.exports = Waterline.Collection.extend({   identity: 'post',   connection: 'mongo',   schema: true,   attributes: {     title: {       type: 'string',       required: true     },     content: 'string',     createTime: 'date',     lastModifyTime: 'date'   },   beforeCreate: function(v, cb){     v.createTime = new Date();     return cb();   },   print: function(v) {     console.log('\tTitle:', v.title, 'create at:', v.createTime);     console.log('\tContent:', v.content);   } });
 | 
这里也演示了如何定义生命周期回调和自定义的方法。
配置
配置里主要是生成 Waterline 的实例,并加载上面的数据集合配置文件。下面是 Waterline 的配置文件,导出了配置所需要的代码,但并没有执行初始化。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 | var Waterline = require('waterline'); var mongoAdapter = require('sails-mongo'); var config = require('./config'); var Post = require('../app/models/post.server.model'); var orm = new Waterline(); var wlconfig = {   adapters: {     'default': mongoAdapter,     mongo: mongoAdapter   },   connections: {     'mongo': {       adapter: 'mongo',       url: config.mongo     }   } }; orm.loadCollection(Post); exports.orm = orm; exports.config = wlconfig;
 | 
这里使用的是 exports 来导出,因为我们有两个值需要导出,一个是 Waterline 的实例,另外一个是 Waterline 的初始化配置。这在 Waterline 初始化的时候会用到。
初始化
初始化是在 bin/www 里完成的,原因是保证 Express 启动监听,必须在 Waterline 的成功初始化之后进行。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 | var app = require('../app'); var config = require('../config/config'); var waterline = require('../config/waterline'); waterline.orm.initialize(waterline.config, function(err, models){   if(err) {     console.log('waterline initialize failed, err:', err);     return;   }   console.log('waterline initialize success.');      app.set('models', models.collections);   app.listen(config.port, function(){     console.log('Express listening on port:', config.port);   }); });
 | 
初始化是直接使用 Waterline 实例的 intialize() 方法,需要传入对应的配置,这两个都是在 waterline.js 配置文件中导出的。为了方便我们在控制器代码中调用 Waterline 的数据集合,这里先将它加入到 Express 实例的配置列表中。
在 Express 控制器中使用 Waterline 数据集合
由于 Waterline 的初始化过程是异步的,所以我们没有办法直接使用 module.exports 或 exports 方法来导出它的实例,也就无法直接以 JavaScript 模块化的方式调用它实例中的数据集合。这里将借助 Express 的实例,来在控制器代码中使用它。
但实际上,在控制器代码中,也是没有办法直接访问 Express 的实例的,所以这里我们在 Express 的配置里,增加一个中间件,将附加在 Express 实例上的数据集合,再加入到 Express 请求对象中,这样便可以在控制器代码中通过请求对象来访问 Waterline 实例的数据集合了。当然,加到响应对象也可以。下面的代码展示了如何在控制器中使用 Waterline 的数据集合。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
 | var express = require('express'); var waterline = require('./waterline'); module.exports = function(){   console.log('express initialing...');   var app = express();      app.use(function(req, res, next){     req.models = app.get('models');     next();   });   require('../app/routes/post.server.routes')(app);      return app; };
 | 
在控制器里,便可以通过请求对象的 models 成员来调用了。
| 1 2 3 4 5 6 7 8 9 10
 | module.exports = {   list: function(req, res, next){     var page = parseInt(req.query.page, 10) ? parseInt(req.query.page, 10) : 1;     var limit = parseInt(req.query.limit, 10) ? parseInt(req.query.limit, 10) : 1;     req.models.post.find().paginate({page: page, limit: limit}).exec(function(err, docs){       res.json(docs);     });   } };
 | 
虽然也可以通过全局变量来调用数据集合,不过在有其它办法的情况下,还是尽量不要使用全局变量吧。