记录一个从枯燥学习 GraphQL 的过程,到发现项目 Gitter,模仿项目 Github-Trending-API,最后作一个本身的学习项目 Github-Trending-GraphQL。 javascript
一开始我是这样想的,最后我是这样作的,复盘整个学习过程。 html
graphql 是什么? 在以前的项目中咱们主要使用 graphql 来作已有接口数据的合并,这个主要处理已有 rest 相关服务接口的状况下,咱们作了一个中间数据处理层。
最近在思考团队服务项目开发的时候,由于在开发中若是基于 rest 接口来开发的会,会定义不少路由。为了偷懒不去定义路由,因而决定在项目中使用 graphql (其实只是为了装B,我在项目中用了最新的XX技术),中间还有一些其余的思考。java
Graphql 模型有三种类型的操做。node
查询数据(R)。git
# standard query { field } # shorthand { fields }
新增、更新或删除数据(CUD)。github
mutation { do( arguments ) { fields } }
表示能够访问的资源。数据库
# Repository 包含项目的内容 # Implements # Connections # Fields
学不动了,省略....npm
在枯燥的文档学习过程当中,中间看到一个博客是推荐本身的小程序 gitter
,出于习惯抓了一下小程序的请求,发现了趋势排行是经过 github-trending-api.now.sh 获取的数据,接着就找到了这个 API 对应的项目 github-trending-api。
在这以前我也看过几回 GitHub GraphQL API,只是趋于时间与其余因素(懒),一直没有使用落实到实际的项目中。发现官方没有提供 Trending API,github-trending-api 项目新增了 V3 中的Trending API,我是否是能够模仿该项目提供一个 GraphQL API。
带着两个目的开始一个新项目:json
最简单的实现方式就是提供一个 GraphQL server,而后直接请求 github-trending-api.now.sh 。这种用法对于项目已有微服务的团队,能够利用中间服务层来合并数据请求,以及嵌套数据查询等。
GraphQL server 使用的是 Apollo Server,用它来建立一个 Node 服务,定义好 Schema,增长 resolver 解析函数。小程序
在一开始学习的基础只是派上用场,GitHub Trending 主要提供两个方面,一个是 Repository ,另一个是 Developer。
type Repository { author: String contributors: [Contributor] currentPeriodStars: Int description: String forks: Int language: Lang name: String stars: Int url: String }
Repository
中除了基本的 scalar type
还有两个是 contributor 和 language,一个数组数据,一个是对象,继续细分类型下去就获得了
type Contributor { avatar: String url: String username: String } type Lang { name: String color: String }
Developer
分析数据后同样获得一个数据结构
type Developer { avatar: String name: String repository: RepositoryMini username: String url: String }
其中项目仓库是一个对象数据,细分下来能够获得一个
type RepositoryMini { description: String name: String url: String }
定义好了基本数据类型 Repository 和 Developer 之后,须要对外提供一个统一的 Query
,因而获得了一个新的根数据类型
type Query { repositories: [Repository], developers: [Developer] }
实际的查询趋势过程当中咱们还会增长参数,一个参数是 language,一个参数 since,其中 since 只能取 daily、 weekly、 monthly ,但实际也能取其它值,只是默认的仍是 daily。修改后获得了下面的结果
type Query { repositories(language: String, since: String): [Repository], developers(language: String, since: String): [Developer] }
若是要验证 since 只能取三个值中的一直,须要新增一个枚举类型
type Query { repositories(language: String, since: Since): [Repository], developers(language: String, since: Since): [Developer] } enum Since { daily weekly monthly }
上述写法实际过程当中可能还会有这样一个问题,若是要同时查询得到 Repository 和 Developer 的数据,须要按照筛选条件查询的适合,须要重复传递参数,再提高一下这两个类型实际是属于类型 Trending 的。新增一个类型
type Trending { repositories: [Repository] developers: [Developer] }
根查询 Query
也能够修改一下了
type Query { trending(language: String, since: String): Trending }
按照最终咱们定义好的数据结构,咱们能够发起一个这样的 query
{ Trending(language: "javascript", since: "daily") { repositories { name author description language { name color } forks stars contributors { avatar url username } currentPeriodStars url } developers { avatar name repository { url name description } username url } } }
若是把 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("https://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。这中间也会有一些须要注意的问题
有了缓存就可能出现缓存失效的问题,咱们新增一个刷新缓存的方法,能够按照指定键名来更新缓存,也能够不传递参数清理所有缓存。
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": "/", "dest": "/src/server.js" }] }
alias
这里配置上 now.sh 的别名是不会直接生效的,这里只是方便备忘。server.js 是一个须要执行的文件,因而咱们将 version
设置为 2,接下来咱们就能够在配置中添加 builds
了,对于普通 js 可指定文件使用 @now/node
,这里的 server.js 是开启一个 Node 服务,因此须要使用 @now/node-server
。
部署成功之后咱们得到了一个 github-trending-graphql-[hash].now.sh 的项目访问地址,若是要访问到项目的实际功能,还须要点开两次两次得到项目功能地址 github-trending-graphql-[hash].now.sh/src/server.js ,若是要直接使用域名直接访问功能,咱们这里就须要添加上述配置 route
。
每一次部署都会产生一个新的镜像,也会获得一个新的二级域名,若是咱们要分享出去不管是本身部署仍是用户使用体验都不是很好,咱们能够为本身的项目设置一个别名,这里咱们为当前项目设置的别名就是 trending.now.sh 。
每次部署的时候咱们须要作的工做就是 now && now alias
,now alias 须要指定当前部署得到的项目域名,以及须要设置的别名,$(now) 能够得到部署后得到的域名,因而上述命名就修改为 now alias $(now) trending.now.sh
了,添加 package.json 中,每次部署只须要执行一下 npm run now
。
github trending graphql api
online demo