iii 触发器实战:注册、元数据、条件门控与反注册全解析 iii 触发器实战注册、元数据、条件门控与反注册全解析【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii触发器Trigger是 iii 中连接事件源与函数的核心机制函数不仅能被worker.trigger或iii trigger直接调用还能在满足某个事件条件时被引擎自动唤起——例如一次http请求、一个cron定时任务、一次state状态变更或任何 worker 发布的自定义事件。本文以 docs/using-iii/triggers.mdx 为主线结合仓库内引擎与 SDK 源码系统讲解触发器注册绑定、触发器元数据、触发器类型未就绪时的乐观注册、多触发器绑定同一函数、条件门控gating与反注册并给出 Node/TypeScript、Python、Rust 三套可运行的完整代码。阅读本文后你将能够在自己的 iii 项目中熟练地把任意函数绑定到事件源、为绑定附加上下文元数据、用条件函数做流量闸门并理解引擎在背后如何存储、延迟激活与回放这些绑定。触发器是什么把事件绑定到函数在 iii 中注册一个触发器register a trigger与注册一个触发器类型register a trigger type是两件不同的事本文聚焦前者注册触发器绑定消费者consumer把某个触发器类型如http绑定到自己的某个函数上声明当这类事件发生时要调用哪个函数注册触发器类型发布者publisher在 worker 里声明一种新的事件类型让其他 worker 的函数可以绑定到它详见 Creating Workers / Triggers。绑定通过function_id完成。一个触发器声明三件事字段含义type触发器类型标识如http、cron、state、durable:subscriberconfig由每个触发器类型自行定义的结构化配置例如http的api_path与http_methodfunction_id触发器触发时要调用的目标函数从引擎源码看绑定最终会落成一条Trigger记录它包含id、trigger_type、function_id、config、可选的metadata以及一组用于命名空间解析的字段namespace、trigger_namespace、home_namespace、provider_namespace见 engine/src/trigger.rs。其中config的类型和字段由各发布 worker 定义引擎为内置类型提供了类型化定义下文有对照表。注册一个触发器三种 SDK 的完整示例下面用同一份把math::add绑定到 HTTP POST 端点的需求展示三个 SDK 的注册写法。Node / TypeScript见 sdk/packages/node/iii/src/iii.ts 的registerTrigger实现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); worker.registerTrigger({ type: http, function_id: math::add, config: { api_path: /math/add, http_method: POST }, });Pythonimport os from iii import register_worker, InitOptions worker register_worker( os.environ.get(III_URL), InitOptions(worker_namemy-worker), ) worker.register_trigger({ type: http, function_id: math::add, config: {api_path: /math/add, http_method: POST}, })Rustuse iii_sdk::{InitOptions, RegisterTriggerInput, register_worker}; use serde_json::json; let url std::env::var(III_URL).expect(III_URL must be set); let worker register_worker(url, InitOptions::default()); worker.register_trigger(RegisterTriggerInput { trigger_type: http.into(), function_id: math::add.into(), config: json!({ api_path: /math/add, http_method: POST }), metadata: None, })?;几个值得注意的细节Node SDK 的registerTrigger在本地用crypto.randomUUID()生成触发器id然后通过 WebSocket 发送RegisterTrigger消息给引擎同时把完整触发器存入本地this.triggers表返回一个带unregister()方法的句柄详见下文反注册一节。命名空间上Node SDK 会做一次未显式声明则默认本 worker 命名空间的处理callNamespace(trigger.namespace, this.namespace, ...)——触发器点名一个函数而函数注册在 worker 自己的命名空间里如果触发器默认落在引擎的default命名空间就会触发却解析不到函数见 sdk/packages/node/iii/src/iii.ts。内置触发器的config形状由引擎统一生成 JSON Schema。例如http的注册配置包含api_path如/users/:id、http_method默认GET以及可选的condition_function_id见 engine/src/trigger_formats.rs。各类型完整的注册配置与触发负载call request形状可在引擎的内置类型表中查到http、cron、subscribe、state、durable:subscriber、stream、stream:join、stream:leave、log、trace、configuration均有类型化定义见 engine/src/trigger_formats.rs。触发器元数据给绑定附加任意上下文注册触发器时可以带上可选的metadata字段上面示例里的null/None。它是一段随触发器一起存储的任意 JSON在触发器触发时会作为一个独立参数与 payload 一起交给目标函数——而不是混在 payload 里。它的典型用途是提供触发上下文当一个函数被多个触发器共享时处理函数可以用metadata反推这次是哪个注册项触发的、带着什么上下文。// Node / TypeScript带上团队与环境标签 worker.registerTrigger({ type: http, function_id: math::add, config: { api_path: /math/add, http_method: POST }, metadata: { team: platform, env: staging }, });# Python worker.register_trigger({ type: http, function_id: math::add, config: {api_path: /math/add, http_method: POST}, metadata: {team: platform, env: staging}, })// Rust worker.register_trigger(RegisterTriggerInput { trigger_type: http.into(), function_id: math::add.into(), config: json!({ api_path: /math/add, http_method: POST }), metadata: Some(json!({ team: platform, env: staging })), })?;底层事实链非常清晰引擎的Trigger结构体把metadata序列化进绑定记录#[serde(skip_serializing_if Option::is_none)]见 engine/src/trigger.rs触发分发时Engine::fire_triggers取出该触发器存储的metadata以独立参数调用目标函数call_with_metadata_ns(namespace, function_id, data, metadata)见 engine/src/engine/mod.rsSDK 协议层同样支持Python 的测试 sdk/packages/python/iii/tests/test_trigger_metadata.py 验证了RegisterTriggerInput与RegisterTriggerMessage均能携带并原样传递metadata。注意两点其一metadata既可以通过registerTrigger提供也可以在直接trigger()调用时提供其二触发器类型本身没有 metadata 字段metadata 是挂在每次绑定上的与触发器类型声明的 schematrigger_request_format/call_request_format不是一回事——前者由消费者在绑定时设置是发布者做台账与发现用的自由标签后者由发布者声明用于描述消费者该传什么、会收到什么。关于 handler 侧如何读取每次调用的元数据可参考 Creating Workers / Functions 中Receive per-invocation metadata一节。触发器类型尚未就绪时乐观注册与延迟激活iii 的触发器注册是与顺序无关的。如果注册时该触发器类型在项目中还没有激活例如发布http事件的 worker 尚未连接引擎不会报错而是把注册乐观地存下来等该触发器类型一上线就自动激活。这一机制在引擎中有一套完整的落盘与恢复逻辑引擎的TriggerRegistry维护两张映射triggers活跃绑定与pending_triggers等待激活的绑定意图见 engine/src/trigger.rsregister_trigger在找不到可用 provider 时会打印[PENDING]警告并把绑定插入pending_triggers返回RegisterTriggerOutcome::Deferred只有在 provider 存在且注册成功时才返回Registered见 engine/src/trigger.rs当某个触发器类型重新注册时register_trigger_type会回放replay已存在的绑定并逐个把pending_triggers里等待该类型的意图取出、激活、移入triggers见 engine/src/trigger.rs发布 worker 重启后之前存储的绑定会被重新建立因此消费者先起来、发布者后起来的顺序完全可以正常工作。下面是一段可完整运行的演示先在httpworker 未启动的情况下注册绑定。// Node / TypeScript —— http worker not started worker.registerTrigger({ type: http, function_id: math::add, config: { api_path: /math/add, http_method: POST }, });# Python —— http worker not started worker.register_trigger({ type: http, function_id: math::add, config: {api_path: /math/add, http_method: POST}, })// Rust —— http worker not started worker.register_trigger(RegisterTriggerInput { trigger_type: http.into(), function_id: math::add.into(), config: json!({ api_path: /math/add, http_method: POST }), metadata: None, })?;随后再启动httpworker。引擎会自动激活已存储的绑定无需重新注册端点即可服务请求# http worker started after registration iii worker add http # the stored binding is now live on the http workers default port (3111) curl -X POST http://localhost:3111/math/add \ -H Content-Type: application/json \ -d {a: 2, b: 3} # 200 OK: the request reaches math::add through the activated trigger对已知触发器类型如http、state、durable:subscriber、stream引擎的[PENDING]提示还会附带提供该类型的 worker 的安装指引。引擎内置了一张触发器类型 → 提供 worker的映射表见 engine/src/trigger.rs触发器类型提供 workerhttphttpcroncronsubscribepubsubstatestatedurable:subscriberqueuestream/stream:join/stream:leaveiii-streamlog/traceiii-observabilityconfigurationconfigurationpending_trigger_warning会基于这张表生成可执行建议例如缺失httpworker 时提示安装命令见 engine/src/trigger.rs。什么时候注册会失败当活跃的 provider 拒绝了给定的config例如触发器配置非法时注册才会失败引擎会把TriggerRegistrationResult带errorbody发回发起注册的 worker 并记录日志。对应地引擎在register_trigger里对 provider 的拒绝做了区分只有provider 已处理并拒绝才是确定性的失败若只是连接通道不可达worker 正在断开则视为投递失败把绑定暂存为 pending等类型重新注册时再重试见 engine/src/trigger.rs。一个函数绑定多个触发器同一个function_id可以绑定任意数量的触发器且可以横跨不同类型的触发器。绑定第二个触发器只需复用相同的function_id换一个类型或配置即可函数本身无需改动——无论是来自 HTTP 请求、cron 定时还是队列消息函数都以同样的方式被调用。下面把reports::generate同时绑到 HTTP POST 与每周一次的 cron 上// Node / TypeScript —— Same handler runs for an HTTP POST and a weekly cron tick. worker.registerTrigger({ type: http, function_id: reports::generate, config: { api_path: /reports/generate, http_method: POST }, }); worker.registerTrigger({ type: cron, function_id: reports::generate, config: { expression: 0 0 9 * * 1 }, // Every Monday at 09:00 });# Python worker.register_trigger({ type: http, function_id: reports::generate, config: {api_path: /reports/generate, http_method: POST}, }) worker.register_trigger({ type: cron, function_id: reports::generate, config: {expression: 0 0 9 * * 1}, # Every Monday at 09:00 })// Rust worker.register_trigger(RegisterTriggerInput { trigger_type: http.into(), function_id: reports::generate.into(), config: json!({ api_path: /reports/generate, http_method: POST }), metadata: None, })?; worker.register_trigger(RegisterTriggerInput { trigger_type: cron.into(), function_id: reports::generate.into(), config: json!({ expression: 0 0 9 * * 1 }), // Every Monday at 09:00 metadata: None, })?;cron类型的expression采用 6 段格式秒 分 时 日 月 星期引擎侧定义见 engine/src/trigger_formats.rs。触发时cron负载会携带trigger、job_id、scheduled_time、actual_time等字段engine/src/trigger_formats.rs。用条件函数门控触发器触发器可以在config中携带可选的condition_function_id。当触发器触发时引擎会先用原本要传给 handler 的同一个 payload 调用条件函数只有当条件函数返回真值时目标function_id才会执行。条件函数就是一个普通的已注册函数。引擎的实现位于 engine/src/condition.rs条件函数在与触发器目标函数相同的命名空间内解析执行返回语义为——Ok(true)放行、Ok(false)跳过、调用失败则整体报错若条件函数返回None或非布尔值check_condition也会放行result.as_bool() ! Some(false)。这一行为在 engine/src/condition.rs 的单测中逐一验证并被内置的 queue、stream、configuration 等 worker 在分发路径上调用例如 engine/src/workers/queue/adapters/builtin/adapter.rs。下面实现一个仅黄金会员订单走加急通道的门控// Node / TypeScript worker.registerFunction( orders::is-priority, async (payload: { customer_tier: string }) payload.customer_tier gold, ); worker.registerTrigger({ type: http, function_id: orders::expedite, config: { api_path: /orders/expedite, http_method: POST, condition_function_id: orders::is-priority, }, });# Python def is_priority(payload: dict) - bool: return payload.get(customer_tier) gold worker.register_function(orders::is-priority, is_priority) worker.register_trigger({ type: http, function_id: orders::expedite, config: { api_path: /orders/expedite, http_method: POST, condition_function_id: orders::is-priority, }, })// Rust use iii_sdk::{RegisterFunction, RegisterTriggerInput}; use schemars::JsonSchema; use serde::Deserialize; use serde_json::json; #[derive(Deserialize, JsonSchema)] struct Payload { customer_tier: String } worker.register_function(RegisterFunction::new( orders::is-priority, |input: Payload| - Resultbool, String { Ok(input.customer_tier gold) }, )); worker.register_trigger(RegisterTriggerInput { trigger_type: http.into(), function_id: orders::expedite.into(), config: json!({ api_path: /orders/expedite, http_method: POST, condition_function_id: orders::is-priority, }), metadata: None, })?;从源码结构看condition_function_id被内置在多个触发器类型的配置结构里http、cron、state、stream、queue、configuration等的配置 struct 都声明了该可选字段因此门控能力并不只属于 HTTP而是事件分发路径上的通用机制见 engine/src/trigger_formats.rs。反注册触发器registerTrigger及各语言等价调用会返回一个带unregister()方法的句柄。调用它即可在运行时移除触发器而当 worker 断开连接时它所注册的所有触发器会被自动清理。// Node / TypeScript const trigger worker.registerTrigger({ type: http, function_id: math::add, config: { api_path: /math/add, http_method: POST }, }); trigger.unregister();# Python trigger worker.register_trigger({ type: http, function_id: math::add, config: {api_path: /math/add, http_method: POST}, }) trigger.unregister()// Rust let trigger worker.register_trigger(RegisterTriggerInput { trigger_type: http.into(), function_id: math::add.into(), config: json!({ api_path: /math/add, http_method: POST }), metadata: None, })?; trigger.unregister();引擎侧的反注册是幂等的unregister_trigger对不存在的 id 返回Ok(false)而非报错重复反注册等价于空操作如果该绑定仍处于 pending 状态反注册等价于丢弃这个等待中的意图。引擎会先通知 provider 的 registrator 解除绑定再在triggers与pending_triggers中清除该 id见 engine/src/trigger.rs。Node SDK 的反注册则通过发送UnregisterTrigger消息并删除本地句柄实现见 sdk/packages/node/iii/src/iii.ts。worker 断线时的批量清理在TriggerRegistry::unregister_worker中完成它会移除该 worker 拥有的触发器类型与绑定并把失去 provider 的绑定重新解析或暂存为 pending确保 provider 重启不会静默丢掉其他人的绑定engine/src/trigger.rs。总结触发器生命周期与源码地图一个触发器从注册到销毁的完整生命周期注册SDK 发送RegisterTrigger消息引擎把Trigger含type、config、function_id、metadata存入注册表激活provider 在线则立即激活并通知 registratorprovider 未上线则进入pending_triggers待类型重新注册时自动回放激活触发事件发生时引擎按类型收集绑定把 payload 与metadata作为独立参数调用目标函数若配置了condition_function_id先求值条件函数再决定是否放行反注册显式unregister()或 worker 断线自动清理均幂等。想深入验证或二次开发可在仓库中按如下地图继续阅读引擎注册表与乐观注册/回放/再归属逻辑engine/src/trigger.rs内置触发器类型的配置与负载 schemaengine/src/trigger_formats.rs条件函数求值语义engine/src/condition.rs触发分发含 metadata 独立传参engine/src/engine/mod.rsNode SDK 的registerTrigger/registerTriggerType与反注册句柄sdk/packages/node/iii/src/iii.tsNode SDK 的TriggerConfig/TriggerHandler类型定义sdk/packages/node/iii/src/triggers.ts编写自定义触发器类型发布者视角docs/creating-workers/triggers.mdx直接调用函数worker.trigger/iii trigger与engine::triggers::list发现能力docs/using-iii/functions.mdx【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考