开发规范

Web后端规范

不久将来,整个行业存在的唯一目的就是消费平台上的数据。你的API越容易使用,那么就 会有越多的人去用它。立足现在,展望未来,restful风格的api是我们必然的趋势。

定义

  • 资源: 一个对象的实例
  • 集合:一群同种对象
  • HTTP:跨网络的通信协议
  • 客户端:可以创建HTTP请求的客户端应用程序
  • 第三方开发者:这个开发者不属于你的项目但是有想使用你的数据
  • 服务器:一个HTTP服务器或者应用程序,客户端可以跨网络访问它
  • 端点:这个API在服务器上的URL用于表达一个资源或者一个集合
  • 幂等:无边际效应,多次操作得到相同的结果
  • URL段:在URL里面已斜杠分隔的内容

URL

URI 表示资源,资源一般对应服务器端领域模型中的实体类。

URL规范

  1. 不用大写字符
  2. 只用下划线_连接
  3. 名词表示资源的集合,使用复数

资源集合,单个资源

资源集合

/zooms/  # 所有动物园
/zooms/1/employees/  # id为1的动物园的所有职工

单个资源

/zooms/1/  # id为1的动物园

避免层级过深的url

/在url中表达层级,用于按实体关联关系进行对象导航,一般根据id导航。

过深的导航容易导致url膨胀,不易维护,如 GET /zooms/1/areas/3/animals/4, 尽量使用查询参数代替路径中的实体导航,如GET /animals/?zoom=1&area=3。

Request

动词

五个非常重要的HTTP动词必须知道

  • GET (选择):从服务器上获取一个具体的资源或者一个资源列表,具备幂等性
  • POST (创建): 在服务器上创建一个新的资源, 不具备幂等性
  • PUT (更新):以整体的方式更新服务器上的一个资源, 具备幂等性
  • PATCH (部分更新):只更新服务器上一个资源的部分属性, 具备幂等性
  • DELETE (删除):删除服务器上的一个资源, 具备幂等性

原则上,只允许客户端或者第三方调用者使用这五个HTTP动词进行数据交互,并且在URL段 里面不出现任何其他的动词。有写操作的动词性很强, 不太容易用一个名词来表述,可以 逻辑上换一个方式来处理,比如作废一张订单,可以采用两种方式来实现。一是修改订单的 状态和相关字段(PATCH方法,这种的业务逻辑性不强,不清晰,不推荐),另一种是把作废 的订单看作一种逻辑资源,只需要在这种资源集合中使用POST方法创建一个资源即可。

另外一般情况下,GET请求要考虑缓存(客户端,服务器都要考虑),减少服务器检索数据的 压力。

版本化

API是服务器与客户端之间的一个公共契约。对服务器上的API做了一个更改,并且这些更改 无法向后兼容,那么就打破了这个契约。这时候既要确保应用程序逐步的演变,又要让客户 端继续可用。那么必须在引入新版本API的同时保持旧版本API仍然可用。所以接口必须版本 化

  1. 如果只是简单的增加一个新的特性到API上,如资源上的一个新属性或者增加一个新的端 点,不需要增加API的版本。因为这些并不会造成向后兼容性的问题,只需要修改文档即可。
  2. 申明不支持一个特性并不意味着关闭或者破坏它。而是告诉客户端旧版本的API将在某个 特定的时间被删除,并且建议他们使用新版本的API。
  3. 在URL中包含版本信息 GET /v1/zooms/ GET /v2/zooms/1/animals/

UserAgent

所有请求的Header信息中必须按照格式填写User-Agent信息,格式:

type|os|os_version|product|version|...

说明:

  • type: 客户端类型, [MOBILE, DESKTOP, BROWSER]
  • os: 操作系统, [Android, iOS, Chrome, IE ...]
  • os_version: 操作系统版本号, 包含完整信息,比如:Windows 7 Ultimate Service Pack 1 64bit
  • product: 产品, [KLICEN_APP, ZEUS(宙斯系统),ZEUS_APP(宙斯系统App), KLICEN_EP(凯励程企业版) ,INSTALL(安装工具), VUS(欣悦途), EWS(预警系统)]
  • version: 客户端版本号

手机端必须增加 手机品牌|手机型号|屏幕分辨率 没有的用空表示;必须包含前面五个字段。例如:

"User-Agent": "MOBILE|Android|7.0|KLICEN_APP|2.2.5|Dalvik/2.1.0 (Linux; U; Android 7.0; FRD-AL00 Build/HUAWEIFRD-AL00)|1080*1794|863549034263964"

Response

内容类型

接口只提供两种类型的返回内容

  • 全部接口默认支持json
  • 部分接口支持excel表格/csv,数据导出作为一种返回格式,导出权限可配置 /animals/?zoom=1&format=excel # 导出id为1的动物园的所有动物数据 ### 预期返回的内容
  • GET /collection/?page=1&page_size=15: 返回一系列资源对象,分页
  • GET /collection/resource/: 返回单独的资源对象
  • POST /collection/: 返回新创建的资源对象
  • PUT /collection/resource/: 返回完整的资源对象
  • PATCH /collection/resource/: 返回完整的资源对象
  • DELETE /collection/resource/: 返回一个空文档

