Web前后端分离开发思路

04-12

一、问题的提出

开发一个Web应用的时候我们一般都会简单地分为前端工程师和后端工程师(注:在一些比较复杂的系统中,前端可以细分为外观和逻辑,后端可以分为CGI和Server)。前端工程师负责浏览器端用户交互界面和逻辑等,后端负责数据的处理和存储等。前后端的关系可以浅显地概括为:后端提供数据,前端负责显示数据。

在这种前后端的分工下,会经常有一些疑惑:既然前端数据是由后端提供,那么后端数据接口还没有完成,前端是否就无法进行编码?怎么样才能做到前后端独立开发?

考虑这么一个场景:Alex和Bob是一对好基友,他们有个可以颠覆世界的idea,准备把它实现出来,但是他们不需要程序员,因为他们就是程序员。说干就干,两个就干上了。Alex写前端,Bob写后端。

Alex和Bob都经过良好的训练,按部就班地把产品的主要功能设计,交互原型,视觉设计做好了,然后他们根据产品功能和交互制定了一堆叼炸天的前后端交互的API,这套API就类似于一套前后端开发的“协议”,Alex和Bob开发的时候都需要遵守。例如其中一个发表评论的功能:

// API: Create New Comment v2
// Ajax, JSON, RESTful
url: /comments
type: POST
request: {content: "comment content.", userId: 123456}
response: 
    - status: 200
        data: {result: "SUCCESS", msg: "The comment has been created."}
    - status: 404
        data: {result: "failed", msg: "User is not found."}

Alex的前端需要向/comments这个url以POST的方式发送类似于{content: "comment content.", userId: 123456}这样的JSON请求数据;Bob的服务端识别后以后,操作成功则返回200状态和上面的JSON的数据,不同的操作状态有不同的响应数据(为了简单起见只列出了两种,200和404)。

API制定完以后,Alex和Bob就开始编码了。Alex把评论都外观和交互写完了,但是写到发表评论功能就纳闷了:Alex现在需要发Ajax过去,但是只能把Ajax代码写好,因为是本地服务器,却无法获取到数据:

// jQuery Ajax
$.ajax({ // 这个ajax直接报错,因为这个是Alex的前端服务器,请求无法获取数据;
    url: "/comments",
    type: "POST",
    data: {content: content, userId: userId},
    success: funtion(data) {
        // 这里不会被执行
    }
})

相比起来Bob就没有这个烦恼,因为后端是基于测试驱动开发,且后端可以轻易地模拟前端发送请求,可以对前端没有依赖地进行开发和测试。

Alex把这种情况和Bob说了,Bob就说,要不我们把代码弄到你本地前后端连接一下,这不就可以测试了吗。Alex觉得Bob简直是天才。

他们把前后端代码代码都部署到Alex的本地服务器以后,经过一系列的测试,调试,终于把这个API连接成功了。但是他们发现这个方法简直不科学:难道每写一个API都要把前后端链接测试一遍吗?而且,Alex的如果需要测试某个API,而Bob的这个API还没写好,Alex这个功能模块的进度就“阻塞”了。

后面还有168个API需要写,不能这么做。Alex和Bob就开始思考这个问题的解决方案。

二、解决思路

在这个场景下,前后端是有比较强的数据依赖的关系,后端依赖前端的请求,前端依赖后端的响应。而后端可以轻松模拟前端请求(基本上能写后端的语言都可以直接发送HTTP请求),前端没有一个比较明显的方案来可以做到模拟响应,所以这里的需要解决的点就是:如何给前端模拟的响应数据。

先来一句非常形而上的话:如果两个对象具有强耦合的关系,我们一般只要引入第三个对象就可以打破这种强耦合的关系。

+---------+              +---------+
|         |              |         |
| Object1 |  <-------->  | Object2 |
|         |              |         |
+---------+              +---------+

               Before               


+---------+              +---------+
|         |              |         |
| Object1 |  <-- ✕ --->  | Object2 |
|         |              |         |
+---+-----+              +-----+---+
    |                          |    
    |                          |    
    |                          |    
    |                          |    
    |                          |    
    |       +---------+        |    
    |       |         |        |    
    +-----> | Object3 | <------+    
            |         |             
            +---------+             

               After                

在我们上述开发的过程中,前后端的耦合性太强了,我们需要借助额外的东西来打破它们的耦合性。所以,在前后端接口定下来以后,我们根据接口构建另外一个Server,这个Server会一一响应前端的请求,并且根据接口返回数据。当然这些数据都是假数据。我们把这个Server叫做Mock Server,而Bob真正在开发的Server叫做Real Server。

+-------------------+                     +-------------------+
|                   | +-------- ✕ ------> |                   |
|     Browser       |                     |    Real Server    |
|                   | <---+               |                   |
+--------------+----+     |               +-------------------+
               |          |                                    
               |          |                                    
               |          |                                    
               |          |                                    
           Request      Response                               
               |          |                                    
               |          |                                    
               |          |                                    
               |     +----+--------------+                     
               +---> |                   |                     
                     |    Mock Server    |                     
                     |                   |                     
                     +-------------------+                     

Mock Server是根据API实现的,但是是没有数据逻辑的,只是非常简单地返回数据。例如上面Alex和Bob的发表评论的接口在Mock Server上是这样的:

// Mock Server
// Create New Comment API
route.post("/comments", function(req, res) {
    res.send(200, {result: "Success"});
})

Alex在开发的时候向Mock Server发出请求,而不是向Bob的服务器发出请求:

// Sending Request to Mock Server
// jQuery Ajax
$.ajax({ 
    url: config.HOST + "/comments",
    type: "POST",
    data: {content: content, userId: userId},
    success: funtion(data) {
        // OK
    }
})

注意上面的config.HOST,我们把服务器配置放在一个全局共用的模块当中:

// Front-end Configuration Module
var config = modules.exports;
config.HOST = "http://192.169.10.20" // Mock Server IP

那么上面我们其实是向IP为http://192.169.10.20的Mock Server发出请求http://192.169.10.20/comments发出POST的请求。

当Alex和Bob都代码写好了以后,需要连接调试了,Alex只要简单地改一下配置文件即可把所有的请求都转向Bob所开发的Real Server:

// Front-end Configuration Module
var config = module.exports;
// config.HOST = "http://192.169.10.20" // Mock Server IP
config.HOST = "http://changing-world-app.com" // Real Server Domain

然后Alex和Bob就可以愉快地分离独立开发,而最后只需要联合调试就可以了。

总结一下基本上前后端分离开发包括下面几个步骤:

  1. 根据功能制定前后端接口(API)。
  2. 根据接口构建Mock Server工程及其部署。
  3. 前后端独立开发,前端向Mock Server发送请求,获取模拟的数据进行开发和测试。
  4. 前后端都完成后,前后端连接调试(前端修改配置向Real Server而不是Mock Server发送请求)。

当然要注意,如果接口修改了,Mock Server要同步修改。