我是GraphQL的新手,正在尝试做一个简单的联接查询。我的示例表如下所示:
{
phones: [
{
id: 1,
brand: 'b1',
model: 'Galaxy S9 Plus',
price: 1000,
},
{
id: 2,
brand: 'b2',
model: 'OnePlus 6',
price: 900,
},
],
brands: [
{
id: 'b1',
name: 'Samsung'
},
{
id: 'b2',
name: 'OnePlus'
}
]
}
我想查询一个返回 phone 对象的商标,而不是其品牌代码。
例如如果查询ID = 2的电话,则应返回
{id: 2, brand: 'OnePlus', model: 'OnePlus 6', price: 900}
答案 0 :(得分:10)
是的,GraphQL 确实支持一种伪连接。您可以看到下面在 my demo project 中运行的书籍和作者示例。
考虑一个用于存储书籍信息的简单数据库设计:
create table Book ( id string, name string, pageCount string, authorId string );
create table Author ( id string, firstName string, lastName string );
因为我们知道作者可以写很多书籍,数据库模型将它们放在单独的表中。这是 GraphQL 架构:
type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
注意 authorId
类型上没有 Book
,而是 Author
类型。 book 表上的数据库 authorId
列不对外公开。这是一个内部细节。
我们可以使用这个 GraphQL 查询拉回一本书及其作者:
{
bookById(id:"book-1"){
id
name
pageCount
author {
firstName
lastName
}
}
}
以下是使用 my demo project 的屏幕截图:
结果嵌套作者详细信息:
{
"data": {
"book1": {
"id": "book-1",
"name": "Harry Potter and the Philosopher's Stone",
"pageCount": 223,
"author": {
"firstName": "Joanne",
"lastName": "Rowling"
}
}
}
}
单个 GQL 查询导致对数据库的两次单独的 fetch-by-id 调用。当单个逻辑查询变成多个物理查询时,我们很快就会遇到臭名昭著的 N+1
问题。
N+1
问题在我们上面的例子中,一本书只能有一个作者,所以我们只能对我们的 2x 数据库进行“读取放大”。如果您在 Author
上添加一个名为 books
且类型为 [Book]
的字段,则进行成像。您可能会使用一些数据进行测试,其中作者只有一两本书要获取并添加到数组中。然后在实际的生产系统中,您会发现一位作者拥有 published 1,075 books。发出 1,076 条查询(即作者的 +1)可能会使您的客户超时。
该示例的简单解决方法是不公开作者的字段 books
。我的演示避免了这种情况。如果我们希望某人能够查询某个作者的所有书籍,我们可以提供一个额外的查询:
booksByAuthor(authorId: ID) [Book]
显然,您可以通过 lastName
或其他方式创建其他查询,并添加数据库索引以支持该查询。该解决方案表明,在性能方面,如何将“逻辑模型”映射到“物理模型”并没有“灵丹妙药”。这称为 Object–relational impedance mismatch 问题。更多内容见下文。
请注意,GraphQl 的默认行为仍然非常有用。你可以将 GraphQL 映射到任何东西上。您可以将其映射到内部 REST API。您可以将某些类型映射到关系数据库,将其他类型映射到 NoSQL 数据库。这些可以在相同的模式和相同的 GraphQL 端点中。没有理由不能将 Author
存储在 Postgres 中并将 Book
存储在 MongoDB 中。这是因为 GraphQL 默认不会“加入数据存储”,它会独立获取每种类型并在内存中构建响应以发送回客户端。在这种情况下,可能您可以使用一个模型,该模型仅连接到获得非常好的缓存命中率的小数据集。然后,您可以将缓存添加到您的系统中,而不会出现问题并从所有 advantages of GraphQL 中受益。
有一个名为 Join Monster 的项目,它会查看您的数据库架构,查看运行时 GraphQL 查询,并尝试即时生成高效的数据库连接。这是 Object Relational Mapping 的一种形式,有时会得到很多“OrmHate”。这主要是由于 Object–relational impedance mismatch 问题。
根据我的经验,如果您编写的数据库模型完全支持您的对象 API,则任何 ORM 都可以工作。根据我的经验,当您尝试将现有数据库模型与 ORM 框架进行映射时,任何 ORM 都会失败。
恕我直言,如果在不考虑 ORM 或查询的情况下优化数据模型,则避免 ORM。例如,如果优化数据模型以节省经典 third normal form 中的空间。我的建议是避免查询主数据模型并使用 CQRS pattern。请参阅下面的示例。
如果您不想使用像 Join Monster 这样的完整 ORM,您可以选择开始避免在 API 上暴露昂贵的 N+1
场景。根据上面的示例,您可以避免在 books: [Book]
上使用 Author
列表,并添加直接进入手写查询的特定 GQL 查询。
如果您确实想在 GraphQL 中使用伪连接但遇到了 N+1
问题,您可以编写代码将特定的“字段提取”映射到手写数据库查询上。随时关注您的架构,以了解任何字段返回数组时的情况。
即使您可以进行手写查询,也可能会遇到这些连接运行速度不够快的情况。在这种情况下,请考虑 CQRS pattern 并对某些数据模型进行非规范化以允许快速查找。例如,ANSI SQL 允许您将数据数组或数据映射打包到一列中。这意味着您可以在作者表上有一列,其中最多包含 1,075 个他们编写的书名和书名。如果您的 DBA 不喜欢将它放在主表上,请将其放入单独的表中,然后创建一个数据库视图,将传统作者表连接到查询优化视图。这将是使用 CQRS 模式加速读取的示例。然后,您的问题就变成了确保当作者出版一本新书时,您将更新的书籍数组写入查询优化的数组列中。
答案 1 :(得分:2)
GraphQL作为前端上的查询语言,在传统的SQL意义上不支持“联接”。
相反,它使您可以选择特定模型中要为组件获取的字段。
要查询数据集中的所有电话,您的查询应如下所示:
query myComponentQuery {
phone {
id
brand
model
price
}
}
您的前端正在查询的GraphQL服务器将具有单独的字段解析器-告诉GraphQL在哪里获取ID,品牌,型号等。
服务器端解析器如下所示:
Phone: {
id(root, args, context) {
pg.query('Select * from Phones where name = ?', ['blah']).then(d => {/*doStuff*/})
//OR
fetch(context.upstream_url + '/thing/' + args.id).then(d => {/*doStuff*/})
return {/*the result of either of those calls here*/}
},
price(root, args, context) {
return 9001
},
},