news 2026/9/13 19:03:09

在 iii 中让函数随状态变化自动执行:Reactive State Pattern 完整实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在 iii 中让函数随状态变化自动执行:Reactive State Pattern 完整实战指南

在 iii 中让函数随状态变化自动执行:Reactive State Pattern 完整实战指南

【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii

Reactive State Pattern 是 iii 中一类"事件驱动"的集成模式:当某个函数的触发条件是某份状态的变更(而不是一次请求或一个定时任务)时,把函数绑定到状态 Worker 所发布的state触发器上,引擎便会在被监听 key 或 scope 每次变化时自动调用该函数。本文将先讲清模式的定义与适用场景,再基于仓库源码逐层拆解触发器配置、事件载荷、匹配规则与端到端示例,让你能在自己的 Worker 中直接落地"数据一变、逻辑即跑"的响应式工作流。

模式是什么:把"数据变了"变成一等触发条件

在 iii 中,函数(Function)的执行由触发器(Trigger)驱动。常见的触发方式有两类:

  • 请求驱动(on demand):HTTP 端点被调用、消息被消费时才执行;
  • 调度驱动(on schedule):按 cron 表达式在固定时间执行。

Reactive State Pattern 则提供第三种语义:状态驱动(on state change)。当函数关心的不是"谁调用了它",而是"某份数据发生了变化"时,不需要任何人显式调用它——引擎会在被监听的状态发生变化时自动触发。

从源码看,这是 iii 内置触发器体系中的一等公民。在 engine/src/trigger_formats.rs 中,引擎为state触发器定义了独立的配置结构与调用载荷结构;在 engine/src/trigger.rs 中,statestate触发器类型被显式注册到触发器目录中,与httpcronqueuesubscribe等内置类型并列。

该模式也是 iii 官方顶层文档中单独成篇的架构模式之一,见 docs/patterns/reactive-state-pattern.mdx。

什么时候使用该模式

当业务动作在概念上是"每当这份数据变化时,就做这件事"("whenever this data changes, do this"),而不是"按调度"或"按需"时,就适合使用该模式。典型例子:

  • 重建派生索引:当源状态变化时,重新计算并写回派生的索引数据;
  • 扇出通知:当用户记录更新时,向相关服务或订阅者广播变更通知;
  • 跨 scope 传播计算值:当某组输入变化时,把计算结果传播到另一个 scope 中。

反面情况也很清晰:如果动作只关心"定时执行"(用cron触发器)或"有人来调用"(用 HTTP 等请求型触发器),就不属于该模式的应用范畴。

模式的三段式结构

该模式的实现结构由三个角色组成:

  1. 状态 Worker 持有源状态(source-of-truth):状态以scope(命名空间)+key的方式组织,任何 Worker 都可以通过state::set/state::update等内置函数读写;
  2. 某个 Worker 中的函数负责处理变化:计算派生值、发送通知等,函数代码与普通函数完全一致;
  3. 状态 Worker 广告的state触发器绑定函数:触发器声明它关心哪个scope/key,引擎在每次变化时评估并触发绑定函数。

关键点在于:函数代码本身没有任何特殊之处,唯一的差异只体现在触发器注册这一步。同一份函数逻辑,既可以挂在 HTTP 触发器下按需调用,也可以挂在state触发器下随状态变化自动执行。

状态 Worker 的读写表面:state::*内置函数

Reactive State Pattern 的"数据源"由状态 Worker 提供,它暴露一组state::*内置函数作为读写接口(各 SDK 中均有类型化封装,见 sdk/packages/node/iii/src/state.ts 的IState接口):

函数作用触发的状态事件
state::set设置(创建或覆盖)一个值键不存在时触发state:created,存在时触发state:updated
state::get读取一个值
state::delete删除一个值触发state:deleted
state::update用一组操作原子更新值按键是否存在触发state:created/state:updated
state::list列出 scope 内所有值
state::list_groups列出所有含数据的 scope

其中state::set的输入为{ scope, key, value },返回值包含old_value(旧值,键不存在时为null)与new_value(新写入值)。state::update支持setmergeincrementdecrementappendremove等原子操作序列,可以避免"读-改-写"竞态。

