美文网首页
小话API技术选型

小话API技术选型

作者: anOnion | 来源:发表于2019-03-14 16:15 被阅读0次

    引言

    前段时间,部门领导层决定全面放弃现行的某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通过强协议解耦各个模块,这时候更细致的分工兴许比所谓的全栈更高效。当然,这些都是需要管理层精细调整的,我也只能纸上谈兵。先谈到这里,希望对大家有些许帮助吧。

    相关文章

      网友评论

          本文标题:小话API技术选型

          本文链接:https://www.haomeiwen.com/subject/mbiqmqtx.html