如何在 Postgres 上使用 GraphQL 订阅构建实时应用程序
已发表: 2022-03-10在本文中,我们将了解构建实时应用程序所涉及的挑战,以及新兴工具如何通过易于推理的优雅解决方案来解决这些挑战。 为此,我们将使用 Postgres、GraphQL、React 构建一个实时投票应用程序(例如具有实时总体统计数据的 Twitter 投票)!
主要关注点是设置后端(部署即用型工具、模式建模)以及前端与 GraphQL 集成的方面,而不是前端的 UI/UX(一些 ReactJS 知识会有所帮助)。 教程部分将采用按数字绘制的方法,因此我们将克隆一个用于模式建模的 GitHub 存储库,以及 UI 并对其进行调整,而不是从头开始构建整个应用程序。
一切 GraphQL
你知道关于 GraphQL 你需要知道的一切吗? 如果您有疑问,Eric Baer 会为您提供有关其起源、缺点以及如何使用它的基础知识的详细指南。 阅读相关文章 →
在您继续阅读本文之前,我想提一下以下技术(或替代品)的工作知识是有益的:
- 反应JS
通过遵循客户端库文档,可以将其替换为任何前端框架、Android 或 IOS。 - Postgres
您可以使用其他数据库,但使用不同的工具,本文中概述的原则仍然适用。
您还可以非常轻松地将本教程上下文改编为其他实时应用程序。

如底部随附的 GraphQL 有效负载所示,我们需要实现三个主要功能:
- 获取投票问题和选项列表(左上角)。
- 允许用户为给定的投票问题投票(“投票”按钮)。
- 实时获取投票结果并将其显示在条形图中(右上角;我们可以忽略该功能以获取当前在线用户列表,因为它是此用例的精确副本)。
构建实时应用程序的挑战
构建实时应用程序(尤其是作为前端开发人员或最近过渡到成为全栈开发人员的人)是一个难以解决的工程问题。 这通常是当代实时应用程序的工作方式(在我们的示例应用程序的上下文中):
- 前端使用一些信息更新数据库; 用户的投票被发送到后端,即投票/选项和用户信息(
user_id
,option_id
)。 - 第一次更新会触发另一个服务,该服务会聚合投票数据以呈现输出,该输出会实时转发回应用程序(每次有人投票时;如果这样做有效,则只会处理更新后的投票数据并只有那些订阅了这个投票的客户端才会更新):
- 投票数据首先由触发
poll_results
服务的register_vote
服务(假设这里发生一些验证)处理。 - 实时汇总民意调查数据由
poll_results
服务中继到前端以显示整体统计信息。
- 投票数据首先由触发

该模型源自传统的 API 构建方法,因此存在类似问题:
- 任何顺序步骤都可能出错,导致 UX 挂起并影响其他独立操作。
- 需要在 API 层上付出很多努力,因为它是前端应用程序的单点接触点,可以与多个服务进行交互。 它还需要实现基于 websockets 的实时 API——没有通用标准,因此对工具自动化的支持有限。
- 前端应用程序需要添加必要的管道以使用实时 API,并且可能还必须解决通常在实时应用程序中看到的数据一致性问题(在我们选择的示例中不太重要,但对于在现实中排序消息至关重要-时间聊天应用程序)。
- 许多实现求助于在服务器端(Firebase 等)使用额外的非关系数据库来轻松实现实时 API 支持。
让我们看看 GraphQL 和相关工具如何应对这些挑战。
什么是 GraphQL?
GraphQL 是 API 查询语言的规范,也是用于执行查询的服务器端运行时。 该规范由 Facebook 开发,旨在加速应用程序开发并提供标准化的、与数据库无关的数据访问格式。 任何符合规范的 GraphQL 服务器都必须支持以下内容:
- 读取查询
用于从数据源(可以是数据库、REST API 或另一个 GraphQL 模式/服务器中的一个或组合)请求嵌套数据的请求类型。 - 写入的突变
将数据写入/中继到上述数据源的请求类型。 - 实时查询订阅
客户端订阅实时更新的请求类型。
GraphQL 还使用类型化模式。 生态系统有很多工具可以帮助您在开发/编译时识别错误,从而减少运行时错误。
这就是 GraphQL 非常适合实时应用程序的原因:
- 实时查询(订阅)是 GraphQL 规范的隐含部分。 任何 GraphQL 系统都必须具备原生实时 API 功能。
- 实时查询的标准规范整合了社区围绕客户端工具所做的努力,从而以一种非常直观的方式与 GraphQL API 集成。
GraphQL 以及用于数据库事件和无服务器/云功能的开源工具的组合为构建具有异步业务逻辑和易于构建和管理的实时功能的云原生应用程序提供了很好的基础。 这种新范式还带来了出色的用户和开发人员体验。
在本文的其余部分,我将使用开源工具基于此架构图构建应用程序:

