Ferry normalize 包源码解析:5 个关键步骤讲透 GraphQL 数据规范化/反规范化核心原理

发布时间:2026/10/21 15:03:51

Ferry normalize 包源码解析:5 个关键步骤讲透 GraphQL 数据规范化/反规范化核心原理 Ferry normalize 包源码解析5 个关键步骤讲透 GraphQL 数据规范化/反规范化核心原理【免费下载链接】ferryStream-based strongly typed GraphQL client for Dart项目地址: https://gitcode.com/gh_mirrors/fer/ferryFerry 是一个基于流Stream-based的强类型 GraphQL 客户端Dart 生态而其normalize包正是整个缓存体系的核心引擎负责把 GraphQL 响应**规范化Normalize为按实体 ID 打平的键值缓存再在需要时反规范化Denormalize**还原成嵌套的查询树。读懂这个包你就掌握了 Ferry 实现跨查询缓存共享、局部更新与引用去重的底层原理。 先搞懂两个概念规范化与反规范化GraphQL 响应天然是嵌套树查询什么样就返回什么样而现代客户端缓存的理想形态是按实体扁平存储概念输入输出作用规范化 Normalize嵌套的 GraphQL 响应 JSONdataId → 实体对象的扁平 Map同一实体只存一份天然支持缓存共享与增量更新反规范化 Denormalize扁平 Map 查询文档嵌套的响应 JSON按$ref引用递归把树拼回来规范化后的缓存长这样$ref即引用键默认由kDefaultReferenceKey定义{ Query: { pokemon: { $ref: Pokemon:25 } }, Pokemon:25: { __typename: Pokemon, name: Pikachu, id: 25 } }两个查询只要引用同一个Pokemon:25就共享同一份数据——这就是缓存高效的秘密。️ 核心源码文件速查表normalize 包代码量不大主干就是两个入口 两个递归函数 一组工具全部位于packages/normalize/lib/src/下源码文件职责normalize_operation.dart规范化总入口normalizeOperation()denormalize_operation.dart反规范化总入口denormalizeOperation()normalize_node.dart递归遍历 AST把节点写入缓存核心算法denormalize_node.dart递归遍历 AST从缓存还原节点核心算法utils/resolve_data_id.dart为实体生成唯一dataIdutils/field_key.dart生成字段名参数的组合键utils/expand_fragments.dart按__typename展开内联/命名 Fragmentutils/deep_merge.dart新旧数据深合并utils/is_dangling_reference.dart判定$ref是否指向不存在的实体policies/type_policy.dart类型级定制keyFields、根类型标记policies/field_policy.dart字段级定制keyArgs/read/mergeconfig/normalization_config.dart贯穿全程的配置上下文对外 API 统一由 normalize.dart 导出。测试文件覆盖了packages/normalize/test/下的query_fragments/、query_variables/、field_policy/、evictions/等目录是很好的行为验证素材。 规范化流水线5 个关键步骤步骤 1可选地为查询注入__typename若调用normalizeOperation()时传入addTypename: true会用AddTypenameVisitor转换查询文档见utils/add_typename_visitor.dart给每个选择集补上__typename。没有__typename的实体无法被规范化——这是整个体系的基石。步骤 2定位操作定义与根类型入口函数normalizeOperation()normalize_operation.dart先通过getOperationDefinition()从文档中找到对应操作再用resolveRootTypename()结合TypePolicy确定根类型Query/Mutation/Subscription最终组装出NormalizationConfig配置上下文。步骤 3为每个实体解析唯一 dataIdresolveDataId()utils/resolve_data_id.dart是 ID 生成器优先级非常明确TypePolicy.keyFields自定义复合主键生成TypeName:{json}形式dataIdFromObject全局自定义解析函数兜底规则id或_id字段生成经典的TypeName:id格式以上都不满足则返回null——该实体不规范化直接以嵌套形式挂在父对象下。步骤 4展开 FragmentexpandFragments()utils/expand_fragments.dart是规范化能正确处理 Fragment 的关键它依据当前实体的__typename与 Fragment 的typeCondition含possibleTypes接口/联合类型推导匹配把内联片段与命名片段的选择集深度合并成扁平的字段列表并自动剔除被skip/include跳过的字段。步骤 5normalizeNode 递归写入缓存核心算法在normalize_node.dart的normalizeNode()逻辑可以概括为列表逐项递归叶子节点无子选择集直接返回标量值对象节点先解析dataId→ 读出现有缓存 → 用FieldKey为每个字段生成带参数值的键如friends(first:1)→ 检查部分数据缺失且无read/merge策略则抛PartialDataException除非allowPartialData→ 与现有数据deepMerge()深合并 →write(dataId, merged)写入缓存并只返回{$ref: dataId}这个引用占位符。 精妙之处非根对象写入后只留引用所以同一个实体出现在 10 个查询里也只占一份内存deepMerge则保证新响应与旧缓存字段级合并实现增量更新。 反规范化如何从扁平缓存反向还原树denormalizeOperation()denormalize_operation.dart同样先解析根类型然后交给denormalizeNode()denormalize_node.dart递归还原引用识别节点含$ref键时通过config.read(dataId)取回实体本体再递归取不到即抛出DanglingReferenceException悬空引用列表容错isDanglingReference()逐个过滤悬空项配合allowDanglingReference开关决定是跳过还是抛错部分数据字段缺失且无法读取时抛PartialDataExceptionreturnPartialData: true则静默跳过handleException: true时整个操作返回null而非崩溃虚拟化字段配置了FieldPolicy.read的字段即使缓存中不存在也能通过 read 函数凭空计算出来。另外还提供denormalizeFragment()用于只针对单个 Fragment 还原数据适合细粒度的局部读取。️ 两套定制策略TypePolicy 与 FieldPolicy规范化行为可通过两级策略精细控制定义见policies/目录TypePolicy类型级keyFields指定复合主键支持嵌套子字段queryType/mutationType/subscriptionType标记根类型空keyFields可声明此类型不规范化。FieldPolicy字段级keyArgs声明哪些参数才算字段身份空列表则完全忽略参数read自定义读取逻辑merge自定义新旧数据合并——对分页列表等场景尤为重要。✅ 一句话 API 速览// 规范化把响应写入缓存 normalizeOperation(write: write, read: read, document: document, data: response, variables: variables); // 反规范化从缓存按文档还原 final data denormalizeOperation(read: read, document: document, variables: variables);write/read是解耦的函数注入上层packages/ferry_cache缓存包与packages/ferry_store存储抽象含 Hive/SQLite 持久化正是基于这两个 API 构建读写链路。 总结Ferry 的normalize包用不到 30 个文件讲清了一件事规范化 解析 dataId → 展开 Fragment → 递归 deepMerge → 写入$ref反规范化 沿 AST 递归 → 遇到$ref就查表还原。配合TypePolicy/FieldPolicy两套策略它既兼容 Relay 规范化的经典心智模型又为复合主键、虚拟化字段、部分数据等现实问题留足了扩展点。想深入验证行为建议直接跑packages/normalize/test/下的分类测试从query/simple_test.dart读起一路到field_policy/与evictions/dangling_reference_test.dart即可完整复现本文讲到的全部机制。【免费下载链接】ferryStream-based strongly typed GraphQL client for Dart项目地址: https://gitcode.com/gh_mirrors/fer/ferry创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/21 15:03:50

