小话API技术选型

引言

前段时间,部门领导层决定全面放弃现行的某F开头的后端渲染框架,团队里开始了热火朝天REST API Best Practice。这个变化对于我们底层开发来说确实是一个极大的鼓舞。可以预见,短时间内开发效率能数倍提升。

这期我将列举几个常见的API设计方案,对比一下它们的优缺点,以及在某些场景中该如何精益求精。

REST

REST(Representational state transfer)是最大路货的设计方案。它是一种强制性的client-server设计模型,服务端负责提供修改资源的接口,客户端负责主动调取这些接口。REST基于原生HTTP实现,要求所有通讯必须是无状态和可缓存的。

它有这么几个基本原则:

  • 操作来自同一个URL

  • 使用HTTP verbs(GET、POST、DELETE、PUT、PATCH),headers和body

  • 可自我描述的错误——是用HTTP约定俗成的状态码

  • Web服务器能够通过浏览器访问

REST的最大优点就是通用,综合性碾压所有其他方案:可以用作web前后端交互,也可以用于服务间调用;开源工具数不胜数,利于浏览器调试,易于横向扩展,状态码无需造轮子等等。当然要挑刺的话,也可以列举这些缺陷:

  • 一般只依赖几个动作(GET、POST、DELETE、PUT、PATCH),复杂操作比较吃力

  • 在移动通讯中很难平衡api数量和资源负载

  • 向后兼容一般通过提供多版本管理,会产生大量的冗余

  • API文档很难管理;一般用Swagger做协议,不过无专人维护,久之也会成为累赘

此外——可能REST拥趸并不喜欢听——它的协议约定似乎太松散了,语义规则就有很多版本;不出意外,最后落地的接口中90%是不能严格遵守任何一套REST语义规则的。这种事很常见也应该释然,现实开发中效率永远是第一位的,至于格式是否规范,前后端也没那么关心。

gRPC

REST只能称为一种设计风格,自由度很高;但RPC(Remote procedure call)从字面来看,已经是一种协议了,相对来说限制更多。客户端与服务端需要紧密捆绑,当然性能也更强。gRPC是RPC框架里的佼佼者:使用protobuf解码、跨语言、支持全平台、Google爸爸。嗯,一切都很美好。

gRPC的工作流程如下所示:

  1. 服务间协定protobuf服务和消息类型

  2. 编译.proto生成语言对应的桩代码

  3. 客户端、服务端调用各自生成的桩代码

workflow

在一些脚本语言里(如NodeJs),甚至可以动态加载.proto文件。

// helloworld.proto
syntax = "proto3";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

上面在helloworld.proto里定义了Greeter服务和相关message的数据结构。NodeJS通过proto-loader动态加载这些protobuf。

// client.js
const grpc = require('grpc');
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync('./helloworld.proto');

const hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;

(function () {
  const client = new hello_proto.Greeter('localhost:50051',
                                       grpc.credentials.createInsecure());
  client.sayHello({name: 'onion'}, function(err, response) {
    console.log('Greeting:', response.message);
  });
})();

gRPC目前最常用的场景还是是微服务间的API调用。与REST传输JSON相比,gRPC利用protobufs序列化降低了数据包的大小,因此较适合资源、带宽、性能敏感场景(潜台词是:在不敏感领域里,也就没啥优势了)。

我个人其实对gRPC的protocol buffers更感兴趣。在REST领域里,一般我们通过@annotation或是第三方应用生成Swagger文档;但注释其实并不可靠,尤其在遗产代码里,注释往往是混淆视听的一大因素,更别说维护单独部署的第三方应用了(靠自觉?)。而protocol buffers则是一套完全不同于swagger的API管理方式——Protobufs本身就是一套描述性语言。它要求客户端和服务端同时拿到proto结构,然后通过这些.proto文件生成桩代码来调取API,可以说是文档即代码。在大型应用开发中,能保证API有专人维护是极为重要。

不过就事论事,gRPC毕竟不够大路货,有时候我们阐述它的优点时,往往锚定了REST的某些缺陷,因此并不能信誓旦旦地断言gRPC胜过REST。尤其是REST学习曲线平缓,自由度高这种先天的巨大优势,在“大众编程”领域里,gRPC等其他框架还不够资格撼动REST的地位。

Graphql

Graphql源自程序员对JSON操作的冲动

