纵观世界历史,生产工具的进步一次次地推动着人类社会的发展。在Web领域,最近几年也出现了一种新的生产工具栈,MEAN(MongoDB,Express,AngularJS,NodeJS),虽然其无法完全取代LAMP(Linux,Apache,MySql,PHP)/LNMP(Linux,Nginx,MySql,PHP),但确实是一种更先进的生产方式。

在MEAN技术栈中,NoSql优等生MongoDB取代了传统的关系型数据库劳模MySql。MongoDB官网上有其与Mysql的功能对比。相比MySql,MongoDB的唯一缺点是没法支持复杂的事务,在现实的互联网应用场景中,我们面对更多的是高并发、非事务性的业务;而对于一些简单事务场景,MongoDB也是可以应对的,请参考关于mongo原子操作的探讨。MongoDB天生是分布式的,支持自动分片(Auto-Sharding)、复制集(Replication Set),使得其可以很好的满足大数据时代背景下,超高并发、自动容错的存储要求。

传统WEB技术(LAMP/LNMP)中的PHP,被NodeJS取代。本质上讲,PHP并不算后端语言,其主要目的是为了从各个数据源获取数据,拼接处理完成后,返回给页面,因此我们也经常将PHP称为“胶水语言”。PHP语言特性决定了其只能串行地从各个数据源获取数据,因此其响应时间为各个数据源的响应时间之和。而NodeJS的异步特性,使得其可以并发的请求多个数据源,其响应时间为各个数据源的响应时间之中最长的。相比PHP,NodeJS更适合作为“胶水语言”

MEAN中的Express,AngularJS分别为后端WEB框架和前端SPA(Single Page Application)开发框架。

现代化的WEB生产方式需要有清晰的架构、高效的调试方法、规范的测试方法、以及工程化的构建方法。下面,我们就从这些方面了解基于MEAN开发的WEB开发方式,本文要求读者有传统LAMP/LNMP开发经验,同时熟悉NodeJS,并了解MongoDB。

1.MEAN基本代码框架

NodeJS是一个强社区驱动的技术方案,除了核心模块外,大部分功能需要第三方模块支持,这就导致一个WEB应用可能有几十个模块,对于初学者,有较高的学习门槛,而一个标准的项目模板是一个比较好的入门方式。《MEAN WEB开发》一书从无到有构建了一个规范的MEAN项目,非常适合作为种子项目,该书的源代码请从github上下载,本文也可以看作该书的读书笔记。让我们先从代码组织架构入手。

(1). 服务端MVC模型

NodeJS实现了CommonJS规范,其天生就支持现代化的模块化开发理念;而基于Express的中间件的架构,我们可以很方便地将常用功能(比如用户权限验证、日志模块)抽象为中间件模块。我们将服务端的代码组织为:

app/
    controllers/
    models/
    views/
    routes/
    config/
    tests/

这里使用ejs作为view层的模板引擎,使用Mongoose作为Model层的ORM(对象关系映射)框架,每一个controller对应的独立express路由文件放在routes文件夹中,config文件夹存放各个中间件(passport、Mongoose)的配置,服务端单元测试脚本在tests文件夹中。

(2). 客户端MVVM模型

客户端的基本框架是AngularJS,其本身也是将模块化理念作为其设计之本。我们将客户端代码组织如下:

public/
    articles/
        aritcle.client.modules.js
        controllers/
        views/
        config/
        services/
        test/
            unit/
            e2e/
    users/
    lib/
    application.js

所有前端代码存储在public中,对应服务端的每一个controller,客户端都有一个相应的模块,每个模块放在单独的文件夹中,比如articles和users文件夹。以articles模块为例,其中包含模块定义文件aritcle.client.modules.js、控制器controllers、视图模板views、路由文件config、以及需要提供给其他模块使用的service,其中test文件夹用来存放单元测试脚本和端到端测试脚本。application.js是前端文件的入口,用于加载各个模块(比如articles和users)。

2.高效的调试

只有高效的调试方式才能适应互联网web产品的快速迭代的需求。下面我们分别介绍基于MEAN开发WEB应用过程中的前端和后端调试方法。

(1). 使用node-inspector调试服务端代码

借助于node-inspector,我们可以像调试浏览器端JS代码一样调试服务端代码。首先安装node-inspector。

npm install -g node-inspector

