Rust中两种ORM框架SeaORM与Diesel比较


在本文中,我们将讨论 Rust ORM 并比较您目前可以在应用程序中使用的最流行的 Rust ORM:SeaORM与Diesel。

什么是 ORM?
关系对象映射器(简称 ORM)是一款软件,旨在通过让您将代码中的对象映射到 SQL 来解决直接使用 SQL 的问题。例如,您可能有一个如下所示的 SQL 查询:

SELECT FROM CAKES WHERE NAME = 'Test Cake';

这可以写成这样:

let name = "Test Cake";
let cake: cake::Model = User::find()
    .filter(cake::Column::Name.contains(name))
    .all(db_connection)
    .await?;

尽管最初的样板设置比使用原始 SQL 库可能使用的要多,但从长远来看,它可以在让 SQL 查询工作时避免开发人员的许多麻烦 - 它还使刚接触代码库的开发人员更容易上手。此外,您还可以获得您想要使用的任何 IDE 插件的优势 - 例如,LSP(语言服务器协议)插件和 Intellisense。

什么是 SeaORM?
SeaORM 是一个完全异步友好的 Rust ORM,旨在“帮助您在熟悉动态语言的情况下使用 Rust 构建 Web 服务”。该库基于 SQLx 构建,并抽象出原始 SQL,以提供一个干净的接口,允许您使用结构作为模型,使用派生宏和特征来构建您想要的体验。它还配备了用于生成迁移、实体和模型的 CLI。

SeaORM 有很多有用的文档,您可以在这里找到。有一个页面几乎涵盖了您可以使用 SeaORM 执行的所有操作。像这样的一些部分ActiveModel非常需要了解,它们主要隐藏在其他页面的部分中,因此可以推断出您将阅读每个页面或使用搜索栏。如果您计划定期使用 SeaORM,那么最好就这样做,但这可能会让休闲浏览变得更加尴尬。

如果您需要使用许多不同的模型或表,SeaORM 非常有帮助。如果您需要跟踪许多不同的事情,并且安装了 Rust LSP 插件或智能感知,则可以轻松确保所有 SQL 数据库交互“正常工作”,而无需调试任何内容!对于成员可能需要与数据库交互但不熟悉 SQL 的团队来说,这解决了一个特别大的问题。

您可能会发现的一个障碍是知道从哪里导入依赖项。特别是如果您在一个文件中使用多个模型,重命名所有内容可能会很烦人!实现您自己的行为也可能有些复杂ActiveModel。如果您是经验不足的开发人员,这可能会导致一些麻烦。设置时间也可能是一个令人厌烦的问题,特别是如果由于方法链接的数量而需要设置大量表时。

此外,新开发人员在使用它时可能会遇到一些最初的障碍 - 特别是需要 CLI 并查看迁移的具体作用。此外,虽然您可以将 SQL 文件直接迁移到 SeaORM 迁移,但生成的迁移文件本身非常长。这是添加一个表和一列的迁移:

// src/migrator/m20220602_000001_create_bakery_table.rs (create new file)

use sea_orm_migration::prelude::*;

pub struct Migration;

impl MigrationName for Migration {
    fn name(&self) -> &str {
        "m_20220602_000001_create_bakery_table"
    }
}

#[async_trait::async_trait]
impl MigrationTrait for Migration {
    // Define how to apply this migration: Create the Bakery table.
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .create_table(
                Table::create()
                    .table(Bakery::Table)
                    .col(
                        ColumnDef::new(Bakery::Id)
                            .integer()
                            .not_null()
                            .auto_increment()
                            .primary_key(),
                    )
                    .col(ColumnDef::new(Bakery::Name).string().not_null())
                    .col(ColumnDef::new(Bakery::ProfitMargin).double().not_null())
                    .to_owned(),
            )
            .await
    }

    // Define how to rollback this migration: Drop the Bakery table.
    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .drop_table(Table::drop().table(Bakery::Table).to_owned())
            .await
    }
}

#[derive(Iden)]
pub enum Bakery {
    Table,
    Id,
    Name,
    ProfitMargin,
}


