使用 Elixir 和 Phoenix 进行 GraphQL 的简单介绍

2025-06-07

使用 Elixir 和 Phoenix 进行 GraphQL 的简单介绍

获取可运行的 GraphQL 应用程序!

最新版本

  • Elixir:v1.6.5
  • 十六进制:v0.17.7
  • 凤凰:v1.3.2
  • Ecto:v2.2.10(phoenix_ecto版本为v3.2)
  • 苦艾酒:v1.4
  • 苦艾酒插头:v1.4

本教程基于Mac OS X 10.11.6系统。

假设

  1. 你至少了解 Elixir 语法和概念
  2. 您已经拥有一个可供使用的开发数据库
  3. 您已安装所有必备软件。必备软件包括:

关于本指南

这是你的朋友@Timber为你带来的客座文章。如果你有兴趣为我们撰稿,请在Twitter上联系我们。

在本指南中,你会看到很多有用的东西,无论是描述、代码还是 shell 命令!每当你看到这样的代码:

$ do the thing
Enter fullscreen mode Exit fullscreen mode

这些块表示正在运行的 shell 命令(具体来说,就是任何$前面带有 的命令)。任何额外的行都将从命令运行中输出。有时,它们...会用来表示为了简洁起见而截取的长文本块。

IEx(Elixir 的交互式 REPL)的命令将表示如下:

iex(1)> "do the thing"
Enter fullscreen mode Exit fullscreen mode

任何额外的行都会从该操作输出。不用担心括号内的数字。它们只是指示你的 IEx shell 当前处于哪个命令编号,因此它们可能不匹配。

代码将以代码块的形式表示,如下所示:

name = "brandon"
name
|> String.upcase()
|> String.reverse()
Enter fullscreen mode Exit fullscreen mode

充分利用本教程的最佳方法是跟着我们一起构建与我们相同的项目(或类似项目)。代码将在 Github 上发布,每个步骤都带有检查点标签,方便您在每次检查时对照最终成品仔细检查代码!

继续应用程序

我们首先要讨论的是这个项目究竟要解决什么问题。在思考用什么构建之前,最好先想想你要构建什么;这样可以避免一些“先有鸡还是先有蛋”的问题:你正在使用一项很棒的技术,而你试图用它构建的项目却完全不兼容。

我们正在构建的项目是一个用于存储/检索事件日志的应用程序。您可以用它来执行诸如跟踪您正在发出的请求、跟踪日志中的审计事件,或者……任何需要存储任意事件(包括类型、消息和负载)的操作。在开始进一步讨论设计之前,我们将首先实际创建项目并完成所有设置。

接下来,我们的技术选择:Elixir 和 Phoenix 非常适合构建极高性能、低维护的系统,而 GraphQL 能够以多种方式获取大量数据,这使得 Elixir 成为一种特别好的共生选择!

我们的项目将被命名为“Greenfy”,是“Green Faerie”的缩写(是的,这是一个愚蠢的苦艾酒参考),但听起来更像初创企业。

创建新项目

事情是这样的:

    $ mix phx.new greenfy

    Fetch and install dependencies? [Yn] y
    ...
    We are all set! Go into your application by running:

    $ cd greenfy

    Then configure your database in config/dev.exs and run:

    $ mix ecto.create

    Start your Phoenix app with:

    $ mix phx.server

    You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server
Enter fullscreen mode Exit fullscreen mode

运行我们的测试,验证绿色。

设置我们的应用程序

接下来,我们要按照 Phoenix 在创建新项目时给我们的说明进行操作,因此我们需要执行以下操作:

$ cd greenfy
$ mix ecto.create
The database for Greenfy.Repo has been created
Enter fullscreen mode Exit fullscreen mode

您应该已经使用可以根据需要创建和修改数据库的用户帐户设置了数据库,因此当您运行该mix ecto.create命令时,Ecto 将帮助我们创建开发数据库,​​以便稍后运行迁移!

