Webiny 架构决策解读:ADR-007 转换函数必须返回新值(Transformations Produce New Values)
CMS后端前端【免费下载链接】webiny-jsOpen-source, self-hosted CMS platform on AWS serverless (Lambda, DynamoDB, S3). TypeScript framework with multi-tenancy, lifecycle hooks, GraphQL API, and AI-assisted development via MCP server. Built for developers at large organizations.项目地址https://gitcode.com/gh_mirrors/we/webiny-js点击查看免费下载导读本文深入解读 Webiny 开源仓库中的架构决策记录 ADR-007Transformations produce new values。该 ADR 为 Webiny 项目确立了“数据处理/转换函数一律返回新对象、绝不修改输入”的编码准则是项目在资产交付Asset Delivery、选项解析Option Parsing与值归一化Value Normalization等模块中遵循的核心约束。读完本文你将理解这条规则背后的动机、它在 Webiny 源码中的落地形态含normalizeImageOptions、Asset.withProps()、normalizeToAsset()三个典型实现以及如何用测试验证“输入不被修改”这一不可变约定并能在自己的代码中直接复用这套模式。一、背景为什么数据转换函数必须“返回新值”1.1 两种风格的对比函数对数据的转换存在两种风格就地修改mutation函数直接修改传入的对象例如delete options.width; options.format parsed调用方持有的变量在函数执行后“悄然变化”。返回新值return new object函数不触碰输入而是构造并返回一个全新的对象。从代码长度看就地修改通常更短、写起来更快。但 ADR-007 明确指出这种做法带来三类问题数据流难以追踪调用方的变量在函数返回后不再是原来的值属性和内容“凭空出现或消失”阅读代码时很难判断某个变量在什么时间点变成了什么。隐含耦合多个函数按顺序修改同一个对象时后一个函数的输入依赖于前一个函数“改了哪些字段”调用顺序错误就会产生难以排查的 bug。共享引用陷阱如果输入对象同时被其他代码持有就地修改会把副作用扩散到所有持有该引用的地方。1.2 决策内容因此 ADR-007 的决策是所有对数据进行转换的函数都必须返回新对象而不是修改输入。输入一律视为只读输出一律是全新的值。该规则适用于选项解析 / 归一化函数option parsing / normalization functions领域对象转换如withProps、clone值归一化与转换器value normalizers and converters这本质上是一种“函数式、只读输入”的编码纪律与 Webiny 项目其他 ADR 一脉相承例如 ADR-006Deprecate incrementally 中为兼容旧数据而保留的ImageValue - Asset转换路径正是通过 ADR-007 所规定的“只读输入 返回新对象”的归一化函数实现的。二、落地实现一normalizeImageOptions——把原始查询串解析为类型化选项2.1 源码位置与职责normalizeImageOptions位于 packages/api-file-manager/src/features/assetDelivery/assetTypes/image/normalizeImageOptions.ts负责把来自 HTTP 查询参数的原始字符串width、quality、format、crop、aspectRatio、focal等解析为类型化的ImageRequestOptions对象。其类型定义在 imageTypes.ts 中export interface ImageRequestOptions { original?: boolean; // 是否请求原图 width?: number; // 目标宽度像素 quality?: number; // 编码质量1–100 format?: ImageFormat; // 具体输出格式已从 auto 解析完成 crop?: AssetCrop; // 请求级裁剪0–1 边距优先于资源级裁剪 aspectRatio?: number; // 目标宽高比宽 / 高 focal?: { x: number; y: number }; // 0–1 归一化焦点配合 aspectRatio 裁切时保持画面主体 }2.2 返回新值而非原地修改ADR-007 中明确记载该函数过去是就地修改options的delete options.width; options.format parsed重构后改为返回全新的ImageRequestOptions对象。当前源码完全遵循这一约定函数内部创建一个新的result对象逐个字段解析后挂载最后整体返回全程不触碰传入的query对象export const normalizeImageOptions ( query: Recordstring, any, acceptHeader: string | undefined ): ImageRequestOptions { const result: ImageRequestOptions { original: original in query }; const width query.width ? parseInt(query.width, 10) : NaN; if (!Number.isNaN(width) width 0) { result.width width; } // quality、format、crop、aspectRatio、focal 同理…… return result; };注意其典型的“白名单 守卫式挂载”写法只有解析成功且语义合法的值才会被写入新对象非法输入width: abc、负数宽度、畸形 crop 等不会污染结果而是直接省略对应字段。2.3 测试如何锁定“输入不被修改”该函数的测试位于 packages/api-file-manager/tests/features/assetDelivery/normalizeImageOptions.test.ts其中专门有一条用例验证不可变约定it(does not mutate the input query, () { const query { width: 800, quality: 75, format: webp }; const original { ...query }; normalizeImageOptions(query, undefined); expect(query).toEqual(original); });这条用例是 ADR-007 决策的直接测试化体现先对输入做浅拷贝快照调用函数后断言输入与快照完全一致。测试还覆盖了大量解析细节可作为参数取值范围与默认行为的权威参考width合法正整数被解析为数字abc、0、-10一律被丢弃返回undefined。quality会被clampQuality钳制到 1–100如150→ 1000→ 1。format支持显式格式webp保留gif被丢弃formatauto时依据Accept头解析image/avif优先于image/webp无匹配时返回undefined。crop0.1,0.2,0.1,0.05解析为{top,left,bottom,right}边距越界值钳制到 0–1全零、位数不足或字符串畸形的 crop 会被丢弃。aspectRatio支持16:9冒号记法与1.5小数记法非法值丢弃。focal0.3,0.7解析为{x:0.3, y:0.7}坐标钳制到 0–1。这套测试不仅验证了“不修改输入”也验证了“非法输入不会以字符串形式残留到新对象上”与源码的守卫式写法互为印证。三、落地实现二Asset.withProps()/clone()——不可变领域对象3.1 源码位置与实现Asset类是 API 文件管理模块中图片交付流程的领域对象位于 packages/api-file-manager/src/delivery/AssetDelivery/Asset.ts。它的核心不可变 API 是withProps与cloneexport class Asset { protected readonly props: AssetData; constructor(props: AssetData) { this.props props; } clone() { return this.withProps(structuredClone(this.props)); } withProps(props: PartialAssetData) { const newAsset new Asset({ ...this.props, ...props }); newAsset.contentsReader this.contentsReader; newAsset.outputStrategy this.outputStrategy; return newAsset; } // getId / getTenant / getKey / getSize / getContentType / getExtension 等只读访问器 }实现要点props被声明为readonly从构造起就不允许在类内部被重新赋值从类型层面封死了就地修改的可能。withProps()绝不修改原实例它通过展开运算符{ ...this.props, ...props }构造一份合并后的新数据再创建新的Asset实例返回。原始实例的props保持原样。为了让新实例保持可用的交付能力withProps会同步复制contentsReader内容读取器与outputStrategy输出策略这两个运行时依赖clone()则利用structuredClone深拷贝props后走同一条withProps路径得到一份完全独立的副本。3.2 该模式带来的工程收益withProps/clone是典型的“返回新值”风格在领域对象上的应用调用链可以安全地链式派生例如先withProps({key: newKey})再withProps({size: newSize})每一步都产生新实例任何一步出错都不会污染前面的中间结果。Asset的只读访问器getId、getKey等与不可变更新器withProps组合恰好构成“读方法不改、写方法返回新对象”的经典不可变对象形态。四、落地实现三normalizeToAsset()——面向旧数据的兼容归一化4.1 源码位置与职责normalizeToAsset位于 packages/website-builder-sdk/src/asset/normalize.ts是 ADR-006增量弃用旧数据与 ADR-007 共同作用的产物它把任意历史遗留的输入形态旧版 Website Builder 的扁平edit字段、6.x 时代无image子对象的文件值、以及已经统一化的新结构归一化为新的WebinyAsset且不修改输入export function normalizeToAsset(input: unknown): Asset | null { if (!isObject(input)) { return null; } const mimeType asString(input.mimeType) ?? ; const asset buildBase(input, mimeType); // 全新对象 const category getAssetCategory(mimeType); // 按 MIME 前缀分桶image / video / document // 根据是否已有 image/document/video 子对象走 legacy 或已统一结构两条分支 // 最终通过 syncImageDimensions 把尺寸镜像到根级返回新的 asset。 return syncImageDimensions(asset); }其关键设计在源码注释中有明确体现为了兼容 6.4 前端读取根级width/height的习惯syncImageDimensions会把image.width/height同步到资源根级——这一步同样是在新对象上完成不触碰输入。底层辅助函数asNumber、asString、isObject保证了对任意不可信输入的安全降级解析失败只返回undefined绝不抛错。4.2 测试验证“任意旧输入 - 新值”测试位于 packages/website-builder-sdk/src/asset/normalize.test.ts覆盖了三类输入形态旧版扁平值含editnormalizeToAsset(legacy)把edit.crop映射为新结构的image.crop把edit.hotspot映射为image.focalPoint同时保留alt、尺寸等信息产出全新的统一对象。6.x 扁平值无edit、无image子对象normalizeToAsset自动补出image: { width, height }让旧数据无需存储迁移即可被新代码渲染。已经统一化的资源normalizeToAsset具备幂等性——对格式良好的新资源传入返回与输入相等的新对象对视频资源则完整保留video.autoplay、video.poster对null、undefined、字符串、数字等非法输入一律返回null。测试还通过assetImageFromLegacyEdit单独验证了“hotspot - focalPoint、crop/alt/caption 保留”的映射细节与 ADR-006 中“保留旧类型、用归一化函数透明转换旧数据无需重新保存即可工作”的决策相互印证。五、权衡与适用边界ADR-007 在 Consequences 一节对收益与代价做了清晰的评估5.1 正面收益数据流可追踪每个变量在其生命周期内只持有“一个值”函数返回后变量内容不会悄悄变化排查问题时无需回溯“这个对象是什么时候被谁改的”。更易调试与测试函数可以当作纯函数对待——相同的输入必然得到相同的输出前提是内部无隐藏状态因此可以写出“输入快照 调用 断言相等”式的测试如 2.3 节所示。无共享引用惊吓调用方无需担心自己的对象被他人修改传参可以放心地把对象交给函数。5.2 代价与边界更多对象分配每次转换都新建对象会带来额外 GC 压力。在热路径上的取舍文档明确指出内层循环、高频操作中这一点可能产生影响而 Webiny 中大多数转换发生在请求级处理request-level processing这一层分配开销可以忽略不计。这给出了该规则的适用范围判断标准开销敏感的内层循环可以做例外评估但业务请求链路中的转换函数应当一律遵守“返回新值”。这也是把该规则写成 ADR架构决策记录而非硬性 lint 规则的原因——它既是一种强默认约束又保留了在明确测量到瓶颈时的权衡空间。六、如何在你的代码中复用该模式结合以上源码可以提炼出一套可直接复用的落地清单函数签名上承诺“输入只读”在 JSDoc 注释中写明“Returns a new object; does not mutate the input”如normalizeImageOptions.ts顶部注释所示。先建新对象再守卫式挂载字段对每个可解析字段采用“解析成功才写入”的白名单写法非法输入直接省略而不是把原始字符串留在结果里。领域对象用readonly 返回新实例如Asset那样将内部状态声明为readonly提供withProps/clone这类“返回新实例”的更新方法必要时复制运行时依赖到新实例。写一条“输入不被修改”的测试这是把 ADR 决策固化为可执行约束的最小成本方式——浅拷贝快照、调用函数、断言toEqual。涉及旧数据兼容时与 ADR-006 配合保留旧类型、用归一化函数如normalizeToAsset透明转换让旧数据无需迁移即可工作同时严格保持输入只读。结语ADR-007 是 Webiny 在“数据转换”上的核心架构约定输入只读、输出全新。它既不是激进函数式教条的照搬也不是一刀切的性能禁令而是一条针对请求级业务处理场景的强默认约束——用一次对象分配换取数据流的可追踪性、可测试性与共享引用安全。通过normalizeImageOptions、Asset.withProps()、normalizeToAsset()三个实现及其测试你可以直接观察这条 ADR 在真实项目中的完整生命周期决策 → 落地 → 测试锁定 → 权衡边界。赞分享CMS后端前端【免费下载链接】webiny-jsOpen-source, self-hosted CMS platform on AWS serverless (Lambda, DynamoDB, S3). TypeScript framework with multi-tenancy, lifecycle hooks, GraphQL API, and AI-assisted development via MCP server. Built for developers at large organizations.项目地址https://gitcode.com/gh_mirrors/we/webiny-js点击查看免费下载相关推荐plannotator 架构决策记录ADR实践指南从 ADR-0001 到 007 的决策治理体系plannotator 架构决策记录ADR实践指南从 ADR 0001 到 007 的决策治理体系 导读 本文围绕 plannotator 仓库中的 ADESLint getter-return 规则详解强制 Getter 必须返回值ESLint getter return 规则详解强制 Getter 必须返回值 getter return 是 ESLint 内置的一条 problem 类开发工具Lint静态分析代码质量ArkAnalyzer返回语句函数返回值分析ArkAnalyzer返回语句函数返回值分析 引言 在ArkTS语言开发中函数返回值分析是静态程序分析的关键环节。ArkAnalyzer作为面向ArkTS语静态分析开发工具OpenHarmony上一篇联想刃7000k BIOS隐藏菜单终极解锁指南3分钟释放硬件隐藏性能下一篇3分钟解锁AI图像分层魔法layerdivider让复杂设计变简单创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

