什么是前后端分离?
“前端”,面向于用户(客户)的一端,例如:互联网的APP、网页,传统的桌面程序等,可以理解为“客户端”;
“后端”,为前端提供服务的一端,可以理解为“服务端”;
”分离“,就是将”前端“和”后端“的工作方式进行分开,有以下二个维度的分离:
- 技术分离,前端可以不用了解后端技术,也不关心后端具体用什么技术来实现,只需要会 HTML/CSS/JavaScript 就能入手;而后端只需要关心后端开发技术;
- 职责分离,前端倾向于呈现,着重处理用户体验相关的问题;后端则倾处于业务逻辑、数据处理和持久化等。在设计清晰的情况下,后端只需要以数据为中心对业务处理算法负责,并按约定为前端提供 API 接口;而前端使用这些接口对用户体验负责,从而可以做到专业人做专业事;
为什么要前后端分离
我们可以先看看一体式web架构示意图和前后分离式web架构示意图的区别:
一体式 Web 架构示意图
前后分离式 Web 架构示意图
也许大家都有这样的经历,在一体式 Web 架构中,前端工程师(Web设计师)根据UI,用HTML + CSS,把UI的效果图,转换成网页风格的效果,然后交给后端工程师,用服务端语言(如:php、JSP)把HTML + CSS 与 JavaScript 植入到服务端的框架中。在产品系统研发期间,每次UI或网页风格有变更,前端修改后,把HTML + CSS代码,打包给后端工程师,重新去替换。
然后在 AJAX 技术流行后,一体式 Web 架构做了一次升级,它要解决的问题,是把数据和页面剥离开来。前端采用静态网页相关的技术,HTML + CSS + JavaScript,通过 AJAX 技术调用后端提供的业务接口。前后端协商好接口方式通过 HTTP 提供,接口数据结构使用 XML 实现,但是这个阶段,网页的渲染,还是由服务端语言完成,不过是这次前端工程师需要熟悉服务端语言中解析标签的含义和用法。
随着 Node 服务端语言的盛行,出现了现在流行的前后分离式 Web 架构,它要解决的问题,是把前后端的架构、技术人员职能、产品系统的职责进行合理分离,前端(Web工程师)通过静态网页相关的技术(HTML + CSS + JavaScript)实现系统页面效果,后端通过 API 方式,向前端提供所需的系统数据,交付给前端去渲染。
通过这样的架构改造,前后端分离之后,前端在开发的时候压根不需要了解后端是用的什么技术,只需要后端提供了什么样的接口可以用来做什么事情就好,前端可以根据用户不同时期的体验需求迅速改版,后端对此毫无压力。同理,后端进行的业务逻辑升级,数据持久方案变更,只要不影响到接口,前端可以毫不知情。
前后端分离架构
任何技术方案都不是万能的,前后端分离有利也有弊。
谁来主导
一般在开发过程中,主导者应该是架构师。然而大部分场景中,架构师往往也是开发人员,所以他们的主要技术栈会极大的影响前后端在整个项目中的主次作用。
- 前端会受到产品经理或客户的影响;
- 前端还要与美工对接;
- 前端还要跟后端对接;
前端可以成为项目沟通的中心,因此前端比后端更合适承担主导的角色。
接口设计
接口分后端服务实现和前端调用两个部分。
从前端的角度来看,重点关注的是用户体验;而从后端的角度来看,重点关注的是数据完整、有效、安全。解决这些矛盾的着眼点就是接口设计,接口设计才是难点。
接口设计时,其粒度的大小往往代表了前后端工作量的大小:接口粒度太小,前端要处理的事情就多,尤其是对各种异步处理就可能会感到应接不暇;粒度太大,就会出现高耦合,降低灵活性和扩展性。
API 通信协议可以定义成 REST 或者 RPC,只要前后端商议确定下来就行。更重要的是在输入参数和输出结果上,最好一开始就有相对固定的定义。
API 通信协议规范(仅供参考):
- 协议类型:http(s);
- API 版本号:实际应用中存在同一个 API 有多个版本的情况,且多版本需要同时提供服务,客户端需要明确指定需要调用哪个版本(主版本号)的 API;
- API 数字签名:客户端通过 API 透传给服务端的数据,为了防止被非法篡改,需要对数据进行数字签名,以确保数据的合法性(把参数的key/value,按一定规则进行加密);
- HTTP 请求方法:GET、POST、PUT、PATCH、DELETE、HEAD;
- HTTP 响应状态码:2xx(200,201,204)、4xx(400,401,403,404,405,422,429)、5xx(500,502,503,504)
- Request Headers
Authorization:Bearer ACCESS_TOKEN 认证信息;
Accept:application/json 客户端允许接收的数据类型;
X-Client-Agent:client-id=客户端ID;ip=客户端IP;version=版本号 客户端标识
X-Device-Agent:name=名称(Web|iOS|Android);id=设备ID 设备标识
X-Api-Agent: version=vnd.vpgame.v1;timestamp=毫秒;sign=数字签名;API标识
X-Request-ID:每个请求头中包含一个随机生成的 UUID 作为 Request-Id
- Request Body
- GET、DELETE 请求使用 key/value 方式传值,例如:
curl -i https://api.example.com/users?name=boo
- POST、PUT、PATCH 等方法使用 json 格式(Content-Type: application/json),例如:
curl -X POST -d '{"username":"Dipper","address":"Mystery Shack"}' https://api.example.com/role
- Response Body
响应内容格式为 json 格式,并且以字面编码多字节 Unicode 字符,避免中文被转换成 \uXXXX 的形式,提高可读性并且避免增加数据大小。例如:
HTTP 1.1 200 OK
Content-Type: application/json
{
"data": {
"id":1,
"mobile":"13800138000",
"name":"演示用户",
"email":"demo@example.com"
}
}
注意:对于无需返回内容的请求,应该使用 204 状态码,并且无内容。
- 错误提示
- code: 错误码,使用7位数字表示,前三位用Http Status Code占位,后4位表示业务逻辑错误code。
- message: 错误信息,将错误码根据不同语言包翻译成用户能理解的信息。
HTTP 1.1 401 Unauthorized
Content-Type: application/json
{
"code": 4010001,
"message": "需要身份验证"
}
- 契约(接口)规范
- URI资源名称,应该使用恰当的英文单词或者约定俗成的缩写,保证可读性;
- URI资源名称,必须符合所有字母都小写;
- URI资源名称,单词间用 / 分隔式的命名规范,一定不能包括 -_ 分隔符;
- URI中的名词表示资源集合,使用复数形式;
- URI中不应该出现动词,动词应该使用HTTP方法表示;
- 避免为了追求REST导致层级过深,适当使用参数表示;
- 请求参数,必须符合所有字母都小写,单词间用下划线分隔式的命名规范;
用户认证
- 基于 OAuth 的认证方案
使用的 OAuth 方案用于前后分离是可行的,但在具体实施上却并不是那么容易。尤其是在安全性上,由于前端是完全暴露在外的,与 OAuth 通常实施的环境(后端⇌服务端)相比,要注意的是首次认证不是使用已注册的 AppID 和 AppToken,而是使用用户名和密码。 - 基于 Token/JWT 的认证方案
这个方案却是目前前后端分离最适合的方案。基于 Token 的认证方案,各种讨论由来已久,而 JWT 是相对较为成熟,也得到多数人认可的一种。
前后端分离的测试
前后分离之后,前端的测试将以用户体验测试和集成测试为主,而后端则主要是进行单元测试和 API 接口测试。与一体化的 Web 应用相比,多了一层接口测试,这一层测试可以完全自动化,一旦完成测试开发,就能在很大程度上控制住业务处理和数据错误。这样一来,集成测试的工作量会相对单一也容易得多。
对于用户输入有效性验证这部分工作在项目时间紧迫的情况下甚至都可以完全抛给 Web API 去处理。不管是否前后端分离,Web 开发中都有一个共识:永远不要相信前端!既然后端必须保证数据的安全性和有效性,那么前端省略这一步骤并不会对后端造成什么实质性的威胁,最多只是用户体验差一点。但是,如果前后端都要做数据有效性验证,那一定要严格按照文档来进行,不然很容易出现前后端数据验证不一致的情况。