让我们尝试使用GraphQL进行实现

你好。今天是 OPENLOGI 节日日历的第四天。

我平时负责仓库管理系统(WMS)的开发。
这次和物流完全无关,但是我用GraphQL来实现了一个内部系统,所以我会写一些关于GraphQL的内容。

2015年,Facebook发布了GraphQL已经过去了两年。我记得当时大家都感到震惊,但现在看来,它似乎还没有成为潮流。

最近,在越来越多的服务中开始逐渐使用GraphQL作为API,Github也不例外。虽然GraphQL的概念逐渐被人们所熟知,但是实际的实现方式和操作却只有少数人真正了解。这让人认为GraphQL的门槛比较高。因此,希望能够有个简单易懂的讲解,告诉大家“如何去实现GraphQL”。

关于GraphQL本身,我认为您可以参考《GraphQL入门——一个令人愿意使用GraphQL的优秀文章》。

无论如何,在本文中,我将大致解释具体的实施方法。(本文不涉及GraphQL的好处。)

スクリーンショット 2017-12-03 21.21.19.png

好的。听描述然后提问获取信息的方式。应该怎么做呢?

准备材料

你可以使用任何语言,但我只在Node.js和Python上有实际实现的经验,所以主要以这些语言为例进行解释。

应用服务器

只需要从客户端那里得到一个终端节点就可以了,所以无论什么都可以。如果是用nodejs的话,就用koa吧。

GraphQL服务器 (GraphQL Server)

这个。GraphQL服务器这个说法经常出现,非常令人困惑!最初我以为是指有特殊功能的服务器。(只有我一个人这样想吗?)
实际上,这只是用来处理发送给GraphQL查询的请求的端点。它的作用就是解析GraphQL查询,并将处理逻辑分派到数据获取的实现中。虽然你也可以自己实现,但大多数语言/框架已经有相应的库,可以直接使用。比如Python的graphene,Node.js的apollo-server,还有Facebook的graphql-js。

作为一个实例,将koa × apollo结合起来,可以得到上述的应用服务器和GraphQL服务器。

import koa from 'koa';
import koaRouter from 'koa-router';
import koaBody from 'koa-bodyparser';
import { graphqlKoa, graphiqlKoa } from 'apollo-server-koa';

const app = new koa();
const router = new koaRouter();

router.post('/graphql', graphqlKoa({ schema: myGraphQLSchema }));

app.use(router.routes());
app.listen(3000);

当然。当请求被发送到/graphql终点时,只需将其传递给graphqlKoa库即可。非常简单。
其中的myGraphqlSchema对象是应用程序的实现部分,包括类型定义、数据获取逻辑等等(稍后会详细提到)。

数据库 (databases)

没问题。GraphQL 只是一个用于获取远程数据的查询语言,也就是一个接口。所以,在服务器端当然需要能够访问数据。无论是使用数据库还是API,如果要模拟它们都可以,不是必需的。如果要使用关系型数据库,也可以选择自己喜欢的OR映射工具。总之,这部分没有任何限制。我们将在前面提到的 myGraphqlSchema 对象中实现数据库访问等操作。

描述你的数据

スクリーンショット 2017-12-03 21.21.19.png

這個看起來好像

type Project {
  name: String
  tagline: String
  contributors: [User]
}

这好像是必须要这样写的样子,但其实并没有必要。这完全取决于上述GraphQL服务器的实现。说到底,Facebook的graphql-js中没有要求。

const Project = new GraphQLObjectType({
  name: "Project",
  description: "Project description",
  fields: () => ({
    name: {type: GraphQLString},
    tagline: {type: GraphQLString},
  })
})

就像这样写,并且用Python的graphene库。

import graphene

class Project(graphene.ObjectType):
    name = graphene.String(name=graphene.String())
    tagline = graphene.String(name=graphene.String())

.
.

以class的形式进行定义,apollo-server是这样的。

type Project {
  name: String
  tagline: String
  contributors: [User]
}

写的是这个,忠实。

只要能定义类型,什么都可以。剩下的事情由库来处理。

那么为什么要进行这种定义呢?简单来说,是为了在解析查询或返回响应时进行类型检查。这是GraphQL非常重要的特点。

