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

查看:21
本文介绍了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 useful to remember that the same solution can be a good pattern in some contexts and an antipattern in others. The value of a solution depends on the context that you use it. — Martin Fowler

循环引用会带来额外的挑战,这是一个有效的观点.正如您所指出的,它们是潜在的安全风险,因为它们使恶意用户能够制作可能非常昂贵的查询.根据我的经验,它们还让客户团队更容易无意中过度获取数据.

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 查询并省略 user<Location 类型中的/code> 字段.即使您需要 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就是这样做.值得指出的是,广度可能与深度一样大——无论您是否有循环引用,您都应该考虑实现分页解析列表时的最大限制,以防止客户端可能一次请求数千条记录.

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 问题",但它在此处也有帮助.除了批处理之外,dataloader 还会缓存加载的记录.这意味着同一记录(在同一请求内)的后续加载不会命中数据库,而是从内存中获取.

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天全站免登陆