WebAPI规范设计——违RESTful

本文首先简单介绍了几种API设计风格(RPC、REST、GraphQL),然后根据实现项目经验提出WebAPI规范设计思路,一些地方明显违反了RESTful风格,供大家参考!

一、几种设计风格介绍

1.1 RPC

这是最常见的方式,RPC说的是本地调用远程的方法,面向的是过程,估计超过50%的API是这种分格的。

  • RPC形式的API组织形态是类和方法,或者说领域和行为。
  • 因此API的命名往往是一个动词,比如GetUserInfo,CreateUser
  • 因为URI会非常多而且往往没有一些约定规范,所以需要有详细的文档。
  • 也是因为无拘无束,HTTP方法基本只用GET和POST,设计起来比较简单。

1.2 REST

REST架构风格有四个级别的成熟度:

  • 级别 0:定义一个 URI,所有操作是对此 URI 发出的 POST 请求。
  • 级别 1:为各个资源单独创建 URI。
  • 级别 2:使用 HTTP 方法来定义对资源执行的操作。
  • 级别 3:使用超媒体(HATEOAS)。

级别0其实就是类RPC的风格,级别3是真正的REST,大多数号称REST的API在级别2。REST实现一些要点包括:

  • REST形式的API组织形态是资源和实体,一切围绕资源(级别1的要点)。

  • 会定义一些标准方法(级别2的要点),然后把标准方法映射到实现(比如HTTP Method):

    • GET:获取资源详情或资源列表。对于collection类型的URI(比如/customers)就是获取资源列表,对于item类型的URI(比如/customers/1)就是获取一个资源。
    • POST:创建资源,请求体是新资源的内容。往往POST是用于为集合新增资源。
    • PUT:创建或修改资源,请求体是新资源的内容。往往PUT用于单个资源的新增或修改。实现上必须幂等。
    • PATCH:部分修改资源,请求体是修改的那部分内容。PUT一般要求提交整个资源进行修改,而PATCH用于修改部分内容(比如某个属性)。
    • DELETE:移除资源。和GET一样,对于collection类型的URI(比如/customers)就是删除所有资源,对于item类型的URI(比如/customers/1)就是删除一个资源。
  • 需要考虑资源之间的导航(级别3的要点,比如使用HATEOAS,HATEOAS是Hypertext as the Engine of Application State的缩写)。有了资源导航,客户端甚至可能不需要参阅文档就可以找到更多对自己有用的资源,不过HATEOAS没有固定的标准。

对于一套设计精良的REST API,其实客户端只要知道可用资源清单,往往就可以轻易根据约定俗成的规范以及导航探索出大部分API。比较讽刺的是,有很多网站给前端和客户端的接口是REST的,爬虫开发者可以轻易探索到所有接口,甚至一些内部接口,毕竟猜一下REST的接口比RPC的接口容易的多。

1.3 GraphQL

如果说RPC面向过程,REST面向资源,那么GraphQL就是面向数据查询了。“GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。”

二、风格选择

  • 在下列情况考虑RPC风格的API或说是RPC:
    • 偏向内部的API
    • 没有太多的时间考虑API的设计或没有架构师
    • 提供的API很难进行资源、对象抽象
    • 对性能有高要求
  • 在下列情况考虑REST风格:
    • 偏向外部API
    • 提供的API天生围绕资源、对象、管理展开
    • 不能耦合客户端实现
    • 资源的CRUD是可以对齐的(功能完整的)
  • 在下列情况考虑GraphQL:
    • 客户端对于数据的需求多变
    • 数据具有图的特点
  • 在下列情况考虑服务端驱动:
    • 客户端发版更新困难,需要极端的灵活性控制客户端
    • 仅限私有API

三、违RESTful规范设计

3.1 文档系统

无论是独立的wiki还是整合在网关系统中,文档系统都应该支持全局模糊搜索。文档有3种,全局设计规范、API参考手册、文档系统本身的使用指南。其中,参考手册的每个API说明应包括这些信息:

  • URL
  • method
  • 传参方式(URL query、HTTP header、body)
  • 请求与响应的参数表
    • 参数名
    • 类型(string、array、object、int、float、bool)
    • 是否必填,必填时的默认值
    • 说明:中文名称、功能意义、取值范围。至少要能看出对应需求文档或UI稿的哪个东西
  • 可能的错误码与错误信息
  • 示例

3.2 全局规范

URL格式

大小写和连接符的规范应该全局统一:snake_case或者camelCase。一般snake_case会更像普通的web URL。如果使用RESTful,URL格式可以是https://api.example.com/{service_name}/{version}/{api_name}?{filters}https://www.example.com/api/{service_name}/{version}/{api_name}?{filters}
注:Web URL和API URL的规范是不同的。

header