正如您所看到的,它相当长。此外,Iden文档中没有向用户清楚地解释导出宏。尽管如此,能够实现迁移本身的定义仍然至关重要。
就性能而言,它比其他 ORM 包(即 Diesel)慢 - 您可以在此处找到指标虽然 SeaORM 是一个可以提供很多功能的包,但您可能必须牺牲一些性能来换取它。

将 SeaORM 与 Shuttle 结合使用
默认情况下,Shuttle 提供来自我们的 crate 的 SQLx 连接shared_db,您可以将其转换为 SeaORM 连接:

#[shuttle_runtime::main]
async fn axum(
    #[shuttle_shared_db::Postgres] pool: PgPool,
) -> shuttle_axum::ShuttleAxum {
    let conn = SqlxPostgresConnector::from_sqlx_postgres_pool(pool);  // pg conn
...
    let app = Router::new()
        .route("/", get(some_route))
        .with_state(Arc::new(conn));

        Ok(app.into())
}

在生产中,宏将自动允许 Shuttle 服务器为您提供 Postgres 实例,无需任何设置!

什么是Diesel?
Diesel 是在 Rust 中使用 ORM 时可能会考虑的 "另一大选择"。更准确地说,它是一个数据映射器和查询生成器。不过,由于它提供了许多 ORM 通常会提供的功能(编译时检查、迁移、将结构映射到数据库对象),因此在功能上被认为与 ORM 相同。

与 SQLx 相比,表格设置要简洁得多:

diesel::table! {
    users {
        id -> Integer,
        name -> VarChar,
        favorite_color -> Nullable<VarChar>,
    }
}


Diesel 将 SQL 类型映射为 Rust 单元结构,而不是直接映射为 Rust 类型。然而,当你在编写一个结构体时,如果你想用它来查询数据库,请注意文档中说你不应该在结构体中直接使用这些类型。

你可以使用 Diesel 的方法来进行简单的插入、更新或选择,而不必直接使用模型或实体:

let new_user = (id.eq(1), name.eq("Sean"));
let rows_inserted = diesel::insert_into(users)
    .values(&new_user)
    .execute(connection);

这两者的结合,使得操作界面比 SeaORM 简单得多。扩展模型没有特定的接口,但您也可以添加手动实现。

Diesel 的主要优势之一是,它通过检查来自表!宏的查询来确保编译时的安全性。这对那些希望确保自己的查询能正常工作的开发人员来说是一个巨大的优势,而且这意味着在运行 SQL 查询时不会出现运行时错误。如果运行的大型查询较多,其中涉及的内容也较多,这一点尤为重要。

如果你想在 Diesel 中使用网络服务,需要注意的是 Diesel 主要是同步的,并使用本地驱动(这是本地异步不兼容的主要原因)。使用 Diesel 时,你使用的是数据库库传输协议的高度优化实现。不过,如果你想使用纯粹的 Rust 栈,Diesel 可能并不适合你。关于启用异步(async),内部已经对此进行了一些讨论,你可以在这里查看。如果你想在异步上下文中使用 Diesel,你可以使用 diesel-async 或 diesel-deadpool (或者其他可以做到这一点的 crates)。

Diesel 有非常广泛的文档,这些文档超越了板条箱本身,包括使用 Diesel 组成应用程序、最佳实践、使用任何你想要的功能扩展 Diesel 以及如何配置 CLI。与 SeaORM 相比,docs.rs 文档的内容相当丰富!有关于编写查询、使用库特质等的明确文档。相比之下,SeaORM 在自己的文档页面上有更多的文档,而它并不基于 docs.rs。虽然在 SeaORM 中查找某些主题(如 ActiveModel)的文档会稍显困难,但两者在这一类别中都没有特别明显的劣势。

由于 Diesel 的构建方式,它大量使用了泛型。使用泛型可以帮助编写板条箱,因为它可以在保持良好性能的同时使结构更灵活。然而,在编写应用程序时,它也可能导致非常无益的错误。其他库(例如 Axum)通过添加一个宏标志(macros flag)来解决这个问题,该标志还允许添加一个 debug_handler 宏,让你可以在任何不使用泛型的函数中添加一个宏来避免错误墙问题。和 Axum 一样,Diesel 也有一个可以自动检查错误的宏,你可以这样使用它:

#[derive(Selectable)]
pub struct SomeStruct {
    #[diesel(check_for_backend(diesel::pg::Pg))]
    some_field: String
}


这将自动允许 Diesel 对你的结构体进行类型检查,而不需要你做任何事情。Diesel 也有一部分文档专门帮助你解决各种与 trait 相关的错误,你可以在here找到。

在 Shuttle 中使用 Diesel
目前,Diesel 并不支持开箱即用,但有一个社区插件可以让你在 Shuttle 上使用 Diesel(通过 diesel-async)--你可以在这里找到更多关于它的信息。

要使用它,你需要运行以下命令:

cargo add shuttle-diesel-async --git <https://github.com/aumetra/shuttle-diesel-async>
cargo add diesel-async

然后就可以像这样将其添加到代码中:

use diesel_async::{
    pooled_connection::deadpool::Pool,
    AsyncPgConnection,
};

#[shuttle_runtime::main]
async fn axum(
    #[shuttle_diesel_async::Postgres] pg: Pool<AsyncPgConnection>
) -> shuttle_axum::ShuttleAxum {
    // .. your code
}

哪种 ORM 更好?
与 Diesel 相比,SeaORM 是一种更完整的 ORM 体验。不过,它也需要更多的设置和模板编写。根据您对编写模板的感受,这可能会让您望而却步。不过,作为交换,它允许您通过使用板条箱来精简应用程序代码,而不必自己实现。

与 SeaORM 相比,Diesel 的社区规模更大,在 GitHub 上的星级也更多。Diesel 的主要社区在 Gitter 和 GitHub Discussions 上。不过,对于某些用户来说,这可能不太方便,这取决于你是否使用 Gitter。相比之下,SeaORM 使用的 Discord 更受欢迎(因此更容易访问),但人数没有那么多。

由于 Diesel 是一个较小的库,主要用作查询生成器和数据映射器,因此该库比较简陋,更多的是留给用户使用。不过,你也可以扩展 Diesel 本身,使其包含任何你想要的行为。一些扩展被添加到社区板条箱中--虽然这很好,但如果你的工作环境要求在使用前对板条箱进行审查,这就不是特别有帮助了。另一方面,SeaORM 完全不允许任何扩展。

归根结底,您应该使用什么取决于您的使用情况。如果您需要的 ORM 可以处理应用程序中的大量不同职责,那么您应该使用 SeaORM。如果您想使用一个更小、更可扩展且性能更好的板条箱,那么 Diesel 可能会更好。

总结
SeaORM 和 Diesel 都是 Rust 中的两种 ORM(对象关系映射)框架,用于简化数据库操作。以下是它们之间的一些比较:

  1. 易用性:
    • SeaORM: 设计注重简单性和易用性,提供直观的 API 和链式调用,使开发人员更容易上手。
    • Diesel: 功能强大,但相对较复杂,学习曲线较陡峭。
  • 语法和表达性:
    • SeaORM: 支持直观的链式调用,查询语法相对清晰。
    • Diesel: 提供更灵活和强大的查询语法,但也更加冗长。
  • 性能:
    • SeaORM: 相对较轻量,可能在某些简单场景下性能更好。
    • Diesel: 功能更全面,但可能在某些情况下引入一些性能开销。
  • 社区和文档:
    • SeaORM: 相对较新,社区和文档可能不如 Diesel 那么成熟。
    • Diesel: 有着较大且成熟的社区支持,文档相对完备。
  • 数据库支持:
    • SeaORM: 支持多种数据库,包括 PostgreSQL、MySQL 和 SQLite。
    • Diesel: 同样支持多种数据库,包括 PostgreSQL、MySQL 和 SQLite,且具有更广泛的数据库驱动支持。
  • 宏使用:
    • SeaORM: 使用了宏,但相对较少。
    • Diesel: 大量使用宏,这使得代码生成和类型检查更加强大,但也可能增加了学习成本。