加入苦艾酒

我们知道我们将要构建一个 GraphQL 项目,所以我们会在流程的早期阶段添加 Absinthe。我们需要添加absinthe和两个库absinthe_plugabsinthe_plug因为我们使用 Phoenix 来构建我们的应用程序,所以需要添加这两个库)。

打开mix.exs并将以下行添加到defp deps do功能块:

    {:absinthe, "~> 1.4"},
    {:absinthe_plug, "~> 1.4"}
Enter fullscreen mode Exit fullscreen mode

确保在元组列表最后一行末尾添加逗号,否则会报语法错误!在函数代码块的顶部application,你需要在 extra_applications 列表中添加一个原子,以支持 Absinthe Plug。:absinthe_plug最终的函数体应该如下所示:

  def application do
    [
      mod: {Greenfy.Application, []},
      extra_applications: [:logger, :runtime_tools, :absinthe_plug]
    ]
  end
Enter fullscreen mode Exit fullscreen mode

然后运行:

    $ mix do deps.get, compile
    ...
    Generated greenfy app
Enter fullscreen mode Exit fullscreen mode

你可能想知道为什么我们要同时添加absintheabsinthe_plug。这样做的原因是,首先,absinthe依赖项处理了我们需要处理的大多数场景,以便顺利集成 GraphQL 和 Elixir,

创建我们的第一个 GraphQL 查询

所以,在阅读本教程之前,您可能已经对 GraphQL 是什么、它能做什么以及它为开发者和最终用户提供了什么有了一定的了解。在我的职业生涯中,我反复遇到的一个陷阱是,大多数 REST API 随着时间的推移变得越来越不 RESTful。您需要在应用程序中构建越来越多的异常,而某些端点变得过于臃肿,难以修改或……

嗯,很多事情都会发生。尤其是当你的移动或客户端应用程序依赖于你的数据时!你为桌面客户端获取的数据可能与为移动客户端获取的数据截然不同,尤其是在网速较慢的情况下。

GraphQL 识别两件事:

  1. 数据的形状将会改变
  2. 您的数据形状需要针对每个客户端而有所不同

基于此,GraphQL 实现了一种更以客户端为中心的从系统获取数据的方法。客户端会告诉服务器它需要什么数据,更重要的是,它需要如何对这些数据进行处理!这使得你可以更快地迭代,构建更好的过滤器和功能,并轻松地在系统中添加对更多数据的支持,而无需构建/版本化一个全新的端点,而且随着时间的推移,你还需要支持它。

另一个很棒的功能是 GraphQL 支持查询模式信息,这意味着您的用户可以做更多的事情来了解数据的形状以及他们可以用它做什么,这对客户端和服务器都非常有帮助!

GraphQL 并非完美无缺。它相当复杂,你可能会遇到很多拥有丰富 REST 经验的人,却不明白 GraphQL 查询的某个部分是如何工作的。你需要培训用户才能有效地使用这个新 API,但最终结果是让客户端以任何他们想要的方式获得他们想要的东西!

了解了这些之后,让我们继续讨论实际的实施情况。

设计数据的形状

我们的应用程序是一个简单的事件日志平台,所以我们只需要一个非常简单的表结构(开始)。

events
___
id
event_type
message
payload
inserted_at
updated_at
Enter fullscreen mode Exit fullscreen mode

id、、inserted_atupdated_at都将作为我们创建的任何 Ecto 表的默认列包含在内,因此我们不必担心如何定义它们。

对于event_typepayloadmessage列,我们将使用它们来跟踪每个事件的类型(不出所料),并且 message 将存储有效负载的精简版本。另外,我们还有一个名为 的列payload,其职责是在必要时存储完整的有效负载。这使得message列保持较小,更易于搜索。

在 Ecto 中运行我们的数据库表

让我们开始使用 Phoenix 的内置生成器来构建此表:

