为什么我们决定对地方国家管理的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或休息或任何协议将其他其他服务器调用,或任何协议。也许我们的数据目前在某处内存,这也很好!我们的数据源的漠不关心是允许此模型的允许,以及数据图的体系结构如此可扩展(Mandi Wise有一个展示这一点的很棒的视频涵盖联邦图的概念)。

无论数据的来源如何,客户端实施都没有真正需要更改,这是至关重要的了解使用GraphQL获取本地管理的概念很重要。想象一下你的应用程序的当地国家:它真的只是另一个数据来源,毕竟不是吗?所以,那么问题是,这是什么阻止我们利用此查询语言范例提供给我们的抽象来管理此数据?

这个怎么运作

好吧,答案是什么。如果您在应用程序中的某个位置存储状态,则可以理解地刚刚将GraphQL查询解析为具有该状态数据,如果需要。实施从阿波罗(我们看来,我们尝试使用GraphQL指令的所有事物GQL的事实上提供者,以表示应以这种方式解决哪些给定查询:@客户

有关那个看起来的示例,让我们想象我们希望拥有用户可以彼此发送消息的应用程序。要获取有关用户有邮件的信息,我们可能会对我们的服务器进行查询。但是,也许我们想知道用户目前是否有一个给定的对话的任何消息窗口。服务器实际上并不关心每次对话的这段信息,但我们的前端应用程序会关心它,因此我们可能会发现我们可能会选择在客户状态下存储类似的东西。假设是这种情况,然后可以为该数据构建合理的查询,包括所包括的本地状态,如下所示:

查询getAllMessages($ UserID:ID!){
用户(ID:$ UserID){
名称
用户照片
消息{
ID
记者{
名称
用户照片
}
isopen @client.
}
}
}

在这个例子中,我们的开了用户邮件的标志可以存储在我们的客户状态中,因为它并不大部分问题。但是,可以从我们的服务器获取其余的数据,并且没有其他需要更改。能够将我们的数据源(客户端与服务器)与单个查询混合是一个非常强大的想法,可以导致一些令人难以置信的灵活的单个查询。

因为策略解决这个问题@客户指令是一个遍历,这意味着该指令可以在我们的数据中递归地应用于父母和邻居关系,允许我们实现数据图的相同体验,除了我们的客户状态。我们的客户状态可以直接访问其父级(非客户端状态)结构,并且它可以像Boolean标志等标量字段一样简单,甚至是更深入地相关和结构的数据。

客户状态不需要与某些服务器数据相关!它真的可以是我们可能想要存储为客户状态的任何数据,例如用户当前的主题偏好的当前设置,完全是前端关注,但仍然存在一些状态。虽然暗模式是如此,但是,对了吗?

那么,这样的工作如何?嗯,在引擎盖下,我们需要向我们的新配置添加一些新配置apolroclient.实例,以便它具有解决客户端查询的策略。为此,我们通过向我们的客户端添加一些新的解压器来显式拼写这一点,就像我们写入我们的服务器实例上的解析查询一样。这apolroclient.实例可以在初始化后以及使用ad-hoc时添加resolversclient.addresolver(Somenewresolvertoadd)。Apollo定义了处理如此如此的函数签名,如果您合作,它应该看起来非常熟悉Apollo-Server.在过去:

键入solderverfn =(
父母:任何,
args:任何,
{cache}:{cache:apolrocache }
)=>任何;

忽略父节点和参数参数,我们看到我们破坏了对象的属性,调用缓存,就像我们破坏a数据源物业在Apollo-Server.与此功能类型并行。这是因为在这种情况下,我们的缓存已经承担了客户端世界的DataSource的责任。让我们看看在此上下文中的客户端解析器安装程序将是什么样的:

const defaultresolver = {
询问: {
用户:{
消息:{
InOpen :(父,args,{cache})=> {
//引用缓存以获取数据返回
cache.readquery({
查询:message_is_open_query,
变量:{
MessageId:parent.id,
},
});
},
},
},
},
};

之后,我们还希望使用一些初始状态提供缓存,因此我们的第一个缓存读取将解决,因此我们也希望在初始化时将其馈送到我们的缓存中并将其馈送到我们的缓存。

//定义初始客户端状态
const defaultstate = {
用户:{
消息:{
isopen:false,
}
}
}
const client = new ApolloClient({
//其他apollo配置,例如链接
//和缓存定义
解析器:defaultResolver,
});
//用初始状态将缓存称为
client.writedata({
查询:message_is_open_query,
数据:{defaultstate,},
});

在此示例中,我们只是为给定消息设置默认状态

Apollo的直觉是不仅提供此指令和解决客户端在客户端的查询的选项,而且可以提供最多的客户端缓存apolroclient.无论如何都定义了实例(通常用于将其查询的响应存储在服务器上的查询)作为存储此客户端状态的位置。这有很多意义,我们有一个本地商店(我们的apolroclient.'s缓存),我们有办法与该商店互动(使用GQL)......这实际上是什么样的解决方案(我肯定需要在这一点上没有介绍)或莫克斯为我们提供对吗?

嗯,是的。它也工作得非常好!然而,当我们将其作为一种选择进行探索时,我们注意到一些最终导致我们决定不依赖阿波罗进行状态管理的事情。

我们遇到的问题

那么我们为什么决定实施这一点?嗯,决定背后的推理肯定是我们的情景,也许不会对他人同样重要,但确实包含一些我认为会考虑任何人沿着阿波罗路径的考虑会重视。

开发人员开销和学习曲线

虽然这是一个舞台管理解决方案,但它带来了一些新的开销。一个人现在必须为他们的客户端状态编写解析器,虽然是这种情况国家管理选项,它不像看起来那么简单。

要真正地编写这些解析器,您将希望在直接与缓存一起工作时有一些体验/能力。第3.0版,阿波罗/客户有一些人对缓存如何处理归一化和非归一化数据的相当大幅度更新。了解缓存如何使用ID__typename.S,决定是否合并或替换数据,并学习如何这样做,都是这种范式的过程。这为我们提供了两个备受缓存数据的选项之一:确保每个查询都请求唯一识别我们所要求的数据的唯一识别字段(这并不易于请求我们想要的任何领域的目的?)或写作明确Typepolicies.要告诉我们的缓存如何标准化我们的数据。从撰写客户端库的人的角度来看,必须为众多不同的使用情况正常工作,我理解这样的解决方案的动机。但是,这不是客户端状态通过像Redux这样的解决方案的问题。

对比这种范例对抗类似的东西React的上下文API与...的串联Userducer Hook.,甚至Redux体系结构,阿波罗解决方案似乎更需要从开发人员的角度来理解和管理。然而,通过这种权衡,我们确实获得了思考和互动的能力我们所有的应用程序的数据以同样的方式,这无疑是一个令人敬畏的好处。但是这值得吗?

一些新的东西,借来的东西?

嗯,我们已经在OKC的旧货币中使用了Redux,即使是回流,也在一些旧代码的示例中。在我们的应用中添加一个新的选项,它将真正导致混乱的混乱,这对船上的人无疑是复杂的,这是一个首先将头包裹在一起。就个人而言,我感到开发人员经验和可维护性应该是定义任何建筑或框架决定的因素。

“Redux已经死了”参数已经多次提出,以及与之相关的传统成本可以轻松地认为样板和包装组件不是非常可扩展的,因为一个人必须在到达任何地方之前对少数文件进行更改。尽管如此,它肯定已经成熟了多年。如果做得好,它绝对可以使用微风,它清楚地确实有一些留力(更不用说,与Redux钩子一起工作实际上非常好)。更不用说,剥夺我们现有的架构需要数月,并添加另一个范例来学习和遵循现有的国家管理范例,当DEVS编写代码时会导致更多的上下文切换,并且可能只是混淆并更多地卷起它们更多。

最重要的是,在一个人的处理中也有无数资源使用和了解Redux(或任何成熟的OSS,或者对于此事项),我当然在研究阿波罗客户端州管理时肯定有问题;Apollo方法中的文档,视频和文章也许是因为与Redux这样的东西相比,它的生活更加模糊或早期。此外,更成熟的解决方案可能会在为开发人员合作的稳定API的条款中可能提供更多价值,而在这方面则更年轻人可能更加动荡(这一切都是非常态度,但我将承认我将承认)。

但是,违背冗余的常见争论必须“改变如此多的文件”和“过度复杂化”并没有真正被Apollo的解决方案纠正。我们仍然希望明智地哄骗腐败师和初始状态的定义,并且在这样做的事情之后,我觉得我只是写了一些redux,这对我来说感到非常讽刺。除了与商店一起工作,似乎直接与缓存直接工作似乎不太复杂。

Apollo还有一个Dev Tools产品,我真的很喜欢使用并发现是有用的,但在忍受像Redux的并行之类的东西时,它感觉太不成熟了。有时,它不想发布。它没有提供有趣的功能,如时间旅行,我对使用它的一件事是为客户端的内省使用它,所以需要定义typedefs.在这一点apolroclient.实例(这基本上是为我们的客户端状态创建架构的过程,这实际上只是担心和管理的另一个整个事情,但我承认,或许可以在这种情况下真正发光的打字或代码。。在我使用的其他库中,没有必要多次定义客户端状态的形状,如果有的话,它开始看起来像一些开始累积的阿波孔侧的样板。

其他选择

我们也一直在尝试反应的上下文API,并且可能希望将来考虑其他一些选择。但是,对我们选择对我们捆绑尺寸的影响的思考也非常重要。Contuct / Apollo可以完全删除对状态管理依赖的需求吗?对于一些更简单的应用程序,我认为那里的例子被证明的上下文已经足够了。同样,Apollo的解决方案的一些例子也足够了!还有Mobx,Facebook的新开源提供畏缩甚至使用状态机建模客户端状态XSTATE.

我们在哪里网?

很难承认阿波罗所做的令人难以置信的工作。Apollo和GraphQL已经完成了清理我们的API和客户的一般网络层的奇迹。Pursuing it as an option for state management, however, has major implications for our codebase, and at this time we just didn’t feel the argument was compelling enough given the maturity of Apollo’s client-side state management and the current setup of our client-side state. The return-on-investment of implementing something new in a codebase with a lot of existing architecture needs to be relatively high in my opinion. I’m just not sure that the return on investment with Apollo is high enough in this case.

Armed with some new insights after having explored Apollo’s offering some more, though, we will hopefully be able to come to a better conclusion eventually about what we should rely on to solve the problem, and how we should think about the approach, architecture, and trade-offs of any library, framework, or tool we decide to roll with. Until then, we’ll continue to keep on our eyes on how Apollo’s client state solution evolves.

188bet金宝搏官网Okcupid Tech Blog.

从每天连接数百万人的工程团队阅读故事

188bet金宝搏官网Okcupid Tech Blog.

从每天连接数百万人的工程团队阅读故事

尼克布朗特

写的

软件工程师·NYC·https://nickbrandt.dev

188bet金宝搏官网Okcupid Tech Blog.

从每天连接数百万人的工程团队阅读故事