而且,这种类型不必与数据库表一对一对应。您可以更改名称,甚至可以使用完全不同的模式,还可以添加新的表,或将多个表组合成一个类型来表示。这些都是根据您希望为应用程序创建什么样的接口而进行的思考。

虽然我并不打算在本文中详细解释GraphQL的类型,但是让我们快速地继续下一步。

问你想要的。

スクリーンショット 2017-12-03 21.21.19.png

是的,就是这种查询语言。而且,客户端会将这个字符串发送到服务器。

{
  project(name: "Graphql"): {
    tagline
  }
}

特征性的是有参数存在。这不仅仅适用于根节点,而且可以在任何节点上指定参数。例如,如果想要仅获取特定项目的前5个贡献者,可以这样做。

{
  project(name: "Graphql"): {
    tagline
    contributers(first: 5) {
      name
    }
  }
}

获得可预测的结果 kě de

スクリーンショット 2017-12-03 21.21.19.png

那么,如何返回数据呢?这当然取决于使用的库,为了更容易理解,我们以apollo-server为例介绍实际的实现。这是上述myGraphqlSchema的具体实现,请先大致浏览一下。

const { makeExecutableSchema } = require('graphql-tools');

// 型の定義部分
// projectは引数に必須で数値のidをとり、contributorsは引数に数値のfirstをとる。
const typeDefs = `
  type Query {
    project(id: Int!): Project
  }
  type Project {
    name: String
    tagline: String
    contributors(first: Int): [User]
  }
  type User {
    name: String
    email: String
  }
`;

const resolvers = {
  // Query型から返す値を取得する実装。
  Query: {
    project: (root, args) =>
      fetch(`select id, name, tagline from projects where id = ${args.id}`)
  },
  // Project型から返す値を取得する実装。
  Project: {
    name: project => project.name,
    tagline: project => project.tagline,
    contributors: (project, args) =>
      fetch(`select * from contributors where project_id = ${project.id} limit ${args.first}`)
  },
  // User型から返す値を取得する実装
  User: {
    name: user => user.name,
    email: user => user.email
  }
};

const schema = makeExecutableSchema({ typeDefs, resolvers });

typeDefs 是描述您的数据的部分,与上述的“描述您的数据”相对应。而 resolvers 是编写实际数据获取逻辑的地方。您需要在 typeDefs 中定义自己属性的获取方法,逐一进行定义。

查询(Query)是一个保留词,用于表示查询的根节点类型。当前,Query类型仅定义了project(项目)类型,但如果想要编写其他查询,例如想要获取用户列表!想要获取活跃用户列表!等等,需要修改并定义Query类型。

type Query {
  project(id: Int!): Project
  users: [User]
  activeUsers: [User]
}

同样地,我们也会实现 resolvers。


const resolvers = {
  // Query型から返す値を取得する実装。
  Query: {
    project: (root, args) => fetch(`select id, name, tagline from projects where id = ${args.id}`),
    users: () => fetch('select * from users'),
    activeUsers: () => fetch('select * from users where active = 1'),
  },
.
.

这样一来,
这么做的话,
如果这样做的话,
这样的话

query {
  users {
    name
  }
  activeUsers {
    name
  }
}

写下这个查询。

{
  "users": [
    {
      "name": "harada"
    }, {
      "name": "yamada"
    }, {
      "name": "tanaka"
    }
  ],
  "activeUsers": [
    {
      "name": "harada"
    }, {
      "name": "yamada"
    }
  ]
}

可以获取类似的数据。

“解析器”的定义就像在使用REST实现时的终端点一样,它是用于访问数据库等获取数据的部分。在GraphQL中,对于某个类型,只需实现与该类型关联的可获取数据,因此范围非常有限,易于理解和实现,这是其特点之一。

最后

那么,简单来说,您能想象一下什么是实现 GraphQL 的大致过程吗?

实际上,各种类型的模块化、n + 1 问题、安全性以及服务器负载等都需要考虑的事项非常多。如果下次有机会的话,我会再写一篇关于这个的。

广告
将在 10 秒后关闭
bannerAds