agent-skills:把智能体能力沉淀为可复用、可编排的技能库

agent-skills:把智能体能干的活,沉淀成一套可复用、可编排的技能库做Agent项目时间长了,你会发现一个挺扎心的规律:单看一个智能体的Demo,跑通并不难,难的是让它稳定地干很多不同类型的活。今天聊的这个项目…

2026/10/21 15:03:50

交通信号灯识别数据集:三种标注格式与82.88% mAP复现

前段时间整理了一个带标注信息的交通信号灯识别数据集,核心能力是识别左转灯、红绿黄灯,实测识别率82.88%,并且同步导出了YOLO、COCO JSON、Pascal VOC XML三种标注格式。这个数字不是拍脑袋报的,是我在自己环境里完整跑训练和验证…

2026/10/21 15:03:50

股票期权适当性考试考点拆解:先建立风险边界再谈开户

简介:面向拟参加上交所股票期权适当性考试的投资者、从业者与备考学员,尤其能让对期权规则不熟悉的读者快速入门;这套题库资料以单选题形式系统梳理期权交易核心考点,适合备考时间紧张、需要反复刷题校验的人群。内容涵盖期权买方…

2026/10/21 15:03:50

IntelliJ IDEA Git高效实战:图形化操作深度指南

1. 这不是Git教程,而是我在IntelliJ IDEA里“摸爬滚打”五年攒下的真功夫你点开这个标题,大概率不是想学Git基础命令——git clone、git commit、git push这些,官网文档写得比谁都清楚。你真正卡住的地方,是当一个分支在IDEA里突然…

2026/10/21 14:58:43

AI证件照制作API全流程:人脸检测、人像分割与背景替换实战

1. 项目背景:为什么证件照不能靠"随手拍 随便裁"解决先说个我自己的经历。前阵子有个朋友着急办证,手头只有一张在咖啡馆拍的侧身自拍,问我能不能帮忙"裁一下"当证件照用。我当时就跟他讲,证件照真不是把脑袋…

2026/10/21 4:53:08

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/21 0:00:35

交通流仿真实战:IDM跟驰与MOBIL换道模型参数调优与避坑指南

简介:这份资源是面向交通工程学习者与Python开发者的微观交通流仿真项目,核心实现智能驾驶模型(IDM)下的车辆跟驰与换道行为,可用于理解速度、密度与流量之间的动态关系,适合具备Python基础、希望深入交通模…

2026/10/21 0:00:35

揭穿智能体表面顺从:用追踪代码重建任务执行确定性

1. 表面顺从不是智能体的“礼貌”,而是任务执行链路上的隐性断点最近在调试一个跨平台长周期任务调度系统时,我反复遇到一种令人困惑的现象:智能体在用户指令发出后,几乎秒级响应“已收到”“正在处理”“请稍候”,UI上…

2026/10/21 0:00:35

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事:AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里,按宣传口径,本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”,而是这话背后的含金量——它把过去只能…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