Nodejs服务端代码的最佳实践
前言
一年前的这会,自己还在使用着SailJs这种开箱即用的Web框架搭建自己的结婚纪念网站,一年后的今天,我开始探讨Express这款Web框架的最佳实践。因为工作的关系,我不再使用SailsJs这种成熟性太强的框架(但是博客网站的代码依旧会持续更新),转而使用诸如koa、express这种简单自由度高的框架。正是因为此类框架的自由度太高(老实说只要你有app.js和package.json文件即可,其他的都不care),所以每个人写的服务器款式各异,千奇百怪,难以抓摸。为了让新接触这些框架的童鞋们能够养成良好的服务器编写习惯,小的不才在这里献丑,聊聊我对整体服务器框架的思考。
Tips: 此次思考的基本框架原型是MVC。
1、框架的文件结构
文章附带有一个demo:express-vue-boilerplate
该demo使用了Express + Vue的技术栈,感兴趣的可以clone/fork下来参考参考。
既然我们是基于MVC框架的,所以自然少不了models、controllers、views这几个文件夹。
文件结构如下所示:
├── .babelrc
├── client
│ ├── App.vue
│ ├── assets
│ │ ├── fonts
│ │ │ ├── Material.css
│ │ │ └── Roboto.css
│ │ └── img
│ │ └── logo.png
│ ├── components
│ │ └── TodoList.vue
│ ├── index.html
│ └── main.js
├── config.dev.json
├── config.prod.json
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── package.json
├── README.md
├── server
│ ├── controllers
│ │ └── todoController.js
│ ├── helper
│ │ └── util.js
│ ├── middleware
│ ├── models
│ │ └── todo.js
│ ├── routes
│ │ └── index.js
│ ├── server.js
│ └── views
├── test
└── webpack
├── build.js
├── utils.js
├── webpack.config.js
└── webpack.prod.config.js
文件解释
- .babelrc: Babel的配置文件
- client: 客户端代码存放路径
- App.vue: 网站入口加载的首个组件
- assets: 存放静态资源,比如图片、字体等
- components: 网站的所有其他组件
- index.html: 网站唯一的一个html文件
- main.js: 网站渲染的入口
- config.dev.json: 开发环境的配置文件
- config.prod.json: 产品环境的配置文件
- .eslintignore: eslint配置忽略对哪些文件的检查
- .eslintrc.js: eslint的配置文件
- .gitignore: git配置忽略上传哪些文件
- package.json: 项目配置文件
- README.md: 项目的guide
- server: 服务器代码存放路径
- controllers: MVC的
C
的代码存放路径 - helper: 服务器共用的代码存放路径
- middleware: 服务器中间件代码存放路径,中间件的目的就是抽出控制器通用的代码然后在每次请求的时候都能够被执行,可以改变请求或者响应的内容或者行为
- models: MVC的
M
的代码存放路径 - routes: 服务器路由配置文件
- server.js: 服务器入口文件
- views: 服务器页面模板存放路径
- controllers: MVC的
- test: 测试文件夹
- webpack: 打包工具webpack配置文件夹
- build.js: 前端代码打包文件
- util.js: webpack打包共用的方法
- webpack.config.js: 开发环境下的打包配置文件
- webpack.prod.config.js: 产品环境下的打包配置文件
2、最佳实践
因为主要考虑服务器端的,所以这里就不细说客户端的代码了。既然提到MVC,那么我们需要先搞清楚的便是MVC中M、C、V的各自职责。
2.1、MVC的职责
- M:所有与数据库直接操作的行为都属于Model,也就是说涉及到直接调用数据库API的操作以及数据库的Schema的定义应该是都属于Model的职责, 当然也应该包含其他业务逻辑。
- C: 连接
M
和V
的桥梁,相当于一个中继器,在C
里面应该处理你的HTTP请求、渲染模板。 - V: 服务器提供给客户端的模板,如果是SPA的话貌似这个文件夹就会处于空的状态,如demo中的例子
2.2、最佳实践的几点思考
清楚了职责的话,我们在编程的时候就会刻意去界定各自的功能,但有的时候可能会贪图方便而导致服务端的代码功能过于错综复杂,于是自己思考一下更深层次的一些指导规则,不断地在项目中实践,久而久之便能养成一个良好的编程习惯了。于是下面的一些规则提供给大家参考:
2.2.1、模型之间不要相互依赖
这条规则着重于软件工程中所谓的解耦,以前提倡的是模块与模块之间尽量解耦,可以提高模块的复用程度也能减少模块的复杂度。推而广之,模型之间也是类似。其优点如下:
- 首先相互独立意味着你可以更加容易地测试,不会有过多的复杂关系需要去处理
- 无论你什么时候需要改变你的模型内部结构都不会影响到你其他的代码
- 相互独立让你的模型变得更加轻巧,这对于可维护性是很重要的。因为你可以容易地追踪到代码的执行逻辑。
如果碰到模型之间是有关联的,以mongoose为例子,大家都知道mongoose是有提供population这种功能来通过一个model去获取另外一个model的内容,而这种情况确实是很普遍的。因此我通用的做法一般都是这么来定义的:
假设有这么一个场景:一个项目会包含很多文件,于是一个一对多的映射关系便会出现,那么此时我们定义project这个model就会如下:
var schema = mongoose.Schema({
createdAt : { type: Date, default: Date.now },
name: {type: String, required: true, index: {unique: true}},
})
file的model如下:
var schema = mongoose.Schema({
createdAt : { type: Date, default: Date.now },
name: {type: String, required: true},
project: { type: String },
updateAt: { type: Date, default: Date.now }
})
上面的代码中两个model没有互相引用,唯一知道关系的是在file model中加了一个project的字段,而且file不用关心我的project存储的是啥,只需要是一个Strin即可。这样二者在测试的时候完全可以独立测试了。因此遵照这样的设计规则,你的服务器代码将会变得越来越来模块化。
2.2.2、控制器应该连接模型之间的数据
控制器是存放介于模型与视图之间的所有操作的文件。你的控制器可以处理web请求,可以服务于你的模板渲染并能够与你的模型交互以处理和获取数据。控制器的最核心作用是将不同的模型的数据连接起来。无论何时,当从视图中收到一条新的请求,控制器都应该更新或者获取所有相关的模型。
一个控制器的最佳实践是永远不要直接访问你的数据库,它应该调用数据库提供的抽象驱动来与模型交互。
2.2.3、共用的代码应该放在独立的文件夹
服务器中或多或少肯定有那么些代码在控制器之间或者模型之间会被用到,那么这些共享的代码应该放到一个独立的文件夹(比如demo中的helpers文件夹),这样不仅可以让你实现一处改变到处生效,而且也让你的代码变得更加容易测试因为它是独立的。
2.2.4、Model和Controller各司其职
在Model中不要参与任何和http或者模板渲染相关的操作,在控制器中也不要调用任何数据库API相关的操作。
你应该尝试让你的model独立于外面的世界,它们不需要知道任何有关其他模型的事情,因此也不应该包含别的模型。
2.2.5、模板视图多渲染少操作
好的模板视图是避免在模板里面写任何的处理。应该是在它呈现之前做好所有的处理,在你的控制器中。同时避免添加太多的逻辑,尤其这个逻辑可以移到控制器处理。这样将会大大提高服务器的渲染速度。
2.2.6、测试文件夹目录建议
测试的话每个项目都是需要测试的,所以可以把所有的测试文件都放在tests目录下,然后里面再分成下面几类测试:
- controllers
- helpers
- models
- middlewares
- integration
- ui
其中的集成测试是综合测试自己app的。
3、题外话
这份"最佳实践"只是我个人的一些思考,如若有个别地方存在错误,欢迎童鞋们提出,让大家在交流中互相进步。
另外demo的代码会不断持续更新,因为里面缺少测试的内容以及vue router,尽管页面少的可怜不需要router,但为了展示一个完整的项目,我也是拼了的。。。
公众号关注一波~
网站源码:linxiaowu66 · 豆米的博客
Follow:linxiaowu66 · Github
关于评论和留言
如果对本文 Nodejs服务端代码的最佳实践 的内容有疑问,请在下面的评论系统中留言,谢谢。