【AICon】 如何构建高效的 RAG 系统?RAG 技术在实际应用中遇到的挑战及应对策略?>>> 了解详情
写点什么

使用 GraphQL 和 Ballerina 操作多个数据源

作者: Anupama Pathirage

  • 2022-07-19
  • 本文字数:7427 字

    阅读完需:约 24 分钟

使用GraphQL和Ballerina操作多个数据源

在当今的数字转型时代,应用程序和 Web 服务之间的相互对话是不可避免的,我们需要通过 API 来实现这些应用程序之间的通信。各种协议和规范定义了消息通过网络传递的语义和语法,最终形成了一种 API 架构。

 

在本文中,我们将探讨如何使用 GraphQL 和 Ballerina 将 MySQL 数据库中的数据作为 API 公开出来。GraphQL 是一种抽象了底层数据源的规范,借助 GraphQL,开发人员能够灵活地使用他们喜欢的编程语言处理数据源,如数据库或 REST API。

 

GraphQL 是什么

 

GraphQL 是一种应用层服务器端技术,由 Facebook 于 2012 年开始开发,并于 2015 年公开发布,用于优化 REST API 调用。GraphQL 既可以被视为一种 API 查询语言,也可以被视为一种服务器端运行时,用于执行由用户定义的查询。

 

GraphQL 的操作类型如下:

 

  • 查询(读取);

  • 突变(写入/更新);

  • 订阅(连续读取)。

 

这些操作都只是一个字符串,需要根据 GraphQL 查询语言规范进行构造。GraphQL 对网络层或消息体的格式没有特别要求,不过最常用的一般是 HTTP 和 JSON。

 

GraphQL 是更好的 REST

 

在过去的十年中,REST 已经成为一种流行的 API 设计架构。REST 和 GraphQL 可以被认为是解决同一问题(通过 Web 服务访问数据)的两种不同的方法。但是,随着客户端对 API 的访问需求发生了快速变化,REST API 已经变得太不灵活了。推出 GraphQL 的目的是为了支持更灵活、更高效的数据访问行为。下面列出了选择 GraphQL 而不是 REST 的一些关键原因。

 

避免过度获取或获取不足

 

过度获取意味着获取的信息超过了你的需要。这在使用 REST 时非常常见,因为它总是从给定的端点返回固定的数据集,而客户端实际上具有特定的数据需求。获取不足意味着特定端点没有提供足够的所需信息,客户端不得不发出额外的请求来获取所需的数据。但在使用 GraphQL 时,你可以使用查询语法定义所需信息的结构,然后通过单个 API 请求就可以获取所需的信息。

 

客户端可以快速进行产品迭代

 

通常,REST API 需要根据客户端应用程序需要的视图来提供端点。如果客户端应用程序发生了变化,它需要的数据可能比以前多也可能比以前少。因此,为了满足新的需求,需要调整 REST API。如果使用的是 GraphQL,由于客户端可以指定准确的数据需求,所以只需要在客户端做出更改,服务器端不需要做任何额外的工作。

 

支持基于模式和类型系统的开发方式

 

GraphQL 有一个强大的类型系统,可用于定义通过 API 公开出来的数据,所有这些类型都可以使用 GraphQL 模式定义语言(SDL)写到模式中。模式成了客户端和服务器端之间的契约,不同的团队可以基于定义好的模式分别处理前端和后端的代码逻辑。

 

为什么选择 Ballerina

 

你可以使用任何流行的编程语言来构建 GraphQL 应用程序,如 Go、Java、Node.js 等。我们选择Ballerina是因为它提供了很多附加价值:

 

  • Ballerina 是一种开源的云编程语言,它让网络服务的调用、组合和创建变得更加容易。

  • 它是一种现代的、工业级的、用于集成和开发网络服务和应用程序的通用语言。

  • 由于具有网络感知类型系统、对网络服务和资源的一流支持、对各种技术(包括 GraphQL)的内置支持以及序列图语法等特性,使得开发者体验更加直观。

 

