はじめに
Rustでアプリケーション開発をしていると、データベースとのやりとりを安全かつスムーズに行いたくなる場面があります。
ORM(Object Relational Mapping)ライブラリを使うことで、SQLをゴリゴリ書かずに型安全なデータベース操作ができるようになります。
他の言語だとORMの選択肢が多く用途に合わせて幅広く選択することができますが、Rustだとあまり選択肢が多くなく、どれがいいのかもあまりわかりにくいように感じました。
今回紹介するのは、Rust製ORMの中でも比較的扱いやすく、公式ドキュメントも充実している SeaORM です。
個人的に触ってみて良かったので、簡単に紹介してみます。
簡単な使い方はこちらで記事にしています
SeaORMとは?
SeaORMはRust製の非同期対応ORMで、以下の特徴があります。
- 非同期API対応(
async
/await
) -
Entity
ベースのデータ構造 - マイグレーションツール(SeaSchemaやSeaMigrate)も提供
- PostgreSQL / MySQL / SQLite に対応
TypeScriptでいうPrismaや、GoでいうGORMのような使い勝手に近い印象です。
簡単な使い方
公式の手順からインストールするとmigrationのプロジェクトが作成されます。
これで下記のようにデータベースの定義を行い、sea-orm-cli migrate
コマンドでデータベースを作成したりし、テーブル更新したりできます。
use sea_orm_migration::{prelude::*, schema::*};
#[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(pk_auto(User::Id))
.col(string(User::Name))
.col(string(User::Email))
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(User::Table).to_owned())
.await
}
}
#[derive(DeriveIden)]
enum User {
Table,
Id,
Name,
Email,
}
さらにsea-orm-cli generate entity
コマンドでmigrateで作成したテーブル定義が実際にアプリで使うためのentityが自動で作成されます。
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "user")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub name: String,
pub email: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
ここら辺はtypescriptで使えるprismaがかなり近いように感じます。
なので、他の言語でよく使用されているORMライブラリと使い方が似ているので導入も違和感なく取り付くことができるように感じます(私はPrisma使った実績があったのでかなり導入は楽でした!)
クエリ作成の簡潔さ
ORMライブラリの重要な要素として、SQLのクエリを書かずにデータベースの操作を行うため、簡潔にかつインジェクションなどもあまり意識せずに実装できることだと思います。
SeaORMも下記のように汎用的な操作は網羅されており、特別な場合を除いてSQL書かなくていいことも良さの一つですね
// CakeテーブルのNameカラムで"chocolate"を含むデータをNameカラム昇順で取得する
Cake::find()
.filter(cake::Column::Name.contains("chocolate"))
.order_by_asc(cake::Column::Name)
.all(db)
.await?;
ここら辺も他のORMライブラリと同じように使用できます。
もちろんSQLでの記載も可能なので、特殊なクエリはこちらも活用できます。
デメリット
デメリットとしては、なんといっても使えるデータベースがmysql、postgres、sqliteしかないため、mongooseなどのNoSQLには対応してないことは選択肢としては狭まると思います。
また、マイグレーションに関して、リレーションテーブルの定義方法やカラムの型定義などのドキュメントがないのでそこら辺については使いづらいなと感じました。
(私が見つけきれてないかもですが...)
まとめ
Rustで使えるORMはまだ選択肢が少ないですが、SeaORMは使いやすさ、ドキュメント、非同期対応などの点で非常に優秀で導入も楽でした。
特にマイグレーションから自動的にエンティティを作成してくれるため、開発効率が格段に上がりました。
現状RustのORMに関してはあまり選択肢も多くないと思うので対応してるデータベースを利用してるのであればとても便利に開発ができると思います!
興味のある方は、ぜひ以下のリンクから公式ガイドを確認してみてください。
個人的にも使い方を紹介してますのでこちらもよろしくお願いします。