iT邦幫忙

2023 iThome 鐵人賽

DAY 11
1
Software Development

Rust Web API 從零開始系列 第 11

Day11 - 使用SeaORM進行持久化(1)

  • 分享至 

  • xImage
  •  

rust有幾個套件可以用來與資料庫溝通

  1. Diesel: 最知名的ORM,功能較為成熟,不支援非同步
  2. sqlx: 直接使用sql與資料庫進行互動,支援非同步
  3. SeaORM: 較新的ORM,基於sqlx開發支援非同步。
    我希望不要直接操作sql語句,加上希望查詢操作支援非同步,所以就選擇了SeaORM,rust生態系中還有其他的套件,這邊就不多做討論。

產生資料表

先安裝sea-orm CLI工具:

cargo install sea-orm-cli

接下來運行初始化指令:

sea-orm-cli migrate init

現在我們來看一下目前的檔案結構:

.
├── Cargo.lock
├── Cargo.toml
├── migration
│   ├── Cargo.toml
│   ├── README.md
│   └── src
│       ├── lib.rs
│       ├── m20220101_000001_create_table.rs
│       └── main.rs
└── src
    ├── application.rs
    ├── handler
    │   ├── health_check.rs
    │   ├── mod.rs
    │   └── subscribe.rs
    ├── lib.rs
    └── main.rs

seaORM的CLI工具產生了一個/migration的資料夾,裡面是一個負責管理資料庫異動的專案。一般而言資料庫異動可以由工程師手動執行,但seaORM提供了一種管理migration的方式,現在我們來看一下m20220101_000001_create_table.rs的內容:

use sea_orm_migration::prelude::*;

#[derive(DeriveMigrationName)]
pub struct Migration;

#[async_trait::async_trait]
impl MigrationTrait for Migration {
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        // Replace the sample below with your own migration scripts
        todo!();

        manager
            .create_table(
                Table::create()
                    .table(Post::Table)
                    .if_not_exists()
                    .col(
                        ColumnDef::new(Post::Id)
                            .integer()
                            .not_null()
                            .auto_increment()
                            .primary_key(),
                    )
                    .col(ColumnDef::new(Post::Title).string().not_null())
                    .col(ColumnDef::new(Post::Text).string().not_null())
                    .to_owned(),
            )
            .await
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        // Replace the sample below with your own migration scripts
        todo!();

        manager
            .drop_table(Table::drop().table(Post::Table).to_owned())
            .await
    }
}

#[derive(DeriveIden)]
enum Post {
    Table,
    Id,
    Title,
    Text,
}

Migration實做了兩個方法updownup方法負責執行我們想要的資料庫異動,而down則是用於將這次的異動回滾,當遷移有問題的時候可以rollback回前一個版本。資料庫的異動是透過SchemaManager實現的,以模板中的程式碼為例,使用create_table方法來生成資料表,裡面並設定了表跟欄位的屬性。最後面的Post enum僅僅只是用來設定資料欄位名稱,實際上也可以直接使用字串。
接下來我們就要設定這次要建立的資料表,這張表要用來存放訂閱的紀錄,目前規劃的shema如下:

CREATE TABLE IF NOT EXISTS "Subscriptions" (
    "Id" UUID NOT NULL PRIMARY KEY,
    "Email" VARCHAR NOT NULL UNIQUE,
    "Name" VARCHAR NOT NULL,
    "SubscribedAt" TIMESTAMP WITH TIME ZONE NOT NULL
);

詳細的migration可以參考腳本1腳本2,因為第一次操作的時候不熟悉所以我做了兩次migration,順便練習alter_table
接下來準備測試用的postgreSQL,為了方便直接使用docker:

docker run --name postgres -e POSTGRES_PASSWORD=postgres -d postgres

在執行migration之前要先設定環境變數,在專案底部新增.env

DATABASE_URL=postgres://postgres:postgres@localhost:5432/marvinhsu_zero_to_production

另外seaORM沒辦法自動生成database,所以我們要手動在postgres容器新增marvinhsu_zero_to_production,最後再運行:

sea-orm-cli migrate up

到這邊應該可以看到資料庫生成了兩張表,分別是seaql_migrations與這是的目標subscriptionsseaql_migrations會紀錄已經執行的腳本,每次執行migrate up指令會執行尚未migrate的腳本。

產生應用程式所需要的model

前面設定的部份較為繁瑣,接下來我們要由資料庫反向產生供應用程式操作使用的model,使用以下指令:

sea-orm-cli generate entity -o src/entity

透過-o可以指定輸出結果的位置,接下來應該可以看到src/底下多了entity資料夾,裡面就有我們需要的subscriptions.rs

//// subscriptions.rs
use super::sea_orm_active_enums::SubscriptionStatus;
use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "subscriptions")]
pub struct Model {
    #[sea_orm(primary_key, auto_increment = false)]
    pub id: Uuid,
    #[sea_orm(unique)]
    pub email: String,
    pub name: String,
    pub subscribed_at: DateTimeWithTimeZone,
    pub status: SubscriptionStatus,
}

impl ActiveModelBehavior for ActiveModel {}

最後一行的impl ActiveModelBehavior for ActiveModel {}是用於設定ActiveModel的行為,這部份等到實際實用在討論,通常情況下不需要異動。

小結

今天使用seaORM進行了第一次的資料庫遷移,並且由資料庫table產生對應的model供應用程式使用。最後我想與C#中知名的Entity Framework Core比較一下,EF Core提供幾種建立資料庫對應的方式,分別是由database生成應用端entity的model first以及由應用程式端的模型轉換成資料庫table的code first,前者較為直覺,後者則能夠取得較大的靈活性。SeaORM的教學有提到,目前rust生態中的兩個ORM,Diesel與SeaORM的設計上都是由table為主的model first,還沒有套件可以由應用程式的模型轉譯成資料庫表。


上一篇
Day10 - 單元測試與Domain Module
下一篇
Day12 - 使用SeaORM進行持久化(2)
系列文
Rust Web API 從零開始30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言