
iii 适配器模式把任意现有服务封装成标准 iii Worker【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii在 iii 系统里接入一个已有服务第三方 API、某个库、内部微服务时Adapter Pattern 给出的做法是不重写该服务而是用一层薄薄的 worker 把它的能力暴露为 iii 函数由 worker 负责在服务原有接口与 iii 函数调用形态之间做翻译。读完本文你将理解该模式的适用边界、三步式 worker 结构以及从 SDK 与引擎源码中看到的函数注册、函数 ID 约定与错误映射的真实实现机制。该模式原始定义见仓库中的 Adapter Pattern 文档。模式定义翻译层而不是改造层Adapter Pattern 的核心主张可以概括为一句话把现有服务包进一个 thin worker让它像 iii 系统里的任何其它 worker 一样被调用。worker 对外暴露的是每个操作对应一个 iii 函数worker 内部做的是协议翻译把 iii 的函数调用JSON 负载 函数 ID 寻址转换成被包装服务的原生调用HTTP 请求、gRPC、库方法等再把结果原路返回被包装的服务本身零改动不重写、不 fork。这种翻译层定位决定了它的成本模型你付出的代价是一个额外进程和一层胶水代码换来的是整个 iii 系统内调用形态的统一。何时使用这个模式原文档给出的适用条件是两个判断的交集你有一个已有服务想从 iii 里调用它但不想重写它你希望调用方用与调用其它 worker 完全相同的方式按函数 ID而不是按 HTTP 端点或库特有的调用形状来寻址它。第二条是关键的架构收益。一旦包装完成业务 worker 里的调用方不需要知道被包装的是 Stripe、OpenAI 还是一个内部 REST 服务——它们面对的永远是worker.trigger({ function_id, payload })这样的统一形态。Node SDK 的trigger接口sdk/packages/node/iii/src/types.ts对此是泛型化的输入输出类型在编译期声明运行期只传function_id与 payload。反向的判别标准同样有用如果调用方本来就持有该服务的原生 SDK、且不需要 iii 的寻址/触发能力那么直接调用原生 SDK 更简单不需要走 adapter。三步式结构源码级印证原文档把 adapter worker 的结构拆成三步连接引擎、为每个操作注册一个函数、在 handler 内调用现有服务并返回结果。下面逐步结合仓库源码看这三步在 iii 中的真实落点。第一步连接引擎worker 启动时先向引擎注册自身身份。Rust SDK 中的WorkerMetadatasdk/packages/rust/iii/src/iii.rs上报的字段包括nameworker 名称默认形如hostname:pid托管身份模式下可被III_WORKER_NAME覆盖description一行人类/LLM 可读的描述源码注释明确指出它会出现在engine::workers::list/engine::workers::info的输出里——对 adapter 来说把本 worker 包装了 XXX 服务写进这一行非常合适namespaceworker 所属命名空间决定了其函数调用与 trigger 绑定默认解析在哪一个 namespace缺省时引擎应用默认命名空间。第二步每个操作注册一个函数一个操作 一个函数在两种 SDK 中都有直接的类型化入口Rust SDK提供同步与异步两个注册方法sdk/packages/rust/iii/src/iii.rs/// 同步输入类型 R 必须匹配调用请求格式输出 O 序列化为响应 worker.register_function::R, O(stripe::create_customer, |input| { Ok(my_stripe_client.create_customer(input)?) }); /// 异步handler 返回 Future worker.register_function_async::R, O(stripe::create_customer, |input| async move { Ok(my_stripe_client.create_customer(input).await?) });从签名约束看R: DeserializeOwned JsonSchema、O: Serialize JsonSchema即输入输出类型必须同时满足 serde 可序列化与 JsonSchema 可描述——这意味着注册时 SDK 能自动生成该函数的request_format/response_formatJSON Schema引擎侧的Function结构engine/src/function.rs也确实持有这两个字段。换句话说adapter 的每个包装函数在引擎眼中自带一份接口契约调用方可以做格式对齐。Node SDK的registerFunction(functionId, handler, options?)返回一个FunctionRef句柄options支持description、request/response formats、metadata句柄支持ref.unregister()卸载函数sdk/packages/node/iii/src/types.tsconst ref worker.registerFunction( stripe::create_customer, async (data: { email: string }) myStripeClient.createCustomer(data), { description: Proxied Stripe customer creation }, ) // 生命周期结束或需要下线该操作时 ref.unregister()函数 ID 约定SDK 文档注释里的真实用例展示了以::分隔域/来源与操作的命名习惯——例如流操作注册为stream::get(...)、stream::set(...)sdk/packages/node/iii/src/iii.ts外部代理函数写作external::my-lambda。对 adapter 而言这种服务名::操作名的写法是从源码结构看最自然的选择能让函数 ID 自解释地标明这个函数代理了谁。第三步handler 内调用现有服务并返回结果引擎侧 handler 的真实签名定义在 engine/src/function.rspub type HandlerFn dyn Fn( OptionUuid, // invocation_id Value, // data调用负载 OptionArcSession, // session OptionValue, // metadata随调用附带的 sidecar ) - HandlerFuture;返回类型是四态枚举FunctionResultSuccess(T)/Failure(E)/Deferred/NoResult。对 adapter 最有意义的是成功与失败两态成功把被包装服务的响应或你提炼后的字段作为Value返回失败映射handler 返回Err(...)时引擎收到Failure(ErrorBody)其中ErrorBody定义在 engine/src/protocol.rs。这意味着 adapter 需要做一次显式的错误翻译把被包装服务的原生错误HTTP 4xx/5xx、异常、超时映射为 iii 的函数错误而不是把原始报文当成功结果透传。原文档 TODO 中提到的error mapping from the wrapped service to iii function errors正是这一步。此外注意 handler 收到的metadata参数它是触发/调用时随 payload 一起携带的 sidecar 通道独立于data。adapter 可以利用它透传链路信息而不污染业务 payload。轻量替代路径直接把远程 HTTP 端点注册为函数Node SDK 的registerFunction第二个参数除了本地 handler还接受一种HttpInvocationConfigurl、method、timeout_ms、auth官方文档注释中给出的示例是把一个 AWS Lambda 代理为 iii 函数sdk/packages/node/iii/src/types.tsconst lambdaRef worker.registerFunction( external::my-lambda, { url: https://abc123.lambda-url.us-east-1.on.aws, method: POST, timeout_ms: 30_000, auth: { type: bearer, token_key: LAMBDA_AUTH_TOKEN }, }, { description: Proxied Lambda function }, )从这段 API 设计可以推断对于被包装服务就是无状态 HTTP 端点的场景可以不启动独立 worker 进程而是在任意 iii worker 内直接把远程端点声明为函数——调用由引擎按该配置发起 HTTP 请求完成。认证采用token_key指向环境变量名的形式示例中为LAMBDA_AUTH_TOKEN即密钥本身不落代码、不落函数配置而是运行时从环境读取。这回答了原文档 TODO 中authentication / secrets 如何处理的一半adapter 的密钥应遵循同一原则——只引用环境变量/密钥引用不在函数注册信息里出现明文。一个完整的 adapter worker 心智模型把三步合起来一个包装内部 REST 服务的 adapter worker 的形态是创建 worker 并上报WorkerMetadata名称、描述、命名空间为服务的每个要暴露的操作注册一个函数ID 建议采用服务名::操作名约定Rust 侧利用类型系统让 request/response format 自动生成每个 handler 内部把 iii 的 JSON 负载反序列化为该操作的原生请求 → 调用现有服务 → 成功时返回响应、失败时映射为 iii 函数错误。此时其它 worker、trigger、channel 都可以像调用本地函数一样按函数 ID 调用它完全感知不到背后的服务边界。与其它模式的边界与 Reactive State Pattern 正交Adapter Pattern 解决如何把一个外部服务变成 iii 里可调用的函数Reactive State Pattern 解决状态变化时由引擎触发函数。二者可叠加——被 adapter 包装的函数同样可以被状态变化 trigger 绑定触发若被包装服务本身需要状态持久化、队列或流这些能力应交给 iii 对应的 state/queue/stream worker 处理adapter 保持无翻译层之外的状态。小结Adapter Pattern 在 iii 中的价值在于把服务边界折叠进统一的函数调用模型调用方按函数 ID 寻址、引擎统一做路由与观测、被包装服务零改造。仓库源码给出了它的三条实现支撑——类型化的register_function/registerFunction注册入口与自动生成的请求/响应格式契约sdk/packages/rust/iii/src/iii.rs、sdk/packages/node/iii/src/types.ts、引擎侧四态FunctionResult与ErrorBody的错误通道engine/src/function.rs、engine/src/protocol.rs以及面向纯 HTTP 服务的HttpInvocationConfig轻量注册路径。掌握这三点后接入任何现有服务的路径都是明确的命名服务名::操作名、注册类型化格式契约、翻译负载与错误双向映射。【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考