然后启动node-inspecor,并在启动WEB应用时使用--debug参数

$: node-inspector
Visit http://127.0.0.1:8080/?port=5858 to start debugging.
$: node --debug server

这里的端口是可以修改的。使用chrome打开http://127.0.0.1:8080/?port=5858,我们就可以像调试常规前端JS文件一样调试服务端代码了。

(2). 使用Batarang调试AngularJS

在调试前端JS代码时,我们经常需要对AngularJS内部进行调试,而由于AngularJS的过度封装,对其内部调试往往很棘手。为此,AngularJS团队开发了一款名为Batarang的Chrome插件。安装完Batarang插件后,用chrome打开基于AngularJS框架的页面时,使用开发者工具面板,就会出现了一个新的AngularJS标签页,使用该标签可以结构化的预览AngularJS各个Scope的变量,并可以查看AngularJS的性能。

chrome-angular.png

3.规范的测试

规范化的测试是项目持续健康发展的根基,这里我们主要介绍服务端单元测试。对于服务端,我们采用Mocha测试框架,Mocha提供了简洁的接口,可以方便的实现单元测试:

describle(description,callback); //描述和封装测试集,callback用于封装测试逻辑
it(description,callback); //描述测试指标,callback用于封装测试逻辑
before、after、beforeEach、afterEach等4个钩子函数用来在测试的不同阶段执行

其中describle用于描述一个单元测试,it用于测试具体的逻辑,一个describle测试单元可以包含多个it测试,只有当所有被包含的it测试都通过时,describle描述的测试单元才能通过。此外,Mocah框架还提供了四个钩子函数,用于在测试前中做一些初始化的工作和在测试后做一些清理工作。

Mocha测试框架并没有提供断言库,因此缺少对测试结果进行逻辑判断的功能。通过使用Should.js提供的语义化接口,我们可以方便对验证代码逻辑的结果:

user.should.be.an.Object.and.have.property('name');

Should.js可以非常方便的测试对象,但却无法对HTTP结果进行测试,我们需要SuperTest断言库来测试HTTP请求的结果。SuperTest断言库也提供了语义化的接口:

request(app).get('/user')
    .set('Accept','application/json')
    .expect('Content-type',/json/)
    .expect(200,done);

通过使用Mocha、Should.js和SuperTest组成的工具集,我们可以方便的实现服务端的单元测试,一个单元测试的例子如下:

//创建Article Model的测试集
describe('Article Model Unit Tests:', function() {  
    //每次测试前执行的勾子函数
    beforeEach(function(done) {
        //创建测试过程中所需要的数据   
        done();
    });

    //测试article是否能够存储成功
    describe('Testing the save method', function() {
        //测试正常文章存储逻辑
        it('Should be able to save without problems', function() {
            article.save(function(err) {
                should.not.exist(err);
            });
        });

        //测试异常文章存储逻辑
        it('Should not be able to save an article without a title', function() {
            article.title = '';
            article.save(function(err) {
                should.exist(err);
            });
        });
    });

    // 每次测试后执行的勾子函数
    afterEach(function(done) {
        //清除测试过程中产生的数据
        done();
    });

运行测试用例:

mocha --reporter spec app/tests

正常情况下,该测试的结果是:

 Article Model Unit Tests:
    Testing the save method
      √ Should be able to save without problems
      √ Should not be able to save an article without a title
 2 passing (135ms)

对于前端,主要采用我们熟悉的AngularJS单元测试和e2e测试,请参考这篇博客

4.自动化的项目流程

前端工作的特点决定了其工程问题多于技术问题,因此在开始实际的项目代码开发之前,应该首先做好项目流程的自动化。前端项目构建工具很多,包括Grunt、Gulp、WebPack以及国内的fis等。一个完善的自动化项目流程主要包括:

自动化构建:js、css代码检查,前端js、css合并、md5戳生成、雪碧图生成、上传的CDN等重复性操作
自动化调试:通过添加特定的参数,比如--debug,可以方便的调试应用
自动化测试:每次运行应用运用之前,将单元测试集运行一遍,确保功能正常
自动化运行:以上三个步骤完成后,就可以运行应用了

全面理解React,实现自己的React

通过实现一个简单的React, 来理解React的原理 Continue reading

同构渲染的常见风险

Published on October 01, 2017

React16升级避坑指南

Published on September 10, 2017