恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Hasura GraphQL Engine 的 Apollo Federation v1 支持:从 RFC 设计到源码实现
首页
资讯中心
/
Hasura GraphQL Engine 的 Apollo Federation v1 支持:从 RFC 设计到源码实现
Hasura GraphQL Engine 的 Apollo Federation v1 支持:从 RFC 设计到源码实现
发布时间:2026/9/19 8:38:17
Hasura GraphQL Engine 的 Apollo Federation v1 支持从 RFC 设计到源码实现【免费下载链接】graphql-engineBlazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.项目地址: https://gitcode.com/gh_mirrors/gr/graphql-engine导读本文以 rfcs/apollo-federation.md 为主体系统讲解 Hasura GraphQL Engine下称 HGE如何将自身接入 Apollo Federated Gateway 成为可被其他子图消费的联邦子图。文章覆盖该特性的设计动机、*_track_table元数据配置、_service/_entities联邦字段的 schema 生成与查询求值原理并结合server/src-lib下的 Haskell 源码与tests-py测试用例给出可验证的实现级依据。读完本文你将理解 HGE 联邦子图模式的工作原理并能独立完成表的联邦接入配置与端到端验证。背景与目标为什么要让 HGE 支持 Apollo FederationApollo Federation 是 Apollo 提出的 GraphQL 联邦架构规范多个子图subgraph服务各自维护一部分 schema由一个网关gateway将它们编排成统一的联邦 schema。要让 HGE 以子图身份挂载到 Apollo Federated Gateway并让其他子图能够引用 HGE 中由数据库表生成的类型RFC 提出了两个核心需求对应 rfcs/apollo-federation.md 的 Requirements 一节HGE 应能挂载到 Apollo Federated Gateway 上——即网关能够通过_service字段获取 HGE 的 SDL完成 schema 合并其他子图可以引用 HGE 的表类型——即 HGE 为参与联邦的表生成key指令并支持_entities查询使网关能够按主键跨子图解析实体。Apollo 官方规范要求一个 GraphQL 服务成为子图需满足四件事RFC Spec 一节引用实现 federation schema 规范支持获取服务能力即_service { sdl }为引用实现 stub 类型生成_Entityunion实现实体的请求解析_entities查询。RFC 将整个特性规划为 v1 实验性功能第一版只要求能用后续版本再渐进增强。启用方式与元数据配置两级开关RFC 设想联邦支持通过环境变量或全局元数据字段开启从当前源码看该开关最终落地为两个层次服务级开关HASURA_GRAPHQL_ENABLE_APOLLO_FEDERATION环境变量或--enable-apollo-federation命令行参数。同时apollo_federation也被列为一个 experimental feature见 server/src-lib/Hasura/Server/Init/Arg/Command/Serve.hs 中 experimental features 帮助文本其中注明apollo_federation: use hasura as a subgraph in an Apollo gateway (deprecated)——即该实验特性开关已被显式配置取代。表级开关在*_track_table元数据 API 中为单个表开启联邦。两级开关的组合逻辑在 ApolloFederation.hs 的getApolloFederationStatus中实现若用户显式设置了ApolloFederationStatus则以显式值为准否则回退到 experimental feature 标志EFApolloFederation是否在集合中。*_track_tableAPI 的扩展RFC 给出了开启单表联邦的请求示例{ source: default, table: Author, configuration: {}, apollo_federation_config: { enable: v1 } }这一 API 设计在当前源码中得到完整落地。在 server/src-lib/Hasura/RQL/Types/Common.hs 中定义了对应类型data ApolloFederationVersion V1 deriving (Show, Eq, Generic) -- enable 字段目前只接受 v1 -- 其余值会报错enable takes the version of apollo federation. Supported value is v1 only. data ApolloFederationConfig ApolloFederationConfig { enable :: ApolloFederationVersion }TableMetadata的 codec 在 server/src-lib/Hasura/Table/Metadata.hs 中增加了可选字段apollo_federation_config。而isApolloFedV1enabled :: Maybe ApolloFederationConfig - Bool直接判断该配置是否存在isJust即只要配置了enable: v1即视为启用。RFC 指出这种配置对象内放置键值对的 API 设计便于未来扩展例如未来支持 v2 指令如为某些列添加sharable允许自定义key指令的fields即联邦主键默认取表主键。行为自动添加key指令当一张表以开启联邦的方式被 track 后该表在 schema 中的类型会被自动加上key指令字段值取自表主键。RFC 以Review表为例type Review key(fields: id) { id: Integer! body: String author: User product: Product }key(fields: id)自动生成其中id是Review表的主键列。这正是其他子图可以引用 Hasura 表类型的关键机制网关依据key的字段值构造_Anyrepresentation再通过_entities查询按主键取回实体。实现原理一SDL 生成与_service字段从 SchemaIntrospection 到 SDL为了让网关能拉取子图 schemaHGE 需要暴露_service字段其类型_Service包含一个sdl: String字段。RFC 提出的方案是在构建 schema 字段解析器的同时生成 SDL并给出了核心思路getSchemaDocument :: G.SchemaIntrospection - G.SchemaDocument getSchemaDocument (G.SchemaIntrospection typeDefMap) G.SchemaDocument completeSchema where allTypeDefns map G.TypeSystemDefinitionType (Map.elems typeDefMap) rootOpTypeDefns getRootOpTypeDefns -- define this completeSchema rootOpTypeDefns : allTypeDefns generateSDL :: G.SchemaIntrospection - Text generateSDL Builder.run . Printer.schemaDocument . getSchemaDocument这一设计在当前源码中已具体化为generateSDLFromIntrospection见 ApolloFederation.hs遍历SchemaIntrospection中所有类型定义并过滤掉以__前缀开头的 GraphQL schema 内建类型filterTypeDefinition从 introspection 中查找query_root、mutation_root、subscription_root三个根操作类型生成RootOperationTypeDefinition最终组装成G.SchemaDocument用graphql-parser库的Printer.schemaDocument渲染为Text。该模块同时提供两个导出函数generateSDL移除 schema 内建类型与generateSDLWithAllTypes保留全部类型后者可用于支持未来 Apollo Federation v2源码中已预留link(url: https://specs.apollo.dev/federation/v2.0, import: [key, shareable])的扩展点注释标明这是为 v2 指令预留。_service字段解析器mkServiceField创建_service的FieldParser其内部定义sdl字段解析器类型String描述为 SDL representation of schema再以selectionSet组装出_Service类型最终生成一个接收SchemaIntrospection、返回QueryRootField的解析器。由于sdl的取值依赖 schema introspection该FieldParser被定义为G.SchemaIntrospection - QueryRootField UnpreparedValue的函数形态在 schema 构建期与实际查询期之间传递 introspection。一个值得强调的细节RFC 中明确说明schema introspection 是角色相关的因此生成的 SDL 不会暴露当前角色无权访问的字段与类型联邦 SDL 天然遵循权限边界。RFC 中的完整 SDL 示例RFC 以一张名为users、含id/name字段、采用graphql-default命名约定的表为例给出了生成 SDL 的缩略版与完整版。缩略版如下完整版见 RFC 原文 rfcs/apollo-federation.mdschema { query: query_root mutation: mutation_root subscription: subscription_root } type query_root { usersAggregate( where: UsersBoolExp orderBy: [UsersOrderBy!] limit: Int offset: Int distinctOn: [UsersSelectColumn!] ): UsersAggregate! users( where: UsersBoolExp orderBy: [UsersOrderBy!] limit: Int offset: Int distinctOn: [UsersSelectColumn!] ): [Users!]! usersByPk(id: Int!): Users } type Users key(fields: id) { id: Int! name: String! }完整版 SDL 还包含__Schema、__Type等 introspection 类型在generateSDL中会被过滤而generateSDLWithAllTypes会保留、query_root/subscription_root/mutation_root三个根类型、UsersAggregate及其聚合辅助类型、orderBy/UsersSelectColumn/UsersConstraint等枚举、IntComparisonExp/StringComparisonExp/UsersBoolExp等输入类型以及UsersInsertInput/UsersOnConflict/UsersPkColumnsInput等变更输入类型。整个 SDL 完整覆盖了 HGE 为该表生成的全部 GraphQL 能力。实现原理二_entities查询与_Entityunion联邦规范要求的字段形态RFC 给出了_entities字段的规范形态# a union of all types that use the key directive scalar _Any union _Entity extend type Query { _entities(representations: [_Any!]!): [_Entity]! }_Any是一个标量每个 representation 至少包含__typename与key指定的主键字段。在当前源码中_Any的解析由anyParser实现见 ApolloFederation.hs它要求输入必须是 JSON 对象提取__typename键缺失或非字符串均报解析错误其余键值对作为afPKValues主键值保存构造出ApolloFederationAnyType { afTypename, afPKValues }。参与 union 的类型来源RFC 指出带key指令的类型可能来自三类来源DB 表为select类型加上key指令字段默认取表主键后续可允许用户自定义Actions需要先扩展set_custom_typesAPI 以支持 directives列为未来工作Remote Schema上游 directives 已被存储可直接复用。在 v1 实现中_Entityunion 的成员在 server/src-lib/Hasura/GraphQL/Schema/Build.hs 中收集对每张表若isApolloFedV1enabled成立则基于该表的 selection set、select 权限、主键列等信息构造convertToApolloFedParserFunc产出(objectTypename, parser)二元组。objectTypename由getTableIdentifierNamemkTableTypeName生成与常规 GraphQL 表类型名一致并遵循 naming case 约定。union 解析器与权限裁剪union 的Parser通过P.selectionSetUnion Name.__Entity (Just A union of all types that use the key directive) entityParserMap创建。RFC 特别提醒需要根据角色权限移除对应的Parser——如果某角色无权访问key指令涉及的字段则应省略该类型的解析器。这一约束在Build.hs中体现为runMaybeT中的guard与hoistMaybe组合无 select 权限的表、无主键的表不会进入联邦 parser 列表。key实体的解析与查询构造convertToApolloFedParserFunc/modifyApolloFedParserFunc将单张表转换为ApolloFederationParserFunction给定一个ApolloFederationAnyType含__typename与主键值它遍历表的主键列从afPKValues中查找对应键值用parseScalarValueColumnType将 JSON 值解析为列类型并为每个主键列构造AEQ NonNullableComparison等值条件所有主键条件以BoolAnd合并为where表达式最终生成一个QDBSingleRow的单行查询IR.AnnSelectG等价于一次按主键的*ByPk查询。同时会应用该列在 select 权限中的 redaction 表达式getRedactionExprForColumn保证联邦查询不绕过列级脱敏。_entities的求值流程RFC 以如下查询为例说明_entities的求值过程query MyQuery { _entities(representations: [{__typename: UsersData, id: 1}, {__typename: TwoPks, id1: 1, id2: 2}]) { ... on TwoPks { internalData } ... on UsersData { id name } } }求值分为四步取 selection set从_entities查询的 selection set 中按 union 成员类型上例为TwoPks与UsersData分别提取 selection set生成参数用查询参数构造各类型的参数例如TwoPks对应(id1: 1, id2: 2)复用ByPk解析器求值用各类型的*ByPk字段解析器TwoPksByPk、UsersDataByPk对 selection set 与参数构造出的 Field 求值汇总结果将所有结果拼接为一个列表。当前源码中的mkEntityUnionFieldParser见 ApolloFederation.hs即按此流程实现对每个 representation按afTypename在 union 解析结果中查找对应 parser找不到时报错typenameis not found in selection set or apollo federation is not enabled for the type找到后调用aafuGetRootField生成QueryRootField最后用concatQueryRootFields RFMulti将多个根字段合并为一个多字段查询执行。RFC 同时注明上述求值方式可能对同一数据库执行多次取数每个 representation 一次这是 v1 的已知优化空间。根字段暴露的完整逻辑apolloRootFields见 ApolloFederation.hs决定最终暴露哪些联邦字段联邦启用且存在带key的表 parser → 同时暴露_service与_entities联邦启用但没有任何联邦表 parser → 只暴露_service足以让网关接入但无实体可被引用未启用联邦 → 不暴露任何联邦字段。这与 RFC 的两个需求一一对应_service保证挂载到网关_entities保证其他子图引用表类型。端到端验证测试用例server/tests-py/queries/apollo_federation/目录下的 YAML 测试用例完整演示了从建表、track 到联邦查询的链路setup.yaml中先执行 SQL 建表并插入数据CREATE TABLE user( id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL, is_admin BOOLEAN NOT NULL DEFAULT false ); INSERT INTO user (id, name, email) VALUES (1, foo, fooemail.com), (2, bar, baremail.com), (3, bar, baremail.com), (4, baz, bazemail.com);随后通过track_table开启联邦- type: track_table args: table: user schema: public apollo_federation_config: enable: v1entities.yaml验证_entities查询以{__typename: user, id: 1}作为 representation返回id: 1、email: fooemail.com、name: foo、is_admin: false证明网关按主键id解析实体成功query EntitiesTest($representations: [_Any!]!) { _entities(representations: $representations) { ... on user { id email name is_admin } } }root_fields.yaml通过 introspection 断言 query 根字段按预期暴露_entities类型为_EntityUNION类型、_service类型为_ServiceNON_NULL包裹的OBJECT以及常规的user列表查询、user_aggregate聚合查询等验证联邦字段与常规字段共存于根 schema。限制与未来工作RFC 在 Future work 一节明确列出 v1 之后的演进方向将 Actions 类型纳入联邦需要内部表示层面的改动如在 action 类型中加入 directives 存储因此 v1 仅覆盖 DB 表与 remote schema 来源允许用户自定义key字段v1 默认取表主键未来可让用户选择主键之外的字段作为联邦键评估 Apollo Federation v2 支持v1 仅实现 v1 规范的key最小子集v2 的shareable、link等指令留待后续源码中已有相关预留注释。此外_entities的多 representation 求值目前会触发多次数据库取数RFC 明确指出这是后续可优化的性能点。小结从 RFC 设计文档 到 ApolloFederation.hs 的实现HGE 的 Apollo Federation v1 支持形成了一个完整闭环服务级与表级两级开关控制启用范围key指令与_service/_entities联邦字段由 schema 构建期自动生成_Any解析、按主键构造单行查询与RFMulti合并求值完成实体解析且全程受 select 权限与列脱敏约束。配合 tests-py 下的联邦测试开发者可以快速验证HGE 作为 Apollo 子图被网关消费的完整链路为后续向 v2 演进与性能优化打下基础。【免费下载链接】graphql-engineBlazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.项目地址: https://gitcode.com/gh_mirrors/gr/graphql-engine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考