最近对前后端分离设计模式的理解总结:
为什么要做前后端分离:
-
有些人会说:因为职责明确,因为不再用模板做视图层 render HTML,后端不用自己写前端就会轻松些... 这些大多说的不错,但还是比较表象,主要的原因其实是 源于需求变化:
在现代web开发中,我们对于前端的需求越来越复杂,我们需要 PC端浏览器、手机端浏览器 、Android APP 和 iPhone APP 、微信小程序等多种需求。
由于我们前端可能涉及设计到的页面种类越来越多,而 vue、angular、react 等前端框架越来越好的支持:“ Code once,run everywhere. ”,所以前端的开发渐渐独立、并更为体系化。
故而曾经那种单凭一套模板渲染页面的方法似乎太局限了。
而一旦采用 前后端分离 的模式,并且采用 restful 设计规范,那么后端就只需要提供数据 API 接口即可,后端只需要长期维护这一套代码就行了。
Django 的 FBV & CBV
FBV:Function base view 基于方法的视图
CBV:Class base view 基于类的视图
在我最开始尝试用 Flask 框架遵循 Restful 规范按路由设计接口时,常常有这样几个疑问和思考:
- 我的需求这么多,难道没一个都要设计一个接口吗?这样方便维护吗?
- 比如一个接口可能有 GET、POST 或是 PUT 等类型的请求,我难道每次都要写一个选择分支:if request.method == ' ... ': 才可以吗?
所以之后我了解到,其实是我没有了解 FBV 与 CBV 的概念。
正所谓:类就是 把数据封装进对象里 ,并赋予对象 行为 的能力。
所以我们完全可以把一个需求的接口封装成为一个类:
from django.views import View
class UserView(View): # 我们假设这是一个关于用户的接口,django/Flask中路由也叫视图。
def get(self, request, *args, **kwargs):
# ... 对应GET请求
def post(self, request, *args, **kwargs):
# ... 对应POST请求
def put(self, request, *args, **kwargs):
# ... 对应PUT请求
def delete(self, request, *args, **kwargs):
# ... 对应DELETE请求
因为继承了 django 的 View 类,所以在默认情况下,会自动根据请求类型映射该类中对应的请求方法。
但是在所有的 python web 框架乃至一些其他语言的框架之中,对 HTTP 请求类型的方法映射都是由一个专门的反射函数来实现的。
# 本身在django的源码 base.py 中:
def dispatch(self, request, *args, **kwargs):
# 是有这样一个基础的反射方法的。
# Override (但在我们继承了 View 的类中,我们可以重载它试验一下)
from django.views import View
class User(View):
def dispatch(self, request, *args, **kwargs):
func = getattr(self, request.method.lower()) #必须要小写化不然映射不到我们刚才写的四个对应方法
ret = func(self, request, *args, **kwargs)
return ret
# ...下面四个方法同上
所以,总结如下:
-
CBV:基于反射实现根据请求方式不同,执行不同的方法。
原理:URL -> view方法 -> dispatch方法(反射执行其他:GET/POST/DELETE/PUT)
另外值得一提的是:自己那个类中的 dispatch 方法中如果不自己去映射而是调用父类(django 的 View)的 dispatch 方法,另外还在前后做一些附加操作,这样的功能跟 “装饰器” 就很相似了。
RESTful API 设计规范
RESTful 规范是一种建议而非硬性要求,但是在前后端分离是大趋势的当下,越来越多的程序员们开始喜欢遵照 RESTful 规范来设计后端接口。
一共有10个项目,那让我们一起来慢慢学习吧!
-
API与用户的通信协议,总是使用HTTPS协议。
- 因为HTTPS比HTTP更加安全,所以在建立、部署大中型网站时企业都会选择使用HTTPS,但是由于HTTPS需要配置专门的证书,而如果需要具备较高的认可度的证书,则需要从证书商处购买,而价格大多不便宜。
-
域名配置要体现自己是个 API:
-
子域名形式:https://api.example.com ( 存在跨域的问题 )
引用他人的 api接口 也得在自己的请求处解决跨域问题
-
-
后端API接口请区分版本
-
一般都是加在 URL 上,其他的一般业内很少用:
-
-
路径 -> 面向资源编程:
何为面向资源编程呢?就是把互联网上的任何东西都视为一种资源,而我们在后端对该实体描述的 API 路由都为名词。
而不是类似于 getUser、addUser 这样的动词!
具体请看下一条 ...
-
methods :HTTP方法
始终记住 RESTful 设计要充分利用 HTTP协议里的 GET、POST、PUT、PATCH 和 DELETE 等 方法标志 来表达对数据的 增删改查。
- GET /collection:返回资源对象的列表(数组)
- GET /collection/resource:返回单个资源对象
- POST /collection:返回新生成的资源对象
- PUT /collection/resource:返回完整的资源对象
- PATCH /collection/resource:返回完整的资源对象
- DELETE /collection/resource:返回一个空文档
-
对资源的条件过滤 -> 用 URL传参 来指定过滤条件
类似于 例如查订单时 http://api.mall.com/orders?sortby=dectime 则是以按降序时间排列订单...
-
一定要使用 状态码:
常见的状态码:
200 系列: 成功及其附属状态信息
- 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
- 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
- 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
- 204 NO CONTENT - [DELETE]:用户删除数据成功。
- ...
300 系列:重定向类
- 例如 301 MOVED PERMANENTLY
- ...
400 系列 :客户端错误
- 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
- 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
- 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
- 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
- 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
- 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
- 422 Unprocessable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
- ...
500 系列: 服务端错误
- 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
- ...
单光用 HTTP状态码 在实际开发中是远远不够的哦!
一般还会加上一个我们自己的 code;
参见Alibaba Alipay支付宝文档:https://docs.open.alipay.com/common/105806 code( 返回码 )
这些自定义的返回码大大增加了我们能够表示的 服务对客户端请求的响应状态的类型。
由于状态码十分有限,所以一般前端大多会被要求处理 自定义的这个code。
-
错误处理
承接上一个小段,当状态码是 4xx 时,应当返回错误信息,error 当做 key:
{ error:"Invalid API key!" }
-
以 API url 来确定返回类型!
- GET /collection:返回资源对象的列表(数组)
- GET /collection/resource:返回单个资源对象
- POST /collection:返回新生成的资源对象
- PUT /collection/resource:返回完整的资源对象
- PATCH /collection/resource:返回完整的资源对象
- DELETE /collection/resource:返回一个空文档
这样干巴巴地说自然很难理解那个 collection 是什么意思:还是以订单举例:
http://api.mall.com/orders/18924442 => orders 是复数(一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。),我的操作目标是 订单集合,而后面的 18924442(一个订单 id 示例)就代表着资源对象的实体 resource!
那么我对这个 api url 的 post 、delete 、put / patch 和 get 就分别对应了该资源的 增删改查 接口。
-
HypermediaAPI:
即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
比如:对于我正准备要实现的 RPZ计协 · 科丁特沃夫咖啡厅论坛 桌帖的评论列表 这件事情:
/coffeeComments?postid=12 第十二个咖啡桌帖的评论列表: [ { id: '100030001', author: '飞翔的海猫', content: '你这个写的有bug呀!' datetime: '2019-01-19 22:10:35', likes: 0 commentsList: 'https://api.sicnurpz.online/coffeeComments?commentid=100030001' } ]
-
前面的都是 评论数据类型该有的基本信息比如 评论者的昵称、评论内容等,但为什么我要在最后加上一个 commentsList,还是个 url 地址字符串呢?
因为这样我就不用再在服务端去拼接 查询该条评论的评论列表 的字符串了! 提高了后端接口处理的效率,在现代服务器存储量充足的情况下,数据存储空间 换 网络响应时间自然是不亏的啦!