如果不用RESTful,最好也不要把参数放到header里,尽量在HTTP协议框架内实现业务。在此前提下,如果存在(所有接口都需要的)公共参数,可以放在URL query里;或者最方便地使所有接口的method都是POST然后放body里。
JSON格式的Content-Typeapplication/json,如果进行了加密和BASE64转码,则应该是text/plain。如有必要,可进一步指定编码:application/json;charset=utf-8

业务参数 以下讨论的是放在body里的JSON

  • 各个key的大小写和连接符的规范应该全局统一:snake_case或者camelCase。

  • 必填参数应该约定默认值。如不指定,可认为各类型的默认值是0、0.0、{}、[]、""、false,决不能是null或undefined

  • 非必填参数在无值时有3种风格,应该选定一种全局统一:

    • 不存在这个key
    • value是null
    • value是undefined
  • 非必填参数不允许是这个参数类型的默认值(0、0.0、{}、[]、""、false

  • 值是数字就用数字类型,不要用字符串。

  • 布尔类型用JSON的语义true/false来表示,不要用1/0

  • 值为枚举时,尽量用字符串表示而不是用数字。牺牲一点点性能但可以大大增强代码可读性。这能大幅降幅维护成本,减少出错。

    // 直接把枚举value写成字符串更便于开发维护
    type: "duck",
    type: "chicken"
    // “用数字表示然后在文档中详细说明”可读性差,通常不会有人把文档复制成代码注释
    type: 1,  // duck
    type: 2   // chicken
    
  • 尽量不要为前端做格式化。例如时间,应返回“1970年1月1日0点至今的秒数”或者“按ISO8601进行格式化的UTC(世界标准时间)时间”,而不是直接返回“2018年11月11日 23:22:33”。让大前端自己做格式化能更好应对UI变化以及兼容特殊要求。比如客户端从中文切换成英文,界面上的“5月6日”需要变成“May 6th”;这种场景下如果是后端传的“5月6日”,那无论是再次请求英文还是客户端自行解释时间后做转换都是糟糕的设计。

  • 保证向后兼容的前提下及时删除废弃的参数或接口。可以先对参数或接口标记Deprecated,在前端发布后或客户端强制升级后删除。

  • 同一意义的字段名,在不同接口返回的命名统一。不要这边叫“page_count”,那边叫“page_size”或"page_amount"。

响应体

较常见的JSON结构是这样的:

{
  "status": 0,
  "message": "",
  "data": {}
}
  • status:0表示正常/成功,非0代表错误码。
  • message:表示错误信息。
  • data:业务数据。所有的业务信息都应该放到data对象上。data一定是对象

其中,错误码和错误信息也可以设计一份全局统一的对照表。需要注意的是,这里的status都表示业务情况,跟HTTP的status不要混用。 各级网关都可能以HTTP status表示错误,故它无法明确表示是业务API的问题。简单的例子是,业务API鉴权失败,HTTP也应该返回200 OK而不是返回401。因为接口是正常的,是数据逻辑不正确。

如果不用考虑多语言,message错误信息可以是面向用户的中文语句,由前端/客户端直接toast告知用户。

分页设计

  • 请求参数应包含:
    1. 当前页码。接口文档应注释是从0还是从1开始计数。
    2. 每页条数
    3. (可选)排序
  • 响应参数应包含:
    1. (可选)当前页码
    2. (可选)每页条数
    3. (可选)排序
    4. 数据数组
    5. 总页数或总条数,或是否还有下一页。总之不要让使用者再请求一次才知道没有更多数据了。

组合请求

为了减少请求数,后端可提供组合请求接口,并且可组合任意接口。假如有3个接口(示例的响应体经简化仅保留data):

/a:请求{"a": "a"}会响应{"data": {"d": "d"}}
/b:请求{"b": "b"}会响应{"data": {"e": "e"}}
/c:请求{"c": "c"}会响应{"data": {"f": "f"}}

增加一个接口/combo可以一次性获取这3个接口的数据:

// 请求
{
  "api": {
    "/a": {"a": "a"},
    "/b": {"b": "b"},
    "/c": {"c": "c"}
  }
}
// 响应
{
  "data": {
    "/a": {"data": {"d": "d"}},
    "/b": {"data": {"e": "e"}},
    "/c": {"data": {"f": "f"}}
  }
}

防攻击

  • 加密机制。可对body做加密,使用AES、DES、3DES、RSA、DSA、ECC等算法。一般会对密文做BASE64转码再在网络上传输。
  • 校验机制,防篡改。对body做签名,可使用MD5、SHA1、HMAC等算法。签名只能放在URL query或HTTP header中。
  • 防重放机制。可使用timestamp、nonce等机制,在一定时间内重复即认为是重放。或者timestamp距今超过一定时间的,认为是非法请求。

可参考资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容