使用ISO8601的国际化时间格式

接收和返回时间数据时只使用UTC格式

"create_at": "2012-01-01T12:00:00Z"

枚举型

枚举类型的字段返回其名值对:

{
    "key": 存储值,
    "verbose": 显示值
}

提供标准的时间戳

提供默认的资源创建时间,更新时间 created_at and updated_at, 例如:

{
  ...
  "created_at": "2012-01-01T12:00:00Z",
  "updated_at": "2012-01-01T13:00:00Z",
  ...
}

这些时间戳可能不适用于某些资源,这种情况下可以忽略省去。

包装

response 的 body 统一做如下包装。示例:

{
    "code": 0,
    "msg": "请求成功"(用户友好),
    "data": {
        "id": 1,
        "name": "张三"
    }
}

使用djangorestframework的情况下,可以在配置文件中配置REST_FRAMEWORK中的DEFAULT_RENDERER_CLASSESutils.renders.CustomJsonRender, 它的具体实现如下:

# -*- coding: utf-8 -*-
from rest_framework.renderers import JSONRenderer

class CustomJsonRender(JSONRenderer):
    """ 自定义返回数据 Json格式
    {
        "code": 0,
        "msg": "success",
        "data": { ... }
    }
    """

    def render(self, data, accepted_media_type=None, renderer_context=None):
        if renderer_context:
            response = renderer_context['response']
            code = 0 if int(response.status_code / 100) == 2 else response.status_code
            msg = 'success'
            if isinstance(data, dict):
                msg = data.pop('msg', msg)
                code = data.pop('code', code)
                data = data.pop('data', data)
            if code != 0 and data:
                msg = data.pop('detail', 'failed')
            response.status_code = 200
            res = {
                'code': code,
                'msg': msg,
                'data': data,
            }
            return super().render(res, accepted_media_type, renderer_context)
        else:
            return super().render(data, accepted_media_type, renderer_context)

可能的错误信息的code可根据实际情况扩展, 所以要让客户端能获取到这些code

code码

  • 0 OK - [GET]: 客户端向服务器请求获取数据成功
  • 201 CREATED - [POST/PUT/PATCH]: 客户端向服务器请求改变数据成功
  • 204 NO CONTENT - [DELETE]: 客户端要求服务器删除一个资源,服务器删除成功
  • 400 BAD REQUEST - [POST/PUT/PATCH]: 客户端向服务器提供了不正确的数据,服务器什么也不做
  • 401 UNAUTHORIZED - [*]: 用户未认证
  • 403 FORBIDDEN - [*]: 已认证但权限不够
  • 404 NOT FOUND - [*]: 客户端引用了一个不存在的资源或集合
  • 500 INTERNAL SERVER ERROR - [*]: 服务器发生内部错误,客户端无法得知结果,请求可能已经处理成功

嵌套外键关系

序列化的外键关系建立在一个有嵌套关系的对象之上, 例如:

{
  "name": "成都动物园",
  "manager": {
    "id": 1
  },
  ...
}

而不是这样, 例如:

{
  "name": "成都动物园",
  "owner_id": 1,
  ...
}

这种方式把相关联的资源信息内联在一起,在返回更多信息时不用改变响应资源的结构, 例如:

{
  "name": "成都动物园",
  "owner": {
    "id": 1,
    "name": "张三",
    "email": "[email protected]"
  },
  ...
}

文档

  • 避免使用文档自动化生成器,即便用了,你也要保证自己审阅过并让它具有更好的版式。
  • 不要截断示例中请求与响应的内容,展示完整的东西。并在文档中使用高亮语法。
  • API的验证授权,包含获取及使用验证tokens
  • API稳定性及版本控制, 包含如何选择所需要的版本
  • 正确的响应和错误的响应都应该文档化,并说明在什么情况下会产生这些的错误

如果时间允许,甚至可以创建一个控制台来让开发者可以立即体验一下API的功能。

API分析

API分析就是持续跟踪那些正为人使用的API的版本和端点信息,这样做的好处是:

  1. 决定什么时候不再支持某个版本
  2. 找到那些使用最广泛的API,用其作为指导业务方向或者优化效率的重要的依据
  3. 更多...

认证与安全

认证

采用OAuth2.0,OAuth2.0提供了一个非常好的方法去做这件事。在每一个请求里, 你可以明确知道哪个客户端创建了请求,哪个用户提交了请求,并且提供了一种标准的访问 过期机制或允许用户从客户端注销,所有这些都不需要第三方的客户端知道用户的登陆认证 信息。

安全

这个暂时不是当前的重点,这里只列出几个方向,后期再完善

  • 采用https
  • 访问频次的控制
  • api访问记录的跟踪分析

你可能感兴趣的:(开发规范)