在小程序里接入 GraphQL

哈德韦

共 4833字,需浏览 10分钟

 ·

2021-10-03 22:34

背景

在前几篇文章里,一直在讲 GraphQL。分别是:



BFF 是 Backend For Frontend 的简称,是为前端服务的后端。要发挥真正的用处,还得通过前端体现。现在就给个实例,讲解如何在小程序里集成万能 BFF。


前端主要有 Web、小程序以及 Native App 等。要在前端集成 GraphQL,一般采用 Apollo Client。为什么本文选择小程序作为例子呢?因为小程序是中国特色,国外没有。对于如何在 Web 端或者 Native App 端集成 GraphQL,只需要按照 https://www.apollographql.com/docs/ 官方文档的指导去做即可。


3eca453c2af322b1b0d0329918668c5e.webp


采用小程序作为例子,不仅弥补了官方文档的空白,而且由于小程序的一些限制,在集成 GraphQL 的过程中,还面临一些额外的挑战。因为更困难,所以更加符合本公众号(哈德韦,即 Hard Way 的音译)的初衷。


最终成果演示

  •       Web 版:https://taro.pa-ca.me/

  •        微信小程序(体验版):

      可以微信扫码打开小程序体验版(由于还没有发布,因此只能以体验的形式),申请体验。我看到申请请求后会第一时间通过,有 100 名的限制哦,如果因为人数超限不能体验,那么请等待我的下一篇文章,如果哈德韦微信小程序正式发布上线,我会再发文通知大家。


项目源代码

  •        https://github.com/Jeff-Tian/weapp



Taro Js


尽管这里只做了微信小程序,但是采用了多端统一开发框架 Taro Js,从而可以打包到不同的平台。


挑战一:在小程序里生成 Apollo Client 实例


基本可以参考官方文档的 React 示例,但是对于小程序,却不能照搬。如果只做 Web 端,可以完全参考官网文档的 React 示例,只需要传入一个 GraphQL 服务的 url 即可。但是对于小程序,只穿 url 会报错,原因是,对于小程序,缺少默认的全局 fetch 函数,因此在生成 GraphQL 客户端实例时,要额外传递自定义的 fetch。当然,对于使用了 Taro js 的项目,只需要将 Taro.request 封装一下就好。


从而最终的 GraphQL 客户端实例的生成是这样的:


import {ApolloClient, HttpLink, InMemoryCache} from "@apollo/client"import Taro from "@tarojs/taro"

export const client = new ApolloClient({

link: new HttpLink({ uri: `https://uniheart.pa-ca.me/proxy?url=${encodeURIComponent('https://jqp5j170i6.execute-api.us-east-1.amazonaws.com/dev/gatsby/graphql')}`,

async fetch(url, options) { const res = await Taro.request({ url: url.toString(), method: 'POST', header: { 'content-type': 'application/json' }, data: options?.body, success: console.log })

return {text: async () => JSON.stringify(res.data)} as any } }), cache: new InMemoryCache()})


挑战二:允许小程序访问 GraphQL 服务


从上面的代码中可以看到这个 URL:https://jqp5j170i6.execute-api.us-east-1.amazonaws.com/dev/gatsby/graphql,这就是上一篇文章《使用万能 BFF,将语雀文章 GraphQL 服务化

》的最终成果,它将语雀博客作为数据源,通过 AWS lambda 暴露成为一个 GraphQL 服务。


而这个长长的 URL

https://jqp5j170i6.execute-api.us-east-1.amazonaws.com/dev/gatsby/graphql 就是利用 serverless 自动创建的 AWS API Gateway 的默认 URL。


直接使用 Taro.request 访问它,在打包成为小程序后,执行到这里就会报错,原因是没有把这个域名配置在白名单里。


这可以尝试通过小程序后台配置 request 合法域名解决:


130880c8d5a29b45c665b58ebbb79088.webp


挑战三:域名备案


一个偷懒的做法,就是将 AWS API Gateway 的默认域名填进去,结果发现通不过域名备案检查!


7ecfce44bc3c83a523fa6ed277d4eddc.webp


挑战四:Serverless 自定义域名


当然没有办法给 AWS 生成的域名去备案,但是可以不要用 AWS API Gateway 自动生成的域名,而是指定一个自定义域名,将这个自定义域名备案。


