如果把 language 和 since 定义在 variables 中,写法就变成了下面这样
# 以下请求只获取了趋势仓库名称 # query query getTrending($language: String, $since: String) { trending(language: $language, since: $since) { repositories { name } } } # variables { "language": "javascript", "since": "daily" }query 和 variables 会作为 request payload 放置在 body 中,其中把自定义的操作方法 operationName 设置为 getTrending
fetch("http://trending.now.sh", { "credentials": "omit", "headers": { "accept": "*/*", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", "content-type": "application/json" }, "referrer": "http://localhost:4000/", "referrerPolicy": "no-referrer-when-downgrade", "body": "{\"operationName\":\"getTrending\",\"variables\":{\"language\":\"javascript\",\"since\":\"daily\"},\"query\":\"query getTrending($language: String, $since: String) {\\n trending(language: $language, since: $since) {\\n repositories {\\n name\\n }\\n }\\n}\\n\"}", "method": "POST", "mode": "cors" }); 服务端解析请求这里用的是 Apollo server,服务收到请求以后,会解析 body 参数。会按照嵌套依次调用 resolver 处理业务逻辑,首先会进入 trending ,接着同时执行 repository 和 developer。
按照根查询定义好的数据结构,tending 解析器会收到两个参数,language 和 since。repository 和 developer 也要使用这两个参数如何处理呢?
// resolver { Query: { trending(parent, args, context, info) { // args => { language: '', since: '' } // parent 参数是可以接收到上层解析器的结果,我们可以把 trending 中收到的数据传递给子解析器 return { language, since } } }, Trending: { repositories(parent, args, context, info) { // parent => { language: '', since: '' } }, developer(parent, args, context, info) { // parent => { language: '', since: '' } }, } } 解析器中需要做什么?解析器按照前文分析的数据,我们可以直接请求 github-trending-api.now.sh 数据接口拿到数据,这里我们本着学习为目的,GitHub Trending 是通过 SSR 输出的页面,数据只能自己分析网页,抓取html页面以后分析页面结构获得自己需要的数据。
export async function fetchRepository() { // 分析html } export async function fetchDeveloper() { // 分析html } export async function fetchLanguage() { // 分析html }具体的分析 html 过程不做分析,使用了 cheerio,用法类似 JQuery。这中间也会有一些需要注意的问题
请求过程很慢。
每次请求都会再次请求 Github Trending 的页面,然后还要分析页面,这个过程其实是比较费时的。我们如果把请求分析后的数据按照查询条件缓存起来,下一次请求直接就从缓存中拿数据,这样就快很多。(仓库和开发者趋势会隔段时间更新,我们缓存一小时;语言变化小,我们缓存了一天的时间)
语言包缓存。
请求仓库和开发者的适合,检测语言缓存是否存在,不存在先缓存一次,后续再次请求仓库和开发者或者直接请求语言包就会直接命中缓存
有了缓存就可能出现缓存失效的问题,我们新增一个刷新缓存的方法,可以按照指定键名来更新缓存,也可以不传递参数清理全部缓存。
如何清理缓存?GraphQL 根处理方法除了 Query ,还有一个 Mutation。对应到的数据库增删改查上面的话,Query 对应的是 R,Mutation 对应的是 CUD。我们要新增的 refresh 的操作是删除缓存,主要针对仓库和开发者缓存,清理以后我们只关心成功失败与否,所以这里我们可以返回一个布尔值
type Mutation { refresh(key: String, language: String, since: String): Boolean }解析器中也需要添加对应的处理方法
{ Mutation: { refresh(parent, args, context, info) { // do something } } } 回顾一下从一开始的需求分析,我们需要开发一个 Github Trending GraphQL API。我们利用了之前学习的 GraphQL 的基础知识,也熟悉了 GraphQL 的工具 Apollo Server,很方便的开发出了对应的API,后续为了优化请求,我们新增了缓存策略,以及清除缓存策略。
到这里我们的项目 github-trending-graphql 就可以提交到 GitHub 仓库中了,对于一个完美的开源项目还有很多事情要做,但是对于一个 GraphQL 的示例差不多已经可以使用了。
一上来就直接看代码是枯燥的,于是我们还需要部署一个 Demo,这样带着使用来熟悉就更容易让人理解了。如何简单的部署 Demo 又成为了一个问题?
如何部署示例trending.now.sh 的部署看域名应该就能猜到使用的是 now 的无服务部署方式。使用方式文档已经讲述的很清楚了。但这中间也还是需要注意一些细节
对于项目部署,我们需要首先在项目根目录建立一个 now.json
{ "version": 2, "alias": ["trending.now.sh"], "builds": [{ "src": "src/server.js", "use": "@now/node-server" }], "routes": [{ "src": "http://www.likecs.com/", "dest": "/src/server.js" }] }