目的
SeaORMの情報が少ないのと、どんな感じに動くのか雰囲気を知りたい人は多いのではと思って、スターターキットを作成しました。
今回は、SeaORMを動かしてみるということに特化した設計のため、クリーンアーキテクチャは無視しています。まずは「こいつ、動くぞ。。。!」を体感してもらえると幸いです。さあ、サイド7からスタートしましょう。
環境
Windows11
Ubuntu -> Ubuntuの場合を参照
Docker
リポジトリ
手順
まずは SeaORM を動かしてみよう
ローカルにクローンする。
git clone git@github.com:Ometeor-Zheero-OMZ/sea-orm-starter.git
.env.exampleから.envを作成する
cp .env.example .env
SeaORMのCLIをインストール
cargo install sea-orm-cli
コンテナを作成
docker compose up -d
make start
一応 Makefile で各コマンドを作成しているので、お好みで使用してください。
マイグレーション実行
Dockerfileにmigrationをコピーするように記述していますので、
コンテナ内でマイグレーションします。
docker exec -it backend sea-orm-cli migrate up -u postgres://postgres:postgres@db/sea-orm-starter
make migrate-up
10秒ほどビルドされた後に、
下記のようになればOK
Applying all pending migrations
Applying migration 'm20241231_132216_create_user_table'
Migration 'm20241231_132216_create_user_table' has been applied
client.http
で簡単に APIテストができるようにしていますので、さっそく確認してみましょう。
まずは、ユーザー取得。
マイグレーションした直後のため、userテーブルにレコードは入っていません。
ユーザー登録でレコードを挿入することができます。
再度、ユーザー取得をすることで、追加したユーザー情報を取得することができます。
新しくSeaORMの環境を作るにあたっての重要事項
マイグレーションの初期化(スターターキットでは実行済み)
今回のスターターキットではすでに初期化済みですが、新規プロジェクトでは、マイグレーションの初期化をする必要があります。cargo new projectの後に実行しておくといいでしょう。
sea-orm-cli migrate init
make migrate-init
マイグレーションファイルの作成
table_name は作成するテーブル名を記述する必要があります。
sea-orm-cli migrate generate create_<table_name>_table
例:user テーブルを作成
sea-orm-cli migrate generate create_user_table
例:make コマンドで user テーブルを作成
make gen table_name=user
以下のようになればOK
$ make gen table_name=user
sea-orm-cli migrate generate create_user_table
Generating new migration...
Creating migration file `./migration\src\m20241231_220934_create_user_table.rs`
Adding migration `m20241231_220934_create_user_table` to `./migration\src\lib.rs`
これにより、migrationディレクトリがRustの新規プロジェクトとして src
と同階層に生成されます。
- migration/src
- main.rs
- m20241231_132216_create_user_table.rs
- lib.rs
- migration/Cargo.lock
- migration/Cargo.toml
- migration/README.md
main.rs
use sea_orm_migration::prelude::*;
#[async_std::main]
async fn main() {
cli::run_cli(migration::Migrator).await;
}
以降、main.rsは編集しなくとも、マイグレーションファイルの生成とリンクはされますので、構造だけ確認しておきましょう。
m20241231_132216_create_user_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> {
manager
.create_table(
Table::create()
.table(User::Table)
.if_not_exists()
.col(
ColumnDef::new(User::UserId)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(User::CognitoId).string().not_null())
.col(ColumnDef::new(User::Username).string().not_null())
.col(ColumnDef::new(User::ProfilePictureUrl).string())
.col(ColumnDef::new(User::TeamId).integer())
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.name("idx_user_cognito_id")
.table(User::Table)
.col(User::CognitoId)
.unique()
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.name("idx_user_username")
.table(User::Table)
.col(User::Username)
.unique()
.to_owned(),
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_table(Table::drop().table(User::Table).to_owned()).await
}
}
#[derive(Iden)]
pub enum User {
Table,
UserId,
CognitoId,
Username,
ProfilePictureUrl,
TeamId,
}
これは、マイグレーションファイルの生成後にテーブル定義を書いたものです。
#[derive(Iden)]
pub enum User {
Table,
UserId,
CognitoId,
Username,
ProfilePictureUrl,
TeamId,
}
これはテーブルのフィールド決定するものです。enum
で定義します。
ここで重要なのは、Iden
トレイトを derive
することです。
manager
.create_table(
Table::create()
.table(User::Table)
.if_not_exists()
.col(
ColumnDef::new(User::UserId)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(User::CognitoId).string().not_null())
.col(ColumnDef::new(User::Username).string().not_null())
.col(ColumnDef::new(User::ProfilePictureUrl).string())
.col(ColumnDef::new(User::TeamId).integer())
.to_owned(),
)
.await?;
Laravelなどのマイグレーションに慣れている方であれば、難しくはないはずです。
重要なのは、.table(User::Table)
で記述する User
は先ほど定義した enum
の User
ですので、他モジュールの User
構造体や enum
を記述しないようにしましょう。
manager
: テーブルを作成するためのインスタンス
.create_table()
: 新規テーブルを作成 - テーブル情報をラップします。
Table::create()
: 新規テーブルを作成 - メソッドチェーンでテーブル情報を追加していく。
.table(User::Table)
: enum
で定義した User
に関するテーブルを指定する。
.if_not_exists()
: 同名テーブルがない場合にテーブルを作成する。
.col(ColumnDef::new())
: フィールドを定義する。
メソッドチェーンで、全てのフィールドを定義していけば、.to_owned()
所有型に変換します。データを所有権で管理することで、SeaORMの最大の特徴である 非同期処理 が実現します。データオブジェクトが複数スレッドでアクセスされたり、バックグラウンドタスクで使用されることを考慮して、所有型である必要があります。
さらに、enum
で User
を定義する理由は、恐らくですがコンパイラがエラーを吐き続けることによって、フィールド定義に漏れが出ないように強制してくれるように設計されたと、私は思っています。
テーブル作成時のメソッドは公式ドキュメントを見て書いていただければと思います。
(SeaORM Docs) Create Table
lib.rs
pub use sea_orm_migration::prelude::*;
mod m20241231_132216_create_user_table;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![
Box::new(m20241231_132216_create_user_table::Migration),
]
}
}
こちらも特に手動で手を加えることは少ないと思いますが、sea-orm-cli
でマイグレーションファイルを作成した際に、自動でマイグレーションファイルを mod
し、vec!
内に、ボックス化したマイグレーションファイルのMigration
トレイトが自動で指定されます。マイグレーションファイルを生成する毎に、それぞれ異なるスキーマの型として生成されるため、トレイトオブジェクトで扱うようになっています。
エンティティ生成 (スターターキットでは生成済み)
既存のデータベースからテーブル情報を基にsrc/entities/内でエンティティを生成します。
sea-orm-cli generate entity -u postgres://postgres:postgres@localhost:5432/sea-orm-starter -o src/entities
make gen-entity
これにより、以下のファイルが生成されます。
- src/entities/prelude.rs
- src/entities/mod.rs
- src/entities/user.rs
src/entities/prelude.rs
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.3
pub use super::user::Entity as User;
※エンティティ生成毎に上書きされます。マイグレーションとは異なり、破壊的に更新されるため注意です。といっても、モジュールを公開しているだけなので、そこまで痛くない。
src/entities/mod.rs
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.3
pub mod prelude;
pub mod user;
※エンティティ生成毎に上書きされます。マイグレーションとは異なり、破壊的に更新されるため注意です。といっても、モジュールを公開しているだけなので、そこまで痛くない。
src/entities/user.rs
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.3
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "user")]
pub struct Model {
#[sea_orm(primary_key)]
pub user_id: i32,
#[sea_orm(unique)]
pub cognito_id: String,
#[sea_orm(unique)]
pub username: String,
pub profile_picture_url: Option<String>,
pub team_id: Option<i32>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
既存テーブル情報をそのままエンティティとして生成できます。
※エンティティ生成毎に上書きされます。マイグレーションとは異なり、破壊的に更新されるため注意です。このファイルに関しては、さすがに痛いので特に注意です。
Ubuntuの場合
手順は全く同じですが、もしかすると sea-orm-cli のインストールに失敗するかもしれません。
大抵の問題は OpenSSLのライブラリ群がインストールされていなかったり、環境パスが設定されていない場合に起こります。
インストールに失敗した場合は下記の手順を試してみてください:
パッケージ・ライブラリをインストール
sudo apt update
sudo apt install -y pkg-config libssl-dev build-essential
OpenSSL をインストール
sudo apt install -y openssl
環境変数を設定
export OPENSSL_LIB_DIR=/usr/lib/x86_64-linux-gnu
export OPENSSL_INCLUDE_DIR=/usr/include/openssl
export OPENSSL_STATIC=false
sea-orm-cli をインストール
cargo install sea-orm-cli --locked
終わりに
今回は SeaORMをさっさと使いたい方または、新規プロジェクトでの参考にされたい方に向けて記事を書きました。
記事作成には慣れていないため、読みづらい箇所があるかと思いますが、最後までお読みいただき大変嬉しく思います。
次回は AxumとNext.jsでフルスタック開発スターターキットを公開しようかなと思います。
気になる方はいいねをいただけるとやる気が出ます。
また、Rustでゲーム開発もしてますので、ある程度いいものが作れたらみんなに見てもらいたいな🐮🤚