state触发器配置:scope 与 key 的匹配规则

state触发器的配置结构在引擎侧定义于 engine/src/trigger_formats.rs:

pub struct StateTriggerConfig { /// State scope to watch (exact match filter) pub scope: Option<String>, /// State key to watch (exact match filter) pub key: Option<String>, /// Optional function ID to evaluate before invoking handler pub condition_function_id: Option<String>, }

三个字段的匹配语义如下:

字段类型语义
scopestring(可选)精确匹配。仅当状态变化发生在该 scope 内时触发;省略时匹配所有 scope
keystring(可选)精确匹配。仅当状态变化发生在该 key 上时触发;省略时匹配所有 key
condition_function_idstring(可选)条件函数。引擎先把状态事件交给它评估,返回false时不调用处理器函数

注意scopekey都是精确匹配(exact match)过滤:配置{ scope: "users" }会监听users下所有 key 的变化;配置{ scope: "users", key: "profile" }则只监听users/profile这一个键的变化。两者都省略时,该触发器会响应所有scope 下的所有状态变化——除非确有全局需求,否则建议至少限定scope,避免无关变更引发大量无效调用。

Rust SDK 提供了对应的构建器风格 API(sdk/packages/rust/iii/src/builtin_triggers.rs):

let config = StateTriggerConfig::new() .scope("users") .key("profile") .condition("conditions::emailChanged");

状态事件载荷:处理器收到的数据

当触发器被匹配并触发时,处理器会收到一个结构化的状态事件对象。引擎侧的线格式定义于StateCallRequest(engine/src/trigger_formats.rs),SDK 侧的类型化封装见StateEventData(sdk/packages/node/iii/src/state.ts):

字段类型说明
typestring固定为"state"
event_typeenum变更类型:"state:created""state:updated""state:deleted"
scopestring发生变更的 scope
keystring发生变更的 key
old_valueany变更前的值;新建键时为null(删除事件时携带被删值)
new_valueany变更后的值;删除事件时为null

三种事件类型在引擎与各 SDK 中保持一致枚举定义(StateEventType),例如 Node SDK 中为Created = 'state:created'Updated = 'state:updated'Deleted = 'state:deleted'(sdk/packages/node/iii/src/state.ts)。

由于事件载荷同时携带old_valuenew_value,处理器天然具备"比较前后差异"的能力——这正是后面条件触发与差异计算的基础。

端到端实战:Node / TypeScript 示例

下面是一个完整的响应式流程:先写入一条用户状态,再注册一个监听usersscope 的state触发器函数,最后再次set触发它。

import { iii } from './iii' import { StateEventType } from 'iii-sdk/state' // 1. 注册处理器函数:代码与普通函数无异 const onUserUpdated = iii.registerFunction( 'state::onUserUpdated', async (event) => { if (event.type === 'state' && event.event_type === StateEventType.Updated) { console.log('State changed:', event.event_type, event.key) console.log('Previous:', event.old_value) console.log('Current:', event.new_value) } return {} }, ) // 2. 注册 state 触发器:绑定函数到 users scope iii.registerTrigger({ type: 'state', function_id: onUserUpdated.id, config: { scope: 'users' }, }) // 3. 写入状态 → 引擎评估触发器 → 自动调用 onUserUpdated await iii.trigger({ function_id: 'state::set', payload: { scope: 'users', key: 'user-123', value: { name: 'Alice', email: 'alice@example.com' } }, })

这段流程被仓库中的集成测试完整覆盖:在 sdk/packages/node/iii/tests/state.test.ts 的reactive state用例中,测试先state::set写入初始值,注册监听{ scope, key }state触发器函数,再次state::set写入新值后,断言处理器确实被调用且收到的new_value等于新写入的数据。测试还展示了对称的清理流程:trigger?.unregister()stateUpdatedFunction?.unregister()

SDK 示例仓库中也有同样的最小写法(sdk/packages/node/iii-example/src/trigger-types.ts):

iii.registerFunction('example::on_user_updated', async (data: { event_type?: string }) => ({ processed: true, event: data.event_type, })) iii.registerTrigger({ type: 'state', function_id: 'example::on_user_updated', config: { scope: 'users' }, })

Python 与 Rust 示例

同一模式在 Python 与 Rust SDK 中完全等价。Python 侧的状态类型定义(StateEventTypeStateEventData)位于 sdk/packages/python/iii/src/iii/state.py:

def on_user_updated(event): print('State changed:', event['event_type'], event['key']) print('Previous:', event.get('old_value')) print('Current:', event.get('new_value')) return {} iii.register_function("state::onUserUpdated", on_user_updated) iii.register_trigger({ 'type': 'state', 'function_id': 'state::onUserUpdated', 'config': {'scope': 'users', 'key': 'profile'}, })

Rust 侧使用RegisterTriggerInput配合StateTriggerConfig(sdk/packages/rust/iii/src/builtin_triggers.rs):

use iii_sdk::{RegisterFunctionMessage, RegisterTriggerInput}; use serde_json::json; iii.register_function( RegisterFunctionMessage::with_id("state::onUserUpdated".into()), |event| async move { println!("State changed: {} {}", event["event_type"], event["key"]); println!("Previous: {:?}", event.get("old_value")); println!("Current: {:?}", event.get("new_value")); Ok(json!({})) }, ); iii.register_trigger(RegisterTriggerInput { trigger_type: "state".into(), function_id: "state::onUserUpdated".into(), config: json!({ "scope": "users", "key": "profile" }), metadata: None, })?;

条件触发:只处理真正相关的变更

condition_function_id允许在触发与执行之间插入一道"闸门":引擎会先把状态事件交给条件函数评估,只有返回false以外的结果时才继续调用处理器。这在"状态确实变了,但并非每次变化都值得处理"的场景中非常有用。

例如,只在邮箱字段真正变化时才发送验证邮件:

const conditionFn = iii.registerFunction( 'conditions::emailChanged', async (event) => event.event_type === 'state:updated' && event.old_value?.email !== event.new_value?.email, ) const fn = iii.registerFunction('state::onEmailChange', async (event) => { await sendVerificationEmail(event.new_value.email) return {} }) iii.registerTrigger({ type: 'state', function_id: fn.id, config: { scope: 'users', key: 'profile', condition_function_id: conditionFn.id, }, })

条件的编写方式在 docs/0-16-0/how-to/use-trigger-conditions.mdx 中有系统讲解,可与本文结合阅读。

引擎视角:触发器如何被匹配与调度

理解引擎侧的调度逻辑,能帮助你更准确地预估触发行为。从源码结构看,iii 引擎的核心职责包括:维护已连接 Worker 的实时注册表、跟踪各 Worker 注册的函数与触发器、以及在触发器命中时把调用路由到提供目标函数的 Worker(参见 docs/0-16-0/understanding-iii/engine.mdx)。

state触发器类型在引擎触发器目录中注册(engine/src/trigger.rs),其配置与载荷的 JSON Schema 由JsonSchema派生自动生成(engine/src/trigger_formats.rs),因此 CLI 与工具链可以程序化地发现该触发器的配置形状。一次完整的状态触发链路为:

