graphql模式循环引用是反模式吗? [英] Is graphql schema circular reference an anti-pattern?

查看:75
本文介绍了graphql模式循环引用是反模式吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

graphql模式如下:

graphql schema like this:

type User {
  id: ID!
  location: Location
}

type Location {
  id: ID!
  user: User
}

现在,客户端发送一个graphql查询.从理论上讲,UserLocation可以无限循环地相互引用.

Now, the client sends a graphql query. Theoretically, the User and Location can circular reference each other infinitely.

我认为这是一种反模式.据我所知,在graphqlapollo社区中都没有中间件或方法来限制查询的嵌套深度.

I think it's an anti-pattern. For my known, there is no middleware or way to limit the nesting depth of query both in graphql and apollo community.

此无限嵌套深度查询将为我的系统消耗大量资源,例如带宽,硬件,性能.不仅在服务器端,而且在客户端.

This infinite nesting depth query will cost a lot of resources for my system, like bandwidth, hardware, performance. Not only server-side, but also client-side.

因此,如果graphql模式允许循环引用,则应该有一些中间件或方法来限制查询的嵌套深度.或者,为查询添加一些约束.

So, if graphql schema allow circular reference, there should be some middlewares or ways to limit the nesting depth of query. Or, add some constraints for the query.

也许不允许循环引用是一个更好的主意吗?

Maybe do not allow circular reference is a better idea?

我更喜欢发送另一个查询并在一个查询中执行多项操作.这要简单得多.

I prefer to sending another query and doing multiple operations in one query. It's much more simple.

更新

我找到了这个库: https://github.com/slicknode/graphql-query-complexity .如果graphql不限制循环引用.该库可以保护您的应用程序免受资源耗尽和DoS攻击.

I found this library: https://github.com/slicknode/graphql-query-complexity. If graphql doesn't limit circular reference. This library can protect your application against resource exhaustion and DoS attacks.

推荐答案

这要视情况而定.

请记住,在某些情况下相同的解决方案可能是一个好的模式,而在另一些情况下则是一个反模式,这很有用.解决方案的价值取决于您使用它的上下文. — 马丁·福勒

循环引用可能会带来其他挑战,这是有道理的.正如您所指出的那样,它们具有潜在的安全风险,因为它们使恶意用户能够编写可能非常昂贵的查询.以我的经验,它们还使客户团队更容易因疏忽而过度获取数据.

It's a valid point that circular references can introduce additional challenges. As you point out, they are a potential security risk in that they enable a malicious user to craft potentially very expensive queries. In my experience, they also make it easier for client teams to inadvertently overfetch data.

另一方面,循环引用可提供更高的灵活性.如果假设以下架构,请运行您的示例:

On the other hand, circular references allow an added level of flexibility. Running with your example, if we assume the following schema:

type Query {
  user(id: ID): User
  location(id: ID): Location
}

type User {
  id: ID!
  location: Location
}

type Location {
  id: ID!
  user: User
}

很明显,我们可以潜在地进行两个不同的查询来有效地获取相同的数据:

it's clear we could potentially make two different queries to fetch effectively the same data:

{
  # query 1
  user(id: ID) {
    id
    location {
      id
    }
  }

  # query 2
  location(id: ID) {
    id
    user {
      id
    }
  }
}

如果您的API的主要使用者是从事同一项目的一个或多个客户团队,那么这可能没什么大不了的.您的前端需要获取的数据具有特定的形状,并且您可以围绕这些需求设计方案.如果客户端始终获取用户,可以以这种方式获取位置,并且不需要该上下文之外的位置信息,则只进行user查询并从Location类型中省略user字段可能是有意义的.即使您需要一个location查询,根据客户的需求,在其上公开一个user字段可能仍然没有意义.

If the primary consumers of your API are one or more client teams working on the same project, this might not matter much. Your front end needs the data it fetches to be of a particular shape and you can design your schema around those needs. If the client always fetches the user, can get the location that way and doesn't need location information outside that context, it might make sense to only have a user query and omit the user field from the Location type. Even if you need a location query, it might still not make sense to expose a user field on it, depending on your client's needs.

另一方面,假设您的API被大量客户端占用.也许您支持多个平台,或者多个执行不同功能但共享相同API来访问数据层的应用程序.或者,也许您正在公开一个旨在使第三方应用程序与您的服务或产品集成的公共API.在这些情况下,您对客户需求的想法非常模糊.突然之间,公开多种查询基础数据的方式以满足当前客户和未来客户的需求变得更加重要.对于需要随时间变化的单个客户端的API来说,也可以这么说.

On the flip side, imagine your API is consumed by a larger number of clients. Maybe you support multiple platforms, or multiple apps that do different things but share the same API for accessing your data layer. Or maybe you're exposing a public API designed to let third-party apps integrate with your service or product. In these scenarios, your idea of what a client needs is much blurrier. Suddenly, it's more important to expose a wide variety of ways to query the underlying data to satisfy the needs of both current clients and future ones. The same could be said for an API for a single client whose needs are likely to evolve over time.

始终可以按照您的建议展平"架构并提供其他查询,而不是实现关系字段.但是,对于客户端而言,这样做是否简单"取决于客户端.最好的方法可能是使每个客户端都能选择适合其需求的数据结构.

It's always possible to "flatten" your schema as you suggest and provide additional queries as opposed to implementing relational fields. However, whether doing so is "simpler" for the client depends on the client. The best approach may be to enable each client to choose the data structure that fits their needs.

与大多数架构决策一样,需要权衡取舍,对于您来说正确的解决方案可能与对其他团队的解决方案不一样.

As with most architectural decisions, there's a trade-off and the right solution for you may not be the same as for another team.

如果您具有循环引用,则所有希望都不会丢失.一些实现具有用于限制查询深度的内置控件. GraphQL.js没有,但是有类似 graphql-depth-limit 的库做到这一点.值得指出的是,宽度 depth 一样是一个大问题-不管您是否有循环引用,都应考虑使用以下方法实现分页解析列表时的最大限制也可以防止客户端一次请求数千条记录.

If you do have circular references, all hope is not lost. Some implementations have built-in controls for limiting query depth. GraphQL.js does not, but there's libraries out there like graphql-depth-limit that do just that. It'd be worthwhile to point out that breadth can be just as large a problem as depth -- regardless of whether you have circular references, you should look into implementing pagination with a max limit when resolving Lists as well to prevent clients from potentially requesting thousands of records at a time.

正如@DavidMaze指出的那样,除了限制客户端查询的深度之外,您还可以使用dataloader来减轻从数据层重复获取同一记录的成本.虽然dataloader通常用于批处理请求,以解决由于延迟加载关联而引起的"n + 1问题",但它在这里也可以提供帮助.除了批处理之外,数据加载器还缓存加载的记录.这意味着相同记录(在相同请求内)的后续加载不会到达数据库,而是从内存中获取.

As @DavidMaze points out, in addition to limiting the depth of client queries, you can also use dataloader to mitigate the cost of repeatedly fetching the same record from your data layer. While dataloader is typically used to batch requests to get around the "n+1 problem" that arises from lazily loading associations, it can also help here. In addition to batching, dataloader also caches the loaded records. That means subsequent loads for the same record (inside the same request) don't hit the db but are fetched from memory instead.

这篇关于graphql模式循环引用是反模式吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