构建实时投票/投票应用程序
通过对 GraphQL 的介绍,让我们回到第一节中描述的构建投票应用程序。
选择了三个功能(或突出显示的故事)来演示我们的应用程序将发出的不同 GraphQL 请求类型:
- 询问
获取投票问题及其选项。 - 突变
让用户投票。 - 订阅
显示投票结果的实时仪表板。

先决条件
- Heroku 帐户(使用免费套餐,无需信用卡)
部署一个 GraphQL 后端(见下一点)和一个 Postgres 实例。 - Hasura GraphQL 引擎(免费、开源) Postgres 上的即用型 GraphQL 服务器。
- Apollo 客户端(免费、开源 SDK)
用于轻松地将客户端应用程序与 GraphQL 服务器集成。 - npm (免费的开源包管理器)
运行我们的 React 应用程序。
部署数据库和 GraphQL 后端
我们将在 Heroku 的免费层上分别部署一个 Postgres 和 GraphQL Engine 实例。 我们可以使用一个漂亮的 Heroku 按钮,只需单击一下即可。

注意:您也可以点击此链接或搜索 Heroku(或其他平台)的 Hasura GraphQL 部署文档。

您不需要任何其他配置,只需单击“部署应用程序”按钮即可。 部署完成后,记下应用 URL:
<app-name>.herokuapp.com
例如,在上面的屏幕截图中,它将是:
hge-realtime-app-tutorial.herokuapp.com
到目前为止,我们所做的是部署一个 Postgres 实例(作为 Heroku 术语中的一个附加组件)和一个配置为使用该 Postgres 实例的 GraphQL Engine 实例。 这样做的结果是,我们现在有了一个现成的 GraphQL API,但是,由于我们的数据库中没有任何表或数据,所以这还没有用。 所以,让我们立即解决这个问题。
建模数据库模式
以下架构图为我们的投票应用程序捕获了一个简单的关系数据库架构:

如您所见,该模式是一种利用外键约束的简单规范化模式。 正是这些约束被 GraphQL 引擎解释为 1:1 或 1:many 关系(例如poll:options
是 1:many 关系,因为每个轮询将有多个选项通过外键约束链接poll
表的id
列和option
表中的poll_id
列)。 相关数据可以建模为图形,因此可以为 GraphQL API 提供动力。 这正是 GraphQL 引擎所做的。

