点击看第四范式、百度、Zilliz、火山引擎公司的AI基础设施变革之路>> 了解详情
写点什么

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

作者: Anupama Pathirage

  • 2022 年 7 月 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 年 7 月 19 日 09:132443

评论 1 条评论

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

哪个低代码平台上手度对小白更友好?专业测评来了!

优秀

低代码 应用开发

从冬奥看中国科技(四):“一鱼多吃”的5G

脑极体

裸辞全职开源的监控告警项目

TanCloud探云

开源 云服务 监控系统 监控告警

从线程与进程的区别这一问题出发

宇宙之一粟

操作系统 2月月更

图解Numpy教程

Peter

Python 机器学习 Numpy

机器学习神器Scikit-Learn入门

Peter

数据挖掘 机器学习 算法

使用 Flink Hudi 构建流式数据湖平台

Apache Flink

大数据 flink 编程 数据湖 实时计算

王者荣耀异地多活架构设计

AUV

「架构实战营」

《MySQL入门很轻松》第5章:数据完整性及其分类

乌龟哥哥

数据库 2月月更

数据分析实际案例之:pandas在餐厅评分数据中的使用

程序那些事

Python 数据分析 pandas 程序那些事 2月月更

Web_Components 系列(九)—— Shadow Host 的 CSS 选择器

编程三昧

前端 组件化 2月月更 WebComponent

机器学习入门知识

Peter

Python 机器学习 算法 监督学习

声网Agora Lipsync 技术揭秘:通过实时语音驱动人像模拟真人说话

声网

人工智能 音视频

剑指offer笔试题 · 常量字符串

安然无虞

C语言

DevOps进阶(四):Jenkins 实战之构建定时项目与远程触发器

No Silver Bullet

DevOps jenkins 定时器 2月月更 触发器

产品经理沟通技巧

史前靓仔

职场 产品经理 沟通技巧

基于LSTM模型的共享自行车需求预测

Peter

Python 机器学习 深度学习 算法

MySQL RR级别的实现

JavaEdge

2月月更

用命令行做数据科学(上)

Render

Numpy可视化绘图

Peter

机器学习 数据分析 Numpy

聊聊 Pulsar: 在 Linux 环境上搭建 Pulsar

老周聊架构

云原生 Apache Pulsar 2月月更

云原生多云应用利器--Karmada 总览篇

Daocloud 道客

云原生 多云管理 Karmada

极光笔记 | 极光推送业务无中断迁移上云实践

极光JIGUANG

企业上云 后端技术 上云

模块七

撿破爛ぃ

架构训练营

架构实战营 第 4 期 模块七作业

架构实战营 模块七 王者荣耀 「架构实战营」

新年新知 | 2022必读的5篇行业趋势(内附下载)

LigaAI

AI 创新 行业趋势 新能源

Kubernetes应用,如何选择适合你的Kubernetes应用发布模式

阿里云云效

阿里云 Kubernetes 云原生 CI/CD 发布模式

「重磅消息」OpenMLDB 官方网站 正式上线!

第四范式开发者社区

机器学习 大数据 OpenMLDB

musl堆利用技巧,你知道多少

网络安全学海

网络安全 信息安全 渗透测试 WEB安全 安全漏洞

实力与颜值并存 —— Apache Pulsar PMC 成员刘昱专访

Apache Pulsar

开源 云原生 Apache Pulsar 社区

基于随机森林模型的心脏病人预测分类

Peter

Python 机器学习 算法

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