VS Code 基于 Bing 的智能设置搜索:架构解析与实战指南

VS Code 基于 Bing 的智能设置搜索:架构解析与实战指南

文档教程 【免费下载链接】vscode-docs Public documentation for Visual Studio Code 项目地址: https://gitcode.com/gh_mirrors/vs/vscode-docs 点击查看 免费下载 导读 VS Code 内置 400 项设置,加上扩展贡献的自定义设置后数量急剧膨胀&#xff0…

2026/10/14 2:17:47 阅读更多 →
星上交换技术全解析:从电路交换到MPLS的体制选型与工程实践

星上交换技术全解析:从电路交换到MPLS的体制选型与工程实践

简介:星上路由交换与处理技术课件聚焦卫星通信中信息高效传输的核心环节,面向通信工程相关专业学生及卫星网络研发人员。课件共65页,系统梳理星上交换的基本概念、微波射频与基带两种实现方式,并对比电路交换、分组交换、ATM交换、…

2026/10/9 14:41:21 阅读更多 →
工业电源路径设计:eFuse与MCU协同实现智能保护

工业电源路径设计:eFuse与MCU协同实现智能保护

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 9:18:21 阅读更多 →

最新新闻

openrig 实战:用 tmux 与 Node.js 编排 Claude Code 和 Codex 多代理工作流

