ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

状态传输的进化之路:从表单 POST、AJAX、REST、GraphQL 到 Electric 的 Local-first 终极形态

状态传输的进化之路:从表单 POST、AJAX、REST、GraphQL 到 Electric 的 Local-first 终极形态 状态传输的进化之路从表单 POST、AJAX、REST、GraphQL 到 Electric 的 Local-first 终极形态【免费下载链接】electricThe agent platform built on sync.项目地址: https://gitcode.com/GitHub_Trending/el/electric状态传输state transfer是联网应用的基本问题Web 从一开始就围绕网络架构展开前后端分离、AJAX、REST 都是对数据如何在网络上移动这一问题的不同回答。本篇技术文章以 Electric 官方博客《The evolution of state transfer》为骨架梳理 Web 状态传输从命令式到声明式、从手动到自动的完整进化脉络并结合当前开源仓库中的同步引擎源码与文档深入讲解 local-first本地优先架构为何被视为这一进化的终局、其背后的混合架构分层思想以及 Electric 如何用可复现的代码与配置把状态传输抽象进数据库层这一愿景落地。[!WARNING] 本文所依据的博客文章发布于 2022 年 12 月面向当时基于嵌入式 SQLite WebSocket 协议的旧版 ElectricSQL 系统撰写现仅作历史背景保留。仓库中当前活跃的是重写后的 Electric 同步引擎Electric Next其架构说明见仓库内 blog/posts/2024-07-17-electric-next.md。下文在介绍历史愿景时会同时标注与当前实现的对应关系。从表单 POST 到 GraphQL前半程的进化Web 上的状态传输始于 HTTP POST。数据在服务端被渲染进页面用户填写表单点击提交后表单数据以application/x-www-urlencoded的编码形式作为 HTTP POST 请求的 body 发送到服务器。这就是状态最初在 Web 上被传输的方式显式且命令式绑定在整页刷新之上直接由用户输入触发。它改变了世界但开发者很快对每次交互都要整页刷新的模型感到不满。AJAX 的发明1998 年Microsoft Outlook 团队发明了XMLHttpRequest次年将其悄悄带入 Internet Explorer 5.0。随后所有浏览器跟进在 Gmail、Google Maps 等应用的推动下AJAX 时代到来。有了 AJAX开发者可以自行决定何时、以何种方式向服务器 POST 数据不再把状态传输与页面刷新耦合在一起。这使 Web 成为一流的交互式应用平台。但随意地 POST、处理零散数据片段仍然复杂且武断开发者寻求标准化AJAX 最终收敛到 JSON 与 REST 之上。标准化到 RESTJSON 与 REST 的组合为 API 设计和异步状态传输带来了通用模式。REST 有助于扩展性、简化后端开发并且由于协议标准化还催生了自动 API 生成的可能——例如 Swagger 可以从 REST API 规范自动生成文档和客户端库Remix 等框架可以根据客户端代码自动创建 API。标准化是好事但 REST 在数据获取上并非最优模式渲染一个页面状态常常需要多次请求常常加载超出所需的数据——REST 端点通常返回整个资源的表示哪怕你只用到其中一部分。GraphQL 登场2015 年Facebook 发布 GraphQL 及配套的 Relay 框架。GraphQL 的两个核心设计目标正是修复 REST 在数据获取上的弱点最小化应用渲染页面所需的请求次数优化取回的数据形状避免加载不必要的数据。在 GraphQL 中组件树上的每个组件都声明自己所需的确切数据形状fragment数据片段Relay 将这些 fragment 聚合、归一化为一个顶层查询从而同时优化网络请求数量与返回数据的形状。例如一个展示书籍信息的页面其子组件需要展示作者简介。你可以为顶层页面组件定义查询并把子组件需要的数据片段包含进来query BookQuery($bookID: ID!) { book(id: $bookID) { title author { ...AuthorDetails_author } } } fragment AuthorDetails_author on Author { name photo { url } }Relay 通过聚合 fragment 来填充查询形状页面实际抓取到的数据为{ book { title author { name photo { url } } } }可以看到GraphQL 是声明式的作为开发者你只声明组件需要的数据系统负责 ① 在网络上传输数据② 将传输的数据量最小化到应用实际使用的形状。这是朝向抽象并优化状态传输迈出的一大步。但协议仍然围绕网络设计——优化的是那个前置的大规模 resolve。开发者仍需用 fetch policies 显式控制数据如何、从哪里获取。写入在默认情况下也仍然是命令式的。乐观写optimistic writes虽然更声明式却引入了处理回滚的代码负担。从 Relay 核心commitMutationAPI 的签名可见一斑commitMutation( environment: Environment, config: { mutation: GraphQLTaggedNode, variables: {[name: string]: mixed}, onCompleted?: ?(response: ?Object, errors: ?ArrayPayloadError) void, onError?: ?(error: Error) void, optimisticResponse?: Object, optimisticUpdater?: ?(store: RecordSourceSelectorProxy) void, updater?: ?(store: RecordSourceSelectorProxy, data: SelectorData) void, configs?: ArrayDeclarativeMutationConfig, cacheConfig?: CacheConfig, }, );归根结底在 GraphQL 下状态传输层的关注点仍以错误处理器、命令式 mutation、fetch policy 语义的形式渗透回应用代码。相比之下local-first 作为一种范式有潜力把状态传输完全抽象掉不让网络层的关注点泄漏进应用代码。Local-first把状态传输抽象进数据库层在 local-first 架构中开发者直接面向本地嵌入式数据库编码。读写是即时的。用户依然可以共享数据、协作但状态传输发生在后台读写先针对本地数据库执行再在后台与服务器同步。与 GraphQL 类似你需要定义哪些数据可以被同步并把数据声明式地绑定到组件上。但与 GraphQL 不同的是你不需要等待网络来确认写入也不需要配置 fetch policy 语义。作为 Web 开发者你可以构建实时、多用户的应用程序而不必思考或围绕网络编码。例如在旧版 ElectricSQL 中把 GraphQL 的useQuery换成useElectricQuery结果就会自动保持同步——无论世界哪个角落的人编辑了items表。因为 SQL 是声明式语言而你查询的是嵌入式 SQLite 数据库因此不再需要图抽象也不需要把关系表映射到 GraphQL schema 的额外 resolver 层const ExampleComponent () { const { results } useElectricQuery(SELECT value FROM items, []) return ( View {results.map((item, index) ( Text key{ index } style{styles.item} Item: { item.value } /Text ))} ) }关键在于系统保持同步的结果来自本地数据库。它不会在组件渲染时为你聚合和抓取数据只是与嵌入式本地数据库对话。因此你可以确信即使网络宕机、后端宕机应用依然能工作而且查询时间一致且极快常常亚毫秒级。当 local-first 做对了开发者无需担心网络及其各种失败模式就能构建响应式、实时、多用户的应用。数据如何、何时传输完全由数据库复制系统决定。由此local-first 把状态传输彻底带入了数据库的领域连同围绕一致性与完整性的全部严谨性——而这些严谨性当你在应用代码里手工处理数据时很容易丢失。配合正确的系统与并发语义你还可以用**终局性finality而非试探性tentativity**进行本地写入即写入一旦在本地被接受就确定不会被拒绝1。于是你不再需要实现上文 GraphQLcommitMutationAPI 中的updater与optimisticUpdater两个回调只需写入本地数据库——如果本地写入成功就完成了。当前仓库中的声明式数据绑定实现这篇 2022 年的博客描述的useElectricQuery属于旧版系统但声明式地把数据绑定到组件这一核心思想在仓库当前的 React 集成中完整继承了下来。当前实现位于 packages/react-hooks/src/react-hooks.tsx对外通过 packages/react-hooks/src/index.ts 导出核心是useShapehook它接收 Shape 配置url、params等通过useSyncExternalStoreWithSelector把 Shape 数据流绑定到组件状态并用streamCache/shapeCache对 ShapeStream 与 Shape 做内存级复用。仓库的 React 集成文档 给出了可直接运行的示例import { useShape } from electric-sql/react const MyComponent () { const { isLoading, data } useShape{ title: string }({ url: http://localhost:3000/v1/shape, params: { table: items, }, }) if (isLoading) { return divLoading .../div } return ( div {data.map((item) ( div{item.title}/div ))} /div ) }声明式理念也体现在 Shape 定义上。仓库文档 guides/shapes.md 指出Shape 是控制同步的核心原语由表名、可选的where过滤子句、可选的列选择子句等构成客户端可以同步一个或多个 Shape多个客户端可同步同一 ShapeShape 之间可以重叠——这正是声明哪些数据可以同步这一愿景在当前实现中的直接映射。数据的最优放置与移动混合架构There are no solutions. There are only trade-offs.没有解决方案只有取舍。 —— Thomas Sowell当然local-first 也有约束。并发写入受合并merge语义支配你必须接受相对论宇宙的现实详见仓库博客 blog/posts/2022-05-20-relativity-causal-consistency.md。设备约束还意味着你往往无法把整个数据库同步到设备上。因此仍然需要一种混合架构——云端作为 local-first 应用的地基它不仅提供持久性与同步还要在首次运行、以及设备所需数据形状变化时把数据加载进本地应用。这种混合架构从中心云到终端设备包含若干层中心云存储如 AWS Aurora多区域地理分布式存储如 PolyScale无服务器云边缘如 Fauna 与 Neon设备端本地数据库如 SQLite 与 DuckDB。管理数据在这些层之间——从云到边缘再到本地设备——的放置与移动极其复杂复杂到无法手工优化。它必须由系统来管理和优化与其命令式地把数据放到云架构的某个位置不如声明你的需求与优化参数让云端处理其余部分。随着云编程走向成熟它似乎不可避免地将脱离传统顺序编程。云是一台巨大的、横跨全球的分布式计算机。并行性在各个尺度上都无处不在。富有创造力的程序员正被遗留编程模型束缚。[需要的是]把分布式程序分离为程序语义、可用性、一致性与优化目标。 —— New Directions in Cloud Programming, Joe Hellerstein 等这就是状态传输的终局静态程序分析与 AI 的结合不仅优化数据在点与点之间的传输还优化数据的放置以及这些点与层本身应该在哪里。对 Electric 的启发从愿景到当前实现Web 开发一直在沿着从手动、命令式的数据传输走向自动、声明式系统的路径进化混合式 local-first 架构正是这条路径的自然终点。这也是 Electric 的构建目标一个框架——你声明哪些数据可以同步、组件需要哪些数据系统处理其余一切。在这个愿景里状态传输被完全抽象进数据库层可以针对一致性、完整性、放置与延迟进行优化而你永远不必再写回滚处理器或检查 HTTP 状态码。需要明确的是博客撰写时的旧版 ElectricSQL 与仓库当前的 Electric Next 同步引擎在技术选型上有重大差异旧版系统嵌入式 SQLite WebSocket 复制协议 DDLX 权限规则等垂直集成方案即博客中所描述的 local-first 平台形态当前系统从 blog/posts/2024-07-17-electric-next.md 可以看到Electric 重写为基于 Postgres 的分区复制同步引擎采用 HTTP 复制协议、以 Shape 定义部分复制通过 packages/react-hooks 等客户端库与 React、MobX、TanStack 等集成同时拥抱试探性tentativity写入模式逐步从只读路径向写入路径演进。但无论选型如何变化把状态传输从应用域中抽象出去这一进化脉络始终是 Electric 的思想内核也是理解其 Shape、HTTP 同步 API 与客户端库设计的一把钥匙。仓库内 examples/linearlite 等示例应用完整演示了如何用这套当前实现构建实时多用户应用可供进一步上手验证。延续阅读博客Electric Next——构建 Electric 的新方法文档Shapes 同步原语指南文档React 集成与 useShape 使用说明源码react-hooks 包实现useShape / useShapeStream / preloadShape文档local-first 与分布式数据库相关文献示例linearlite——基于同步引擎的实时应用相关理论基础见 Highly Available Transactions 与 Cure 两篇论文均收录于仓库 docs/sync/reference/literature.md 的文献页。↩【免费下载链接】electricThe agent platform built on sync.项目地址: https://gitcode.com/GitHub_Trending/el/electric创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表