Prelude 预导入
Prelude 预导入模式详解
为了提升开发效率、减少模板代码并统一项目编码风格,我们的项目中广泛采用了 Prelude (预导入) 模式。你会发现在 model
和 service
模块下都有一个 prelude.rs
文件。
本文档将详细解释这个模式是什么,以及我们如何利用它来构建更清晰的代码。
核心思想
将一个模块(如 model
或 service
)内部最常用、最核心的类型、Trait 和函数集中到一个 prelude.rs
文件中进行 公共导出 (pub use)。然后,在该模块的其他文件中,只需一行 use crate::module::prelude::*;
即可引入所有必需的依赖。
为什么要使用 Prelude 模式?
想象一下,在没有 Prelude 模式之前,每个 service
或 model
文件开头都可能充斥着大量的 use
语句:
// 冗长的导入列表
use crate::common::error::{Error, Result};
use crate::common::ApiResponse;
use crate::DB;
use sea_orm::{EntityTrait, QueryFilter, Set, ActiveModelTrait};
use serde::Serialize;
use tracing::info;
use crate::model::user::Entity as UserEntity;
// ... 可能还有 10 行
这种方式有几个痛点:
- 重复性高:每个文件都在重复相同的导入。
- 难以维护:如果一个常用依赖的路径改变了,需要修改所有引用它的文件。
- 认知负担重:开发者需要记住所有常用工具的具体路径。
Prelude 模式完美地解决了这些问题。 现在,你只需要:
// 清爽、统一的导入
use crate::model::prelude::*;
1. model::prelude
- 数据模型的基石
model
模块负责定义所有与数据库表直接交互的实体 (Entity)、活动模型 (ActiveModel) 和相关数据结构。因此,model::prelude
导出的都是与 数据定义、数据库操作和序列化 相关的核心工具。
model::prelude
包含什么?
它主要集成了以下几类依赖:
点击展开查看 `model::prelude` 的内容分类
数据库核心 (SeaORM)
EntityTrait
,ModelTrait
,ActiveModelTrait
, ...: 定义和操作模型的必需 Trait。QueryFilter
,ColumnTrait
,Condition
, ...: 构建查询语句的核心工具。DatabaseConnection
,DbErr
,TransactionTrait
: 数据库连接和错误处理。PaginatorTrait
: 用于分页查询。
序列化 (Serde)
Serialize
,Deserialize
: 将数据结构与 JSON 等格式进行相互转换,是 API 开发的基石。
Web 框架 (Axum)
IntoResponse
: 允许模型直接转换为 HTTP 响应,虽然不常用,但在某些简单场景下很方便。
项目内公共组件
Error
,Result
: 统一的错误处理类型。ApiResponse
: 标准化的 API 响应结构。VJson
,VQuery
: 带有验证功能的自定义提取器。DB
,GID
: 全局数据库连接池和唯一 ID 生成器的访问器。UserInfo
: 当前登录用户的信息。
自定义的通用数据结构
ListData<T>
: 标准化的列表/分页响应结构。PageParams
: 标准化的分页请求参数。
如何使用?
在 任何与数据模型定义、数据库查询相关 的文件中(通常是 src/model/
目录下的所有文件),在文件顶部添加:
use crate::model::prelude::*;
// 现在你可以直接使用所有预导入的工具
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "users")]
pub struct Model {
// ...
}
// ... 其他实现
2. service::prelude
- 业务逻辑的工具箱
service
模块负责处理应用的 核心业务逻辑。它接收来自上层 (Handler) 的请求,调用 model
层与数据库交互,并执行各种业务规则。service::prelude
因此专注于提供 业务流程处理 所需的工具。
service::prelude
包含什么?
它与 model::prelude
有重叠,但也包含了一些 service
层特有的工具:
点击展开查看 `service::prelude` 的内容分类
Web 框架提取器 (Axum)
Path
,Query
,Multipart
:service
层经常需要直接处理这些从 HTTP 请求中提取出的数据。
数据库核心 (SeaORM)
- 同样包含数据库操作的核心 Trait,因为
service
是执行数据库操作的主要场所。
- 同样包含数据库操作的核心 Trait,因为
模型引用
pub use crate::model as model;
: 这是一个非常巧妙的技巧!它直接将整个model
模块导出为model
,这样在service
文件中就可以直接通过model::user::Entity
来引用用户模型,而不需要再写use crate::model::user;
。pub use crate::model::prelude::{ListData,PageParams};
: 复用了model
层定义的通用数据结构。
项目内公共组件
- 与
model::prelude
类似,同样包含了Error
,Result
,ApiResponse
,DB
,GID
,UserInfo
等全局工具,因为业务逻辑层是使用这些工具的核心。
- 与
如何使用?
在 任何实现业务逻辑 的文件中(通常是 src/service/
目录下的所有文件),在文件顶部添加:
use crate::service::prelude::*;
pub struct UserService;
impl UserService {
pub async fn find_user_by_id(user_id: i64) -> Result<Option<model::user::Model>> {
// 直接使用 model::user::Model,无需额外 use
// 直接使用 EntityTrait 中的 find_by_id 方法
let db = DB.get().unwrap();
let user = model::user::Entity::find_by_id(user_id).one(db).await?;
Ok(user)
}
}
最佳实践与规范
重要提示:关于 `*` (Glob Import)
在常规 Rust 代码中,通常不推荐使用 use module::*;
这样的通配符导入,因为它可能导致命名空间污染和名称冲突。
但是,在 Prelude 模式中,*
导入是推荐的、惯用的用法。 Prelude 的设计初衷就是为了被通配符导入。
何时向 Prelude 添加内容?
- 当你发现某个类型或 Trait 在 超过 80% 的同模块文件中都被使用时,它就是加入 Prelude 的绝佳候选者。
何时不应向 Prelude 添加内容?
- 只在一两个文件中使用的特定类型(例如,某个特定的第三方库的错误类型)不应加入 Prelude,否则会使 Prelude 变得臃肿。
遵守层级
- 在
model
文件中,总是 使用use crate::model::prelude::*;
。 - 在
service
文件中,总是 使用use crate::service::prelude::*;
。 - 不要 在
model
文件中引入service::prelude
,这会破坏项目的分层结构。
- 在