我个人是比较推崇Graphql的设计模式,它基本就是照着REST缺点设计出来的。

  • 单点 v.s. 多点
  • 强类型 v.s. 重复的类型检测
  • 复杂查询 v.s. 多API组合
  • 自定义资源数 v.s. 资源过载
  • 增量升级 v.s. 多版本管理

Graphql一般先定义好如下数据类型User和查询方法me

type User {
  id: ID!
  name: String
}

type Query {
  me: User
}

接着前端自己决定获取的资源。比如,现在只想获取User的name并不需要id,所以我把查询语句写成如下格式:

query {
  me {
    name
  }
}

然后前端向后端单点/graphql发起查询请求,最后获得如下JSON。

{
  "me": {
    "name": "Onion"
  }
}

GraphQL带来的好处是精简请求响应的内容,不会出现冗余字段。前端可以决定后端返回什么数据;后端接口只需要一次性提供完整的资源,不必逐个开发。Graphql后端可以大量精简API接口,当后端有数据变化时也只需通过增量完成升级,前端不需修改任何代码。

此外,GraphQL还有如下几点优势:

  • Mock

    相比Swagger需自定义各种mock数据,Graphql天然的强类型能由引擎自动生成mock数据。

  • 文档

    REST是注释即文档,gRPC是文档即代码,Graphql则是代码即文档。Grapqhl引擎能自动生成代码对应的文档,成熟的工具或插件有Apollo Client Devtoolsgraphiqlvoyager等等。如下是graphiql界面,最右就是引擎自动生成的文档:

    Graphiql
  • 微服务

    在微服务治理中,Graphql可以扮演单点api gateway的角色。由于前端自定义获取资源的特点,BBF(Backend For Frontend)可以成为很好的实践。后端不必再开放多点api(类似于/mobile/api/pc/api)。只要后端提供充足的资源,前端各取所需即可。

    microservices

Graphql还可以在BBF里扮演类似于DDD里value object的角色。如下是某个graphql schema的定义,不同数据源的聚合可以发生自同一个结构体里,调取顺序是兄弟域异步操作,父子域同步操作。相对于REST的代理实现,Graphal提供了一个更友好的dispatch形式。

type User {
  id: ID! # from token
  name: String # from front-end
  car:[Car]  # from User-Service
  house: [House] # from User-Service
}

type Car {
  id: ID!
  brand: String  # get brand from Car-Service after id from User-Service
}

type House {
  id: ID!
  address: String  # get address from House-Service after id from User-Service
}

Webhook

我对webhook几乎没有接触过,这里通过道听途说简单介绍一下。Webhooks可以说是彻头彻尾的反模式,因为其定义是:前端不主动发送请求,完全由后端推送。它解决的是前端轮询的问题,主要用于服务器主动更新客户端资源的场景。举个例子,比如你给朋友发了一条信息,后端就会主动将这条信息推送给这个朋友的应用。

总结

最后再总览一下上面提到的API设计方案(某F开头的框架就不提了)

  • REST是最通用的API技术选型,可以应用于前后端,也可以用作服务间通讯。协议约定较为松散,落地后很难follow语义规则。不适合对性能敏感的场景,但是一般小厂也碰不到这种场景。

  • gRPC是REST很强的竞争者,在跨语言服务通信这块优势巨大,也有grpc-web应用于web客户端。非常优秀的框架,适合对性能要求高或环境苛刻的场景,只是入门难度较高。对于小厂来说,技术水平、管理能力、资源配置都比较薄弱,盲目使用可能会自讨苦吃。

  • Graphql是一种全新的前后端交互方式,目的就是取代REST。但是在遗产代码重构Graphql可能会得不偿失;比较适合新开或是重写项目时尝鲜。

  • Webhooks解决的是特殊场景的问题。对于第三方平台验权、登陆等没有前端界面做中转的场景,或者强安全要求的支付场景等等适合用Webhooks做数据主动推送。

OK,工具列举完了,但是实际开发中还需要因势而为,毕竟一切开发工具最终还是服务于软件工程管理。比如,从后端渲染跨度到REST意味着业务权重更多放在了前端,人力分配和后端设计应该及时跟进;Graphql的话,前端要拼出query,事实上分担了后端很多工作,这时候后端使用NodeJS可能更容易抹平语言壁垒;RPC通过强协议解耦各个模块,这时候更细致的分工兴许比所谓的全栈更高效。当然,这些都是需要管理层精细调整的,我也只能纸上谈兵。先谈到这里,希望对大家有些许帮助吧。

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

推荐阅读更多精彩内容