基于上述,我们必须创建以下表和约束来对我们的模式进行建模:
-
Poll
用于捕获投票问题的表格。 -
Option
每个投票的选项。 -
Vote
记录用户的投票。 - 以下字段(
table : column
)之间的外键约束:-
option : poll_id → poll : id
-
vote : poll_id → poll : id
-
vote : created_by_user_id → user : id
-
现在我们已经有了架构设计,让我们在 Postgres 数据库中实现它。 要立即启动此架构,我们将执行以下操作:
- 下载 GraphQL 引擎 CLI。
- 克隆这个 repo:
$ git clone clone https://github.com/hasura/graphql-engine $ cd graphql-engine/community/examples/realtime-poll
- 转到
hasura/
并编辑config.yaml
:endpoint: https://<app-name>.herokuapp.com
- 使用 CLI 从项目目录中应用迁移(您刚刚通过克隆下载):
$ hasura migrate apply
这就是后端。 您现在可以打开 GraphQL 引擎控制台并检查所有表是否都存在(该控制台位于https://<app-name>.herokuapp.com/console
)。
注意:您也可以使用控制台通过创建单个表然后使用 UI 添加约束来实现架构。 在 GraphQL 引擎中使用对迁移的内置支持只是一个方便的选项,因为我们的示例 repo 具有用于调出所需表和配置关系/约束的迁移(无论您是否正在建立一个爱好,这也是强烈推荐的)项目或生产就绪的应用程序)。
将前端 React 应用程序与 GraphQL 后端集成
本教程中的前端是一个简单的应用程序,它在一个地方显示投票问题、投票选项和汇总投票结果。 正如我之前提到的,我们将首先专注于运行这个应用程序,以便您立即获得使用我们最近部署的 GraphQL API 的满足感,看看我们在本文前面看到的 GraphQL 概念如何为此类应用程序的不同用例提供支持,然后探索 GraphQL 集成如何在幕后工作。
注意:如果你是 ReactJS 的新手,你可能想看看这些文章中的一些。 我们不会深入了解应用程序的 React 部分的细节,而是更多地关注应用程序的 GraphQL 方面。 您可以参考 repo 中的源代码,了解有关如何构建 React 应用程序的任何详细信息。
配置前端应用程序
- 在上一节克隆的 repo 中,编辑src/apollo.js
HASURA_GRAPHQL_ENGINE_HOSTNAME
(在/community/examples/realtime-poll
文件夹内)中的 HASURA_GRAPHQL_ENGINE_HOSTNAME 并将其设置为上面的 Heroku 应用程序 URL:export const HASURA_GRAPHQL_ENGINE_HOSTNAME = 'random-string-123.herokuapp.com';
- 转到存储库/应用程序文件夹的根目录 (
/realtime-poll/
) 并使用 npm 安装必备模块,然后运行应用程序:$ npm install $ npm start

