GraphQL 缝合和联合

Posted

技术标签:

【中文标题】GraphQL 缝合和联合【英文标题】:GraphQL stitch and union 【发布时间】:2020-07-15 11:05:19 【问题描述】:

我需要将多个 graphQl 服务(具有相同架构)“聚合”为单个只读(仅查询)服务,从而公开来自所有服务的数据。例如:

---- domain 1 ----
    "posts": [
      
        "title": "Domain 1 - First post",
        "description": "Content of the first post"
      ,
      
        "title": "Domain 1 - Second post",
        "description": "Content of the second post"
      
    ]

---- domain 2 ----
    "posts": [
      
        "title": "Domain 2 - First post",
        "description": "Content of the first post"
      ,
      
        "title": "Domain 2 - Second post",
        "description": "Content of the second post"
      
    ]

我知道“拼接”并不是针对这样的 UC,而是更多地将不同的微服务组合到同一个 API 中。为了在单个 API 中使用相同的类型(名称),我通过动态地将域名附加到所有数据类型来实现“穷人命名空间”。但是,我只能使用以下两种不同类型进行查询:

query 
  domain_1_posts 
    title
    description
  
  domain_2_posts 
    title
    description
  

但是,它的数据集包含两个数组:


  "data": 
    "domain_1_posts": [
       ...,
    ],
    "domain_2_posts": [
       ...,
    ]
  

我想听听您的想法,我可以做些什么来将它组合成仅包含 posts 的单个数据集? 一种想法是添加自己的解析器,它可以调用实际的解析器并将结果组合到单个数组中(如果完全支持的话)。 此外,作为 B 计划,我可以发送“域”参数进行查询,然后构造对第一个或第二个域的查询(但是,为了保持初始查询“与域无关”,例如在查询本身中不使用域名?

提前感谢所有建议...

【问题讨论】:

您可以像用户令牌一样在标头中发送域名(使用 apollo 链接) 谢谢@xadm 的建议,我会研究链接的可能性 【参考方案1】:

我设法为我的用例找到了解决方案,所以我会把它留在这里,以防有人碰到这个线程......

如前所述,拼接应该用于从多个 API 段(微服务)组合单个端点。如果您尝试拼接包含相同类型或查询的架构,您的请求将被“路由”到预先选择的实例(因此,只有一个)。

正如@xadm 所建议的那样,将来自多个模式的数据“合并”到单个数据集中的关键在于对用于远程模式的链接使用自定义获取逻辑,如下所述:

1) 定义符合您业务需求的自定义获取函数(简化示例):

const customFetch = async (uri, options) => 

    // do not merge introspection query results!!!
    // for introspection query always use predefined (first?) instance
    if( operationType === 'IntrospectionQuery')
      return fetch(services[0].uri, options);
    

    // array fecth calls to different endpoints
    const calls = [
        fetch(services[0].uri, options),
        fetch(services[1].uri, options),
        fetch(services[2].uri, options),
        ...
    ];

    // execute calls in parallel
    const data = await Promise.all(fetchCalls);

    // do whatever you need to merge data according to your needs
    const retData = customBusinessLogic();

    // return new response containing merged data
    return new fetch.Response(JSON.stringify(retData), "status" : 200 );


2) 使用自定义获取函数定义链接。如果您使用相同的架构,则不需要为每个实例创建链接,只需一个就足够了。

const httpLink = new HttpLink(services[0].uri, fetch: customFetch );

3) 使用 Link 创建远程可执行架构:

const schema =  await introspectSchema(httpLink );
return makeRemoteExecutableSchema( 
    schema, 
    link: httpLink,
    context: ( req ) => 
        // inject http request headers into context if you need them
        return 
            headers: 
                ...req.headers,
            
        
      , 
)

4)如果你想将http headers一直转发到fetch函数,使用apollo ContextLink:

   // link for forwarding headers through context
   const contextLink = setContext( (request, previousContext) => 
     if( previousContext.graphqlContext )
         return 
            headers: 
                ...previousContext.graphqlContext.headers
            
             
    
  ).concat(http);  

顺便提一下,用于此的依赖项:

const  introspectSchema, makeRemoteExecutableSchema, ApolloServer  = require('apollo-server');
const fetch = require('node-fetch');
const  setContext  = require('apollo-link-context');
const  HttpLink  = require('apollo-link-http');

我希望它对某人有帮助......

【讨论】:

以上是关于GraphQL 缝合和联合的主要内容,如果未能解决你的问题,请参考以下文章

GraphQL 联合和输入类型?

类型-graphql。字符串、布尔值和数字的联合类型失败

如何使用 GraphQL 和 ApolloStack 解析联合/接口字段

处理子查询中的 graphql 模式拼接错误

Graphql 联合在扩展关系上返回 null

了解 GraphQL 联合类型