有两种设计 GraphQL 端点的方法:

 

  • 模式优先方法:需要使用 GraphQL 模式来创建 GraphQL 服务。

  • 代码优先方法:模式是不必需的,可以直接使用代码编写端点,然后生成模式。

 

Ballerina 使用代码优先的方法来设计 GraphQL 端点。Ballerina 的 GraphQL 实现使用 HTTP 作为底层协议。在下一节中,我们将探讨这些特性如何帮助你开发 GraphQL 应用程序。

 

一个书店示例

 

GraphQL 服务器的数据源可以是任何东西,如数据库、另一个 API 或提供数据的服务等。此外,GraphQL 可以与任意的数据源组合发生交互。这个示例演示了如何使用 Ballerina 实现 GraphQL 服务器,将 MySQL 数据库中的数据以及通过另一个 API 调用获取的数据公开出来。

 

MySQL 数据库中保存了与书店相关的数据,包括书籍和作者的信息。与书籍相关的其他信息通过Google Books API获得。书店的客户端可以通过 GraphQL API 完成以下这些操作:

 

  • 获取所有书籍的详细信息;

  • 通过提供书名获取书籍的详细信息;

  • 向数据库中添加新书。

 

上述操作的信息来源如下:

 

  • 书名、出版年份、ISBN、作者姓名、作者国籍——从数据库获取;

  • 平均评分和评分计数——通过 ISBN 查询 Google Books API。

 


这个示例使用 MySQL 数据库和 Google Books API 作为数据源

 

这个示例的所有源代码都可以在 Github 上找到。

 

用示例数据填充数据库

 

首先用示例数据填充 MySQL 数据库。Bookstore 数据库有两张表,“Book”和“Author”,包含以下这些字段。

 


Bookstore 的数据库模式

 

可以在 data.sql 文件中找到创建数据库、表和填充数据的 SQL 语句。如果将这些语句保存到一个文件中,请在数据库中执行以下命令。

 

mysql -uroot -p < /path/to/file/data.sql
复制代码

 

使用 Ballerina 实现 GraphQL 服务

 

创建 Ballerina 项目

 

通过执行下面的命令创建一个 Ballerina 项目。有关 Ballerina 项目结构的更多细节,请参考“Organize Ballerina code”。

 

bal new bookstoreCreated new package 'bookstore' at bookstore.
复制代码

 

└── bookstore	├── Ballerina.toml	└── main.bal
复制代码

 

1 个目录,2 个文件。

 

因为这是一个服务,不需要 main.bal 文件,所以可以把它删除。

 

创建记录类型

 

添加一个叫作“bookservice.bal”的 Ballerina 文件,用于实现 GraphQL 服务逻辑。第一步先定义用于表示书籍和作者数据的记录类型。在 Ballerina 中,记录是特定类型字段的集合。其中有命名的键,并定义了字段的类型。{|和|}分隔符表示这个记录类型只包含所描述的字段。

 

下面的“BookDetails”记录表示从数据库中获取到的书籍的详细信息。

 

type BookDetails record {|   string title;   int published_year;   string isbn;   string name;   string country;|};
复制代码

 

接下来创建用于表示从 Google Books API 获取到的数据的记录类型。在创建所需的记录之前,需要分析一下根据指定 ISBN 从 Google Books API 获取的的 JSON 响应消息的格式。它返回一个 JSON 对象,其中包含了一个“items”的数组。它还有另一个叫作“volumeInfo”的对象,这个对象包含了与书籍评论相关的信息,字段名分别为“averageRating”和“ratingsCount”。

 


在 Ballerina 中有两种处理 JSON 的方式。你可以直接使用内置的“json”类型,或者将 JSON 转换成用户定义的“anydata”子类型。下面的示例使用了第二种方法,直接将响应消息映射成记录,因为 Ballerina 的 HTTP 客户端数据绑定为此提供了很好的支持。因为你只对与书籍评级相关的字段感兴趣,所以可以只用这些字段来创建记录。另外,你用不到字段名为“items”的中间对象,所以这里可以使用匿名记录,因为你不需要通过名称引用这个记录类型。

 

