【问题标题】:GraphQL 动态查询构建
【发布时间】:2018-12-21 16:06:12
【问题描述】:

我有一个 GraphQL 服务器,它能够为指定的源(例如,传感器数据)提供时间序列数据。获取传感器数据的示例查询可能是:

query fetchData {
    timeseriesData(sourceId: "source1") {
      data {
        time
        value
      }
    }
}

在我的前端,我希望允许用户选择 1 个或多个来源并显示一个图表,其中每个来源都有一条线。使用这样的查询似乎可以做到这一点:

query fetchData {
    series1: timeseriesData(sourceId: "source1") {
      data {
        time
        value
      }
    }
    series2: timeseriesData(sourceId: "source2") {
      data {
        time
        value
      }
    }
}

大多数 GraphQL 教程似乎都关注静态查询(例如,唯一改变的是变量,而不是请求的实际形状) - 但在我的情况下,我需要查询本身 是动态的(我选择的每个 id 都有一个 timeseriesData 请求)。

我有以下限制:

  1. 修改服务器的架构不是一种选择(例如,我不能将一组 ID 传递给解析器)
  2. 我的查询是使用模板字符串指定的,例如gql`...`
  3. 我不想手动将查询构建为字符串,因为这似乎是灾难的根源,并且意味着我会失去所有工具优势(例如自动完成、语法突出显示、linting)

我正在使用的堆栈是:

  • Apollo 客户端(特别是 apollo-angular)
  • 角度
  • 打字稿
  • graphql-tag(用于定义查询)

理想情况下,我想做的是将两个查询合并为一个,这样我就可以按照第一个示例定义它们,然后将它们连接到一个抽象层中,这样我就可以得到一个查询,例如第二个示例通过网络发送。

但是我不确定如何实现这一点,因为 graphql-tag 正在将查询解析为 AST,我很难理解以这种方式操作查询是否可行。

有哪些技术可以生成这样的动态查询,其中查询的形状是事先不知道的?

【问题讨论】:

    标签: graphql apollo apollo-client graphql-tag


    【解决方案1】:

    请记住,查询只是一个字符串。您可以使用简单的模板文字来实现您的动态查询。

    const generateQuery = () => {
      let query = ""
    
      for (let i = 1; i < 3; i++) {
        const series = `series${i}`
        const source = `source${i}`
    
        query += `
          ${series}: timeseriesData(sourceId: "${source}") {
            prices
            queried
          }
        `
      }
    
      return query
    }
    
    const fetchDataQuery = gql`
      query fetchData {
        ${generateQuery()}
      }
    `
    

    【讨论】:

      【解决方案2】:

      您可以使用片段来定义公共字段,并在该片段上使用变量绑定@include(if: Boolean)@skip(if: Boolean) 指令来获取在执行时已知的动态字段。

      根据规范,最好避免手动字符串插值来构造动态查询。

      指令1 可以根据作为查询变量传递的布尔表达式来包含或跳过字段。指令可以附加到字段或片段包含,并且可以以服务器所需的任何方式影响查询的执行。

      query Hero($episode: Episode, $withFriends: Boolean!) {
        hero(episode: $episode) {
          name
          friends @include(if: $withFriends) {
            name
          }
        }
      }
      

      在变量中:

      {
        "episode": "JEDI",
        "withFriends": false
      }
      

      当然,您可以像在第二个示例中那样发送命名查询。客户端会自动批处理请求。

      【讨论】:

      • @Matt Wilson 你有没有想过解决这个问题?我也有同样的情况。我从另一个服务中获得了一组 ID,对于每个我需要调用一个 graphql 查询并希望将它们作为 1 或批量调用
      • @Mr.7 嗨,我正在使用 Apollo 和 Apollo Client,发现这个“BatchHttpLink”是一种方法。我把我的解决方案放在这里:koala-moon.com/vue-js-apollo-set-up-with-batch-query-processing 和 Apollo Docs:apollographql.com/docs/link/links/batch-http 在客户端,您只是在创建一个与(Apollo)graphql 服务器的新连接,该连接会在批量发送请求之前等待一段时间。在过去的几个月里,它一直在处理 100 个用户查询,没有出现任何问题
      • 谢谢@user3067684 我去看看:)
      【解决方案3】:

      我认为当用户动态选择传感器时,您别无选择,只能使用字符串功能,即使您不知道开发时(而不是运行时)的传感器。

      const mainQuery = gql
        `query fetchData($sourceId: String!) {
          timeseriesData(sourceId: $sourceId) {
            data {
              time
              value
            }
          }
        }`;
      
      const mainQueryStr = mainQuery.loc.source.body;
      

      mainQueryStr 是查询的字符串值(用于处理问题的动态性) 然后循环传感器并将$sourceId替换为每个传感器的ID

      // You have to remove the query wrapper first
      // Then replace sensor id
      const sensorsQueries = sensors.map(sid => mainQueryStr
        .split(`\n`)
        .slice(1, 7)
        .replace(`$sourceId`, sid)
      )
      

      然后你应该加入 sensorQueries 并进行新的 GraphQL 查询

      const finalQuery = gql`
        query fetchData {
          ${sensorsQueries.join(`\n`)}`
        };
      

      在这种情况下,您可以使用自动完成、语法高亮等工具优势...... 对于mainQuery 查询,而不是finalQuery(因为您是动态创建的)

      【讨论】:

        【解决方案4】:

        我认为您可以为此使用fragments!但是在这种情况下,您仍然必须写 2 "queries" fragments

        首先让我们为每个timeSeries创建一个fragment,请检查您的timeSeries查询类型,我将其称为timeseriesDataQuery

        const series1Q = gql`
          fragment series1 on timeseriesDataQuery {
            series1: timeseriesData(sourceId: "source1") {
              data {
                time
                value
              }
            }
          }
        }
        
        const series2Q = gql`
          fragment series2 on timeseriesDataQuery {
            series2: timeseriesData(sourceId: "source2") {
              data {
                time
                value
              }
            }
          }
        }
        

        然后在查询中将它们拼接起来:

        export const mainQuery = gql`
            query fetchData {
              ...series1 
              ...series2
            }
            ${series1Q}
            ${series2Q}
        `    
        

        【讨论】:

        • 快速问题,如果我有多个片段,我不知道长度,我可以这样构造 mainQuery 吗?比如,在查询中动态添加多个片段?
        • @Evilsanta,只要你在主查询中动态定义de片段及其引用,我认为是可以的。
        猜你喜欢
        • 2019-11-15
        • 2012-11-23
        • 2020-01-21
        • 2018-11-25
        • 2019-04-08
        • 2012-12-21
        • 1970-01-01
        • 1970-01-01
        • 2013-08-12
        相关资源
        最近更新 更多