
SpacetimeDB Reducer 错误处理完全指南区分 Sender Error 与 Programmer Error 的工程实践【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB导读在 SpacetimeDB 中编写 reducer数据库端函数时如何优雅地处理业务校验失败如用户不存在、余额不足与模块自身缺陷如空数据断言、解析崩溃是每个模块开发者都必须掌握的技能。本文以 Error Handling 官方文档 为核心骨架系统讲解两类错误的语义区别、TypeScript / C# / Rust / C 四种语言的具体写法并结合仓库源码bindings 与 core 运行时实现剖析错误从模块抛掷到客户端感知的底层机制帮助你写出既对用户友好、又可观测的 reducer。为什么需要区分两类错误Reducer 在执行时可能遇到两类性质完全不同的错误它们对外的语义、处理策略和运维方式截然不同错误类型产生原因预期程度处理策略对客户端的影响Sender Error发送者错误客户端输入非法/不满足业务约束可预期、应被正常处理在 reducer 中显式抛出/返回错误携带人类可读信息发送者收到优雅失败的通知与消息Programmer Error程序员错误模块代码中的 bug、未捕获的异常不可预期由开发者修复代码记录到日志显示在项目仪表盘需配置告警从运行时源码可以验证这种分类不是文档层面的约定而是执行引擎的真实语义在 module_host_actor.rs 中ExecutionError枚举将错误分为三类pub enum ExecutionError { User(Boxstr), // 对应 SenderError携带给用户的字符串消息 Recoverable(anyhow::Error), // 可恢复的内部错误 Trap(anyhow::Error), // 模块崩溃 / 陷阱 }其中User(Boxstr)正是 SenderError 在运行时中的落地形态——它携带的字符串消息会原样通知给发起调用的客户端而Trap与Recoverable则属于程序员错误范畴走的是日志与仪表盘路径。Sender Errors可预期的业务校验失败Sender Errors 由无效的客户端输入引起例如请求转账的用户不存在、余额不足、参数格式错误它们是业务流程中的正常分支应当优雅地处理reducer 返回失败信息但事务不会以崩溃方式终止调用方能够收到明确的失败原因。四种语言各自采用符合语言惯例的写法TypeScript 抛出SenderError或返回错误对象、C# 抛出异常、Rust 返回Result、C 返回Err。TypeScript抛出 SenderError 或返回错误对象import { SenderError } from spacetimedb/server; export const transferCredits spacetimedb.reducer( { toUser: t.identity(), amount: t.u32() }, (ctx, { toUser, amount }) { const fromUser ctx.db.users.identity.find(ctx.sender); if (!fromUser) { throw new SenderError(User not found); } if (fromUser.credits amount) { throw new SenderError(Insufficient credits); } // ... perform transfer } ); // Alternative: return error object export const transferCreditsResult spacetimedb.reducer( { toUser: t.u64(), amount: t.u32() }, (ctx, { toUser, amount }) { // ...validation... if (error) { return { tag: err, value: Insufficient credits }; } // ... } );SenderError的源码定义位于 crates/bindings-typescript/src/lib/errors.ts它是一个继承自Error的类其文档注释明确说明——由 reducer 抛出、向发送者指示问题的错误。当 reducer 抛出该错误时发送者将被告知 reducer 因给定消息而优雅失败failed gracefully。同文件中还定义了InternalError服务器运行时返回的内部 reducer 错误不要将二者混用。C#抛出异常[SpacetimeDB.Reducer] public static void TransferCredits(ReducerContext ctx, Identity toUser, uint amount) { var fromUser ctx.Db.User.Identity.Find(ctx.Sender); if (fromUser null) { throw new InvalidOperationException(User not found); } if (fromUser.Value.Credits amount) { throw new InvalidOperationException(Insufficient credits); } // ... perform transfer }Rust返回 Result#[reducer] pub fn transfer_credits( ctx: ReducerContext, to_user: Identity, amount: u32 ) - Result(), String { let from_user ctx.db.users().identity().find(ctx.sender()) .ok_or(User not found)?; if from_user.credits amount { return Err(Insufficient credits.to_string()); } // ... perform transfer Ok(()) }Rust 采用Result(), String作为 reducer 返回值Err中的字符串即为通知给发送者的消息这与运行时ExecutionError::User(Boxstr)携带字符串的设计完全对应。C返回 Err / Ok#include spacetimedb.h using namespace SpacetimeDB; struct User { Identity identity; uint32_t credits; }; SPACETIMEDB_STRUCT(User, identity, credits); SPACETIMEDB_TABLE(User, users, Private); FIELD_PrimaryKey(users, identity); SPACETIMEDB_REDUCER(transfer_credits, ReducerContext ctx, Identity to_user, uint32_t amount) { auto from_user ctx.db[users_identity].find(ctx.sender()); if (!from_user) { return Err(User not found); } if (from_user-credits amount) { return Err(Insufficient credits); } // ... perform transfer return Ok(); }注意C 模块能力目前需要特定版本支持文档中以CppModuleVersionNotice /组件标注版本提示编写前请确认你的 SpacetimeDB 版本包含 C 模块功能。Programmer Errors模块缺陷应被修复而非处理Programmer Errors 是模块代码中的 bug 导致的意外错误空数据时未加保护、解析函数抛异常、逻辑断言失败等。它们不应出现在正常业务流程中开发者必须通过修复代码来解决。它们会被记录日志并在项目仪表盘Project Dashboard中可见建议为其配置告警以便第一时间感知。TypeScript抛出普通 Errorexport const processData spacetimedb.reducer( { data: t.array(t.u8()) }, (ctx, { data }) { // Regular Error indicates a bug if (data.length 0) { throw new Error(Unexpected empty data); } // ... } );C#未捕获的异常[SpacetimeDB.Reducer] public static void ProcessData(ReducerContext ctx, byte[] data) { // This indicates a bug Debug.Assert(data.Length 0, Unexpected empty data); // Uncaught exception indicates a bug var parsed ParseData(data); // May throw // ... }RustPanic 或未捕获的错误#[reducer] pub fn process_data(ctx: ReducerContext, data: Vecu8) - Result(), String { // This panic indicates a bug assert!(data.len() 0, Unexpected empty data); // Uncaught Result indicates a bug let parsed parse_data(data).expect(Failed to parse data); // ... Ok(()) }C断言与 LOG_PANIC#include spacetimedb.h #include cassert using namespace SpacetimeDB; SPACETIMEDB_REDUCER(process_data, ReducerContext ctx, Vecuint8_t data) { // This indicates a bug assert(!data.empty() Unexpected empty data); auto parsed parse_data(data); if (!parsed) { LOG_PANIC(Failed to parse data); } // ... return Ok(); }运行时如何区分两类错误源码级机制SpacetimeDB 的 TypeScript 运行时V8 宿主通过模块注册的SenderErrorClass钩子来判定抛出对象是否属于SenderError。相关实现位于 crates/core/src/host/v8/syscall/hooks.rs注册SenderErrorClass钩子与 crates/core/src/host/v8/syscall/v2.rs异常处理。核心判定逻辑如下// if (typeof exc object exc instanceof SenderError) if let Ok(exc) exc.try_cast::Object() exc.instance_of(scope, hooks.sender_error_class.unwrap().into()) ... { let message exc.get(scope, key.into())...; return Ok(Some(ExecutionError::User(message.to_rust_string_lossy(scope).into()))); }这意味着当 reducer 抛出的异常对象是SenderError的实例时运行时会提取其message并转换为ExecutionError::User即 Sender Error 路径该消息最终会传达给发送者而任何其他异常普通Error、断言失败等则不会命中该分支从而走程序员错误路径记录日志、进入仪表盘。这也解释了为什么文档强调——TypeScript 中Regular errors (notSenderError)属于程序员错误两种写法在运行时会被精确区分。TypeScript 侧的SenderError导出位于 crates/bindings-typescript/src/server/errors.ts其还定义了SpacetimeHostError数据库函数可能抛出的宿主错误基类以及一系列带错误码的宿主错误如NoSuchTable、UniqueAlreadyExists、NoSuchRow等见 errors.ts这些是基础设施层面的错误分类与应用层的 Sender/Programmer 分类互补。实践建议与错误处理清单结合文档语义与源码机制可总结以下工程实践要点业务校验失败一律走 Sender Error 路径参数非法、记录不存在、余额不足等一切客户端输入导致的失败都应显式抛出/返回错误并附带人类可读消息让发送者获得明确反馈。Programmer Error 留给真正的 bug不要在业务逻辑中把预期失败伪装成Error/ panic / 断言这些错误意味着代码缺陷应通过日志与告警暴露而不是被静默吞掉。为 Programmer Errors 配置告警文档明确说明程序员错误会记录在项目仪表盘中建议针对其设置告警通知确保生产环境中模块缺陷能被及时发现。注意各语言的表达惯例TypeScript 用SenderError或返回错误对象、C# 用异常、Rust 用Result(), String、C 用Err/Ok——遵循语言惯例而非机械照搬代码可读性与可维护性更佳。利用运行时语义做正确分层从ExecutionError枚举User / Recoverable / Trap与 V8 的instanceof SenderError判定可以看出SpacetimeDB 已将用户可见错误与内部错误做了明确的运行时隔离模块开发者应充分利用这一机制来设计对外 API 的失败语义。通过正确区分与处理这两类错误你的 reducer 既能向客户端返回清晰、友好的失败原因又能让模块自身的缺陷在日志与仪表盘中无处遁形从而构建出健壮、可观测的 SpacetimeDB 模块。【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考