ModelManager + Bmc:rust-web-app数据访问层的设计哲学与实战
【免费下载链接】rust-web-appCode template for a production Web Application using Axum: The AwesomeApp Blueprint for Professional Web Development.项目地址: https://gitcode.com/gh_mirrors/ru/rust-web-app
rust-web-app 是一个面向生产的 Rust Web 应用脚手架(基于 Axum),它的核心亮点之一就是清晰的数据访问层设计:ModelManager统一管理数据库连接池等数据资源,Bmc(Backend Model Controller)负责每个业务实体的 CRUD 操作,二者通过Dbx组件实现按需开启的数据库事务。本文用通俗的语言带你读懂这套设计哲学,并在实战中展示如何三步给项目新增一个数据实体。
🧩 一图看懂数据访问层的三层分工
rust-web-app 把所有数据访问都收敛到lib-core的 model 模块中(设计说明就在 model/mod.rs 文件头部的注释里),分为三层:
| 层级 | 角色 | 一句话理解 |
|---|---|---|
ModelManager | 资源管家 | 握着数据库连接池,随请求传入各处使用 |
Bmc(如UserBmc、ConvBmc) | 实体管家 | 每个业务表对应一个,封装该表的增删改查 |
Dbx | 事务开关 | 决定每次查询走"普通执行"还是"事务执行" |
这种分层的最大好处:业务代码永远不直接碰数据库驱动,换库、做压测、写单元测试时只需要盯着这三层改。
🏠 ModelManager:数据资源的"单一入口"
在 model/mod.rs 中,ModelManager结构非常简单,核心就是一个Dbx字段(内部是 sqlx 的 PostgreSQL 连接池,连接池创建逻辑见 store/mod.rs)。
它的设计哲学有三点:
- 构造即就绪:
ModelManager::new()一次性建好连接池(测试环境自动降为 1 连接,规避 tokio 调度抖动,源码注释写得非常坦诚)。 - 可共享:实现
Clone,在 Axum 中作为 App State 注入所有路由,参考 main.rs 中mm.clone()传入路由的用法。 - 可"变身":调用
new_with_txn()即可得到一个事务版副本——这是后文事务机制的关键。
📦 Bmc:一个实体一个"控制器"
Bmc全称 Backend Model Controller,比如UserBmc管理user表、ConvBmc管理conv表。每个 Bmc 做两件事:
- 声明元信息:实现
DbBmctrait,指定表名等元数据(定义见 base/mod.rs),例如ConvBmc额外声明has_owner_id()返回true,框架就会在创建时自动写入owner_id。 - 委托通用 CRUD:真正的 SQL 拼装全部委托给 base/crud_fns.rs 里的泛型函数(
create/get/list/update/delete),Bmc 自己只写业务特化逻辑。
以 user.rs 为例,UserBmc只声明了const TABLE: &'static str = "user",一行get方法的实现就只是转发到base::get,干净利落。
🔁 Dbx:按需开启的数据库事务(亮点设计)
这套设计最精彩的部分在 store/dbx/mod.rs。默认情况下ModelManager是非事务的:每条查询独立执行,简单且快。当业务需要"要么全成功、要么全回滚"时(比如创建用户要先插行再写密码),流程是:
let mm = mm.new_with_txn()?; // 变身事务版 mm.dbx().begin_txn().await?; // 开启事务 // ... 执行多条查询 ... mm.dbx().commit_txn().await?; // 提交完整实例可搜索mm.dbx().begin_txn(),最佳示范就是 UserBmc::create:先建用户行、再哈希写入密码,两步共享同一个事务。
Dbx内部还有两个值得称道的小设计:
- 引用计数:
TxnHolder带 counter,嵌套调用begin_txn只计数不重开事务,commit_txn计数归零才真正提交——避免嵌套事务踩坑。 - 错误前置:如果误用非事务副本开事务,会立刻返回
CannotBeginTxnWithTxnFalse等明确错误(见 dbx/error.rs),而不是静默执行。
⚡ 声明式宏:把样板代码一键清零
手写 Bmc 样板太累?项目提供了generate_common_bmc_fns!宏(定义于 base/macro_utils.rs),一个声明就能生成整套标准 CRUD:
generate_common_bmc_fns!( Bmc: ConvBmc, Entity: Conv, ForCreate: ConvForCreate, ForUpdate: ConvForUpdate, Filter: ConvFilter, );见 conv.rs:ConvBmc立刻拥有create、get、list、count、update、delete等全部方法,然后还能追加自定义方法(如add_msg)。重要原则:宏是可选的加分项——需要定制逻辑的实体完全可以手写,甚至只挑部分方法用。
🛠️ 实战:三步新增一个数据实体
以"给项目加一个实体"为例,套路已经完全标准化:
- 建表:在 sql/dev_initial/01-create-schema.sql 中建表,统一带上
cid/ctime/mid/mtime四个时间戳审计列(框架会自动维护它们)。 - 定义类型 + Bmc:在
crates/libs/lib-core/src/model/下新建实体文件,声明实体结构、ForCreate/ForUpdate/Filter三种 DTO,然后实现DbBmc并调用generate_common_bmc_fns!。 - 暴露 RPC 接口:在 web-server 的 rpcs 目录 中用配套的
generate_common_rpc_fns!宏生成 JSON-RPC 端点,路由自动挂载到/api。
想快速看完整链路?直接跑 quick_dev.rs 示例,它演示了ModelManager初始化、登录、实体 CRUD 的全流程。
📝 总结:这套设计哲学教会我们的事
- 单一入口:所有数据访问必须穿过 Model 层,资源(连接池、未来的 S3/Redis 客户端)集中在
ModelManager。 - 按需事务:默认非事务保性能,
new_with_txn()+ 引用计数事务让"开启事务"成为一行显式决策,而非全局默认。 - 约定大于配置:通用 CRUD 下沉到泛型函数,声明式宏消灭样板,但始终保留"手写逃生舱"。
- 面向测试:连接池在测试态自动收敛、
#[cfg(test)]精细控制,单元测试就是活文档(user.rs 底部测试 覆盖了创建、查询与清理)。
如果你正在用 Rust + Axum 搭建生产级 Web 应用,这套ModelManager + Bmc + Dbx的分层,值得作为你数据访问层设计的蓝本参考。
【免费下载链接】rust-web-appCode template for a production Web Application using Axum: The AwesomeApp Blueprint for Professional Web Development.项目地址: https://gitcode.com/gh_mirrors/ru/rust-web-app
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考