$ mix phx.gen.context Log Events events event_type:string message:string payload:text
Enter fullscreen mode Exit fullscreen mode
  • 创建 lib/greenfy/log/events.ex
  • 创建 priv/repo/migrations/20180614191428_create_events.exs
  • 创建 lib/greenfy/log/log.ex
  • 注入 lib/greenfy/log/log.ex
  • 创建 test/greenfy/log/log_test.exs
  • 注入测试/greenfy/log/log_test.exs

记得通过运行迁移来更新你的存储库:

    $ mix ecto.migrate
Enter fullscreen mode Exit fullscreen mode

接下来,让我们运行迁移命令:

$ mix ecto.migrate
[info] == Running Greenfy.Repo.Migrations.CreateEvents.change/0 forward
[info] create table events
[info] == Migrated in 0.0s
Enter fullscreen mode Exit fullscreen mode

这实际上足以让我们开始构建应用程序的 GraphQL 部分!

在 GraphQL 中构建对我们第一个查询的支持

让我们看一下我们的Log.Events模式的示例查询是什么样的:

{
    events {
        id
        event_type
        message
        payload
        inserted_at
        updated_at
    }
}
Enter fullscreen mode Exit fullscreen mode

为了实现这一点,我们需要定义 GraphQL 的类型和模式,以便了解如何将数据库中的数据以及 Elixir 数据结构中的数据转换为 GraphQL 数据结构。我们首先定义类型。在 下创建一个lib/greenfy_web/schema名为 的新文件data_types.ex(您需要创建schema目录),然后开始定义我们的第一个对象类型:

defmodule Greenfy.Schema.DataTypes do
  use Absinthe.Schema.Notation

  object :event do
    field :id, :id
    field :event_type, :string
    field :message, :string
    field :payload, :string
  end
end
Enter fullscreen mode Exit fullscreen mode

你可能会注意到,这些:inserted_at:updated_at没有被包含在我们的数据类型符号中!原因是 Absinthe 的标准内置类型不太容易表示它们,所以我们scalar稍后需要定义一些更高级的类型!(别担心,我们稍后会详细解释!)

侧边栏:GraphQL 定义

在谈论 GraphQL 时,我们非常自由地使用了很多术语,但并没有做太多解释它们实际上是什么,所以让我们快速地讨论一下到目前为止我们讨论过的一些术语(以及我们稍后会讨论的术语)。

首先,我们有类型。顾名思义,类型就是我们将为 GraphQL 应用程序定义的各种数据类型。上面的对象定义就是一个很好的例子:事件是一种 GraphQL对象类型,目前为止它定义了四个字段(我们接下来会讲到字段)。

字段本质上是一些可查询的信息。对象,就像我们之前定义的数据类型一样,存储着一系列不同的字段。每个字段至少应该定义其名称(或键)和类型。

接下来,我们将使用Schemas。如果说Types定义了哪些数据可以在 GraphQL API 中查询以及数据的形式,那么Schema则定义了我们如何从 GraphQL API 中获取这些数据。

稍后,你会看到一个名为Resolvers的东西。如果Schema告诉客户端如何从我们的应用程序中查询数据,那么Resolver就会告诉服务器如何响应该查询以及如何解释这些 GraphQL 查询。

最后,这是我们稍后会用到的东西,但你之前会听到它被提及:Mutations!Mutations 指的是修改 GraphQL 应用程序中的数据的方式!你只需要定义MutationsPOST即可,而不是像 REST API 中的、PUTPATCH端点那样

回到我们的实现

接下来,我们需要创建我们的Schema。创建lib/greenfy_web/schema.ex并赋予它以下内容:

defmodule Greenfy.Schema do
  use Absinthe.Schema

  import_types Greenfy.Schema.DataTypes

  query do
    @desc "Get a list of events"
    field :events, list_of(:event) do
      resolve fn _parent, _args, _resolution ->
        {:ok, Greenfy.Log.list_events()}
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