openrig 实战:用 tmux 与 Node.js 编排 Claude Code 和 Codex 多代理工作流

1. openrig 到底想解决什么问题第一次看到 openrig 这个名字,加上关联的 Claude Code、Codex、Node.js、tmux 这几个词,我脑子里冒出来的判断是:这大概率是一个把多个 AI 编码工具统一编排起来的工程化脚手架。后来实际折腾了一圈&#xff0c…

2026/10/15 0:04:56 阅读更多 →
AI入门避坑指南:零基础6个月可验证学习路线

AI入门避坑指南:零基础6个月可验证学习路线

1. 这不是“学AI”的路线图,而是帮你绕开99%初学者陷阱的生存指南你搜“人工智能入门”,页面刷出来全是“30天速成大模型”“零基础玩转ChatGPT”“PythonTensorFlow年薪30万”。我盯着屏幕看了三分钟,默默关掉网页——不是内容不好&#xff…

2026/10/15 0:04:56 阅读更多 →
探矿数据进RAG前的清洗全攻略:TXT、Word、PDF、网页四类文档处理实践

探矿数据进RAG前的清洗全攻略:TXT、Word、PDF、网页四类文档处理实践

1. 探矿数据进RAG之前,为什么必须先过清洗这一关1.1 探矿领域的文档生态:比想象中更杂做探矿业务的知识库和做通用知识库完全是两码事。通用场景里的文档再乱,基本也就是网页、PDF、Word这几类,格式相对规整。探矿业务呢&#xff…

