2026/8/24 10:09:01

Ferry架构揭秘:TypedLink可组合设计,为什么Client本身就是一个Link

Ferry架构揭秘:TypedLink可组合设计,为什么Client本身就是一个Link Ferry架构揭秘TypedLink可组合设计为什么Client本身就是一个Link【免费下载链接】ferryStream-based strongly typed GraphQL client for Dart项目地址: https://gitcode.com/gh_mirrors/fer/ferryFerry 是面向 Dart 和 Flutter 的流式、强类型 GraphQL 客户端它的网络层不是写死的一条管线而是由一个个可组合的TypedLink串联而成。更妙的是Ferry 的Client本身就是一个 Link——你调用的client.request()本质上只是链式执行入口。本文将带你看懂这套 TypedLink 可组合架构的设计动机与运作方式帮助你在自定义网络层、缓存策略时有的放矢。什么是 TypedLinkFerry 架构的最小积木TypedLink是整个架构的核心抽象定义在 packages/ferry_exec/lib/src/typed_link.dart 中。它的职责很单纯接收一个OperationRequest返回一个OperationResponse的Stream流。每个 Link 只关心一件事执行完成后可选择调用forward把请求交给链上的下一个 Link链尾的终结型 Link如负责网络请求的GqlTypedLink则不再转发。这种洋葱式结构让每个功能错误处理、缓存、鉴权…都能独立开发、独立测试再像积木一样拼起来。5 个内置 TypedLink各司其职打开核心文件 packages/ferry/lib/ferry.dart可以看到Client在构造函数中用TypedLink.from组装了这样一条链ErrorTypedLink链首哨兵。捕获下游抛出的异常统一转换成响应里的linkException属性保证错误永远以数据的形式流出而不是崩溃。源码见 packages/ferry/lib/src/error_typed_link.dart。RequestControllerTypedLink管理requestController流。重新获取refetch只需再向控制器塞一个相同requestId的请求分页则通过updateResult回调把新旧数据合并。同一请求的响应流会被缓存共享避免重复执行。源码见 packages/ferry/lib/src/request_controller_typed_link.dart。AddTypenameTypedLink自动为查询添加__typename字段保证缓存归一化时类型信息完整。UpdateCacheTypedLink可选在 Mutation 执行前给业务代码一次预更新缓存的机会用于乐观 UI。FetchPolicyTypedLink链尾的策略路由器。根据每个操作的FetchPolicyCacheFirst、NetworkOnly、CacheAndNetwork等决定读缓存、走网络还是两者都要它内部又嵌套了一条子链OptimisticTypedLink → GqlTypedLink后者负责真正调用你传入的gql_link完成 HTTP/WebSocket 通信。源码见 packages/ferry/lib/src/fetch_policy_typed_link.dart。为什么 Client 本身就是一个 Link这是 Ferry 架构最反直觉也最优雅的一点。看ferry.dart里的类声明class Client extends TypedLinkWithCacheAndRequestController { // ... late TypedLink _typedLink; Client({ required this.link, ... }) { _typedLink TypedLink.from([ ErrorTypedLink(), RequestControllerTypedLink(this.requestController), if (addTypename) AddTypenameTypedLink(), ... FetchPolicyTypedLink(link: link, cache: this.cache, ...) ]); } override StreamOperationResponseTData, TVars requestTData, TVars( OperationRequestTData, TVars request, [forward] ) _typedLink.request(request, forward); }Client直接继承自TypedLink其request()方法就是把内部分好的链跑一遍。这样设计带来三个实实在在的好处统一的入口语义client.request(query)和someLink.request(operation)是同一套接口。任何 Link 都可以独立替换 Client 做调试或测试Client 也可以被拼进更大的链路里。零成本的扩展点TypedLink 提供了一整套组合工具——TypedLink.from()串联多个、concat()拼接、split()按条件分流、route()动态路由、TypedLink.function()函数式 Link。想加个日志记录、请求限流或鉴权头只需写一个自己的 TypedLink再client.concat(myLink)即可完全不用改动内部代码。每个环节可单独测试仓库的packages/ferry/test/typed_links/目录下每个 Link 都有独立测试文件正是得益于Link 即函数的纯粹接口。上手建议如何阅读这套架构源码从 packages/ferry/lib/ferry.dart 看 Client 的链组装过程只需 80 多行再看 packages/ferry_exec/lib/src/typed_link.dart 理解from / concat / split / route四个组合原语的实现所有内置 Link 的导出清单在 packages/ferry/lib/typed_links.dart官方文档 docs/how-it-works.md 对请求→响应全流程有更完整的叙述配合本文的链路拆解食用更佳。一句话总结Ferry 把GraphQL 客户端拆解成了一串职责单一的强类型 Link而 Client 只是这条链的包装与入口——这既是它高度可定制的原因也是它易于理解的核心秘密。【免费下载链接】ferryStream-based strongly typed GraphQL client for Dart项目地址: https://gitcode.com/gh_mirrors/fer/ferry创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考