经常会有同学来问我前端是否适合向NodeJS方向发展,其实大多数时候我是拒绝的,为什么呢?其实前端接触一下NodeJS这个东西本身我是不排斥的,毕竟是基于JS,能够掌握一门手艺,拓展一下自己的技术视野,然后写一些读写文件,grunt插件之类的工具,也是极好的。我所谓的拒绝,主要是基于很多同学并非如此考虑,很多同学想借NodeJS轻轻松松的把魔爪伸向纯服务端开发,NodeJS本身对于前端来说是极其容易掌握的,照着文档写一个服务器也是极其简单的,很多同学觉得自己一下子跨进了服务端开发的大门,然而,真是如此么?
现在很多技术方面的营销文,荼毒!让新人变得也浮躁起来。
在我看来,如果我面前有一个牛逼的前端和牛逼的PHP工程师,让我选一个来转型NodeJS服务端开发,我会毫不犹豫的选择PHP。这样我需要做的就是让他去看一个周的JS语法,给他花一天讲解下callback(对php,java,ruby来说这个的确很难理解),花一点时间熟悉下nodejs的模块和写法。然后下面就很顺畅了。
而对于一个前端,好喽,麻烦喽,以为服务端开发只是简单的堆积业务么?服务端逻辑那么紧密,如何hold住?服务端跟其他系统之间的耦合,如何hold住?整体业务的把握,请求量太大天天挂,IO瓶颈了如何搞定,缓存该怎么用,应用架构如何设计,分层如何设计,数据库如何优化,代码如何调试,如何分析内存泄露,单元测试写没写过,HTTP原理了解多少,Nginx配置服务器部署知道几何,性能监控跟踪呢?
扯远了。
其实我本来只是想说一下服务端开发思维在NodeJS开发方面也是非常重要的,而今天要分享的就是其中重要的一部分:MVC。
在写NodeJS之前,我写过一阵Java,基于spring框架,所以思维中对MVC模式也算有个比较简单的理解,所以在构建NodeJS框架的过程中,多少带入了一点Spring的思维。
基本要素:
三要素啦,不用质疑,再复述一遍。
Controller ,负责业务逻辑的控制,是中枢,连接数据和视图之间的关系。不过controller一般不会直接操作数据,这中间需要再加一层,这个后面说。所以一个干净的controller里,应该调用各方数据,最后组装成一个可用的数据,最后把数据传给视图。
Model,从概念上来说,Model就是一个数据层,不过在我们的NodeJS项目中,Model是通过一个ORM抽象成对象的,变成了实体定义,这个也是后面具体讲。
View,视图层,一般通过模板引擎把数据渲染成动态的页面,我们公司统一使用Jade,Jade的结构严格清晰,速度也够快,功能够丰富,无它。
这个名字是从Spring里面学过来的,感觉Service这个词本身就很能代表这个分层所担当的角色,那就是服务层。
什么是服务呢?它凌驾于数据层之上,对数据操作进行抽象,通过服务类型抽象成不同的服务,提供给不同的业务调用。例如在我们公司最基础的几个服务:车辆数据服务,用户数据服务,推送服务,搜索服务。这些服务大部分不是单纯的操作某个数据表之类的,而是在顶层的封装,因为车辆相关的业务可能有很多数据表,例如车辆基础信息,车辆审核信息,各种状态变更记录等一系列数据表,而且会横跨在mysql,mongodb,以及redis缓存之间的各种逻辑。我们把种种数据操作通过service层完全隐藏起来,把数据操作变成了一个个针对车辆的“操作”。这就是service最后提供出来的功能:“操作”。
所以说,其实service层是整个架构中最重的一层,大部分业务逻辑都集中于这一层。
每个请求进来,进入controller的时候,controller就会调用各个service来拼装服务数据,最后把数据返回给前端。到此,其实service层是做什么的,为什么有其存在的必要就很清楚了。
这里注意一点,一个清晰的MVC里,controller里不应该有任何直接对数据库的操作,所有操作都调用service层来操作数据,这样controller才能称为一个“干净的controller”。
咦,奇怪的一层,Route跟controller有啥区别?怎么说呢,很难分清。controller是每个请求进来之后处理的逻辑,而route则定义了请求应该进入那个controller。
这里把Route单独拿出来说,只是为了展示下我们是如何管理Route的。那就是我们自己修改过的一个小库:rainbowy。
这个玩意的作用是:用文件目录结构自动生成route。恩就是这么简单,然后我们给他集成了一些小特色,例如在route中直接定义接口文档,然后最后汇总生成一个api文档汇总页面,供前端自己模拟请求接口。
具体文档和代码见: https://github.com/xinyu198736/rainbow
我们不是用包的方式引入的,而是直接内置到项目lib里,所以这里不能保证及时更新,其实原理很简单。
不要小看这种route管理方式,当route变得成百上千的时候,文件夹嵌套的方式就变得非常实用了,可以快速定位到某个业务代码中。
ORM是一种将数据库操作抽象成语言原生对象操作的方法工具。在NodeJS里有不少orm库,不过已经稳健成熟的不多,例如我们使用的sequelize,已经发展很久了,基础功能稳定了,但是很多功能还在beta,偶尔还会出现一些蛋疼的bug,例如他的promise,里面出现错误,会导致两次回调之类的。
不过既然用了那也没话说了,ORM除了性能上有一定影响之外,好处还是大于坏处的,首先是数据操作很方便,几句话搞定,然后维护更方便,增加字段之类的工作,基本就是分分钟搞定。
一个木有ORM的MVC结构是不完美的。为了让我们的结构更完美,我们很蛋疼的在ORM上面又封装了一层。名字叫做SuperModel。
SuperModel是一个基于ORM的顶层封装,作用有二:
A. 统计mysql和mongodb的操作接口,完全一致化,甚至你根本不知道你在用mysql还是mongo的说。
B. 把ORM封装成统一的链式操作,跟mongoose的操作有点类似。
var userModel = new SuperModel(“user”);
这句话就会生成一个SuperModel实例,user是什么呢?是一个json格式的数据结构定义,一般放在项目里的model目录下
一个典型的操作:
Example.findAll().where({id:{lt:3}}).offset(10).limit(10) .order({id:"desc"}).fields(['id','title','content']) .done(function(error,datas){ })
因为我们的项目是移植过来的,新版的业务才使用了ORM,所以只能看到很小一部分数据结构定义。
这个跟MVC没什么关系了,不过一般都会有些工具类单独抽取出来,例如JSON请求统一返回类,字符串处理,日期处理类,编码处理类,推送短信类,加密解密等。这些工具跟数据库无关,纯工具性质,所以抽取出来作为Util存在。
这也是一个奇怪的层,是由rainbow引入的概念,将其作为controller的前置层,大概就是一个前置中间件的东西。
功能方面,主要做些 用户登录前置验证等。所谓的filter,字面意思也是如此的,在进入纯净的controller之前过滤一下。
具体用法见 上面 rainbow的文档。
其实说是要总结MVC基础要素一样的文章,最后目的只是为了分享下我们项目的应用结构。也算是我们的一点点最佳实践,希望对初学者能够有一定的启发,对资深开发能够有另外一种思路,也欢迎大家一起交流。
下面还会有其他一系列文章,事先预告下:
存储,缓存,云服务 方面的一些最佳实践和经验。
NodeJS服务性能监测,调优,调试,监控报警,应急处理方面的话题。
NodeJS线上多机部署,测试环境部署和使用,方面的话题。
欢迎大家持续关注我们的专栏。
另外,我们的NodeJS服务端开发团队也在招聘小伙伴一起加入工作学习研究。关于我们公司,这里就不多介绍啦,C轮刚过的二手车解决方案互联网企业,坐标杭州,300人团队,大概100名开发产品设计,我的团队现在大概16人,分为前端和NodeJS两部分,都缺人,各种求才。简历直接发我邮箱即可:sunxinyu@souche.com