基于express的复杂应用——代码结构分层

项目之初,我一般选择用 express 的脚手架工具 express-generator 生成目录结构,比较快捷,生成的文件结构也比较直观。

1
2
npm install express-generator -g // 全局安装  
express -e myapp // 创建工程,生成目录,使用ejs作为模板语言

生成的目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
├── app.js
├── bin
│ └── www
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
├── routes
│ ├── index.js
│ └── users.js
└── views
├── error.ejs
├── index.ejs
└── layout.ejs
  • app.js 作为入口文件
  • public 存放静态资源
  • routes 存放路由文件
  • views 模板

对于功能比较 单一/复杂度较低 的应用,我们只需要增加一个 models 文件夹,将逻辑与数据操作封装后放置其中,然后在路由文件中引入即可,如:

1
2
3
4
5
6
7
8
9
10
11
routes/index.js:

const getIndexlists = require('../modals/getIndexlists');

app.get('/list', function(req, res) {
getIndexlists(req, function(err, result){
// 向models中模块传递参数,利用回调函数得到结果,然后操作res
if(!err) res.render('list', result)
....
})
});

models/getIndexlists.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const mysql = require('mysql');  
const conf = require('../conf/db.js');
// 连接数据库
const pool = mysql.createPool( conf.mysql );

module.exports = function(req, callback){
let id = req.params.id;
pool.getConnection(function(err, connection){
if(err){
callback(err);
return;
}
connection.query('select * from list where id='+"id",function(err, result){
// 得到结果后回调
callback(err, result)
});
//释放连接
connection.release();
})
}

这时的项目结构长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
├── app.js
├── bin
│ └── www
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
├── routes
│ └── index.js
├── models
│ └── getIndexlists.js
└── views
└── list

对于路由较为简单,功能较少的应用,这样分离路由与数据操作已经够用了。但是我们会发现,上述的示例中,路由直接传递参数给 model,model 与进项数据操作后直接返回结果给 route,然后 route 直接拿数据渲染 view 模板。然而通常我们拿到数据后需要对数据进行一系列的处理,或者是进行多个表的数据操作,将结果整合,从而得到我们期待的数据结构。那么这些操作我们应该放在那里呢,放在 model 中明显不合理,因为这明显不与数据库操作挂钩,那么放在 route 文件中呢?看着好像还可以。 这时我们的 route 文件大概是这样:

routes/index.js

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
26
27
28
29
30
31
32
33
34
const async = require('async');  
const getIndexlists = require('../modals/getIndexlists'),
getUserInfo = require('../modals/getIndexlists'),
...;

app.get('/list', function(req, res) {
// 多任务
async.parallel({
list: function(callback){
getIndexlists(req, function(err, result){
// 拿到数据,进行格式处理后返回
result.data = result.data.map(function(item){
return item+1;
})
.....
callback(err, result)
})
},
user: function(){
getUserInfo(req, function(err, result){
....数据处理
....数据处理
callback(err, result)
})
}
...其余操作
},
function(err, result){
//最终返回结果
if(!err){
res.render('list', result);
}
})
});

这时候我们的路由文件实际上已经变的臃肿了,不仅处理路由信息,还要处理多任务数据操作及数据格式问题,这样的形式写的再多一点,route 文件就很混乱了。这时候我们就会想把(任务处理、数据处理)逻辑处理给抽出来,放到 services 文件夹中,形成 route-service-model 的结构,route 负责单一的路由处理,service 负责逻辑处理,model 负责数据访问。这样每个文件各司其职,代码结构就会比较清晰。将 service 抽离出来的另一个好处是——可复用性,实际上很多数据处理的格式相同,我们可以把这些相同的操作封装到一个 service 文件中,在需要的地方直接调用即可,同理,对于 model 中的数据操作,我们也可以将常用的增删改查操作封装起来,提高代码的复用性。

这时我们的代码结构长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
├── app.js
├── bin
│ └── www
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
├── routes
│ └── index.js
├── models
│ └── getIndexlists.js
├── services
│ └── dealListData.js
└── views
└── list

到此为止,我们的代码结构已经很清晰了,基本实现了高复用,低耦合的理念。 那么当我们的应用进一步复杂,我们的系统可能有好多功能模块,几十个路由,这时候出现的问题就是路由文件泰国庞大,所有的路由都集中在一个路由文件中,阅读调试起来相当不便。这是我们会想到将路由进行二级分化,主路由进行大方向的路由控制,将同一功能模块(路由相似)的路由集中到一个路由文件中。我们把自路由控制文件放置于 controller 文件夹中,这样路由的控制实际是 route-controller 这样的二次处理,减轻了主路由的负担,提高代码的可维护性与可读性。 到此为止,我们的express应用的运行流程是:route-controller-service-model

这样,即便是复杂的 express 应用,也会分解的条理清晰。当然在实际项目中我们还需要日志显示,那么我们最终的项目结构因该是长这样:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
.
├── app.js
├── bin
│ └── www
├── conf
│ ├── db.js
│ └── log.js
├── controllers
│ ├── indexController.js
│ ├── homeController.js
│ ├── userController.js
│ ├── pageController.js
│ └── resultController.js
├── logs
├── models
│ ├── analyze.js
│ ├── create.js
│ ├── list.js
│ └── result.js
├── package.json
├── public
│ ├── fonts
│ ├── images
│ ├── javascripts
│ ├── stylesheets
│ └── uploads
├── routes
│ └── index.js
├── service
│ ├── delResult.js
│ ├── delUser.js
│ ├── getAnalyze.js
│ ├── getIndexLists.js
│ ├── mysql.js
│ ├── publishArticle.js
│ ├── ssoLogin.js
│ └── upload.js
└── views
├── index
├── home
├── user
├── page
└── common

经验之谈,说的不对的地方,望大家指正。

附:创建二级路由
express.Router
可使用express.Router类创建模块化、可挂载的路由句柄。Router实例是一个完整的中间件和路由系统,因此常称其为一个 “mini-app”。

下面的实例程序创建了一个二级路由模块,定义了一些路由,并且将它们挂载至应用的主路由上。

二级路由controllers/birds.js内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
var express = require('express');  
var router = express.Router();

// 定义网站主页的路由
router.get('/', function(req, res) {
res.send('Birds home page');
});
// 定义 about 页面的路由
router.get('/about', function(req, res) {
res.send('About birds');
});

module.exports = router;

主路由routes/index.js内容:

1
2
3
var birds = require('./birds');  
...
app.use('/birds', birds);

应用即可处理发自/birds/birds/about的请求。

Author

Ludis

Posted on

2017-05-16

Updated on

2018-02-13

Licensed under

Comments