拓冰建站拓冰建站
首页 / 资讯中心 / 正文

iii Adapter Pattern 实战:用薄 Worker 将存量服务接入 iii 函数网格

iii Adapter Pattern 实战用薄 Worker 将存量服务接入 iii 函数网格【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iiiAdapter Pattern适配器模式是 iii 中一种将存量系统第三方 API、内部微服务、遗留库接入 iii 平台而无需重写的标准做法用一个薄 Worker 包装既有服务将其能力以service::name形式的 iii 函数暴露给整个网格。读完本文你将掌握适配器 Worker 的结构、注册与调用链路、错误映射与密钥处理的实现要点并能够参考仓库源码写出可运行的适配器。模式是什么包装而非重写iii 的架构是一个 WebSocket 路由的 Worker 网格单个引擎进程默认端口49134维护所有已连接 Worker、每个 Worker 暴露的函数以及绑定到函数的触发器的实时注册表。Worker 是独立的进程通过 WebSocket 连接引擎并注册Functionsservice::name处理器和Triggers触发这些函数的事件Worker 之间没有直连流量所有调用都经由引擎路由——即caller → engine → handler函数 ID 是任意两个 Worker 之间唯一的契约见 engine 内建iii-engine-functionsWorker 的架构说明。Adapter Pattern 正是建立在这个模型之上当你希望把一个既有服务引入 iii 系统而不重写它时就把它包在一个薄 Worker 里让该 Worker 把服务的接口翻译成 iii 的函数调用形态function-call shape。调用方因此可以像对待网格里任何其他 Worker 一样对待它——按函数 ID 寻址而不是按 HTTP 端点或库特有的调用形态。何时使用该模式当你有一个希望从 iii 使用、但不想重写的既有服务并且希望调用方以与其他 Worker 相同的方式通过函数 ID而不是 HTTP 或库特有的调用形态来寻址它时就使用此模式。适用场景包括第三方 API如支付、AI、消息推送服务内部自研微服务已有 REST/gRPC/私有协议接口仓库内已有的库或工具需要暴露为网格可调用能力需要在不改动调用方的前提下为某个服务增加可观测性、触发绑定、队列化等 iii 能力。结构三步薄 Worker一个 Adapter 就是一个薄 iii Worker它连接引擎——通过 SDK 建立与引擎的 WebSocket 连接为每个要暴露的操作注册一个函数——每个函数对应存量服务的一个操作在每个处理器内部调用存量服务并把结果作为函数的响应返回——翻译层就在这一步。这与仓库中 functions.mdx 文档 描述的编写函数模型完全一致Worker 通过注册函数向系统贡献能力每个函数有一个service::name形式的 ID、一个接收 payload 并返回结果的 handler以及可选的描述请求/响应形态的 JSON Schema。连接引擎registerWorker 与地址解析适配器 Worker 的第一步是与引擎建立连接。以 Node/TypeScript SDK 为例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);从 SDK 源码看registerWorker(address?, options?)返回一个已连接的 Worker 客户端WebSocket 连接会自动建立。地址解析遵循明确的优先级resolveAddress显式传入的address参数优先级最高环境变量III_URL——通常由孵化该进程的监督者iii compose、容器运行时、systemd设置与III_NAMESPACE、III_WORKER_NAME一并注入兜底默认值DEFAULT_ENGINE_URL ws://127.0.0.1:49134定义于 iii.ts。注意默认地址特意写成 IPv4 回环地址因为localhost在主机仅监听 IPv4 时可能解析到::1。注册函数service::name 命名与 JSON Schema连接建立后为存量服务的每个操作注册一个函数。函数 ID 采用service::name形式——这既是调用方的寻址契约也是触发器绑定时的function_id。在 Node SDK 的registerFunction中函数 ID 不能为空且不能重复注册重复会抛出function id already registered错误。建议的命名约定service段使用被包装服务的域名化缩写例如stripe、openai、ordersname段使用该服务的具体操作例如create-payment、completion、get-by-id。这样整个网格的函数清单可通过engine::functions::list查看一目了然也便于用prefix过滤出某个适配器的全部函数。附带请求/响应 Schema可以在注册时为请求和响应附带 JSON Schema让契约随函数一起文档化——这些 Schema 会与函数一同存储并在 iii console、iii trigger --help等界面呈现见 functions.mdx 的 Schema 章节。需要明确的是当前引擎不做运行时校验Schema 仅作为函数调用、Agent 与 console 的契约文档引擎不会拒绝与 Schema 不匹配的 payload 或返回值。worker.registerFunction( orders::get, async (payload: { orderId: string }) { // 调用存量订单服务…… return { orderId: payload.orderId, status: shipped }; }, { request_format: { type: object, properties: { orderId: { type: string } }, required: [orderId], }, response_format: { type: object, properties: { orderId: { type: string }, status: { type: string }, }, required: [orderId, status], }, }, );一个完整的适配器示例以下是一个包装内部订单 REST 微服务的示意适配器API 签名均取自仓库 SDK 的真实实现。它演示了三步结构的完整落地连接引擎、按操作注册函数、在 handler 内调用存量服务并映射结果。import { registerWorker, InvocationError } from iii-sdk; const url process.env.III_URL; if (!url) throw new Error(III_URL must be set); const worker registerWorker(url, { workerName: orders-adapter }); // 存量服务客户端可以是内部 REST 客户端、SDK 或数据库连接 const ordersService createOrdersClient({ baseUrl: process.env.ORDERS_SERVICE_URL, // 由部署环境注入 apiKey: process.env.ORDERS_SERVICE_API_KEY, // 密钥走环境变量不进代码 }); worker.registerFunction( orders::get, async (payload: { orderId: string }) { const res await ordersService.get(payload.orderId); // 调用存量服务 if (!res.ok) { throw new InvocationError({ code: UPSTREAM_ERROR, message: orders service returned ${res.status}, function_id: orders::get, }); } return res.json(); // 返回标准 iii 函数响应 }, { description: Fetch an order from the legacy orders service, request_format: { type: object, properties: { orderId: { type: string } }, required: [orderId] }, response_format: { type: object }, }, );Python 与 Rust 的写法可参考 functions.mdx 中的多语言示例import os from iii import register_worker, InitOptions worker register_worker( os.environ.get(III_URL), InitOptions(worker_nameorders-adapter), ) def get_order_handler(payload: dict) - dict: return orders_service.get(payload[orderId]) # 调用存量服务 worker.register_function(orders::get, get_order_handler)错误映射从上游错误到 iii 函数错误Adapter 的翻译层不仅要翻译成功响应还要翻译失败。Node SDK 提供了统一的类型化错误InvocationError它包装线上的ErrorBody形态{ code, message, stacktrace? }以及被调用的function_id使调用方在所有失败模式RBAC 拒绝 FORBIDDEN、handler 级失败、等待引擎响应的超时下都能通过同一个错误类型区分问题且能通过err.code快速识别失败类别。其 message 以code: message形式呈现错误对象自带code、function_id、stacktrace字段自我描述性强。实践建议上游服务返回业务错误时映射为带语义化code的InvocationError如UPSTREAM_ERROR、NOT_FOUND、RATE_LIMITED让调用方可以按 code 编程式处理不要把上游的原始堆栈直接透传而是保留 message 并适当记录日志若适配器同时暴露多个操作为每个 handler 的InvocationError带上对应的function_id便于追踪是哪个适配操作失败。另外SDK 中还有RegistrationRejectedError当引擎拒绝 Worker 的身份注册例如同一(namespace, worker_name)已被另一个存活 Worker 占用时抛出SDK 会停止且不重连。命名空间冲突FUNCTION_NAMESPACE_CONFLICT则是非致命的仅记录日志。这提醒我们部署适配器时要注意 Worker 名称与命名空间的唯一性。认证与密钥处理原文档的 TODO 特别点名了如何在外包服务时处理认证/密钥。结合仓库约定推荐做法是密钥一律走环境变量或部署平台提供的注入机制绝不硬编码进源码。SDK 本身依赖III_URL、III_NAMESPACE、III_WORKER_NAME等环境变量完成引导见resolveAddress适配器可沿用同样的约定读取ORDERS_SERVICE_API_KEY之类的变量来构造存量服务客户端上游认证失败应映射为明确的错误 code如AUTH_FAILED、UNAUTHORIZED而非让调用方看到裸的 HTTP 401不要在接受外部 payload 的 handler 中拼接敏感凭据到响应里如需审计记录到日志/可观测性系统而不是返回给调用方。关于可观测性SDK 的 handler 包装层会在调用前后记录iii.invocation.input/iii.invocation.output追踪事件受III_DISABLE_TRACE_PAYLOADS环境变量控制见 iii.ts 中的 handler 包装。也就是说适配器天然获得请求/响应 payload 的追踪能力便于观察调用方 → 引擎 → 适配器 → 存量服务整条链路。暴露适配器用 Trigger 把函数接到事件上适配器注册的函数默认只能被显式调用worker.trigger/iii trigger/ SDK 调用。若要让它响应事件就绑定 Trigger。SDK 的registerTrigger将一个 trigger 配置绑定到已注册函数上返回带unregister()的句柄// 暴露为 HTTP 端点 worker.registerTrigger({ type: http, function_id: orders::get, config: { api_path: /orders/get, http_method: GET }, }); // 定时轮询存量服务并同步状态 worker.registerTrigger({ type: cron, function_id: orders::sync, config: { expression: 0 * * * * * }, // 7 字段sec min hr dom mon dow year });仓库中的引擎内建 Worker README 归纳了常用 trigger 类型及其配置键见 engine_fn README 的 Canonical trigger types 表typeProvider配置键httphttp{ http_method, api_path }croncron{ expression }— 7 字段sec min hr dom mon dow yearqueuequeue{ queue, retries }statestate{ scope, condition_function_id }streamiii-stream{ stream_name }一个值得注意的命名空间语义registerTrigger若未显式指定命名空间则落在该 Worker 自己的命名空间因为函数注册在 Worker 的命名空间下——命名空间不一致会导致 trigger 触发后解析不到函数见 registerTrigger 的源码注释。适配器与调用方在同一命名空间时通常无需显式指定。通过引擎内建函数观察适配器部署适配器后可以用引擎自带的engine::*内省函数验证它是否正确入网无需额外安装内建于引擎见 engine_fn READMEengine::functions::list { prefix: orders:: }— 确认适配器注册的函数已出现在注册表engine::functions::info { function_id: orders::get }— 查看该函数的 Schema、属主 Worker 与已绑定的触发器engine::workers::list/engine::workers::info { name: orders-adapter }— 查看连接中的 Worker 及其完整暴露面。小结与工程建议Adapter Pattern 在 iii 中是一个薄壳模式翻译层只做三件事——连接引擎、按操作注册函数、在 handler 里调用存量服务并映射结果。它把 HTTP 或库特有的调用形态统一收敛为service::name函数契约让调用方与存量服务的耦合降到最低。工程落地时重点把握四条原则每个存量操作 一个函数命名遵循service::name并附带请求/响应 Schema 作为契约文档错误统一走类型化错误如InvocationError用语义化code区分失败类别密钥走环境变量由部署环境注入不在源码与响应中暴露用 Trigger 决定暴露方式HTTP、定时、队列等命名空间要与函数注册保持一致。参考实现与文档入口适配器骨架与多语言写法见 functions.mdx引擎路由模型与内省函数见 engine_fn READMESDK 连接与注册 API 见 iii.ts、errors.ts。【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门