为什么我们决定对GraphQL本地状态管理

前言和动机
在OkCupid188bet金宝搏官网,我们非常的忠实粉丝GraphQL。时获取的数据在任何我们的客户平台,查询语言的抽象提供了赠款的灵活性为我们获取准确的数据,我们需要在每一个情况。
在一天结束的时候,GraphQL真的只是一个抽象。突变,查询,订阅类型抽象模型的基本方式与任何数据。模式作为数据来源和目的地之间的合同,它定义了数据可以查询以及它如何应该查询。传入的数据查询大纲将由我们解决GraphQL服务器实例在大多数情况下,但数据的目的地(在我们的例子中,假设它是一个移动应用程序或web应用程序作为客户端),不需要知道数据的源或解决战略问题。
这是非常好的,因为它意味着数据可以来自任何地方,GraphQL服务器访问。也许我们需要解决我们的数据使用的文件系统,或者一个本地数据库,或者远程。也许我们可以调用其他服务器暴露,通过RPC或休息或任何协议,真的。也许目前我们的数据在内存中某个地方,技术好!冷漠的数据源(s)是允许这个模型,和数据图是可伸缩的架构(曼迪明智的有一个伟大的视频,演示了这一点覆盖联邦的概念图)。
无论源的数据,客户端实现并不需要改变,这就是最重要的是重要的概念理解使用GraphQL本地状态管理。想象一下应用程序的本地状态:它真的只是另一个源的数据,毕竟,不是吗?所以,问题是,是什么阻止我们利用我们提供的抽象这种查询语言模式来管理这些数据吗?
它是如何工作的
好吧,答案是没有。如果你状态存储在您的应用程序,您可以从理论上解决GraphQL查询状态数据,如果你想。实现从阿波罗(在我们看来,事实上的提供者的gql),我们尝试了使用GraphQL指令来表示的一个给定的查询应以这种方式解决:@client
。
那将会是什么样子的一个例子,假设我们想要有一个应用程序,用户可以发送消息。让用户留言信息,我们会使我们的服务器查询。然而,也许我们想知道是否用户目前消息窗口开放对于任何给定的对话。服务器实际上并不关心这条信息对于每一个对话,但我们的前端应用程序很多关心它,所以它很有意义,我们可能会选择这样的存储在客户端状态。假设的情况下,一个可以构造一个合理的查询数据,包括本地状态,如下所示:
查询getAllMessages (userId美元:ID !) {
用户(id: $ userId) {
的名字
profilePic
消息{
id
记者{
的名字
profilePic
}
isOpen @client
}
}
}
在这个例子中,我们的isOpen
国旗为每个用户的信息可以存储在客户的状态,因为它不是一个后端问题。其余的数据,但是可以从服务器获取和什么需要改变。混合的能力我们的数据源(客户机和服务器)一个查询是一个非常强大的概念,并能导致一些令人难以置信的灵活的单个查询。
由于策略为解决这@client
指令是一个遍历,这意味着这个指令可以递归地应用于父子和neighbor-neighbor关系在我们的数据,允许我们实现相同的数据图的经验,除了我们的客户状态。我们的客户状态可以直接访问的母公司(non-client-state)结构,它可以一些简单的标量布尔标志、甚至更深入相关和结构化的数据。
客户端状态不需要相关的一些服务器数据,!它真的可以是任何数据我们可能想要存储客户端状态,也许像一个用户的当前设置的主题偏好,一个完全前端担忧,但仍然有状态的东西。黑暗的模式在这些天,对吧?
所以,这样的东西是如何工作的呢?引擎盖下面,我们需要添加一些新的块配置给我们ApolloClient
实例为了有一个策略来解决客户端查询。为此,我们通过添加一些新的解析器显式地拼写出来给我们的客户,就像我们写解决查询我们的服务器实例。的ApolloClient
可以添加解析器实例初始化时,以及在临时使用client.addResolver (someNewResolverToAdd)
。阿波罗定义处理的函数签名决议一样,它应该看起来很熟悉的如果你已经使用过apollo-server
过去:
类型ResolverFn = (
父:任何,
参数:任何,
{缓存}:{缓存:ApolloCache <一>}
)= >;
忽略了父节点和参数的参数,我们看到我们变性属性的对象,调用缓存
,就像我们变性数据源
财产的apollo-server
平行于这个函数类型。这是因为在这种情况下,我们的缓存了数据源的责任在我们的客户端。让我们看看客户端解析器设置看起来像在这种情况下:
const defaultResolver = {
查询:{
用户:{
消息:{
isOpen:(父母,args,{缓存})= > {
/ /引用缓存数据返回
cache.readQuery ({
查询:MESSAGE_IS_OPEN_QUERY,
变量:{
消息id: parent.id,
},
});
},
},
},
},
};
之后,我们还需要提供一些初始状态的缓存,所以我们的第一个缓存读会解决,所以我们想定义也喂给我们的缓存的初始化。
/ /定义初始客户端状态
const defaultState = {
用户:{
消息:{
isOpen:假的,
}
}
}const客户= new ApolloClient ({
/ /其他阿波罗配置,如链接
/ /缓存定义
解析器:defaultResolver,
});/ / '缓存的初始状态
client.writeData ({
查询:MESSAGE_IS_OPEN_QUERY,
数据:{defaultState,},
});
在这个例子中,我们设置默认状态为给定的消息
阿波罗的直觉是不仅提供这个指令和选项解决查询在客户端,但大多数提供了客户端缓存ApolloClient
实例定义无论如何(这是通常用于存储响应我们的服务器上查询,确实解决)作为一个位置来存储这个客户机的状态。这使很多意义上说,我们有一个本地存储(我们的ApolloClient
缓存),我们有一个相互交流的方式存储(使用gql)……从本质上讲,这是一个解决方案回来的(我敢肯定不需要介绍在这一点上)MobX给我们提供了对吧?
嗯,是的。它的工作原理非常好!然而当我们探索这个作为一个选项,我们注意到一些事情,最终导致我们做出的决定不是依靠阿波罗状态管理。
我们遇到的问题
那么为什么我们决定反对实施这个?嗯,背后的推理决策无疑是特定于我们的场景中,也许不会像重要他人,但包含了一些见解,我觉得会考虑任何沿着阿波罗的道路将不得不权衡。
开发人员开销和学习曲线
虽然这是一个阶段管理解决方案,它有一些新的开销。一个必须为他们的客户端状态编写解析器现在,尽管是这样的任何状态管理选项,它不像看起来那么简单。
真正写这些解析器正确,你会想要一些经验/能力直接使用缓存。从版本3.0开始,阿波罗/客户端
已经取得了一些激烈的更新缓存如何处理规范化和非规范化数据必须执行哪些操作。了解缓存使用id
年代和__typename
年代,决定是否合并或替换数据,学习如何这样做与这个范例都是意料之中的事。旁注,哈利勒@阿波罗刚刚出版令人难以置信的博客进入深度对阿波罗缓存和理解缓存正常化。这给了我们一个缓存数据的两种选择:要么确保每个查询请求一个唯一标识字段的数据我们请求(这失败的目的要求我们想要的字段?)或写明确typePolicies
告诉我们如何规范化数据缓存。从别人的角度编写一个客户端库,为许多不同的用例,正常工作我明白了这样一个解决方案的动机。然而,这只是一个问题通过一个解决方案与客户端状态不像回家的。
对比这种模式类似反应的上下文API在串联useReducer钩,甚至回来的架构,似乎更喜欢阿波罗的解决方案是理解和管理从开发人员的角度来看。权衡,不过,我们获得思考和与之交互的能力我们所有的应用程序的数据以同样的方式,这无疑是一个可怕的好处。但这是值得的吗?
一点新,一点借来的?
好吧,我们已经与回来的在俄合作,甚至在一些年长的例子代码中,回流。添加一个新的选择状态管理在我们的应用程序会引起纷乱,不可否认会复杂,新员工培训的人到我们队来包装他们的头。就我个人而言,我觉得开发经验和可维护性应该定义任何体系结构或框架的因素决定。
“回来的已死”的论点已经多次提出,和传统的成本的形式与它相关联的吨样板和包装组件可以很容易地认为不是很可伸缩的,作为一个有更改一些文件之前。尽管如此,不过,它确实已经成熟。如果做得正确,绝对可以处理,它显然有一些持久力(更不用说,工作回来的钩子实际上是很好的)。更不用说,拔出现有架构将耗时数月,并添加另一个模式学习和遵循与现有的状态管理模式会导致更多的上下文切换开发者编写代码时,他们更可能只是迷惑和负担。
除此之外,还有无数的资源由其支配使用和理解回来的(或任何成熟的OSS,),有一件事我个人当然有问题当研究阿波罗客户端状态管理;只是没有尽可能多的文档、视频和文章在阿波罗的方法,也许因为它是更模糊或早期的生活相比,就像回家的。同时,一个更成熟的解决方案可能会提供更多的价值的定义一个稳定的API为开发人员,而年轻的一个在这方面会更加动荡(这是所有非常情境,我承认)。
然而,拥有的共同反对回来的“改变这么多文件”和“它过于复杂”似乎并不弥补由阿波罗的解决方案。我们仍然想明智地把解析器的定义和初始状态,做这样的自己后,我感觉我只是写一些回来的样板,我觉得很讽刺。直接使用缓存似乎并不那么复杂,我工作的商店,。
阿波罗也有一个开发工具提供,我真的很喜欢使用,发现是有用的,但也感觉有点不成熟时提出反对像回家的平行。有时,它不想发射。它不提供有趣的特性,如时间旅行,我很兴奋的一件事关于使用它,这是客户端自省,需要定义typedef
在ApolloClient
实例(这就是为我们的客户端创建一个模式的过程状态,也就是另一个成套的担心和管理,但我承认也许打印稿或codegen真的可以照耀在这个场景中)。我使用其他库,没有一个需要定义的形状我们的客户端状态多次,如果有的话,它开始看起来像一些Apollo-side样板开始积累。
其他选项
我们也一直在研究反应的上下文API,大概将来要考虑一些其他的选择。然而,我们选择有影响的考虑包大小对我们非常重要。上下文/阿波罗删除需要一个状态管理完全依赖吗?对于一些简单的应用程序,我认为那里的例子,上下文被证明是足够了。同样,有一些例子的阿波罗的解决方案不够多!还有MobX, Facebook的新开源供应区反冲,甚至你的客户端状态用状态机建模XState。
我们网络出哪里?
很难不认识到阿波罗所做的令人难以置信的工作。阿波罗和GraphQL奇迹清理我们的api和客户的一般网络层。追求它作为状态管理的选择,然而,有重大影响,我们的代码库,这个时候我们就不感觉足够的论点是引人注目的成熟度阿波罗的客户端状态管理我们的客户端状态和当前的设置。实现新东西的投资回报在代码库的现有架构需要相对较高的在我看来。我只是不确定,阿波罗的投资回报是足够高的。
带着一些新的见解后阿波罗的探索提供一些更多,不过,我们希望能够得出更好的结论最终我们应该依靠为了解决这个问题,我们应该如何思考的方法,架构,和权衡任何库、框架或工具我们决定卷。在那之前,我们将继续坚持我们的眼睛对阿波罗的客户端解决方案的发展状态。
最初发表在https://tech.188bet金宝搏官网okcupid.com2020年8月20日。