如何在 GraphQL 中进行简单的连接?

amb*_*bar 10 javascript database graphql

我对 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'
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

我想有一个查询来返回一个带有品牌名称而不是品牌代码的电话对象。

例如,如果用 查询电话id = 2,它应该返回:

{id: 2, brand: 'OnePlus', model: 'OnePlus 6', price: 900}
Run Code Online (Sandbox Code Playgroud)

sim*_*905 18

TL; 博士

是的,GraphQL 确实支持一种伪连接。您可以在我的演示项目中看到下面运行的书籍和作者示例。

例子

考虑一个用于存储书籍信息的简单数据库设计:

create table Book ( id string, name string, pageCount string, authorId string ); 
create table Author ( id string, firstName string, lastName string );  
Run Code Online (Sandbox Code Playgroud)

因为我们知道作者可以写很多书籍,数据库模型将它们放在单独的表中。这是 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
}
Run Code Online (Sandbox Code Playgroud)

注意typeauthorId上没有,Book而是 type AuthorauthorIdbook 表上的数据库列不对外暴露。这是一个内部细节。

我们可以使用这个 GraphQL 查询拉回一本书及其作者:

{
  bookById(id:"book-1"){
    id
    name
    pageCount
    author {
      firstName
      lastName
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这是使用我的演示项目运行的屏幕截图:

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
}
Run Code Online (Sandbox Code Playgroud)

单个 GQL 查询导致对数据库的两个单独的 fetch-by-id 调用。当单个逻辑查询变成多个物理查询时,我们很快就会遇到臭名昭著的N+1问题。

N+1问题

在我们上面的例子中,一本书只能有一个作者,所以我们只能对我们的 2x 数据库进行“读取放大”。如果在Author调用books类型上添加字段,则进行成像[Book]。您可能会使用一些数据进行测试,其中作者只有一两本书要获取并添加到数组中。然后在实际的生产系统中,您会发现一位作者已出版了 1,075 本书。发出 1,076 条查询(即作者的 +1)可能会使您的客户超时。

该示例的简单解决方法是不公开booksAuthor 上的字段。我的演示避免了这种情况。如果我们希望某人能够查询某个作者的所有书籍,我们可以提供一个额外的查询:

booksByAuthor(authorId: ID) [Book]
Run Code Online (Sandbox Code Playgroud)

显然,您可以通过lastName或其他方式创建其他查询并添加数据库索引以支持该查询。该解决方案表明,在性能方面,如何将“逻辑模型”映射到“物理模型”并没有“灵丹妙药”。这被称为对象关系阻抗失配问题。更多关于下面的内容。

Fetch-By-ID 这么糟糕吗?

请注意,GraphQl 的默认行为仍然非常有用。你可以将 GraphQL 映射到任何东西上。您可以将其映射到内部 REST API。您可以将某些类型映射到关系数据库,将其他类型映射到 NoSQL 数据库。这些可以在相同的模式和相同的 GraphQL 端点中。没有理由不能Author存储在 Postgres 中并Book存储在 MongoDB 中。这是因为 GraphQL 默认不会“加入数据存储”,它会独立获取每种类型并在内存中构建响应以发送回客户端。这可能是你可以使用一个模型,只有加入一个小数据集变得非常好缓存命中率的情况下。然后,您可以将缓存添加到您的系统中,并且不会出现问题并受益于GraphQL 的所有优势

ORM 怎么样?

有一个名为Join Monster的项目,它会查看您的数据库架构,查看运行时 GraphQL 查询,并尝试即时生成高效的数据库连接。这是对象关系映射的一种形式,有时会得到很多“ OrmHate ”。这主要是由于对象关系阻抗失配问题。

根据我的经验,如果您编写数据库模型来完全支持您的对象 API,则任何 ORM 都可以工作。根据我的经验,当您尝试将现有数据库模型与 ORM 框架进行映射时,任何 ORM 都会失败。

恕我直言,如果在不考虑 ORM 或查询的情况下优化数据模型,则避免 ORM。例如,如果优化数据模型以在经典第三范式中节省空间。我的建议是避免查询主数据模型并使用CQRS 模式。请参阅下面的示例。

什么是实用的?

如果您不想使用像 Join Monster 这样的完整 ORM,您可以选择开始避免N+1在 API 上暴露昂贵的场景。根据上面的示例,您可以避免使用books: [Book]on列表Author并添加直接进入手写查询的特定 GQL 查询。

如果您确实想在 GraphQL 中使用伪连接但遇到N+1问题,您可以编写代码将特定的“字段提取”映射到手写的数据库查询上。当任何字段返回数组时,请密切关注您的架构。

即使您可以输入手写查询,也可能会遇到这些连接运行速度不够快的情况。在这种情况下,请考虑CQRS 模式并对一些数据模型进行非规范化以允许快速查找。例如,ANSI SQL 允许您将数据数组或数据映射打包到一列中。这意味着您可以在作者表上有一列,其中最多包含 1,075 个他们编写的书名和书名。如果您的 DBA 不喜欢将它放在主表中,请将其放入单独的表中,然后创建一个数据库视图,将传统作者表连接到查询优化视图。这将是使用 CQRS 模式加速读取的示例。然后,您的问题就变成了确保当作者出版一本新书时,您将更新的书籍数组写入查询优化的数组列中。


Roz*_*nMD 12

GraphQL 作为前端查询语言不支持经典 SQL 意义上的“连接”。

相反,它允许您选择要为组件获取特定模型中的哪些字段。

要查询数据集中的所有电话,您的查询将如下所示:

query myComponentQuery {
  phone {
    id
    brand
    model
    price
  }
}
Run Code Online (Sandbox Code Playgroud)

然后,前端正在查询的 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
  },
},
Run Code Online (Sandbox Code Playgroud)