在实际开发中,一个项目后端会拆分成几个微服务进行写,如:用户一个模块体系,订单一个模块体系,支付一个模块体系等,后端会提供不同二级域名的
api
接口。如果后端使用Graphql
而不是使用Restful Api
,这样我们就要考虑多个Apollo
客户端。在实际项目中我也遇到这个问题了。今天我们翻译篇文章,关于REACT
使用多个Apollo
客户端,后续我也会把我最后完成的demo放出来,供大家参考。这篇文章来自于Medium上面,作者:Rafael Nunes,翻译文章原始地址
Apollo Multiple Clients with React?
这篇文章快速解释了,如何在同一个React应用程序中使用不同的Apollo clients,但在最后,在使用多个GraphQL APIs
时讨论了其他方法。这并不是有意要以任何方式去质疑GraphQL
原理!
写这篇文章的原因是,作者发现自己遇到问题了,在React
应用程序中怎么使用多clients查询不同GraphQL APIs
。这表明Apollo GitHub
项目中存在很多问题,讨论需求并提出实施建议。
TL;DR: passing any ApolloClient instance to Query/Mutation/Subscription components as props works just fine! Check: https://github.com/peaonunes/apollo-multiple-clients-example
下面列出了与相关问题、讨论和方案的一些链接。一些旧方案确实也已经合并,并且带有老的react-apollo
版本。然而,从2.1版本开始Apollo客户端的使用和查询发生了许多变化(for better)。
- https://github.com/apollographql/react-apollo/pull/481
- https://github.com/apollographql/react-apollo/issues/464
- https://github.com/apollographql/react-apollo/issues/1588
- https://github.com/apollographql/react-apollo/pull/729
Why would we need multiple clients?
Apollo Client
在初始化的时候只接受一个client uri
。因此,就意味着同时只能使用一个client。
import ApolloClient from "apollo-boost";
const client = new ApolloClient({
uri: "https://48p1r2roz4.sse.codesandbox.io"
});
例如,如果在React
应用程序中需要从两个不同的GraphQL
服务中获取数据,就不能使用相同的client
或者程序实例了。
具体来说,我只是在找一个快速的实施方法从两个GraphQL APIs
获取数据来验证解决方案。不必担心架构冲突,因类型、缓存、状态等等不会重叠。
在场景中,在Apollo
上查询API时,有一种切换客户端的方法是有意义的。在当前方法中,使用ApolloProvider
组件包裹你的整个应用程序,该组件通过上下文给应用程序传递实例化好的客户端。
import React from "react";
import { render } from "react-dom";
import { ApolloProvider } from "react-apollo";
import ApolloClient from "apollo-boost";
const client = new ApolloClient({
uri: "https://w5xlvm3vzz.lp.gql.zone/graphql"
});
const App = () => (
<ApolloProvider client={client}>
<div>
<h2>My first Apollo app 🚀</h2>
</div>
</ApolloProvider>
);
render(<App />, document.getElementById("root"));
实际上使用Query Component
使查询数据变得简单,但这也意味着,当查询时,客户端提供的上下文变量是唯一的。
作者花了一些时间查阅了大量问题和相关项目,结果发现有一种方法可以覆盖Query
和Mutation
组件的客户端上下文,通过另一个客户端的props
传递。
<Query client={anotherClient} query={query}>
{({ data }) => (<div>{data.name}</div>)}
</Query>
Update, Aug 2019: Although they have changed the implementation it still works. https://github.com/apollographql/react-apollo/blob/master/packages/components/src/Query.tsx#L17
// https://github.com/apollographql/react-apollo/blob/master/src/Query.tsx#L167
// https://github.com/apollographql/react-apollo/blob/master/src/Mutation.tsx#L120
...
this.client = props.client || context.client;
...
官方文档并没有提及此功能。我们可以为组件传递任何client
,这些client
将优先通过props
而不是context
传递。下面案例:
// ...
const customClient = new ApolloClient({
uri: "http://other-api/graphql"
});
const Dogs = ({ onDogSelected }) => (
<Query query={GET_DOGS} client={customClient} >
{({ loading, error, data }) => {
if (loading) return "Loading...";
if (error) return `Error! ${error.message}`;
return (
<select name="dog" onChange={onDogSelected}>
{data.dogs.map(dog => (
<option key={dog.id} value={dog.breed}>
{dog.breed}
</option>
))}
</select>
);
}}
</Query>
);
// ...
作者已经实现了一个可运行的示例,该示例使用了两个不同的clients
,地址: https://github.com/peaonunes/apollo-multiple-clients-example
即使这个方法是有效的,也应该记住,除非同时传递两个clients
的缓存,否则不会为两个clients
同时运行Apollo
功能(如果发生模式冲突,可能会有风险),单独管理其他功能。Apollo
功能将会受到损害,并且随着应用程序的成长,代码库变得更冗余,开发将可能变得缓慢。
What would be the ideal approach then?
Solving the problem in the Frontend
本文讨论的结果,folks已经提出了一些解决方法,为解决这个问题他们做了自己的抽象/实现层。
Community own package implementations
Michael Duve写一个插件react-apollo-multiple-clients,用这个插件可以在多个clients
之间转换。这个插件有多个提供者,它提供了一个高级组件(HOC
),接收client prop
,切换到使用者想要的client
。进入里面讨论
Paul Grieselhuber,他建议了一种方法,所有的请求都是一个单client
,选择一个uri
方便的触发上下文,client
就将请求分发出去。你可以在这里关注讨论。
Client-side schema stitching
尽管支持服务端,很少有人尝试在客户端上直接解决问题,在客户端上有一些问题正在寻找或请求联合,例如:#797
不过,Hasura公司给出了一个可行方案,关于客户端模式联合,在你的案例它可能已经足够了。
尽管作者认为这些方法可以解决这个问题,但是也认为随着应用程序增长,这些解决方法会增加前端程序的复杂性。我的观点,这个功能应该由后端来做,为所有的不同的APIs
应提供统一的接口。
Gateways for Frontends
API网关是一种众所周知的模式,在我们的“微服务热潮”时代,采用频率越来越高。API网关是服务端和客户端之间的单个接口。
GraphQL开发中似乎已经达成共识,API网关是与不同GraphQL API进行连接的方式。然后有时网关会超越此范围,因为网关本身可以为其它REST和RPC API创建GraphQL 接口。
通过唯一网关提供不同APIs的真正问题是如何管理和编排不同的数据结构。
Client-side schema stitching
正如本文前面提到的,Apollo团队提倡的首次尝试是数据结构粘合。
经过一段时间的发展,社区反馈这个方法是错弱的,现在已经被弃用了。
Apollo Federation
Apollo最近推出了一个新概念,叫做“Apollo Federation”,解决一个网关管理不同数据结构的问题。
“Apollo Federation is our answer for implementing GraphQL in a microservice architecture. It’s designed to replace schema stitching and solve pain points such as coordination, separation of concerns, and brittle gateway code.” James Baxley III
在这之前他们已经推出了Federation规范,在一些语言中已经实行了,例如:apollo-gateway。这个想法要有一个组成架构的网关,并且可以通过keys
联合服务(就像主键),还能够扩展types
。所有的这些仅仅用了GraphQL常规的规范。
建议花一些时间观看下面视频,并花一些时间尝试一下这种更好的方法。
作者亲自尝试过,并且看到公司正在基于这种新方法开发解决方案。同样值得注意的是,还有其他挑战和空间,例如管理身份验证/授权,网关应具有的灵活性等其他讨论。希望Federation根据社区和公司的反馈而不断发展。
Conclusion
正如这篇文章之前提到的,它不是明确的给出多个GraphQL API的“正确”方法,是要指出希望解决当前这个问题的方法。
作者认为使用API网关和管理不同的GraphQL数据结构的整个讨论才刚刚开始,社区将致力于更完美更好的解决方案。
翻译结束
如翻译有问题请多谢指出!
这篇文章作者也提到了两个方案解决apollo multiple clients,一个是在单个query里面添加client,一个是通过网关的方式解决。在单个query里面添加client代码不是很优雅;后端确实也会做相应的网关,但不能解决全部问题,实际项目中还是需要解决。在下一篇文章我将介绍另一个方法使用Apollo multiple clients,这个也许是个更好的解决方案。