rust有幾個套件可以用來與資料庫溝通
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
實做了兩個方法up
與down
,up
方法負責執行我們想要的資料庫異動,而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
與這是的目標subscriptions
。seaql_migrations
會紀錄已經執行的腳本,每次執行migrate up
指令會執行尚未migrate的腳本。
前面設定的部份較為繁瑣,接下來我們要由資料庫反向產生供應用程式操作使用的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,還沒有套件可以由應用程式的模型轉譯成資料庫表。