< 返回我的博客

blossom-001 发表于 2025-11-18 20:17

在构建任何复杂的应用程序时,数据持久化都是核心环节。通常会采用**仓储模式(Repository Pattern)**来解耦业务逻辑和数据访问逻辑。这带来了清晰的架构,但也引入了一个常见的问题:大量的样板代码(Boilerplate Code)。

每个实体都需要一个仓储接口,以及对应的实现。find_by_id, find_by_email, exists_by_name, delete_by_id... 这些简单却重复的方法,我们一遍又一遍地编写,不仅枯燥,还容易出错。

痛点分析:传统仓储层的重复劳动

下面是 UserRepository 在没有自动化工具时的完整面貌:

1. Trait 定义 - 接口膨胀

// src/domain/repositories/user_repository.rs
pub trait UserRepository {
    // 基础CRUD
    async fn find_by_id(&self, txn: &mut Txn, id: &UserId) -> Result<Option<User>>;
    async fn save(&self, txn: &mut Txn, user: &mut User) -> Result<()>;
    async fn delete_by_id(&self, txn: &mut Txn, id: &UserId) -> Result<()>;
    
    // 各种查询方法
    async fn find_id_by_email(&self, txn: &mut Txn, email: &Email) -> Result<Option<UserId>>;
    async fn find_id_by_user_name(&self, txn: &mut Txn, user_name: &str) -> Result<Option<UserId>>;
    async fn exists_by_email(&self, txn: &mut Txn, email: &Email) -> Result<bool>;
    async fn find_by_status(&self, txn: &mut Txn, status: UserStatus) -> Result<Vec<User>>;
    // ... 更多类似方法,接口越来越庞大
}

2. 实现类 - 重复的SQL模板

// src/infrastructure/repositories_impl/user_repository_impl.rs
pub struct UserRepositoryImpl;

#[async_trait::async_trait]
impl UserRepository for UserRepositoryImpl {
    async fn find_by_id(&self, txn: &mut Txn, id: &UserId) -> Result<Option<User>> {
        // 每个方法都要写几乎相同的模板代码
        txn.query_sql_one("SELECT * FROM ua_user WHERE id = $1", [id.into()]).await
    }

    async fn find_id_by_email(&self, txn: &mut Txn, email: &Email) -> Result<Option<UserId>> {
        // 只是表名、字段名、参数位置变化,结构完全一样
        txn.query_sql_one("SELECT id FROM ua_user WHERE email = $1", [email.into()]).await
    }

    async fn find_id_by_user_name(&self, txn: &mut Txn, user_name: &str) -> Result<Option<UserId>> {
        // 重复第三次...
        txn.query_sql_one("SELECT id FROM ua_user WHERE user_name = $1", [user_name.into()]).await
    }

    async fn exists_by_email(&self, txn: &mut Txn, email: &Email) -> Result<bool> {
        // 稍微复杂一点,但模式仍然固定
        txn.query_sql_one("SELECT EXISTS(SELECT 1 FROM ua_user WHERE email = $1)", [email.into()]).await
    }
    
    // 保存逻辑更复杂,但也是固定模式
    async fn save(&self, txn: &mut Txn, user: &mut User) -> Result<()> {
        if user.is_new() {
            // INSERT 逻辑
            let sql = "INSERT INTO ua_user (id, email, user_name, ...) VALUES ($1, $2, $3, ...)";
            txn.execute_sql(sql, params![user.id(), user.email(), ...]).await?;
        } else {
            // UPDATE 逻辑  
            let sql = "UPDATE ua_user SET email = $1, user_name = $2, ... WHERE id = $3";
            txn.execute_sql(sql, params![user.email(), user.user_name(), user.id()]).await?;
        }
        Ok(())
    }
}

3. 测试类 - 更多的重复

// tests/user_repository_test.rs
// 还需要为每个方法编写测试,Mock 各种依赖...

这种模式的问题很明显:

  • 代码重复:每个查询方法都是相似的模板
  • 维护困难:表结构变更需要修改多个地方
  • 容易出错:手写SQL容易有拼写错误
  • 效率低下:花费大量时间在机械性编码上

✨ 解决方案:#[repository] 宏的威力

现在让我们看看使用 #[repository] 宏之后的变化:

// src/domain/repositories/user_repository.rs
use core_common::repository;

