ARTICLE DETAIL

资讯详情

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

III 适配器模式实战:把现有服务封装成 iii Worker 与函数

III 适配器模式实战:把现有服务封装成 iii Worker 与函数 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当你要把一个已有服务——第三方 API、某个库、或公司内部微服务——纳入 iii 系统而不想重写它时正确做法是用一个薄 Worker把它包裹起来该 Worker 把服务的能力逐个注册为 iii 函数并在每次被调用时翻译成底层服务的原生接口。本文基于 Adapter Pattern 文档展开结合 iii 仓库中的 SDK 用法与引擎源码讲清楚适配器的结构、函数 ID 约定、错误映射、认证与密钥处理以及引擎侧iii-http-functions这一零代码适配器变体的实现细节。模式定位让旧服务获得统一的函数地址iii 中一切能力的寻址单位是函数 IDfunction_id。队列、cron、状态、HTTP、pub/sub 等所有触发器最终都通过function_id调用函数。因此适配器模式的目标非常明确当你有一个现有服务希望在 iii 中使用它但不重写它并且希望调用方以和调用其他所有 Worker 完全相同的方式按函数 ID而不是按 HTTP 路径或某个 SDK 库特有的调用形态来寻址它时就用适配器模式。这意味着调用方无需知道被包裹服务是 REST、gRPC 还是某个 Python 库它们只面对service::name这样的函数 ID。这一模式也出现在 iii 官方的架构模式速查中——skills/iii-architecture-patterns/SKILL.md 的 Pattern Map 把复用既有能力、组合成后端架构归纳为由Function、Trigger、状态、队列等原语搭建适配器正是其中最基础的接入环节。适配器结构三步搭建一个薄 Worker原文档给出的结构定义是一个薄 iii Worker做三件事连接引擎为你想暴露的每个操作注册一个函数在每个 handler 内部调用现有服务并把结果作为该函数的响应返回。下面逐条落到 iii 的实际 API 上。第一步连接引擎三种官方 SDK 的连接入口分别是见 Functions 文档import { registerWorker } from iii-sdk; const url process.env.III_URL; if (!url) throw new Error(III_URL must be set); const worker registerWorker(url, { namespace: orders });import os from iii import register_worker, InitOptions worker register_worker( os.environ.get(III_URL), InitOptions(worker_nameinvoice-worker, namespaceorders), )use iii_sdk::{InitOptions, register_worker}; let url std::env::var(III_URL).expect(III_URL must be set); let worker register_worker( url, InitOptions { namespace: Some(orders.into()), ..Default::default() }, );namespace决定了该 Worker 所有函数所处的命名空间这是多套环境dev/prod或多业务线共用一个引擎时避免函数 ID 冲突的基本手段。第二步与第三步每个操作注册一个函数handler 里调用旧服务函数 ID 的约定形式是service::name。以把一个内部发票服务REST 接口暴露为 iii 函数为例// invoices-adapter-worker.ts import { registerWorker } from iii-sdk; const url process.env.III_URL!; const worker registerWorker(url, { workerName: invoices-adapter, namespace: orders, }); // 每个被暴露的操作 一个函数handler 负责翻译 worker.registerFunction(invoices::create, async (payload: { amount: number; customer: string }) { const resp await fetch(${process.env.INVOICES_API_URL}/v1/invoices, { method: POST, headers: { content-type: application/json, // token 由 worker 进程的环境变量提供而不是写死在代码里 authorization: Bearer ${process.env.INVOICES_API_TOKEN}, }, body: JSON.stringify(payload), }); if (!resp.ok) { // 预期失败抛出错误让 iii 把它当作调用错误传播给调用方 throw new Error(invoices service returned ${resp.status}: ${await resp.text()}); } return await resp.json(); // 返回值即 iii 函数响应 }); worker.registerFunction(invoices::fetch, async (payload: { id: string }) { const resp await fetch(${process.env.INVOICES_API_URL}/v1/invoices/${payload.id}); if (!resp.ok) throw new Error(invoices service returned ${resp.status}); return await resp.json(); });要点函数 ID 约定service::name两段式前缀是被适配服务的逻辑名与 Functions 文档 中math::add等示例一致带 namespace 后引擎侧完整 ID 会包含命名空间前缀。注册即暴露registerFunction的id就是触发器使用的function_id因此注册完invoices::create后队列/cron/状态/HTTP 等任意触发类型都能直接绑定它无需任何额外适配代码。Worker 生命周期registerFunction返回的句柄带有unregister()方法当 Worker 断连时其名下所有函数自动从引擎移除未完成的调用会报错。Worker 部署侧同样走 iii 的统一设施如 HTTP 文档 演示的那样可以先跑起引擎与 Compose daemon然后执行iii trigger -n dev compose::add worker./invoices-adapter-worker把该 Worker 目录加入项目适配器就进入了与所有 Worker 相同的管理与观测平面。函数 ID 约定与请求/响应契约原文档的 TODO 中提到应说明函数 ID 约定这里结合仓库文档补全ID 形式service::nameservice段是你对被适配服务的命名name段对应一个具体操作create/fetch/charge……。同一逻辑服务下保持前缀一致调用方与 Agent 都能凭前缀推断能力归属。JSON Schema 随注册携带注册函数时可以附request_format/response_format两个 JSON Schema它们与函数一起存储并出现在 iii console、iii trigger --help等位置worker.registerFunction( invoices::create, async (payload) { /* 调用被适配服务 */ }, { request_format: { type: object, properties: { amount: { type: number }, customer: { type: string } }, required: [amount, customer], }, response_format: { type: object, properties: { id: { type: string }, status: { type: string } }, required: [id], }, }, );需要注意根据 Functions 文档 的说明当前版本没有运行时校验——Schema 是纯元数据引擎不会因 payload 不匹配而拒绝调用。对适配器而言Schema 的真实价值是给 console、Agent 和调用方一份契约文档让function_id Schema组合起来成为可被 LLM 理解与检索的能力描述。metadata注册时还可挂任意 JSONmetadata如{ owner: billing-team, public: true }引擎不解释它但 worker-manager 的 RBAC 白名单可以按metadata:选择器暴露/屏蔽函数——对适配器来说这是给被暴露能力打访问控制标签的自然位置。文档同时警告访问控制必须系统侧把关不能依赖不可信 Worker 自报。错误映射把被适配服务的失败翻译为 iii 调用错误原文档 TODO 明确列出要讲error mapping这正是适配器最容易做错的环节。iii 的语义来自 Functions 文档Return values and errors一节是函数 handler返回的值就是函数响应由你负责整形到声明的响应 Schemahandler内部抛出的错误会作为调用错误invocation error传播给调用方并附带 Worker 的栈信息Node 转发error.stackPython 转发traceback.format_exc()Rust 转发底层错误的栈引擎不会吞掉它们。官方建议据此区分两类失败预期失败如被适配服务返回 4xx 业务错误返回结构化错误值意外失败网络中断、5xx、未知异常抛出 / raise / 返回Err。对适配器的直接推论被适配服务的 HTTP 状态码、超时、重试语义都要在 handler 内显式处理并二选一映射为结构化返回例如{ error: invoice-not-found }或抛错让 iii 的队列重试/DLQ 机制接管。如果该函数被queue触发器驱动抛出的错误会进入队列的重试与死信链路——这等于免费获得了旧服务调用的持久化重试能力前提是重试语义与被适配服务兼容幂等。不要在 handler 里静默吞掉底层异常再返回一个看起来正常的对象那会让function_id的调用方与 Agent 无法区分成功与失败。认证与密钥Secret 不进 Worker 源码更不过 SDK 通道原文档 TODO 还要求说明认证/密钥处理仓库给出了两种层次的做法第一层Worker 进程内调用上文示例——密钥放在运行该 Worker 进程的环境变量里如INVOICES_API_TOKEN代码只引用变量名。这是最直接的封装。第二层引擎侧 HTTP 直连零代码适配器——如果你的被适配服务就是标准 HTTP 接口iii 提供了iii-http-functionsWorker可以让引擎直接发起调用Worker 只声明端点不写任何请求代码。Functions 文档 给出的HttpInvocationConfig字段表字段类型默认值说明urlstring(必填)函数被调用时引擎请求的端点methodGET \| POST \| PUT \| PATCH \| DELETEPOSTHTTP 方法timeout_msnumber30000每次请求的超时毫秒headersRecordstring, string{}附加到每次调用的头authHttpAuthConfig(无)bearer、hmac或api_key加token_key/secret_key/value_key之一注册示例Nodeworker.registerFunction( notifications::send, { url: https://hooks.provider.example.com/notify, method: POST, timeout_ms: 5000, headers: { X-Service: iii-worker }, auth: { type: bearer, token_key: PROVIDER_API_TOKEN }, }, { description: POST a notification to the provider webhook }, );关键安全语义token_key/secret_key/value_key填的是环境变量的名字而不是密钥本身。引擎在自己进程环境中解析它们因此密钥留在引擎宿主进程永远不会经过 SDK 的 WebSocket 通道传到 Worker。对把内部 REST 服务包进 iii的场景这是比第一层更强的隔离Worker 进程甚至不需要知道密钥。引擎侧还有默认安全策略兜底。从 engine/src/workers/http_functions/config.rs 的HttpFunctionsConfig及其测试同文件 的default_config用例可以看到iii-http-functions的默认安全配置为url_allowlist [*]URL 白名单block_private_ips true默认拦截指向内网地址的调用require_https true默认要求 HTTPS。生产环境应显式收窄url_allowlist到具体域名配合require_https与block_private_ips把适配器能触碰的端点限制在最小集合内。引擎侧实现一个函数如何变成一次 HTTP 调用对零代码适配器变体引擎源码揭示了完整的适配链路位于 engine/src/workers/http_functions/mod.rsHttpFunctionsWorker内部用一个DashMap缓存已注册的HttpFunctionConfig键是(namespace, function_path)二元组。源码注释解释了为什么必须带 namespace同一函数 ID 可能被两个不同命名空间的 Worker 合法注册仅用 path 作键会让后注册的覆盖前者而先注册者注销时会误删幸存者的条目——这正是函数 ID 约定必须包含命名空间这一约定在引擎层的落地。每个函数注册时Worker 通过create_handler_wrapper把远端配置url、method、timeout_ms、headers、auth打包成一个闭包 handler该 handler 在函数被触发时调用HttpInvoker::invoke_http并以一个uuid::Uuid::new_v4()作为本次调用的关联 ID 发起请求。也就是说无论函数是 Worker 进程内的 handler 还是引擎直连的 HTTP 端点在引擎看来都是同一个注册函数 → 触发器寻址 → handler 执行 → 结果/错误返回的统一形态。这与原文档调用者按函数 ID 寻址而不是按 HTTP 或库特定调用形态的核心主张完全一致适配发生在注册时运行时的调用形态对调用方无感。错误路径同样统一Functions 文档 说明引擎把被调用端点的 payload 作为 JSON 请求体发出任何非 2xx 响应或网络错误都被视为调用失败并回传调用方HTTP-invoked 函数与进程内 handler 一样出现在函数列表中可被 console 正常发现。相关行为在引擎测试中也有覆盖例如 engine/tests/serverless_http.rsHTTP 直连函数与 engine/tests/http_e2e_security.rs安全控制。与触发器和架构模式的关系适配器产出的函数天然是 iii 系统中可被任意触发的一等公民worker.trigger、iii trigger以及 queue、cron、state、subscribe 等所有绑定触发器都无需改动即可驱动它们。由此可以自然衔接文档目录中的其他模式被适配服务的输出想驱动下游逻辑该数据变化后做某事时套用 Reactive State Pattern把适配结果写入 state用 state 触发器绑定下游函数多个被适配服务之间的顺序协作套用 skills/iii-architecture-patterns/SKILL.md 中的 Durable Workflow函数之间通过命名队列Enqueue传递重试与 DLQ 由队列策略承担该技能文档给出的选择规则也适用于适配器的边界划分同步组合用于调用方需要最终结果的短管线不可靠、慢、必须完成的步骤走 enqueue独立扇出走 pub/sub自动派生视图走 state 触发器。实操检查清单按原文档的三条结构定义把现有服务适配进 iii 的落地清单连接确认III_URL可达选定workerName与namespace跑起引擎engine/config.yaml为主配置与目标 Worker注册为每个要暴露的操作注册一个service::name函数标准 HTTP 服务可评估用HttpInvocationConfig走零代码路径为每个函数补request_format/response_format与metadata翻译在 handler 内完成参数整形、认证注入、超时处理预期失败映射为结构化返回意外失败抛出密钥一律以环境变量名引擎侧或进程环境变量Worker 侧注入验证通过函数列表与 iii console 确认函数可见、Schema 正确用iii trigger或任一触发器发起一次真实调用检查响应与错误传播符合预期收紧安全若使用引擎直连变体收窄url_allowlist、保持block_private_ips与require_https默认开启见 engine/src/workers/http_functions/config.rs 的默认值测试。小结iii 的适配器模式没有独立的adapter API——它的本质是用 iii 已有的函数注册、Schema 元数据、metadata 访问控制、统一错误传播和iii-http-functions引擎直连这五块既有机制把任意现有服务翻译成service::name形态的函数。读完本文你应该能够判断一个既有服务适合走进程内封装还是引擎直连写出函数 ID、Schema、错误映射与密钥处理都符合 iii 语义的适配 Worker并依据 engine/src/workers/http_functions/mod.rs 与 config.rs 中的实现与默认安全值解释适配器在引擎侧究竟如何被调用与保护。【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表