为什么我们反对GraphQL的当地国家管理

前言和动机
在Okcupid188bet金宝搏官网,我们非常大的粉丝使用GraphQL.当涉及到在任何客户机平台上获取数据时,查询语言提供的抽象为我们提供了在每种情况下精确获取所需数据的灵活性。
在一天结束时,GraphQL真的就是:抽象。突变,查询和订阅类型被抽象地绘制了我们与任何数据交互的基本方式。该模式用作某些数据源及其目的地之间的契约,它定义了可以查询的数据以及它应该如何查询。这data that an incoming query outlines would be resolved by our GraphQL server instance in most cases, but the destination of that data (in our case, let’s say it’s a mobile app or web app acting as a client), doesn’t really need to know about the source or resolution strategy of the data in question.
这非常好,因为这意味着数据可以来自GraphQL服务器能够访问的任何地方。也许我们想要使用文件系统中的某些东西来解析数据,或者使用本地数据库,或者远程数据库。也许我们可以通过RPC或REST或任何协议调用向我们公开的其他服务器。也许我们的数据目前在内存中的某个地方,这在技术上也很好!数据源的无差异使得这个模型和数据图的架构具有如此高的可伸缩性(Mandi Wise有一个很好的视频演示了这一点)介绍了联邦图的概念).
无论数据的来源如何,客户端实施都不需要完全更改,这是最重要的是对于理解使用GraphQL进行本地状态管理的概念非常重要。想象一下您的应用程序的本地状态:它实际上只是另一个数据源,不是吗?那么,问题是,是什么阻止了我们利用查询语言范式提供给我们的抽象来管理这些数据呢?
它是如何工作的
答案是什么都没有。如果将状态存储在应用程序中的某个地方,理论上可以使用状态数据解析GraphQL查询。实现从阿波罗(在我们看来,实际上是gql的所有东西的提供者),我们尝试使用一个GraphQL指令来表示给定查询的哪些部分应该以这种方式解析:@客户
.
举个例子,假设我们想要一个用户可以互相发送消息的应用程序。为了获得用户向谁发送了消息的信息,我们可能会向服务器进行查询。然而,也许我们想知道用户当前是否有一个针对他们给定对话的消息窗口打开。服务器实际上并不关心每个会话的这段信息,但我们的前端应用程序非常关心它,因此我们可以选择在客户机状态中存储类似这样的信息。假设是这种情况,那么就可以为该数据构建一个合理的查询,包括本地状态,如下所示:
query getAllMessages($userId: ID!) {
用户(id: $ userId) {
的名字
用户照片
消息{
id
记者{
的名字
用户照片
}
isopen @client.
}
}
}
在这个例子中,我们的isOpen
每个用户消息的标记都可以存储在我们的客户端状态中,因为它不是一个后台问题。但是,其余的数据可以从我们的服务器获取,其他的不需要更改。为单个查询混合数据源(客户端和服务器)的能力是一个非常强大的想法,可以产生一些非常灵活的单个查询。
因为策略为解决这@客户
指令是一个遍历,这意味着该指令可以在我们的数据中递归地应用于父母和邻居关系,允许我们实现数据图的相同体验,除了我们的客户状态。我们的客户状态可以直接访问其父级(非客户端状态)结构,并且它可以像标量字段一样简单,如布尔标志,甚至是更深入地相关和结构的数据。
客户端状态也不需要与某些服务器数据相关!它可以是我们想要存储为客户机状态的任何数据,比如用户当前的主题首选项设置,这完全是一个前端问题,但仍然是有状态的。黑暗模式现在很流行,对吧?
那么,这种东西是怎么运作的呢?在引擎盖下,我们需要添加一些新的配置到浮潜
实例是为了让它具有解析客户端查询的策略。为此,我们通过向我们的客户端添加一些新的解压器来显式拼写这一点,就像我们写入我们的服务器实例上的解析查询一样。这浮潜
实例可以在初始化后以及使用ad-hoc基础上添加解析器client.addResolver (someNewResolverToAdd)
.Apollo定义了处理如下分辨率的函数签名,如果您合作,它应该看起来非常熟悉Apollo-Server.
过去:
键入solderverfn =(
父:任何,
参数:任何,
{cache: ApolloCache}
) = >;
忽略父节点和实参参数,可以看到我们将一个属性从名为缓存
,就像我们破坏a数据源
财产的Apollo-Server.
与此函数类型平行。这是因为在这个场景中,缓存承担了客户端数据源的责任。让我们看看在这个上下文中客户端解析器的设置是什么样的:
const defaultResolver = {
查询:{
用户:{
消息:{
InOpen :(父,args,{cache})=> {
//引用缓存以获取数据返回
cache.readQuery ({
查询:MESSAGE_IS_OPEN_QUERY,
变量:{
消息id: parent.id,
},
});
},
},
},
},
};
之后,我们还希望使用一些初始状态提供缓存,因此我们的第一个缓存读取将解决,因此我们也希望在初始化时向我们的缓存汇合并将其馈送到我们的缓存。
//定义初始的客户端状态
const defaultState = {
用户:{
消息:{
isOpen:假的,
}
}
}const client = new ApolloClient({
//其他阿波罗配置,例如link
//和缓存定义
解析器:defaultResolver,
});//使用初始状态初始化缓存
client.writeData ({
查询:MESSAGE_IS_OPEN_QUERY,
data: {defaultState,},
});
在此示例中,我们只是为给定消息设置默认状态
阿波罗的直觉是,不仅要提供这个指令和在客户端解析查询的选项,还要提供客户端缓存浮潜
无论如何都定义了实例(通常用于将其查询的响应存储在服务器上实际解析为存储此客户端状态的位置。这有很多意义,我们有一个本地商店(我们的浮潜
我们有一种与存储交互的方法(使用gql)…这就是解的本质雷(我确定现在不需要介绍了)或者莫克斯为我们提供吧?
嗯,是的。它也工作得非常好!然而,当我们将其作为一种选择进行探索时,我们注意到一些最终导致我们决定不依赖阿波罗进行状态管理的事情。
我们遇到的问题
那么,为什么我们决定不执行这个呢?这个决定背后的理由肯定是特定于我们的设想的,也许对其他人来说不那么重要,但确实包含了一些见解,我认为这是任何踏上阿波罗之路的人都必须考虑的因素。
开发开销和学习曲线
尽管这可以作为一个阶段管理解决方案,但它也带来了一些新的开销。现在,我们必须为客户端状态编写解析器,尽管情况正是如此任何国家管理选项,它不像看起来那么简单。
要真正正确地编写这些解析器,你需要有一些直接使用缓存的经验/能力。从3.0版开始,阿波罗/客户端
有一些人对缓存处理规范化和非规范化数据的方式进行了相当激烈的更新.理解缓存如何使用id
年代和__typename
S,决定是否合并或替换数据,并学习如何这样做,都是这种范式的过程。旁注,哈利勒@阿波罗最近出版了一篇不可思议的博客文章深入了解阿波罗贮藏和贮藏正常化。这为我们的缓存数据提供了两种选择之一:要么确保每个查询请求一个唯一标识我们所请求的数据字段(这难道不违背请求我们想要的任何字段的目的吗?Typepolicies.
告诉缓存如何对数据进行规范化。从某些编写客户端库的人的角度来看,这个库必须正确地工作于许多不同的用例,我理解这种解决方案的动机。然而,通过Redux这样的解决方案,这并不是客户端状态的问题。
将这个范例与反应的上下文API与…一致useReducer钩,甚至Redux体系结构,阿波罗解决方案似乎更需要从开发人员的角度来理解和管理。然而,通过这种权衡,我们确实获得了思考和互动的能力所有应用程序的数据以同样的方式,这无疑是一个可怕的好处。但这值得吗?
新东西,借来的东西?
好吧,我们已经在OkC使用Redux了,在一些旧代码的例子中,甚至使用了Reflux。在我们的应用程序中添加一个新的状态管理选项将会导致一团混乱,这对于刚加入我们团队的人来说无疑是非常复杂的。就我个人而言,我认为开发人员经验和可维护性应该是任何架构或框架决策的决定性因素。
“Redux已经死了”的说法已经被提了很多次,而与之相关的传统成本是以吨可以轻松地认为样板和包装组件不能非常可扩展,因为一个人必须在获得任何地方进行更改。尽管如此,这一年肯定已经成熟了。如果做得吧,它肯定可以用来与之合作,它清楚地确实有一些留力(更不用说,与Redux钩子一起工作实际上非常好)。更不用说,剥夺我们现有的架构需要数月,并添加另一个范例来学习和遵循现有的国家管理范例,当DEVS写作代码时会导致更多的上下文切换,并且可能只是混淆和负担更多。
最重要的是,还有无数的资源可以用来使用和理解Redux(或任何成熟的OSS,就这一点而言),我个人在研究阿波罗客户端状态管理时肯定有一个问题;关于阿波罗方法的文档、视频和文章没有那么多,可能是因为它比Redux之类的东西更晦涩或还处于早期阶段。此外,一个更成熟的解决方案在为开发人员定义一个稳定的API方面可能提供更大的价值,而一个较年轻的解决方案在这方面可能更不稳定(不过,我承认,这是非常具体的情况)。
然而,违背赎回的常见争论必须“改变如此多的文件”和“过于复杂的”并不是真的由阿波罗的解决方案纠正。我们仍然希望明智地哄骗解析器和初始状态的定义,并且在这样做的事情之后,我觉得我只是写了一些redux,这对我来说感到非常讽刺。除了与商店一起工作,似乎直接与缓存直接工作似乎不太复杂。
Apollo也有一个开发工具提供,我真的很喜欢使用并发现是有用的,但在忍受像Redux的平行之类的东西时,它对TAD不成熟。有时,它不想发布。它并没有提供有趣的功能,如时间旅行,我对使用它的一件事是兴奋的,这是客户端的内省,所以需要定义typedef
在浮潜
实例(这基本上是为我们的客户端状态创建架构的过程,这实际上只是担心和管理的另一个整个事物,但我承认,或许可以在这种情况下真正发光的打字或Codegen).在我使用的其他图书馆中,没有必要多次定义客户端状态的形状,如果有的话,它开始看起来像一些开始累积的阿波孔侧的样板。
其他选择
我们也一直在试验React的上下文API,并且在未来可能也会考虑一些其他的选择。然而,考虑我们选择的内容对捆绑包大小的影响也非常重要。Context/Apollo能够完全消除对状态管理依赖的需求吗?对于一些更简单的应用程序,我认为已经有一些例子证明Context已经足够了。同样,也有一些阿波罗解决方案的例子!还有Facebook的新开源产品MobX反冲,甚至使用状态机来建模客户端状态XState.
我们在哪里网?
很难不承认阿波罗所做的令人难以置信的工作。Apollo和GraphQL在清理api和客户端的通用网络层方面做得很好。然而,将其作为状态管理的一种选择,对我们的代码库有重大的影响,而且考虑到Apollo的客户端状态管理的成熟度和我们当前的客户端状态设置,我们只是觉得这个争论不够有说服力。在我看来,在拥有大量现有架构的代码库中实现一些新内容的投资回报率需要相对较高。我只是不确定阿波罗的投资回报是否足够高。
带着一些新的见解后阿波罗的探索提供一些更多,不过,我们希望能够得出更好的结论最终我们应该依靠为了解决这个问题,我们应该如何思考的方法,架构,和权衡任何库、框架或工具我们决定卷。在那之前,我们将继续关注阿波罗的客户状态解决方案如何发展。
最初出版https://tech.188bet金宝搏官网okcupid.com2020年8月20日。