在Elixir中考虑GraphQL的基本实现

(这篇文章是“福冈EX x Zaki研究实验室 2017年圣诞日历”的第6天)

昨天是@twinbee先生的“在Elixir的iex中捕捉丢失的PID”的文章。

首先

由于最近对GraphQL的研究和使用机会增多,我想在这里整理一下。

GraphQL 是一种用于API开发的查询语言和运行时。

我认为最好的方式是查看官方网页了解什么是GraphQL,所以我提供链接给你。

简单来说,这是一个Web API。你可以向它请求想要获取的数据,并且它会返回给你(很方便的)。

Elixir的GraphQL

在 Elixir 的 GraphQL 库中,最出名的是 “Absinthe”。它不仅支持 Phoenix,还提供了与 Plug 和 Ecto 相应的库,所以印象中非常易于使用。

实施环境

Elixir : 1.6.5
Phoenix : 1.3.2

魔药:1.6.5
凤凰:1.3.2

实施方案

0. 安装

首先,使用mix进行安装。

defp deps do
  [
    {:absinthe, "~> 1.4"},
    {:absinthe_plug, "~> 1.4"}
  ]
end

在1.4版本中使用最新的版本。因为这次我们还要使用Plug,所以也一起安装absinthe_plug。

1. 路由设置

由于使用了Phoenix,首先在Phoenix端进行路由设置。由于GraphQL只有一个单一的端点,所以路由本身相当简单。

scope "/" do
  pipe_through(:api)

  forward(
    "/",
    Absinthe.Plug,
    schema: MyApp.Schema
  )

  forward(
    "/graphiql",
    Absinthe.Plug.GraphiQL,
    schema: MyApp.Schema,
    interface: :simple,
    context: %{pubsub: MyApp.Endpoint}
  )
end

这只是设置路由的内容。这样就可以指定终端点了。

  forward(
    "/",
    Absinthe.Plug,
    schema: MyApp.Schema
  )

这里是终节点。使用Absinthe.Plug,在schema: MyApp.Schema中指定了这个GraphQL的模式。

  forward(
    "/graphiql",
    Absinthe.Plug.GraphiQL,
    schema: MyApp.Schema,
    interface: :simple,
    context: %{pubsub: MyApp.Endpoint}
  )

顺便提一下,这里指定了GraphiQL的端点。GraphiQL是一个可以连接到GraphQL客户端软件的工具,可以参考模式定义,并将其作为文档查看。

2. schema定义

模式定义

我们来定义Schema。这次我们只会使用Query和Mutation,所以我们会看一下这两个的定义。

defmodule MyApp.Schema do
  use Absinthe.Schema
  import_types(MyApp.Schema.ContentTypes)

  query do
    field :all_todos, list_of(:todo) do
      resolve(&MyApp.Resolver.Content.all/3)
    end
  end

  mutation do
    field :create_todo, :todo do
      arg(:title, non_null(:string))
      arg(:check, non_null(:boolean))
      resolve(&MyApp.Resolver.Content.create_todo/2)
    end
  end

创建用于 schema 的模块。
首先调用 Absinthe.Schema。
使用 import_types(MyApp.Schema.ContentTypes) 来定义在 schema 中使用的 object、input_object、scalar 等(在此之后会详细解释)。
关于 mutation,我们不会详细说明,但会指定参数。
使用 arg 函数确定参数和类型(non_null 表示不可为空)。

query do
  field :all_todos, list_of(:todo) do
    resolve(&MyApp.Resolver.Content.all/3)
  end

此处是实际模式定义部分,成为查询的定义。定义了名为all_todos的模式,并定义了返回值为list_of(:todo)。顺便说一句,list_of会返回一个数组。使用resolve(&MyApp.Resolver.Content.all/3)指定返回GraphQL值的函数。

3. Object的定义

我将定义在GraphQL中使用的对象。

defmodule MyApp.Schema.ContentTypes do
  use Absinthe.Schema.Notation

  object :todo do
    field(:id, :id)
    field(:title, :string)
    field(:check, :boolean)
  end

首先,需要先编写 Absinthe.Schema.Notation 来定义 Object。

object :todo do
  field(:id, :id)
  field(:title, :string)
  field(:check, :boolean)
end

这是关于对象定义的描述。定义了一个todo对象,其中包含id、title和check字段。
我们可以在之前的模式部分中使用它,并将其作为响应的值。
关于这个定义的方法还可以更加复杂,但是仅仅是这样已经足够庞大了,所以这次我们省略掉。

执行 resolve 函数

我們現在立即來看一下resolve指定的函數。

defmodule MyApp.Resolver.Content do
  alias MyApp.Repo
  alias MyApp.Todo

  def all(_args, _info) do
    {:ok, Repo.all(Todo)}
  end

使用 Repo.all(Todo) 方法返回 Todo 的所有列表。

如果请求正常返回,则在返回值的元组头部添加 :ok。

如果失败,则在头部添加 :error。

5. 实际的请求

我现在已经准备好了一系列请求和回应的状态。
那么,我想先在这里发送一个请求来试试看。

スクリーンショット 2018-06-23 23.18.39.png

这次的请求和响应都顺利完成了。顺便提一下,我使用的是GraphQL Playground作为GraphQL的客户端工具。

总结

尽管GraphQL在最近时而成为话题,时而不被提及,但在我试用了之后,我觉得它非常适合。它具有规范本身的验证功能,并能轻松获取结构化数据和保证响应值,这样能让开发者更轻松。但是,选择是使用RESTful构建API还是选择GraphQL取决于系统,所以必须有一定的选择标准。此外,我认为设计也是相当困难的。

明天将会是@takasehideki的「使用Elixir进行物联网#1.3.2:调整处理器核心运行频率并进行比较评估」的活动。

bannerAds