在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. 实际的请求
我现在已经准备好了一系列请求和回应的状态。
那么,我想先在这里发送一个请求来试试看。

这次的请求和响应都顺利完成了。顺便提一下,我使用的是GraphQL Playground作为GraphQL的客户端工具。
总结
尽管GraphQL在最近时而成为话题,时而不被提及,但在我试用了之后,我觉得它非常适合。它具有规范本身的验证功能,并能轻松获取结构化数据和保证响应值,这样能让开发者更轻松。但是,选择是使用RESTful构建API还是选择GraphQL取决于系统,所以必须有一定的选择标准。此外,我认为设计也是相当困难的。
明天将会是@takasehideki的「使用Elixir进行物联网#1.3.2:调整处理器核心运行频率并进行比较评估」的活动。