2026/10/15 0:04:56 阅读更多 →
基于bbox的PDF文档解析:多栏重排与水印过滤实战

基于bbox的PDF文档解析:多栏重排与水印过滤实战

1. 项目概述:多栏和水印为什么是 RAG 解析里的两座山先说个场景。上个月我把一批技术论文和旧版产品手册塞进自建的 RAG 知识库,原以为 PDF 解析早就不是问题,结果检索结果让我怀疑人生:同一个问题的答案碎片化,有的片…

2026/10/15 0:04:56 阅读更多 →
AI视频生成的时间一致性难题与时空建模演进

AI视频生成的时间一致性难题与时空建模演进

1. 逐帧生成时代的"薛定谔主角":断裂感从哪来先从一个我自己的翻车经历说起。去年底我用某款主流的AI视频工具做了一个两秒钟的镜头:一个女孩从画面左侧走到右侧,中途回头笑了一下。单看每一帧,光影、质感、脸部的细腻程…

2026/10/15 0:04:56 阅读更多 →
亨得利客户服务中心官方网点分布一览:2026年全国多城门店地址与最新联系电话公布

亨得利客户服务中心官方网点分布一览:2026年全国多城门店地址与最新联系电话公布

亨得利客户服务中心2026年全国官方网点分布现已公布,涵盖多座重点城市的直营售后门店地址与最新联系电话。表友可通过亨得利全国统一服务电话:400-901-0695 进行维保预约、网点查询、故障咨询指导与服务解读报价,也可关注亨得利官方公众号&am…

2026/10/15 0:03:53 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →