GraphQL

GraphQL

官方描述:
GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。

优点

  1. GraphQL可以让我们通过请求控制返回的字段,以此来减少restful api的设计理念带来的请求多次的问题。
    比如我们要获取指定id的文章相关信息,包括标题、作者、发布时间以及前两条评论;同时加载当前用户信息。
// 两趟查询,难以拓展
GET /user/111
GET /article/1001?comment=2
// 一趟查询,易于扩展
{
  article (id: 1001){
    title,
    author,
    time,
    comments (first: 2)
      nickname,
      time,
      content
    }
  },
  user (id: 111){
    nickname,
    photo,
    sign
  }
}
  1. 可拓展性
    前端自由选择返回
  2. 不需要额外代码处理冗余字段
  3. 提供 schema
    schema 可以在运行时被获取到, 类似thrift的idl文件,比较清楚,简单的方法甚至不需要文档。
  4. 调试比较方便

缺点

  1. 缓存麻烦
    https://graphql.cn/learn/caching/
    Relay 和 apollo-client 是两个graphql的前端模块,可以帮你在前端缓存数据。
  2. GraphQL 在前端如何与视图层、状态管理方案结合,目前也只有 React/Relay 这个一个官方方案。
    换句话说,如果你不是已经在用 Node + React 这个技术栈,引入 GraphQL 成本略高,风险也不小,这就很大程度上限制了受众。
    而且 FB 官方就只有一个 Node.js 的 reference implementation,其他语言都是社区做的。
  3. 每一个 field 都对数据库直接跑一个 query,会产生大量冗余 query,虽然网络层面的请求数被优化了,但数据库查询可能会成为性能瓶颈。
    FB 本身没有这个问题,因为他们内部数据库这一层也是抽象掉的,写 GraphQL 接口的人不需要顾虑 query 优化的问题。
    如何解决?
    DataLoader, DataLoader 的主要功能是 batching & caching,帮你合并请求。
  4. 需要服务端的全力配合
  5. GraphQL不存在鉴权方案,需要自行解决

get start

GraphQL为express和koa提供了插件,可以方便的搭建GraphQL服务器。
看下面的代码:

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');

// 使用 GraphQL schema language 构建 schema
var schema = buildSchema(`
  input MessageInput {
    content: String
    author: String
  }

  type Message {
    id: ID!
    content: String
    author: String
  }

  type Query {
    getMessage(id: ID!): Message
  }

  type Mutation {
    createMessage(input: MessageInput): Message
    updateMessage(id: ID!, input: MessageInput): Message
  }
`);

// 如果 Message 拥有复杂字段,我们把它们放在这个对象里面。
class Message {
  constructor(id, {content, author}) {
    this.id = id;
    this.content = content;
    this.author = author;
  }
}

// 映射 username 到 content
var fakeDatabase = {};

var root = {
  getMessage: function ({id}) {
    if (!fakeDatabase[id]) {
      throw new Error('no message exists with id ' + id);
    }
    return new Message(id, fakeDatabase[id]);
  },
  createMessage: function ({input}) {
    // Create a random id for our "database".
    var id = require('crypto').randomBytes(10).toString('hex');

    fakeDatabase[id] = input;
    return new Message(id, input);
  },
  updateMessage: function ({id, input}) {
    if (!fakeDatabase[id]) {
      throw new Error('no message exists with id ' + id);
    }
    // This replaces all old data, but some apps might want partial update.
    fakeDatabase[id] = input;
    return new Message(id, input);
  },
};

var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4000, () => {
  console.log('Running a GraphQL API server at localhost:4000/graphql');
});

我们通过GraphQL提供的语法,建立一个schema,类似typescript的interface。
有多种基本类型:ID, String, Int, []。
Query是查询操作,Mutation是增删改操作。
所以这里,我们提供了一个getMessage的方法,返回Message类型的信息。
Mutation类型这里我们提供了createMessage,和updateMessage两个方法。

定义完schema,还需要定义方法的处理:
定义root对象进行函数处理。

执行node index.js。然后访问localhost:4000/graphql就可以看到相应的调试页面。(前提是graphiql:true)。

这个调试页面还是很方便的。
打开调试页面,先create一个message:

然后查询这个message:

type 为query的时候,可以不写query。

前端请求

var dice = 1;
var sides = 6;

var query = `query RollDice($dice: Int!, $sides: Int) {
  rollDice(numDice: $dice, numSides: $sides)
}`;

fetch('/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  },
  body: JSON.stringify({
    query,
    variables: { dice, sides },
  })
})
  .then(r => r.json())
  .then(data => console.log('data returned:', data));

这是官网给的试例。fetch请求,把query传过去就可以啦,如果请求的是个函数,需要传参variables。如上dice和sides两个参数

连接数据库

这里给个连接mysql的schema和rootValue


var { buildSchema } = require('graphql');

const mysqlCon = require('./mysql');

// 使用 GraphQL schema language 构造一个 schema
let count = 0;
var schema = buildSchema(`
    type UserInfo {
        id: ID!
        name: String
        uid: Int
        age: Int
        sex: String
        createdTime: String
        updatedTime: String
        description: String
    }
    type Query {
        getUsers: [UserInfo]
        getUserById(id: ID!): UserInfo
    }
    type Mutation { 
        invokeCount: Int
    }
`);

// root 为每个端点入口 API 提供一个解析器
var root = {
    async getUsers() {
        count += 1;
        let users = await mysqlCon.pifySelect('select * from Tab_User_Info');
        console.log(users);
        return users;
    },
    async getUserById({id}) {
        let users = await mysqlCon.pifySelect(`select * from Tab_User_Info where id=${id}`)
        return users[0];
    },
    invokeCount() {
        return count;
    }
};

module.exports = {
    root, 
    schema,
}

mysql.js

const mysql = require('mysql');

const connection = mysql.createConnection({
    host: '127.0.0.1',
    port: 3306,
    user: 'root',
    password: '123456',
    database: 'Test_User'
});

connection.connect(function(err) {
    if (err) {
        console.error('error: ' + err.stack);
        return;
    }
});

Object.defineProperty(connection, 'pifySelect', {
    value: function(sql) {
        return new Promise((resolve, _)=>{
            connection.query(sql, function (error, results) {
                if (error) console.log('mysql select err:', error);
                resolve(results);
            }); 
        })
    }
})

module.exports = connection

用docker搞个mysql环境:

docker pull mysql:5.6
docker run -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.6

docker exec -it mysql bash进入bash然后初始化一下mysql,插几个数据用来操作。
用koa-graphql或者express-graphql启一个服务,就可以直接访问啦。

使用方

facebook, twitter,github,我们公司(toutiao)部分业务在用。

posted @ 2019-01-21 18:50  chenby  阅读(1943)  评论(2编辑  收藏  举报