我们使用提供的Absinthe.Schema宏来构建 Schema 主体。接下来,我们定义查询主体,它以一个@desc语句开头,该语句设置一个名为 的模块变量desc(不出所料),该变量描述了此查询的一般用途。可以将其视为客户端的一份文档。

接下来,在我们的查询中,我们有一个字段。同样,我们的字段是可查询数据,所以我们说我们的“查询”中有一个名为 的键events。我们还告诉 Absinthe 我们应该events返回一个事件 列表,所以我们使用 函数list_of(:event)(注意这里的 和 是单数形式event,它是一个原子)。这告诉 Absinthe,当有人查询 时,我们应该获取我们之前在数据类型文件中定义的events数据类型列表。Event

最后,我们有一个Resolver,它告诉 Absinthe,当客户端查询事件列表时,此函数的返回语句是应该将哪些数据传回给客户端(当然,需要经过一些转换)。resolve语句接受三个参数:parentargs(查询的参数,也就是我们要查找或过滤的内容)以及resolution,它告诉我们在获取所有数据后该做什么,以及最后如何处理这些数据!稍后我们会在此基础上将解析器移到它们自己的代码中,但现在我们先来简单实现一下。

开始测试之前的最后一步

不过,在深入之前,让我们先修改一下路由器,这样我们就可以开始测试我们的应用程序并进行适当的重构。打开lib/greenfy_web/router.ex,删除以下代码块:

  scope "/" do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
  end
Enter fullscreen mode Exit fullscreen mode

取消注释/apiscope 部分,并从 scope 块中删除对应用程序模块“Greenfy”的引用。然后,我们需要添加 GraphiQL 路由,以便为测试应用程序提供一个环境。scope 语句应如下所示:

  scope "/api" do
    pipe_through :api

    forward "/graphiql", Absinthe.Plug.GraphiQL, schema: Greenfy.Schema
  end
Enter fullscreen mode Exit fullscreen mode

最后,测试一下

在您的终端中,运行以下命令启动您的服务器:

$ iex mix -S phx.server
Enter fullscreen mode Exit fullscreen mode

然后打开浏览器窗口并指向http://localhost:4000/api/graphiql。你应该会看到一个如下所示的窗口:

elixir-graphql-1 (1)

您可以在此屏幕截图中看到,我们的初始查询正在运行,选择主键 eventType(请注意与的区别event_type;GraphQL 需要 JavaScript 风格的 Camel-case,而 Elixir 默认需要 snake_case 风格。Absinthe 会为我们处理这种转换!)我们还没有选择inserted_atupdated_at列;我们稍后还有时间去做这件事!

出发前

在您读完之前,我想告诉您,我们是一家基于云的日志公司,致力于通过无缝地将上下文添加到日志中,从而简化调试。我们已经开发了一款很棒的产品,您可以免费试用!

页脚

概括

现在,我们已经有了基础的 GraphQL API,并且支持非常有限的数据导出操作!我们对 GraphQL、所使用的术语以及它们如何协同工作有了更多的了解,那么接下来我们该怎么做呢?

很简单:我们要让这个应用更健壮!首先,除非我们能把数据导入其中,否则我们无法验证它是否运行良好。此外,测试本身就很困难,那么我们究竟该如何测试一个如此灵活多变的东西呢?

敬请期待!我们还有很多内容要讨论,还有很多内容需要理解,希望我们最终能够成为在 Elixir 中实现 GraphQL API 的专家。

还在继续吗?你可以在Github上的教程仓库中查看你的工作。Tag/Release v0.1.0 是本教程此阶段的完整版本。

文章来源:https://dev.to/timber/a-gentle-introduction-to-graphql-with-elixir-and-phoenix-736
PREV
值得关注的全栈 YouTuber 🏃 — 从初学者到专家,每个人都有自己的起点
NEXT
不要在简历上写技能栏!