确保图被明确定义、稳定且一致
你的公司应当只有一个统一的图,而不是多个团队分别创建的多个图。
只使用一个图,你可以最大化 GraphQL 的价值:
可以通过单个查询访问更多数据和服务
代码、查询、技能和经验可跨团队移植
图的每个用户都可以查看所有可用数据的中心目录
实现成本最小化,因为无需重复图的实现工作
图的中心管理——例如,统一访问控制策略——变得可能
当不同团队在没有协调工作的情况下分别创建独立的图时,几乎不可避免出现图之间的重叠,于是只能以不兼容的方式向图添加相同的数据。在最好的情况下也会造成很高的返工成本;而在最坏的情况下会造成混乱。应当尽早在公司的数据图使用中遵循本原则。
虽然只有一个图,但该图应该由多个团队联合实现。
如果没有高度专业化的基础结构,单体架构很难扩展,数据图也不例外。不应当在单个代码库中实现组织的整个数据图这一层,而是应当在多个团队之间划分定义和实现图的职责。每个团队都应当负责维护公开其数据和服务的那一部分 schema,同时具有独立开发和在自己的发布周期中运行的灵活性。
这样可以保持图作为单一统一视图的价值,同时解耦整个公司的开发工作。
注册和追踪图时应当有一个单一的事实来源。
就像在版本控制系统中跟踪源代码一样,跟踪 schema 注册表中对于图的定义非常重要。你的公司应当有一个单独的 schema 注册表作为对图的权威定义,而不是依赖于当前正在运行的任何进程或是在开发人员的笔记本电脑上签入的任何代码。与源代码控制系统一样,schema 注册表应该存储更改历史记录以及创建它们的人员,并且应该理解图的多个版本的概念(例如暂存、生产环境或不同的开发分支),在某种程度上类似于软件开发过程。
Schema 注册表应成为系统的中心枢纽,为开发者工具、工作流或任何将从数据图以及对其任何实际或建议的更改中受益的业务流程提供支持。
Schema 应当作为抽象层以隐藏服务实现细节并为消费者提供灵活性。
GraphQL 的一大价值就是在服务和消费者之间提供了一个抽象层,因此 schema 就不该与特定服务实现或者特定现存消费者强耦合。通过将实现细节从 schema 中分离,就能重构实现图关系的服务的同时 —— 例如,从单体架构切换到微服务架构,或者切换服务的实现语言 —— 而不会影响现有应用。同样地,schema 也不应该和特定应用取数据的方法所耦合。如果新的应用与旧的十分相似,那么应该只需要微微修改图关系。
为了达到这个目的,我们使用面向需求的 schema:专注于提供良好的开发体验,使得开发者可以更方便地使用现有图关系开发新的特性。以此标准为目标的话,可以防止图关系与一个将要变化的服务实现相耦合,且提升了添加到图关系的每一个字段的复用价值。
Schema 应当根据实际需求增量构建,并随着时间的推移平滑演进。
有件十分有诱惑力的事:提前定义你所有数据的"完美 schema"。然而,真正让一个 schema 产生价值的是贴合用户的需求,但需求永远都不没法完全得知而且还会一直变化。真正通向"完美 schema"的方式是构建易于跟随实际需求变化而进化的图关系。
字段不应该凭臆断添加,理想情况下,每个字段都对于一个具体的消费者附加需求,且要设计成可被其他消费者类似需求最大化复用的形式。
图关系的更新应该是一个持续的过程。相比较周期性的发布新"版本"的图关系(例如每 6 个月或者 12 个月),更好的是必要情况下能够一天修改多次。新字段可以在任何时间添加。而移除字段,则需要先将其标记为弃用,然后等到没有消费者在使用它的时候再移除。Schema 注册表使得图关系的敏捷演进成为可能,通过相关的流程和工具,可以让每个人都知晓影响他们的字段改变。这样就能保证只有全面审查过的改变才能进入生产环境。
性能管理应当是一个连续的、数据驱动的过程,可以平滑地适应不断变化的查询负载和服务实现。
数据图关系层是保存服务团队与消费其服务的应用开发者之间关于性能与容量的会话的绝佳场所。这个会话是一个持续的过程,使服务开发者能够持续主动地了解消费者是如何使用服务的。
相比较优化图关系中每一个可能的用法,更推荐专注于提供生产环境所实际需要的查询情形。相关工具应该提取新提出的查询情形,并在投入生产之前,将其延迟要求和预计查询量呈现给所有受影响的服务团队。一旦这个查询投入到生产之中,它的性能就将被持续监控。当这个原则得到采用实施的时候,问题就能被追溯到表现不合预期的服务。
开发人员应当在整个开发过程中对图充分了解。
GraphQL 的一个主要价值就是它为开发者带来的巨大生产力提升。为了最大化这一提升效果,开发工具应该让开发者对数据图有更普遍的理解,而开发者也应该在整个开发周期内使用这些工具。
每当开发者开展数据管理或者服务连接相关的工作,他们的工具就应该将图关系的实时信息展示在他们指尖。这信息应该保证总是最新的,而工具应该保证是高度智能化的,能够将对图关系的感知以强大而有效的方式应用于手头的工作。当工作完成之际,不仅仅开发者的生产力和幸福度得到提升,GraphQL 也在前后端团队之间穿针引线,让团队间能在整个开发周期内无缝对话。
以下是部分数据图感知工具的实际用例:
能在开发者键入这个查询后,就在他们的编辑器中给出所用图数据和服务的实时文档,且总是最新的。
关于弃用字段的信息能被广播到用了这些字段的开发者的编辑器中,同时还给出推荐的备用字段。
一个查询的成本估计(延迟或者服务器资源)能在开发者打这个查询后就给出,基于实时的生产数据。
运维团队能追溯后端服务的负载到具体应用、版本、特性,甚至代码所在行,使他们能够全面地了解开发人员是如何使用他们的服务。
当服务开发者更改了 schema 时,更改的影响可以被自动判定,作为持续集成的一部分。 如果更改会破坏现有客户端(通过重放最近的生产使用情况确定),服务开发者则可以确定那些具体的客户端、版本和开发人员将受到影响。
当应用程序开发者构建功能时,支持这些功能的新查询可以从代码中提取,并与运维团队共享。有了这种认知后,如果无法批准预期的查询规模,运营团队则可以在开发过程的早期介入,并主动提供能力以支持所需规模。
当应用程序使用 TypeScript、Java 或 Swift 等带类型的语言开发时,类型信息可以从服务的类型声明一直传播到应用程序中的每行代码,从而确保全栈的类型正确性和错误信息的即时反馈。
基于每个客户端授予对图的访问权限,并管理客户端可以访问的内容和方式。
数据图中的授权具有两个同等重要的方面:访问控制,其指示允许用户访问的对象和字段;以及需求控制,其指示允许用户访问这些资源的方式(以及多少)。虽然经常被提及是访问控制,但是也需要注意需求控制,因为它在 GraphQL 的任何生产部署中都至关重要。允许用户执行任何可能的查询而不考虑成本是错误的,这会导致无法管理其对生产系统的影响。无论执行访问还是需求控制都必须在充分了解数据图的语义和性能的情况下才执行。在不分析实际发送的查询的情况下,不应该限制用户的每分钟查询数,因为查询可以访问相当广泛的服务,并且查询的成本可以在多个数量级上变化。
数据图中的鉴权同样有两个方面:请求操作的应用以及使用该应用的用户。虽然访问控制可能以用户为中心,但需求控制至少保证同等的应用级控制和用户级控制,因为特定的查询情形是由是应用的开发者,而不是应用的用户负责的。
需求控制的最佳实践包括:
当未认证用户访问生产系统时,他们应该只能发送应用中由被认证的开发者预注册的查询,而不是允许他们使用应用的凭据发送任意查询。不过对于仅仅分发给受信任用户的内部应用而言,这个限制可以放宽。
对于预计会发送大量查询的应用,各团队应设计一个能与更宽泛软件开发周期保持一致的查询审批工作流程,以便在查询投入生产之前对其进行审核。这可确保它们不会获取不必要的数据,并且提前准备对应的服务器容量来支撑它们。
作为第二道防线,在执行查询之前估计查询的成本,并建立用户级和应用级查询成本预算,可以防止过度使用预注册的操作或者无法使用预注册操作的场景。
开发者应该能够禁用特定应用在生产中发送特定查询,譬如作为紧急情况下的安全网或发现第三方应用程序以不可接受的方式使用数据图等场景。
捕获所有图操作的结构化日志,并以之为主要工具了解图的使用情况。
可以捕获关于在图关系执行的每个操作(读取或写入)的大量信息:哪位用户和什么应用执行了操作、访问了哪些字段、操作的实际执行方式、执行效果等。这些信息非常有价值,应该系统化地捕获以备以后使用。相比较文本日志,它更应该捕获成结构化的机器可读的格式,以便尽可能多地利用它。
图关系操作的记录称为追踪。追踪应该在一个地方汇集有关操作的所有相关信息,包括业务信息(谁执行的操作、访问或更改的内容、由哪位开发者构建的应用的哪一个功能、是否成功、执行效果如何)以及纯技术信息(哪个后端服务被调用、每个服务如何导致延迟、是否使用了缓存)。
因为追踪能捕捉图关系是怎样被使用的,所以它们能被用于广泛的用途:
了解某个弃用字段是否可以删除,如果不可以,那么找到那些访问它的客户端,分析那些客户端的重要性
实时预测某个查询的执行所需时间:当开发者在 IDE 中键入查询,即可基于生产数据实时得出
自动检测生产中的问题(例如增加的延迟或错误率)并诊断其根本原因
提供权威审计跟踪,显示特定记录有哪些用户访问
为商业智能查询助力(当人们在炎热的地方时,人们会更频繁地搜索冰淇淋吗?)
根据 API 使用情况为合作伙伴生成发票,以及可以根据访问的特定字段或消耗的资源生成详细的成本模型
所有图关系操作的数据应该集中保存,以便有一个权威的追踪流。然后可以再将此流传输到其他观测系统(对于不支持 GraphQL 的现有系统可能需要进行简单转换),或者存储在一个或多个数据仓库中以备今后使用(采样、汇总然后转换成预算、用例和需求规模)。
采用分层架构将数据图功能分解为单独的层,而不是融入到每个服务中。
在大多数 API 技术中,除了开发期之外,客户端都不与服务器直接通信。而是采用分层方法,譬如负载平衡、缓存、服务定位或 API 密钥管理之类的一些问题被单独分层。然后,这些层就可以与后端服务分开设计、运作和扩展。
GraphQL 也同样适用。相比较将完整数据图系统所需的所有功能都整合到每个服务中,大多数数据图功能应该被提取到位于客户端和服务之间的单独层,使每个服务都能专注于处理客户端的具体请求。该层可以由多个进程组成,譬如执行访问控制和需求控制、联邦、追踪收集和缓存等功能。此层的某些部分是 GraphQL 特有的,需要深入了解数据图,而其他功能,如负载平衡和客户端身份验证,可以使用现存系统。
即使在只有一个应用和一个服务的简单场景中,这个单独的层也能发挥价值;否则,本该属于中间层的功能就得在服务器中实现了。在复杂的应用中,此层可能看起来像异地分布式系统:通过多个入口点接收传入查询,利用边缘缓存处理边缘网络的查询,将查询的子组件路由到多个数据中心在公共云、私有云或由合作伙伴运营的云中,最后将这些组件组装成查询结果,同时记录一次包含整个操作的追踪。
在某些情况下,此数据图层将使用 GraphQL 与后端服务进行通信。但是,最常见的是,后端服务保持不变,并继续通过现有 API(如 REST,SOAP,gRPC,Thrift 甚至 SQL)进行访问,再由数据图层的服务器将这些 API 映射到数据图层中去。