  1. 某个 Worker 调用state::set/state::update/state::delete写入状态;
  2. 引擎持久化到状态适配器,并得到old_valuenew_value
  3. 引擎把变更事件与触发器注册表中的所有state触发器逐一匹配(scope / key 精确匹配);
  4. 命中后(若有condition_function_id先评估条件函数)把StateCallRequest载荷路由到绑定函数的宿主 Worker 并调用。

需要说明的是,原模式文档在 TODO 中列出的"ordering guarantees(顺序保证)"与"bursts of changes(变更突发)"两个话题尚未在文档中展开。从现有材料可以确认的边界包括:state::update本身是原子操作(一组ops要么整体生效),这为"一次写入产生一次事件"提供了基础;而若要过滤高频变更中的无关部分,最直接的手段就是condition_function_id。具体的跨事件排序语义,建议以对应版本 Worker 文档的后续更新为准。

配置状态 Worker:适配器选择

状态 Worker 本身可以配置不同的持久化与分布后端。参考同系列 Worker 文档 docs/0-11-0/workers/iii-state.mdx(该文档对state触发器的 Worker Docs 做了完整展开),内置适配器包括:

- name: iii-state config: adapter: name: kv config: store_method: file_based file_path: ./data/state_store save_interval_ms: 5000
适配器说明关键配置
kv内置键值存储,支持内存与文件持久化store_methodin_memory/file_based)、file_pathsave_interval_ms(默认5000
redis以 Redis 作为状态后端redis_url(支持${REDIS_URL:...}环境变量占位符)
bridge通过 Bridge Client 转发状态操作到远端 III 引擎实例

选择持久化适配器时需注意:in_memory模式在 Worker 重启后数据会丢失,生产环境建议使用file_basedredis,以保证响应式流程所依赖的源状态可靠。

实战注意事项小结

  • 函数复用:处理函数与普通函数无异,可同时被多个触发器绑定(HTTP + state),复用同一逻辑;
  • 精确匹配优于模糊监听scope/key均为精确匹配,尽量限定监听范围,避免全量监听带来无效调用;
  • 善用condition_function_id:把"是否需要执行"的判断从处理器中抽离,配合old_value/new_value做差异比较;
  • 清理注册:在 Worker 卸载或测试结束时调用trigger.unregister()与函数unregister()(参考 sdk/packages/node/iii/tests/state.test.ts),避免悬挂注册;
  • 原子更新:需要"读-改-写"时优先使用state::update的 ops 序列,减少中间态事件;
  • 事件类型三态state:created/state:updated/state:deleted是三种独立事件,处理器可按event_type分流,old_valuenull即新建、new_valuenull即删除。

延伸阅读

  • 模式文档源文件:docs/patterns/reactive-state-pattern.mdx(0-16-0 版本位于 docs/0-16-0/patterns/reactive-state-pattern.mdx)
  • 状态 Worker 完整文档(配置、函数、事件载荷、错误码):docs/0-11-0/workers/iii-state.mdx
  • 触发器类型定义与 JSON Schema 生成:engine/src/trigger_formats.rs
  • 触发器注册与调度:engine/src/trigger.rs
  • Node SDK 状态类型与接口:sdk/packages/node/iii/src/state.ts
  • Python SDK 状态类型:sdk/packages/python/iii/src/iii/state.py
  • Rust SDK 触发器配置构建器:sdk/packages/rust/iii/src/builtin_triggers.rs
  • 响应式触发集成测试:sdk/packages/node/iii/tests/state.test.ts
  • 触发器条件编写指南:docs/0-16-0/how-to/use-trigger-conditions.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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 19:00:03

SEO优化失败原因与提升流量的系统解决方案

1. SEO效果不佳的常见原因分析SEO&#xff08;搜索引擎优化&#xff09;是每个网站运营者和内容创作者必须掌握的核心技能。但很多人在投入大量时间精力后&#xff0c;发现自己的SEO效果并不理想。根据我多年的实战经验&#xff0c;这通常是由以下几个关键因素导致的&#xff1…

作者头像 李华
网站建设 2026/9/13 18:59:02

Neko 路线图解析:从 V3 服务器迁移、客户端重写到模块化架构

Neko 路线图解析&#xff1a;从 V3 服务器迁移、客户端重写到模块化架构 【免费下载链接】neko A self hosted virtual browser that runs in docker and uses WebRTC. 项目地址: https://gitcode.com/GitHub_Trending/ne/neko Neko 是一个运行在 Docker 中、基于 WebRT…

作者头像 李华
网站建设 2026/9/13 18:57:15

烧录地址的本质:芯片启动时的硬件寻址逻辑

1. 烧录地址不是“乱填的数字”&#xff0c;而是芯片启动逻辑的物理指纹 你第一次在Keil里点“Download”时&#xff0c;烧录器弹出窗口里那个地址栏——0x08000000、0x6000、0x0000……你是不是下意识就照着例程抄&#xff1f;抄完程序跑起来了&#xff0c;松一口气&#xff1…

作者头像 李华