一位Rails工程师尝试了用GraphQL并进行了比较
首先
你好!我是榊原。
我已经在Toreta工作一年了。
我觉得这一年是非常变化多端的一年。
特别是从今年1月开始,公司全面远程化,并在餐饮业引入了可以用自己的手机点餐的系统以及可以在预订时指定座位的系统等各种适应时代变化的新服务。
在我们提供的服务中,有许多暂时处于市场接受阶段的东西。在工程师数量有限的情况下,为了推进最快速且灵活的开发,最近我们公司开始使用的是GraphQL。
本次我们将重点介绍GraphQL与我们公司自创始以来一直运用的Ruby on Rails进行比较的说明。
在解释GraphQL的时候,图论是不可或缺的,但由于有许多其他文章涵盖了这个主题,本次将不做详述。
适用于的读者
-
- Railsは使用していてもGraphQLは触ったことがないけど触ってみたい
- サーバーサイドを担当していたけど、業務で使用しなければならなくなった
目标应用程式
-
- GraphQLエンジン v1.3.3
https://github.com/hasura/graphql-engine/
DB: PostgreSQL v13.1
GraphQL到底是什么东西?
GraphQL是由Facebook開發的。
最初,移動應用程序是Web的封裝應用程序。
但由於RESTful API服務器和FQL(Facebook的SQL)數據表的使用,出現了性能下降和頻繁崩潰等問題,需要改進。
因此,在解決Facebook客戶端和服務器應用程序的性能問題和滿足數據結構要求的需求的解決方案方面,GraphQL應運而生。
現在,幾乎所有從Facebook獲取的數據都使用GraphQL。
在GraphQL的客户端应用中,有Relay、Apollo以及最近的Hasura等选项。
GraphQL的第一步 (GraphQL的功能)
以下是一个简单的GraphQL描述示例,假设在Rails中存在一个用于获取Persons表所有信息的API。
铁轨
def PersonsController
def index
data = Person.all
render json: data
end
end
在这种情况下,您只需使用GraphQL编写以下类型的查询即可获取。
GraphQL 图形查询语言
query {
persons {
id
name
birthday
created_at
updated_at
}
}
输出结果在GraphQL中以data哈希的形式包裹,但将以以下的格式输出。
{
"data": {
"persons": [
{
"id": 1,
"name": "佐藤太郎",
"birthday": "2000-01-01",
"created_at": "2020-12-05T06:31:43.250885+00:00",
"updated_at": "2020-12-05T06:31:43.250885+00:00"
},
〜 略 〜
{
"id": 4,
"name": "伊藤花子",
"birthday": "2010-03-03",
"created_at": "2020-12-05T06:32:37.952758+00:00",
"updated_at": "2020-12-05T06:32:37.952758+00:00"
}
]
}
}
由于GraphQL是一种查询语言,因此在Rails中可以省略Model、Controller、Render等,只需使用简单的代码即可创建RESTful API。
作为后续Rails的示例代码,我们尽量不涉及Controller或render,并以使用ActiveRecord为主的数据描述为中心。
解释使用的 Schema
本次使用的是下面的三个表格,根据简单的社交网络构建而成:
– persons(用户)
– photos(用户发布的照片)
– photo_person_tags(给照片打标签的人)
简单的查询 de
那么,现在让我们来看一下之前的查询。
query { // Select句
persons { // テーブル名
id // カラム名 (以下同一)
name
birthday
created_at
updated_at
}
}
在GraphQL中使用了CQRS,因此首先使用查询(Select语句)使用query,使用命令(Create、Update、Delete语句)使用mutation。由于这次是选择操作,所以使用了query。
接下来,您只需写下您想要获取的表名和列名,就可以获得先前提到的数值。
有条件的查询
尽管可以获取全部,但是当然也有只想获取符合条件的情况。
在这种情况下,可以按照以下方式进行描述。
我们要搜索名字中带有“佐藤”字样的人。
铁轨
persons.where('name like ?','佐藤%').limit(2)
GraphQL 图形查询语言
query {
persons(where: {name: {_like: "佐藤%"}}, limit: 2) {
id
name
birthday
created_at
updated_at
}
}
只是在persons的中间添加了where句和Limit句。基本表达是相同的。请参考以下可用于条件语句的内容。
请将以下内容用中文进行释义:
https://hasura.io/docs/1.0/graphql/core/queries/query-filters.html
对多个表进行查询
我认为在实际创建服务的过程中,很多情况下我们希望能够一次性获取多个表格的信息。
在这里,我们将尝试以一个简单的JSON格式输出persons和photos的数据。
铁轨
persons = persons.all
photos = photos.all
data = {
persons: persons,
photos: photos
}
GraphQL (only need one option) — 图灵程序语言
query {
persons {
id
name
birthday
created_at
updated_at
}
photos {
id
person_id
url
created_at
updated_at
}
}
输出结果
{
"data": {
"persons": [
{
"id": 1,
"name": "佐藤太郎",
"birthday": "2000-01-01",
"created_at": "2020-12-05T06:31:43.250885+00:00",
"updated_at": "2020-12-05T06:31:43.250885+00:00"
},
〜 略 〜
{
"id": 3,
"name": "佐藤花子",
"birthday": "2010-03-03",
"created_at": "2020-12-05T06:32:37.249905+00:00",
"updated_at": "2020-12-05T08:21:50.992633+00:00"
}
],
"photos": [
{
"id": 1,
"person_id": 1,
"url": "https://example.com/photos/1",
"created_at": "2020-12-05T06:33:19.847425+00:00",
"updated_at": "2020-12-05T06:33:19.847425+00:00"
},
〜 略 〜
{
"id": 5,
"person_id": 2,
"url": "https://example.com/photos/4",
"created_at": "2020-12-05T06:33:43.201778+00:00",
"updated_at": "2020-12-05T06:33:43.201778+00:00"
}
]
}
}
在GraphQL中,只需添加想获取的表格和列,就可以进行获取。
使用Relay查询
我分别获取了人员和照片,但实际上大多数情况下只想获取与关联有关的值。
以下是根据已知person_id获取对应照片的示例。
轨道系统
person = Person.find(1)
photo_urls person&.photos&.each_with_object([) do |photo, hash|
hash << {url: photo.url}
end
data = {
name: person.name,
birthday: person.birthday,
photos: photo_urls
}
GraphQL:读取和修改应用程序的数据的查询语言和运行时环境。
{
persons_by_pk(id: 1) {
photos{
url
}
name
birthday
}
}
输出结果
{
"data": {
"persons_by_pk": {
"photos": [
{
"url": "https://example.com/photos/1"
},
{
"url": "https://example.com/photos/2"
},
{
"url": "https://example.com/photos/3"
},
{
"url": "https://example.com/photos/3"
}
],
"name": "佐藤太郎",
"birthday": "2000-01-01"
}
}
}
如果连接关系设定正确,你就可以像获取普通的列一样来获取信息。
从这个方面来讲,使用GraphQL可以省去空值检查和循环处理等步骤,变得更加简单。
(实际上,在Rails中可能不需要重新填写URL。)
计算查询的数量
在开发过程中,有时候我们希望计算总和、最大值、平均值等数值。我们现在来尝试获取 ID 为 1 的 person 用户发布的照片数量。
这个词是指一种用于Web应用程序开发的开源框架。
photos = Person.find(1).photos
data = photos.each_with_object({}) do |photo, hash|
hash << {
id: photo.id,
url: photo.url,
photo_person_tag: {
count: photo.photo_person_tags.count
}
}
end
※ 我们省略了类似于graphql的聚合名称,这些名称仅在GraphQL端使用。
图灵表述语言
query {
persons_by_pk(id: 1){
photos {
url
id
photo_person_tags_aggregate {
aggregate {
count(columns: id)
}
}
}
}
}
输出结果
{
"data": {
"persons_by_pk": {
"photos": [
{
"url": "https://example.com/photos/1",
"id": 1,
"photo_person_tags_aggregate": {
"aggregate": {
"count": 1
}
}
},
〜 略 〜
{
"url": "https://example.com/photos/3",
"id": 4,
"photo_person_tags_aggregate": {
"aggregate": {
"count": 0
}
}
}
]
}
}
}
尽管在GraphQL中也具备简单的计算处理功能,但是相比之下,Rails更易于书写简单的计算操作。
使用了片段的查询
碎片是可以在多个位置重复使用的选择集的概念。
这就是在Rails中将处理分离到方法中的概念。
现在让我们以前一个例子为基础来看一下。
轨道
由于本次使用了Rails中已经切割出的count方法,所以省略了具体实现。
GraphQL就是一种查询语言。
fragment personInThePhotoCount on photos {
photo_person_tags_aggregate {
aggregate {
count(columns: id)
}
}
}
query {
persons_by_pk(id: 1){
photos {
url
id
...personInThePhotoCount
}
}
}
输出结果
{
"data": {
"persons_by_pk": {
"photos": [
{
"url": "https://example.com/photos/1",
"id": 1,
"photo_person_tags_aggregate": {
"aggregate": {
"count": 1
}
}
},
〜 略 〜
{
"url": "https://example.com/photos/3",
"id": 4,
"photo_person_tags_aggregate": {
"aggregate": {
"count": 0
}
}
}
]
}
}
}
使用变量的查询
在实际的应用程序中运行时,用户操作的客户端输入可能会在查询中使用,如WHERE子句等。
在这种情况下,需要注意输入值的验证。
在这种情况下,可以使用查询变量(Query Variables)。
通过在查询中定义动态变量,可以进行类型检查、避免空值等,并且具有高度的可重用性。
以下是一个用于获取姓氏为”佐藤”且生日在2005-01-01之前的人的查询示例。
GraphQL是一种查询语言。
查询
query MyQuery($name: String!, $birthday: date) {
persons(where: {name: {_like: $name}, birthday: {_lt: $birthday}}) {
id
name
birthday
}
}
查询变量
{
"name": "佐藤%",
"birthday": "2005-01-01"
}
输出结果
{
"data": {
"persons": [
{
"id": 1,
"name": "佐藤太郎",
"birthday": "2000-01-01"
}
]
}
}
查询 MyQuery($name: String!, $birthday: date)
这个查询有两个参数,
一个是名字(必须输入,类型为字符串)
一个是生日(可以为空,类型为日期)
突变
下面将解释突变。当需要进行更改时,可以使用变异。
变异可分为创建、更新、删除三种类型。
那个,我们各自来看一下吧。
记录创建的变更 de
火车轨道
Person.create(
name: "佐藤三郎",
birthday: "2005-01-01"
)
图生议查询语言
查询
mutation MyMutation($name: String!, $birthday: date!) {
insert_persons_one(object: {birthday: $birthday, name: $name}) {
id
name
birthday
created_at
updated_at
}
}
查询变量
{
"name": "佐藤三郎",
"birthday": "2005-01-01"
}
输出结果
{
"data": {
"insert_persons_one": {
"id": 6,
"name": "佐藤三郎",
"birthday": "2005-01-01",
"created_at": "2020-12-08T10:27:17.430325+00:00",
"updated_at": "2020-12-08T10:27:17.430325+00:00"
}
}
}
Query和Mutation的基本形状是相同的。
另外,insert_persons_one的{}部分将成为返回值。请仅输入所需内容。
insert_persons_one(key:value) {【这一部分】}
此外,正如从名称insert_persons_one可以看出的那样,这将用于创建单个记录,如果是多个记录,可以使用insert_persons。
值的更改的变幅 (zhí de de
我打算在这次更改中改变值。
将 ID 为 5 的用户的生日更改为 2002年1月1日。
轨道系统
person = Person.find(5)
person.birthday = "2002-01-01"
person.save
GraphQL
查询
mutation MyMutation($birthday: date) {
update_persons(where: {id: {_eq: 5}}, _set: {birthday: $birthday}) {
affected_rows
}
}
查询变量
{
"birthday": "2002-01-01"
}
输出结果
{
"data": {
"update_persons": {
"affected_rows": 1
}
}
}
可以使用任何其他标识来确定要更新的对象,本次我们使用了ID。此外,通过在 _set 中输入更新信息,可以更新更新内容。
这次的返回值是 “affected_rows”: 1,这是因为设置了返回值更新的行数,确认只有一行发生了更改。
执行删除操作的Mutation
我想刪除這次的ID為5的person。
铁轨
person = Person.find(5)
person.destroy
GraphQL
图灵位洛夫查询语言
mutation {
delete_persons(where: {id: {_eq: 5}}) {
affected_rows
}
}
输出结果
{
"data": {
"delete_persons": {
"affected_rows": 1
}
}
}
这个也能轻松地删除。
总结
感谢你阅读到最后。
GraphQL在表达上有其限制,并且将模式信息公开给外部,导致对模式变更较为脆弱的问题,但相较于Rails更容易创建RESTFul API。
以下是我目前記述的內容中的一部分GraphQL。
如果您想更深入了解GraphQL,並且希望使用它,我建議您首先查閱GraphQL的官方文件。
https://graphql.org/learn/
请注意,本次使用的Hasura GraphQL引擎与标准的GraphQL存在一些显著的差异,因此,如果您想要使用Hasura的GraphQL,请阅读Hasura的GraphQL文档。
以下是中文的原生释义,仅需要一种平行译文:
https://hasura.io/docs/1.0/graphql/core/index.htmlHasura官方文档1.0版GraphQL核心指南链接
最后
在我们公司,我们正在逐渐开始在假设验证阶段的开发服务中引入GraphQL,以获得更快的开发速度感。
我们正在招募愿意一起验证这种技术和服务的工程师。
请点击以下链接确认一下,我们还有一些休闲面谈的机会!
https://corp.toreta.in/recruit/midcareer/