type BookReviews record {   record {       VolumeInfo volumeInfo;   }[] items;//通过内联的方式定义匿名记录类型}; type VolumeInfo record {   int averageRating?;   int ratingsCount?;};
复制代码

 

创建服务对象

 

Ballerina 记录类型和服务类型都可以作为 GraphQL 的对象类型。记录的字段被映射到 GraphQL 对象的字段,记录字段的类型被映射到 GraphQL 对应字段的类型。

 

服务类型中的每一个资源方法表示 GraphQL 对象的一个字段,资源方法可以有输入参数,这些输入参数被映射到相应字段的参数。

 

使用记录类型作为对象有局限性,因此,在这个示例中,我们使用服务类型来表示“Book”对象。

 

在这个服务中,“BookDetails”是一个 final 的只读字段,在初始化后不能被赋值。

 

Ballerina GraphQL 服务中的资源可以有层级资源路径。如果出现了层级路径,例如下面的 author/…,就会为每一个同名的中间路径段创建一个对象类型。路径段下的每一个子路径都将作为所创建类型的一个字段。

 

service class Book {   private final readonly & BookDetails bookDetails;    function init(BookDetails bookDetails) {       self.bookDetails = bookDetails.cloneReadOnly();   }   resource function get title() returns string {       return self.bookDetails.title;   }   resource function get published_year() returns int {       return self.bookDetails.published_year;   }   resource function get isbn() returns string {       return self.bookDetails.isbn;   }   resource function get author/name() returns string {       return self.bookDetails.name;   }   resource function get author/country() returns string {       return self.bookDetails.country;   }   resource function get reviews() returns VolumeInfo|error {       string isbn = self.bookDetails.isbn;       return getBookReviews(isbn);   }}
复制代码

 

创建 GraphQL 服务

 

现在开始编写 GraphQL 服务。Ballerina 对基于网络的交互提供了一流的支持,因此编写服务就变得很简单。服务对象支持通过远程方法和资源方法进行网络交互。监听器提供了网络和服务对象之间的接口。

 

首先,你需要导入 ballerina/graphql 模块。然后,你通过指定要监听的端口来创建 GraphQL 监听器对象,并将其附加到服务上。

 

资源方法以 REST 的方式公开服务,而远程方法则以过程方式公开服务。Ballerina 服务可以有资源方法和远程方法,资源方法用于表示 GraphQL 查询类型,远程方法用于表示可变类型。

 

下一步是加入远程函数或资源函数。allBooks 和 bookByName 是通过 GraphQL 查询获取书籍数据的资源函数,因此,它们返回“Book”数组。要将新书添加到数据库中,可以调用“addBook”远程方法。它将书籍的信息作为输入参数,并返回一个 int 值,这个值表示已插入的书籍的索引,如果发生错误就返回-1。

 


Ballerina GraphQL 服务

 

下一步是实现数据访问逻辑,也就是实现远程方法和资源方法。

 

完整的代码在bookservice.bal中。服务的代码如下所示。与 DB 交互和 API 调用相关的 getBooks、addBookData 和 getBookReviews 函数的更多细节请参阅下一小节。

 

service /bookstore on new graphql:Listener(4000) {   resource function get bookByName(string title) returns Book[] {       return getBooks(title);   }   resource function get allBooks() returns Book[] {       return getBooks(());   }   remote function addBook(string authorName, string authorCountry, string title,                                               int published_year, string isbn) returns int {       int|error ret = addBookData(authorName, authorCountry, title, published_year, isbn);       return ret is error ? -1 : ret;   }}
复制代码

 

实现数据访问逻辑

 

由于本例使用 MySQL 数据库作为后端数据存储,因此需要提供查询数据库和添加新记录的功能。Ballerina 为 DB 交互提供了一流的支持。

 

现在,在项目中添加另一个名为bookdatastore.bal的文件,用于 DB 交互和 API 调用相关的实现。首先,你需要创建一个 MySQL 数据库客户端,并导入ballerinax/mysqlballerina/sqlballerinax/mysql.driver模块。

 

你可以在初始化客户端时提供配置信息,不过本例使用了Ballerina的配置功能来提供配置信息。用户可以根据不同的环境通过外部输入来改变系统行为,而且敏感数据(如密码)不会通过代码暴露出来。在定义了配置数据库所需的可配置记录之后,就可以按照如下方式创建 DB 客户端。

final mysql:Client dbClient = check new (database.host, database.username, database.password, database.name, database.port);
复制代码

 

然后可以通过项目中的Config.toml文件来提供配置信息。

 

现在添加一个 HTTP 客户端,用于从 Google Books API 获取所需的数据。你需要导入ballerina/http模块,并按照如下方式创建客户端。

final http:Client bookEp = check new ("https://www.googleapis.com/books/v1");
复制代码

 

这样,你就完成了这个场景的实现。完整的访问数据库的代码可以在bookdatastore.bal中找到。

 

使用生成的图表

 

因为存在多个实体之间的交互,所以集成用例就变得很复杂。因此,理解整个流程和顺序对于维护、改进和解释场景来说至关重要。Ballerina 内置了图表功能,可以基于已编写的代码生成完整的序列图。图表可以作为代码的文档,相比直接阅读源代码,这种方式更易于理解程序。你可以使用Ballerina VSCode插件查看和编辑这些图表。

 

下面是 getBooks 方法对应的图表。其他方法也有类似的图标,你可以使用 VSCode 插件查看和编辑它们。

 


基于源代码生成的 Ballerina 图表

 

运行 Bookstore 服务

 

现在,让我们运行并测试 GraphQL 服务。要运行这个服务,需要在 bookstore 项目的根目录下执行下面的命令:

bal run
复制代码

 

如果你使用 GraphQL 客户端工具连接到这个服务,它将显示下面这个的模式:

 

type Query { allBooks: [Book!]! bookByName(title: String!): [Book!]!} type author { country: String! name: String!} type VolumeInfo { averageRating: Int ratingsCount: Int} type Book { reviews: VolumeInfo! published_year: Int! author: author isbn: String! title: String!} type Mutation { addBook(   authorName: String!   authorCountry: String!   title: String!   published_year: Int!   isbn: String! ): Int!}
复制代码

 

测试 Bookstore 服务

 

要调用 GraphQL 服务器,需要使用客户端。你可以在命令行中使用 curl 向端点发送 HTTP POST 请求,并将 GraphQL 查询作为 JSON 传递给它。另外,你也可以使用Ballerina GraphQL客户端工具为给定的 GraphQL 模式(SDL)和 GraphQL 查询生成 Ballerina 客户端。如果你喜欢使用图形用户界面,可以使用GraphiQLAltair等。

 

所有请求的端点都是http://localhost:4000/bookstore

 

示例请求 1:获取所有书籍的书名

 

GraphQL 查询:

{allBooks {title}}
复制代码

 

响应:

{ "data": {   "allBooks": [     { "title": "Pride and Prejudice" },     { "title": "Sense and Sensibility" },     { "title": "Emma" },     { "title": "War and Peace" },     { "title": "Anna Karenina" }   ] }}
复制代码

 

使用 curl 命令发送相同的请求:

curl -X POST -H "Content-type: application/json" -d '{ "query": "{allBooks {title}}" }' 'http://localhost:4000/bookstore'
复制代码

 

示例请求 2:获取所有书籍更多的信息

 

这就是 GraphQL 真正强大的地方。用户可以按照自己需要的格式请求所需的信息,无需指定不同的端点,只需修改查询即可。你可以看到这里的一些评级是“null”,因为 Google Books API 调用返回的一些 JSON 响应没有包含这些信息。

 

GraphQL 查询:

{allBooks {title, author{name}, reviews{ratingsCount, averageRating}}}
复制代码

 

响应:

{ "data": {   "allBooks": [     {       "title": "Pride and Prejudice",       "author": {         "name": "Jane Austen"       },       "reviews": {         "ratingsCount": 1,         "averageRating": 5       }     },     {       "title": "Sense and Sensibility",       "author": {         "name": "Jane Austen"       },       "reviews": {         "ratingsCount": 3,         "averageRating": 4       }     },     {       "title": "Emma",       "author": {         "name": "Jane Austen"       },       "reviews": {         "ratingsCount": null,         "averageRating": null       }     },     {       "title": "War and Peace",       "author": {         "name": "Leo Tolstoy"       },       "reviews": {         "ratingsCount": 5,         "averageRating": 4       }     },     {       "title": "Anna Karenina",       "author": {         "name": "Leo Tolstoy"       },       "reviews": {         "ratingsCount": 1,         "averageRating": 4       }     }   ] }}
复制代码

 

使用 curl 命令发送相同的请求:

curl -X POST -H "Content-type: application/json" -d '{ "query": "{allBooks {title, author{name}, reviews{ratingsCount, averageRating}}}" }' 'http://localhost:4000/bookstore'
复制代码

 

示例请求 3:指定输入参数获取书籍的详细信息

 

GraphQL 查询:

{bookByName(title: "Emma") {title, published_year}}
复制代码

 

响应:

{ "data": { "bookByName": [{ "title": "Emma", "published_year": 1815 }] } }
复制代码

 

使用 curl 命令发送相同的请求:

curl -X POST -H "Content-type: application/json" -d '{ "query": "{bookByName(title: \"Emma\") {title, published_year}}" }' 'http://localhost:4000/bookstore'
复制代码

 

示例请求 4:将数据插入数据库

 

GraphQL 查询:

mutation {addBook(authorName: "J. K. Rowling", authorCountry: "United Kingdom", title: "Harry Potter", published_year: 2007, isbn: "9781683836223")}
复制代码

 

响应:

{ "data": {   "addBook": 6 }}
复制代码

 

使用 curl 命令发送相同的请求:

curl -X POST -H "Content-type: application/json" -d '{ "query": "mutation {addBook(authorName: \"J. K. Rowling\", authorCountry: \"United Kingdom\", title: \"Harry Potter\", published_year: 2007, isbn: \"9781683836223\")}" }' 'http://localhost:4000/bookstore'
复制代码

 

总结

 

在现代应用程序开发中,GraphQL 可能是比 REST 更好的选择。Ballerina 为网络抽象提供了的一流的支持,可以通过简单而强大的方式开发 GraphQL 服务。在我们的示例中,我们实现了一个书店的 GraphQL 应用场景,结合了多个后端数据源,包括 MySQL 数据库和 Google Books API。

 

你可以访问ballerina.io来了解更多关于 Ballerina 的信息。

 

你可以通过检查问题来参与 Ballerina GraphQL 的模块开发。

 

示例项目的完整源代码可以在这里找到。

 

更多信息

 

可以参考以下资源了解更多关于 Ballerina GraphQL 的信息:

 

Ballerina GraphQL API文档

Ballerina GraphQL规范

Ballerina GraphQL示例

Ballerina GraphQL模块

 

作者简介:

 

Anupama Pathirage 是 WSO2 的工程总监、工程经理和 Ballerina 语言的开发者。她为 Ballerina 的各个方面(如编译器、运行时、事务、表、数据库客户端和数据处理等)做出了贡献。Anupama 拥有软件架构硕士学位和斯里兰卡莫拉图瓦大学计算机科学与工程系一等荣誉学士学位。Anupama 在 DZone、InfoQ 和 Medium 上发表文章,并定期在国际技术大会上发表演讲。

 

原文链接

Using GraphQL and Ballerina with Multiple Data Sources

2022-07-19 09:135755

评论 1 条评论

发布
用户头像
Ballerina 利用了一下GraphQL的流量
2022-08-06 11:41
回复
没有更多了
发现更多内容

一块显存为 12GB 的 GPU 卡能够在CST 时域求解器仿真的最大网格数是多少?

思茂信息

cst cst使用教程 cst操作 cst电磁仿真 cst仿真软件

硬核!阿里2023版Spring全家桶进阶笔记流出,堪称Java跳槽神器

Java你猿哥

spring Spring Boot spring security Spring全家桶 Spring Cloud Aliababa

使用 Node.js、Socket.IO 和 GPT-4 构建 AI 聊天机器人

devpoint

node.js websocket GPT-4

5月书讯 | 《这就是ChatGPT》来了!

图灵社区

数学 书单 书单推荐 GPT

500行代码手写docker-实现硬件资源限制cgroups

蓝胖子的编程梦

容器 k8s ,docker Cgroups #k8s

4 种 MySQL 同步 ES 方案,yyds!

Java你猿哥

Java MySQL sql elasticsearch Binlog

GitHub发布即百万!字节内网超实用 java性能优化手册,star超十万

Java你猿哥

Java ssm jvm调优 Java性能优化

一次错误的解码处理导致Netty堆外内存泄漏问题的分析

Java你猿哥

Java Netty ssm RSS 逻辑

Health Kit文档大变样,一起尝鲜!

HMS Core

HMS Core

Kafka集群是如何选择leader,你知道吗?

做梦都在改BUG

Java kafka 集群

刚入职的后端开发问我SpringBoot如何跨域配置?我表示

Java你猿哥

Java Spring Boot ssm 跨域

从7天到1天,Kyligence 和亚马逊云科技助力欣和提高数据应用价值

Kyligence

数字化转型 指标平台

Spring Boot实现第一次启动时自动初始化数据库

做梦都在改BUG

Java spring Spring Boot

Git入门指南:从新手到高手的完全指南

小万哥

git Linux 程序员 后端 C/C++

GitHub上开源24小时获星96K的,从零到实战Java架构师成长手册

Java你猿哥

架构 架构师 高可用架构 高性能架构 高可扩展

Flutter三棵树系列之BuildOwner | 京东云技术团队

京东科技开发者

flutter 移动开发 源码解读 企业号 5 月 PK 榜 BuildOwner

5月书讯 | 《这就是ChatGPT》来了!

图灵教育

数学 书单 GPT

Solaris Network:BSC上首个链上合成资产解决方案

鳄鱼视界

前端开发如何更好的避免样式冲突?级联层(CSS@layer)

vivo互联网技术

CSS 级联 @layer

引领下一代云计算技术的变革:无服务器架构

xfgg

云计算 severless

如何科学地利用MTTR优化软件交付流程?

SEAL安全

DevOps MTTR 企业号 5 月 PK 榜

如何将千亿文件放进一个文件系统,EuroSys'23 CFS 论文背后的故事

百度Geek说

数据库 云计算 百度 企业号 5 月 PK 榜

kafka集群是如何选择leader,你知道吗?

JAVA旭阳

kafka

大语言模型技术原理

NineData

AIGC ChatGPT AI大语言模型 大语言模型 技术原理

如何避免写重复代码:善用抽象和组合

阿里技术

Java 代码实战

深度理解:Redis Hash(散列表)实现原理

Java你猿哥

redis 开源 ssm hash 散列表

解锁人机交互新技术 华为开发者联创日·全栈AI黑客松大赛等你来

说山水

2023数博会丨软通智慧发布“一屏统揽”TongView 数字政府慧治平台解决方案

说山水

阿里架构组分布式架构技术使用心得:全在这一份文档里面了

Java你猿哥

架构 分布式 ssm 分布式架构 安全架构

MatrixOne 助力开启分布式计算格局新征程

MatrixOrigin

分布式数据库 HTAP MatrixOrigin MatrixOne 矩阵起源

阿里大神级Elasticsearch学习笔记,还学不会就埋了

做梦都在改BUG

Java elasticsearch 分布式搜索引擎 ES

使用GraphQL和Ballerina操作多个数据源_语言 & 开发_InfoQ精选文章