第一次接触GraphQL那是一年前的盛夏,涉猎甚广的同事A媚眼一笑的对我说,“Github支持GraphQL接口了~”。自此,我们开始了从跟进了解到迅速全面部署GraphQL的历程。
文章会先简单介绍GraphQL的特点,以及对初创企业的吸引力,然后阐述在微服务应用中部署GraphQL要面对的问题与解决方法。
SOAP->RESTful->GraphQL
GraphQL是什么
GraphQL简称GQL,已知SQL是Database操作语言,那么把GQL看成API操作语言来理解就清真了。简而言之,GraphQL通过定义树状结构(Graph)语句,高效、有力、灵活的调用了API资源。
为什么选择GraphQL:强大的表达能力
聚合请求
以打开一个前后端分离的文章页面为例,假设RESTful需要请求文章内容、作者信息、文章评论等3个资源,那么就会产生3次http请求。换成GraphQL,只需构造好请求语句,一次http请求就可以拿到3个资源的数据。原来的分散式请求变成了一次集中请求,所有GraphQL请求都可以看作是针对同一个端点(endpoint)的POST请求。这样一些原本需要在前端聚合的信息,现在后端一并处理,优化了带宽的使用。
循环/非循环请求
GraphQL的资源定义方式,就是数据类型的定义方式,数据结构内的一个字段类型可能是另一个数据结构。因此在获取一个数据类型的数据同时,我们可以不断下探访问,由此形成了一个树状网络请求。当一个字段返回了某层父级结构,那么就实现了循环请求。关于循环请求,请想象一个例子,后端和前端无需投入额外精力,就轻松实现一个查询全国行政规划的优雅接口,有种无心插柳柳成荫,水到渠成又一村的清新感觉。
为什么选择GraphQL:前端从RESTful过度简单
前端简单改动即可对接GraphQL
团队已经在使用RESTful开发,此时前端要接入GraphQL,无非就是定义一个特殊的POST方法,取回的还是json格式的数据。但需要注意的是,GraphQL的数据返回基于后端定义的请求树,此时json层级可能已有3到4层或者更多,这么一来前端就要考虑是否需要二次处理数据再传递给组件使用,或与后端协商优化接口设计。
为什么选择GraphQL:舒适的前后端对接环境
工程师快速写文档
后端可以轻松的“像写注释一样写文档”,通过官方的GraphiQL工具展现出一个可阅读、可调试、可复现的前后端调试环境。至少在我们团队,这套流程很大程度上解决了后端不喜欢写文档的老大难问题。
官方推荐的GraphiQL调试工具
我们如何用GraphQL
标准的出现能够让不同的工程师开发出较为相似的接口,
简约标准能让不同水平的工程师开发出体验相似的接口。
一开始我们就尝试了Apollo GraphQL,也了解了一番Relay标准,发现大厂的规范落实下来确实水土不服。最终我们团队GraphQL接口设计只要求遵循3大准则:
(query)=>(meta, data)规范。
基于JWT的token规范。
隐式声明query/mutation。
1. (query)=>(meta, data)规范
(query)=>(meta, data)
我们约定FromQuery参数为可选且变量名为query,limit默认值为10且不超过100,所有声明了FormQuery作为参数的节点必须返回meta和data格式的数据。如此一来,团队一次性解决了数组返回及翻页搜索问题,极大提高了对接的工作效率。
2.基于JWT的token规范
基于JWT的token规范
基于JWT的鉴权机制已经非常成熟,我们要求后端开发的GraphQL接口除了能够处理标准的JWT基于Header传递方式外,还要支持变量明为token的可选参数,且非空值会覆盖前者已接收的token。如此一来,团队一次性解决了鉴权问题,也为后来在GraphiQL调试带来了极大便利。
3.隐式声明query/mutation
query/mutation是GraphQL规定的:所有查询接口都是query的子节点,所有改变服务器资源的请求都应该属于mutation的子节点。开发中后端会经常告诉前端新加了那些接口,比如“mutation->me->writeSomething”,或“query->me->readSomething”。我们通过节点名称的命名来到达区分query/mutation的目的,比如“meEdit->writeSomething”,或“me->readSomething”,这不仅方便了前后端沟通,还另一些后端语言在编写GraphQL接口的时候可以复用定义和组织代码,如Go语言的github.com/graph-gophers/graphql-go框架。我们希望简单有效的开发方式总是受欢迎并经得起考验的。
微服务与GraphQL
整体式架构与微服务架构
微服务的基本思想在于围绕着业务领域组件来创建应用,这些应用就可以独立地进行开发、管理和加速。
单个微服务单元可能部署了分布式,有多个等效的GraphQL端点。
微服务的应用提供了多个作用不同GraphQL端点。
去中心化的全分布式架构 VS 中心化的GraphQL端点
业务划分后,各个微服务单元实现数据库隔离。通过部署多实例来提供高可用RPC接口,其他微服务通过thrift框架同步调用所提供的方法。因为微服务间通过RPC同步通信,所以和GraphQL还没有发生冲突。
每个微服务单元除了提供RPC接口外,还提供了业务内的GraphQL端点。但每个端点都是等效的,可以通过Nginx等网关进行负载均衡,因此这些GraphQL端点还是可以中心化的。
不同的微服务单元提供了作用不同的GraphQL端点,这就与GraphQL“一个入口端点”的设计理念冲突了。如何整合不同的微服务GraphQL端点,成为了解决GraphQL在微服务中应用的关键。
GraphQL整合方案1:前缀
schema前缀整合
在合并Schema时,通过添加前缀能够避免不同服务出现重复字段造成冲突的可能,这种方式的实现成本比较低。读者可以搜索GraphQL at massive scale: GraphQL as the glue in a microservice architecture。
GraphQL整合方案2:粘合
schema粘合整合
它需要我们在合并 Schema 的地方手动对不同 Schema 之间的公共资源以及冲突类型进行处理,还要定义一些用于解析公共类型的Resolver;除此之外,目前 GraphQL 的 Schema Stitching 功能对于除 JavaScript 之外的语言并没有官方的支持。
GraphQL整合方案3:组合
schema组合整合
GraphQL作为一种中心化的接口提供方式,通过 RPC 调用其他服务的接口并进行合并和整合。本文的参考文章https://draveness.me/graphql-microservice用的就是这种方案。但该方案需要工程师在API网关处手动拼接各个服务的接口以外提供 GraphQL服务,开发流程繁琐,也显提高了后期的维护难度。
我们的GraphQL整合方案:简单路由
我们目前采用的方案类似上文提到的方案1前缀整合,只是更为简单粗暴:全站只有一个GraphQL端点,前端在http请求的头部需要定义X-Server字段来声明要访问的微服务模块名称,接着Nginx网关便会将请求路由至该模块。
这样虽然放弃了GraphQL“统一入口端点”的追求,但有效的利用了微服务架构带来的各种优势:基于模块的开发与调试,分布式去中心化的高可用性,允许熟练度不一的工程师协同开发同一项目。确确实实提高了我们团队的开发效率,用敏捷开发拥抱复杂业务模型。
增加service可选参数,访问不同模块
简单路由后的跨资源请求
上文提到我们用简单路由整合了多GraphQL资源,但问题也显而易见:在前端如何实现一次http请求获取多个微服务资源?
面对这类问题,我们提出从3个步骤去解决:
能否解耦业务逻辑,使该请求只涉及一个服务资源;如不行再下一步
在涉及的多个服务中选择一个主要服务,通过调用其他服务的RPC完善该GraphQL接口以满足需求;如不行再下一步
与产品经理协商调整业务逻辑。这并不是笑话,当年微博的“最新的5000个粉丝列表”需求,就是技术与产品人员的沟通协商后的执行结果。
小结
本文简单介绍了GraphQL的核心思想,着重笔墨讲了它对初创团队的吸引力,之所以使用GraphQL是因为尝试后发现确实提升了团队的开发效率。我们后端是通过改造框架,使之前RESTful业务也可以用GraphQL表达后,才引导前端在新项目中开始全部使用GraphQL的,所以也不是贸然行动。
基于RPC形式的微服务架构在引入GraphQL前团队就已经在使用了,很快我们就果断放弃了GraphQL“一个端点”的思想而采用Nginx简单路由。以及放弃引入Relay标准,延续之前在RESTful时期就使用良好的data-meta格式。
GraphQL只是表现形式,全分布式和去中心化的微服务才是我们探索的目标。以前我们在增强API表达能力和自动化文档上也做过不少努力,但远不及GraphQL来得彻底和简洁,所以会有种如获至宝的感觉。总而言之,我们不是为了GraphQL而GraphQL,而是它真的太适合我们团队协作开发了!
思考
协议转换的问题,使微服务的服务途径多样化。目前我们用Go语言写的微服务已经可以复用相同的业务代码,同时提供RESTful和GraphQL两套接口表达。以后是否需要提供新的格式?比如json-rpc。
细粒度的API组合,有些资源组合请求还无法优雅的完成。这可能需要继续不断完善和积累RPC功能,甚至RPC First的设计理念。
建立API网关,配合前端探讨响应式编程。当前端实现响应式编程后,后端就要开始考虑将微服务间的通信由同步的RPC,改为异步的消息队列模式,以期完全解耦模块。
维护团队技术栈始终在低维度水平,时刻准备着探索和接纳新技术、新思想。“简单可信赖,极致出真知”是我们的理念。
参考文章:https://draveness.me/graphql-microservice
如果您喜欢我们的文章,欢迎关注公众号并星标哦。
我们会经常推送一些原创、干货、有趣的文章给您。
领取专属 10元无门槛券
私享最新 技术干货