您现在应该可以使用该应用程序了。 继续投票,随心所欲,您会注意到结果实时变化。 事实上,如果您设置此 UI 的另一个实例并将其指向同一个后端,您将能够看到所有实例的聚合结果。
那么,这个应用程序是如何使用 GraphQL 的呢? 继续阅读。
幕后花絮:GraphQL
在本节中,我们将探索为应用程序提供支持的 GraphQL 功能,然后在下一节中演示易于集成。
投票组件和汇总结果图
左上角的 poll 组件获取包含所有选项的投票,并在数据库中捕获用户的投票。 这两个操作都是使用 GraphQL API 完成的。 为了获取民意调查的详细信息,我们进行了查询(还记得 GraphQL 介绍中的这一点吗?):
query { poll { id question options { id text } } }
使用react-apollo
中的 Mutation 组件,我们可以将突变连接到 HTML 表单,以便在提交表单时使用变量optionId
和userId
执行突变:
mutation vote($optionId: uuid!, $userId: uuid!) { insert_vote(objects: [{option_id: $optionId, created_by_user_id: $userId}]) { returning { id } } }
为了显示投票结果,我们需要从投票表中的数据中得出每个选项的投票数。 我们可以创建一个 Postgres 视图并使用 GraphQL 引擎对其进行跟踪,以使这些派生数据在 GraphQL 上可用。
CREATE VIEW poll_results AS SELECT poll.id AS poll_id, o.option_id, count(*) AS votes FROM (( SELECT vote.option_id, option.poll_id, option.text FROM ( vote LEFT JOIN public.option ON ((option.id = vote.option_id)))) o LEFT JOIN poll ON ((poll.id = o.poll_id))) GROUP BY poll.question, o.option_id, poll.id;
poll_results
视图连接来自vote
和poll
表的数据,以提供每个选项的投票总数。
在这个视图上使用 GraphQL 订阅、react-google-charts 和react-apollo
的订阅组件,我们可以连接一个响应式图表,该图表在任何客户端发生新投票时实时更新。
subscription getResult($pollId: uuid!) { poll_results(where: {poll_id: {_eq: $pollId}}) { option { id text } votes } }
GraphQL API 集成
正如我之前提到的,我使用了 Apollo Client,这是一个开源 SDK,用于将 ReactJS 应用程序与 GraphQL 后端集成。 Apollo Client 类似于任何 HTTP 客户端库,例如对 python 的请求、用于 JavaScript 的标准 http 模块等。 它封装了发出 HTTP 请求(在本例中为 POST 请求)的详细信息。 它使用配置(在src/apollo.js
中指定)来发出查询/变异/订阅请求(在src/GraphQL.jsx中指定,可以选择使用可以在你的 REACT 应用程序的 JavaScript 代码中动态替换的变量)来一个 GraphQL 端点。 它还利用 GraphQL 端点后面的类型化模式为上述请求提供编译/开发时间验证。 让我们看看客户端应用程序向 GraphQL API 发出实时查询(订阅)请求是多么容易。
配置 SDK
Apollo Client SDK 需要指向 GraphQL 服务器,因此它可以自动处理这种集成通常需要的样板代码。 所以,这正是我们在设置前端应用程序时修改src/apollo.js时所做的。
发出 GraphQL 订阅请求(实时查询)
在src/GraphQL.jsx文件中定义我们在上一节中查看的订阅:
const SUBSCRIPTION_RESULT = ` subscription getResult($pollId: uuid!) { poll_results ( order_by: option_id_desc, where: { poll_id: {_eq: $pollId} } ) { option_id option { id text } votes } }`;
我们将使用这个定义来连接我们的 React 组件:
export const Result = (pollId) => ( <Subscription subscription={gql`${SUBSCRIPTION_RESULT}`} variables={pollId}> {({ loading, error, data }) => { if (loading) return
加载中...</p>; 如果(错误)返回
错误:</p>; 返回 ( <div> <div> {渲染图表(数据)} </div> </div> ); }} </订阅> )
这里需要注意的一点是,上述订阅也可能是一个查询。 只需将一个关键字替换为另一个关键字就可以为我们提供“实时查询”,这就是 Apollo Client SDK 将这个实时 API 与您的应用程序挂钩的全部内容。 每次我们的实时查询中有一个新的数据集时,SDK 都会使用更新的数据触发我们的图表的重新渲染(使用renderChart(data)
调用)。 而已。 真的就是这么简单!
最后的想法
通过三个简单的步骤(创建 GraphQL 后端、对应用程序模式建模以及将前端与 GraphQL API 集成),您可以快速连接一个功能齐全的实时应用程序,而不会陷入不必要的细节,例如设置一个 websocket 连接。 这就是社区工具支持像 GraphQL 这样的抽象的力量。
如果您发现这很有趣,并希望为您的下一个项目或生产应用程序进一步探索 GraphQL,那么您可能希望使用以下一些因素来构建您的 GraphQL 工具链:
- 性能和可扩展性
GraphQL 旨在直接由前端应用程序使用(它并不比后端的 ORM 更好;这样做会带来真正的生产力优势)。 因此,您的工具需要能够有效地使用数据库连接,并且应该能够轻松扩展。 - 安全
由此可见,需要一个成熟的基于角色的访问控制系统来授权对数据的访问。 - 自动化
如果您是 GraphQL 生态系统的新手,那么手写 GraphQL 模式和实现 GraphQL 服务器可能看起来是一项艰巨的任务。 最大限度地提高工具的自动化程度,以便您可以专注于重要的事情,例如构建以用户为中心的前端功能。 - 建筑学尽管上述工作看起来微不足道,但生产级应用程序的后端架构可能涉及高级 GraphQL 概念,如模式拼接等。此外,轻松生成/使用实时 API 的能力为构建异步、反应式提供了可能性具有弹性和固有可扩展性的应用程序。 因此,评估 GraphQL 工具如何简化您的架构至关重要。
相关资源
- 您可以在此处查看该应用程序的实时版本。
- 完整的源代码可在 GitHub 上获得。
- 如果您想探索数据库模式并运行测试 GraphQL 查询,可以在此处进行。