#[repository(aggregate = User, table_name = "ua_user")]
pub trait UserRepository: Send + Sync {
    // 复杂的、需要特殊逻辑的查询,保持手写实现
    async fn get_all_permissions(
        &self,
        user_id: &UserId,
        tenant_id: Option<TenantId>,
    ) -> Result<PermissionIdHashSet, RepositoryError> {
        // 这里仍然可以手写复杂实现
        // 比如联表查询、特殊业务逻辑等
    }

    // 简单查询:只需定义方法签名,空函数体 {}
    // 宏会自动生成完整的SQL实现!
    
    // 根据ID查询
    async fn find_by_id(&self, id: &UserId) -> Result<Option<User>, RepositoryError> {}
    
    // 根据各种字段查询ID
    async fn find_id_by_user_name(&self, user_name: &str) -> Result<Option<UserId>, RepositoryError> {}
    async fn find_id_by_email(&self, email: &Email) -> Result<Option<UserId>, RepositoryError> {}
    async fn find_id_by_phone(&self, phone: &Phone) -> Result<Option<UserId>, RepositoryError> {}
    
    // 存在性检查
    async fn exists_by_email(&self, email: &Email) -> Result<bool, RepositoryError> {}
    async fn exists_by_user_name(&self, user_name: &str) -> Result<bool, RepositoryError> {}
    
    // 复杂条件查询
    async fn find_by_email_and_status(&self, email: &Email, status: UserStatus) -> Result<Vec<User>, RepositoryError> {}
    async fn find_by_email_or_phone(&self, email: &Email, phone: &Phone) -> Result<Vec<User>, RepositoryError> {}
    
    // 统计查询
    async fn count_by_status(&self, status: UserStatus) -> Result<i64, RepositoryError> {}
    
    // 删除操作
    async fn delete_by_id(&self, id: &UserId) -> Result<(), RepositoryError> {}
    
    // 保存操作 - 宏会自动处理INSERT/UPDATE逻辑
    async fn save(&self, user: &mut User) -> Result<(), RepositoryError> {}
}

宏的魔法效果详解:

  1. 自动实现生成

    • 编译时自动为每个空方法生成完整的SQL实现
    • 无需手动编写 UserRepositoryImpl
    • 生成的代码与手写代码质量相当,但零错误
  2. 智能参数注入

    • 自动添加事务参数 txn: &mut TC
    • 自动处理参数类型转换(如 Email → 数据库类型)
    • 最终方法签名实际上是:find_id_by_email(&self, txn: &mut TC, email: &Email)
  3. CRUD 功能继承

    • 通过 aggregate = User 自动继承 CrudRepository<User, TC>
    • 免费获得:find_by_id, save, delete_by_id, exists_by_id 等标准方法
    • 无需重复定义基础CRUD操作
  4. 完整的测试支持

    • 自动利用 mockall 生成Trait的Mock实现
    • 在测试中可以直接:let mut mock = MockUserRepository::new();
    • 极大简化单元测试的编写

🧙♂️ 魔法揭秘:命名约定解析引擎

宏的核心是一个强大的方法名解析器,它将自然语言风格的方法名转换为精确的SQL查询。

查询类型推断规则

方法名前缀 生成的 SQL 类型 返回类型 示例
find_by_... SELECT * Option<T>Vec<T> find_by_email
find_{field}_by_... SELECT {field} Option<FieldType> find_id_by_email
find_{field1}_and_{field2}_by_... SELECT {field1}, {field2} 元组或自定义结构体 find_id_and_email_by_phone
exists_by_... SELECT EXISTS(...) bool exists_by_email
count_by_... SELECT COUNT(*) i64 count_by_status
delete_by_... DELETE () delete_by_id

条件运算符映射

方法名后缀 SQL 运算符 示例 生成 WHERE 子句
(无后缀) = find_by_email email = $1
_not != find_by_status_not status != $1
_like LIKE find_by_name_like name LIKE $1
_gte >= find_by_age_gte age >= $1
_lte <= find_by_age_lte age <= $1
_between BETWEEN find_by_age_between age BETWEEN $1 AND $2
_in IN find_by_status_in status IN ($1, $2, ...)
_is_null IS NULL find_by_deleted_at_is_null deleted_at IS NULL