由于我们的 lambda 使用了 serverless 框架自动化,要使用自定义域名,可以简单地通过增加一个 serverless 插件:serverless-domain-manager 来帮助我们自动关联这个自定义域名。利用这个插件,可以自动生成 AWS Route 53,以及关联相应的 Gateway。


然而,真要这样做,需要去 AWS 上购买域名,或者将自己的域名过户到 AWS 的控制台。这……


总之看起来要使用自定义域名,不那么友好,可能还需要产生额外的费用,那这个就没意思了。


挑战五:转发 GraphQL 请求


出于节省成本的考虑,以及尽可能最大化复用已有服务,决定使用转发 GraqphQL 请求的方式。这里介绍下前情提要,我早些年备案了一个域名:pa-ca.me,并且在这上面部署了一个服务:https://uniheart.pa-ca.me ,该项目源代码在这里:https://github.com/Jeff-Tian/alpha


听说有的大神,可以盲写代码直接上线一次过,没有 BUG。这真令人羡慕,不过我今天也感受了一次一把过,即给已有项目 https://github.com/Jeff-Tian/alpha 添加了一个转发 GraqphQL 请求的新功能,一次提交,自动发布后就可以使用了,效率实在令自己满意:https://github.com/Jeff-Tian/alpha/commit/e58dcf0e7f80643b192561e795bbb3cf050993fd。至今没有发现 BUG,是真的很神吗?其实不是,只是有一个好习惯而已:


测试先行


这个已有项目是基于 eggjs 的,eggjs 其实还是有些坑的,即在发送请求时,有一个 contentType 选项,对于发送 POST 请求(GraphQL 查询本质上是一个 HTTP Post 请求),我相信多数开发都会自然设置 contentType = "application/json",但是在 eggjs 生态里,这样设置是没有效果的,会导致 GraphQL 服务收不到请求 body。这一次新功能的添加,之所以一次部署就能过,其实是因为在改代码前,先写好了自动化测试。即先写好了一个期待的转发功能的正确表现,然后运行,让测试失败。然后写实现代码,再次运行测试,直到测试通过。这其中并没有想象中的那么顺利,需要尝试各种请求选项的设置,知道找到一个(或者几个)能够工作的设置组合。自动化测试的好处是让我的尝试可以很快得到验证。


测试用例




const graphql = async () => { const res = await app.httpRequest() .post(`/proxy?url=${encodeURIComponent('https://jqp5j170i6.execute-api.us-east-1.amazonaws.com/dev/gatsby/graphql')}`) .type('application/json') .send({ query: '{ \n yuque(id: "53296538") {\n id\n title\n description\n \n }\n \n allYuque {\n nodes {\n id\n title\n }\n }\n}', variables: null }) .expect(200);

assert.strictEqual(res.body.errors, undefined); assert(res.body.data.yuque.title === '快速下载 GitHub 上项目下的子目录'); };

it('should proxy graphql', graphql);


最终实现


 subRouter.post('/', controller.proxy.proxy.post);

public async post() { const { ctx } = this;

const { data } = (await ctx.curl(ctx.query.url, { streaming: false, retry: 3, timeout: [ 3000, 30000 ], method: 'POST', type: 'POST', contentType: 'json', data: ctx.request.body, dataType: 'json', }));

ctx.body = data; }


可见这个 contentType 必须设置成 json,才能触发 ctx.curl 以及其底层的 urlib 自动将 header 中的 content-type 设置成 application/json


至此,就解释清楚了挑战一中,为什么生成 apollo 客户端实例时,会有一个 proxy 的 url 出现了。这一切弯弯绕绕都是因为小程序的限制,如果你足够有钱,可以接受在 AWS Route 53 里再申请一个域名,那么这一切可以得到一些简化。


总结


本文给万能 BFF 最终在前端的使用举了一个例子,详解了如何在小程序中接入 GraphQL。因为利用了 TaroJs,所以同步部署了一个 Web 端:https://taro.pa-ca.me。Web 端的集成只需要参考 Apollo 官方文档,因此没有赘述其实现,而是找了一个相对更难的实现方案:微信小程序。这不仅弥补了官方示例的空白,而且体现了中国特色。


浏览 27
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报