Graphql實戰系列(上)

背景介紹

graphql愈來愈流行,一直想把個人凝膠項目除了支持restful api外,也能同時支持graphql。因爲該項目的特色是結合關係數據庫的優勢,儘可能少寫重複或雷同的代碼。對於rest api,在作完數據庫設計後,百分之六十到八十的接口就已經完成了,但還須要配置上api文檔。而基於數據庫表自動實現graphql,感受仍是有難度的,但若能作好,連文檔也就同時提供了。node

不久前又看到了一句讓我深覺得然的話:No program is perfect, even the most talented engineers will write a bug or two (or three). By far the best design pattern available is simply writing less code. That’s the opportunity we have today, to accomplish our goals by doing less. git

so, ready go...github

基本需求與約定

  • 根據數據庫表自動生成schema
  • 充分利用已經有的支持restful api的底層接口
  • 能自動實現一對多的表關係
  • 能方便的增長特殊業務,只須要象rest同樣,只需在指定目錄,增長業務模塊便可
  • 測試表有兩個,book & author
  • book表字段有:id, title, author_id
  • author表字段有: id, name
  • 數據表必須有字段id,類型爲整數(自增)或8位字符串(uuid),做爲主鍵或創建unique索引
  • 表名爲小寫字母,使用名詞單數,如下劃做爲單詞分隔
  • 表關聯自動在相關中嵌入相關對象,Book對象增長Author對象,Author對象增長books列表
  • 每一個表會默認生成兩個query,一個是以id爲參數進行單條查詢,另外一個是列表查詢;命名規則;單條查詢與表名相同,列表查詢爲表名+s,若表名自己以s結尾,則變s爲z

橋接庫比較與選擇

我須要在koa2上接入graphql,通過查閱資料,最後聚焦在下面兩個庫上:數據庫

  • kao-graphql
  • apollo-server-koa

kao-graphql實現

開始是考慮簡單爲上,試着用kao-graphql,做爲中間件能夠方便的接入,我指定了/gql路由,能夠測試效果,代碼以下:api

import * as Router from 'koa-router'
import BaseDao from '../db/baseDao'
import { GraphQLString, GraphQLObjectType, GraphQLSchema, GraphQLList, GraphQLInt } from 'graphql'
const graphqlHTTP = require('koa-graphql')
let router = new Router()

export default (() => {
    let authorType = new GraphQLObjectType({
        name: 'Author',
        fields: {
            id: { type: GraphQLInt},
            name: { type: GraphQLString}
        }
    })
    let bookType = new GraphQLObjectType({
        name: 'Book',
        fields: {
            id: { type: GraphQLInt},
            title: { type: GraphQLString},
            author: { 
                type: authorType,
                resolve: async (book, args) => {
                    let rs = await new BaseDao('author').retrieve({id: book.author_id})
                    return rs.data[0]
                }
            }
        }
    })
    let queryType = new GraphQLObjectType({
        name: 'Query',
        fields: {
            books: {
                type: new GraphQLList(bookType),
                args: {
                    id: { type: GraphQLString },
                    search: { type: GraphQLString },
                    title: { type: GraphQLString },
                },
                resolve: async function (_, args) {
                    let rs = await new BaseDao('book').retrieve(args)
                    return rs.data
                }
            },
            authors: {
                type: new GraphQLList(authorType),
                args: {
                    id: { type: GraphQLString },
                    search: { type: GraphQLString },
                    name: { type: GraphQLString },
                },
                resolve: async function (_, args) {
                    let rs = await new BaseDao('author').retrieve(args)
                    return rs.data
                }
            }
        }
    })

    let schema = new GraphQLSchema({ query: queryType })
    return router.all('/gql', graphqlHTTP({
        schema: schema,
        graphiql: true
    }))
})()
這種方式有個問題,前面的變量對象中要引入後面定義的變量對象會出問題,所以投入了apollo-server。但apollo-server 2.0網上資料少,大可能是介紹1.0的,而2.0變更又比較大,所以折騰了一段時間,仍是要多看英文資料。
apollo-server 2.0集成不少東西到裏面,包括cors,bodyParse,graphql-tools 等。

apollo-server 2.0實現靜態schema

經過中間件加載,放到rest路由以前,加入順序及方式請看app.ts,apollo-server-kao接入代碼:promise

//自動生成數據庫表的基礎schema,併合並了手寫的業務模塊
import { getInfoFromSql } from './schema_generate'
const { ApolloServer } = require('apollo-server-koa')

export default async (app) => {    //app是koa實例
  let { typeDefs, resolvers } = await getInfoFromSql()  //數據庫查詢是異步的,因此導出的是promise函數
  if (!G.ApolloServer) {
    G.ApolloServer = new ApolloServer({
      typeDefs,                   //已經不須要graphql-tools,ApolloServer構造函數已經集成其功能
      resolvers,
      context: ({ ctx }) => ({    //傳遞ctx等信息,主要供認證、受權使用
        ...ctx,
        ...app.context
      })
    })
  }
  G.ApolloServer.applyMiddleware({ app })
}

靜態schema試驗,schema_generate.ts瀏覽器

const typeDefs = `
  type Author {
    id: Int!
    name: String
    books: [book]
  }
  type Book {
    id: Int!
    title: String
    author: Author
  }
  # the schema allows the following query:
  type Query {
    books: [Post]
    author(id: Int!): Author
  }
`

const resolvers = {
  Query: {
    books: async function (_, args) {
               let rs = await new BaseDao('book').retrieve(args)
               return rs.data
           },
    author: async function (_, { id }) {
               let rs = await new BaseDao('author').retrieve({id})
               return rs.data[0]
           },
  },
  Author: {
    books: async function (author) {
               let rs = await new BaseDao('book').retrieve({ author_id: author.id })
               return rs.data
           },
   },
   Book: {
    author: async function (book) {
               let rs = await new BaseDao('author').retrieve({ id: book.author_id })
               return rs.data[0]
           },
   },
}

export {
  typeDefs,
  resolvers
}

項目地址

https://github.com/zhoutk/gels

使用方法

git clone https://github.com/zhoutk/gels
cd gels
yarn
tsc -w
nodemon dist/index.js

而後就能夠用瀏覽器打開連接:http://localhost:5000/graphql 查看效果了。restful

小結

這是第一部分,肯定需求,進行了技術選型,實現了接入靜態手寫schema試驗,下篇將實現動態生成與合併特殊業務模型。app

相關文章
相關標籤/搜索