逻辑连接符处理

连接词 SQL 逻辑 示例 生成条件
_and_ AND find_by_email_and_status email = $1 AND status = $2
_or_ OR find_by_email_or_phone email = $1 OR phone = $2

排序和分页支持

后缀 SQL 子句 示例 效果
_order_by_{field}_asc ORDER BY field ASC find_by_status_order_by_created_at_asc 按创建时间升序
_order_by_{field}_desc ORDER BY field DESC find_by_status_order_by_created_at_desc 按创建时间降序
_first / _top LIMIT 1 find_by_status_first 只返回第一条
_limit_{n} LIMIT n find_by_status_limit_10 返回前10条

实际解析示例

简单查询:

async fn find_id_by_email(&self, email: &Email) -> Result<Option<UserId>> {}

↓ 生成 SQL:

SELECT id FROM "ua_user" WHERE email = $1

复杂查询:

async fn find_id_by_email_and_status_order_by_created_at_desc(
    &self, 
    email: &Email, 
    status: UserStatus
) -> Result<Option<UserId>> {}

↓ 生成 SQL:

SELECT id FROM "ua_user" 
WHERE email = $1 AND status = $2 
ORDER BY created_at DESC

存在性检查:

async fn exists_by_email_and_tenant_id(
    &self, 
    email: &Email, 
    tenant_id: &TenantId
) -> Result<bool> {}

↓ 生成 SQL:

SELECT EXISTS(
    SELECT 1 FROM "ua_user" 
    WHERE email = $1 AND tenant_id = $2
)

⚙️ 灵活的配置选项

宏支持丰富的配置参数来适应不同场景:

// 基础配置
#[repository(aggregate = User, table_name = "ua_user")]

// 多租户支持
#[repository(aggregate = User, table_name = "ua_user", tenant = true)]

// 禁用Mock生成(用于性能敏感场景)
#[repository(aggregate = User, table_name = "ua_user", mock = false)]

// 自定义事务上下文
#[repository(
    aggregate = User, 
    table_name = "ua_user", 
    context = "db: &Db"  // 使用自定义的db参数而非默认txn
)]

// 复杂配置组合
#[repository(
    aggregate = User,
    table_name = "ua_user",
    tenant = true,
    mock = true,
    context = "conn: &mut PgConnection"
)]

🎯 最佳实践和适用场景

推荐使用宏的场景:

  • 简单CRUD操作:95%的数据库查询都适合
  • 标准查询模式:等值查询、范围查询、存在性检查等
  • 快速原型开发:快速验证业务逻辑,后期可替换为手写优化SQL
  • 团队规范统一:确保所有开发者使用一致的查询模式

建议手写实现的场景:

  • 复杂联表查询:涉及多个表的复杂JOIN操作
  • 自定义聚合查询:GROUP BY、HAVING等复杂聚合
  • 数据库特定优化:需要数据库特定语法或优化提示
  • 存储过程调用:调用数据库存储过程或函数

📊 实际效果对比

代码量减少:

  • 传统方式:每个查询方法需要 5-10 行实现代码
  • 宏方式:每个查询方法只需 1 行声明
  • 节省比例:约 80-90% 的代码量

开发效率提升:

  • 新查询方法:从 5分钟/个 减少到 30秒/个
  • 维护成本:表结构变更时,只需修改宏参数,无需改动多个方法
  • 错误率:编译时检查替代运行时SQL错误

总结

#[repository] 宏不仅仅是一个代码生成工具,它代表了一种声明式数据访问的新范式。通过将开发者的重心从"如何实现"转移到"想要什么",它真正实现了:

  • ⚡ 极致效率:声明即实现,开发速度提升5-10倍
  • 🔒 绝对安全:编译时检查确保所有查询的类型安全
  • 📚 规范统一:强制执行一致的代码标准和命名约定
  • 🧪 测试友好:开箱即用的Mock支持,测试编写效率大幅提升
  • 🛠 灵活演进:复杂场景仍可手写实现,兼顾简单与复杂需求

在Rust强大的元编程能力支持下,我们能够构建出这样既智能又实用的开发工具。这不仅是技术的进步,更是开发体验的革新——让我们能够更专注于业务逻辑本身,而不是重复的机械性工作。

评论区

写评论

还没有评论

1 共 0 条评论, 1 页