Slide 1

Slide 1 text

Rust をこれから大きく支えるかもしれない SeaQL Projects について

Slide 2

Slide 2 text

このプレゼンテーションの目標 SeaQL Projects という取り組みを知ってもらう。 Sea 系のプロダクトが技術選定の候補にあがってくる状態にする。

Slide 3

Slide 3 text

免責 私は本番環境でこのプロダクトを利用したことがありません。

Slide 4

Slide 4 text

SeaQL Projects とは 「 SeaXXX」とつくクレート群の総称を yuki が勝手に呼んでいる。 ORM やそれに関連する GraphQL、ストリーミング処理ライブラリ、デバッガーなどを 作っている。 有名どころでは「 SeaORM」がある。 https://www.sea-ql.org/

Slide 5

Slide 5 text

SeaQL Projects の製品群 SeaORM / SeaORM X SeaQuery Seaography SeaStreamer FireDBG

Slide 6

Slide 6 text

SeaORM / SeaORM X 一番知名度があるかもしれない、 ORM のクレート。近年話題。 MySQL、 Postgres、 SQLite に対応している。 「 X」の方は SQLServer 対応のもの。 Rails の Active Record に使用感は近そうに感じる。

Slide 7

Slide 7 text

SeaQuery SeaORM 内で使われているクエリビルダー。

Slide 8

Slide 8 text

Seaography SeaORM で作ったモデルの機能を拡張して GraphQL 対応させることができる。 async-graphql と SeaORM を元に作られている。

Slide 9

Slide 9 text

SeaStreamer いわゆるストリーム処理を行えるようにするクレート。 Kafka や Redis などをバックエンドとすることができる。

Slide 10

Slide 10 text

SeaXXX 系に言えること 非同期処理対応がデフォルトでなされていて ◎。 ドキュメントが充実している。 団体でメンテナンスしているので、これからもおそらくメンテナンスが続くだろうとい う期待を持てる。

Slide 11

Slide 11 text

今日は SeaORM を簡単にみていきます

Slide 12

Slide 12 text

サンプルコード https://github.com/yuk1ty/seaql-examples

Slide 13

Slide 13 text

SeaORM Pros 普通に実用的なデザインをしている。 Rust にはまだ数少ない O/R マッパーを利用できる。 sea-orm-cli と組み合わせるとマイグレーションもスムーズ。 Cons 手続き的マクロで生成される箇所のコンパイルエラーの読み解きが難しい。 Rust 1.78 から入った #[diagnostic::on_unimplemented] とかをもうちょ っと活用してよくできるかも。期待。 ボイラープレート多い。 O/R マッパーはいろいろ設定が必要な手前、仕方ない気もする。

Slide 14

Slide 14 text

SeaORM を使う

Slide 15

Slide 15 text

今回扱うモデル

Slide 16

Slide 16 text

モデルの定義 Model の定義、ならびに DeriveEntityModel から Entity を生成させる。 ActiveModel 関連の操作定義をする。 Related を使って関係性(リレーション)を定義する。

Slide 17

Slide 17 text

モデルの定義 : customer モデルの定義 // src/customer.rs use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "customers")] pub struct Model { #[sea_orm(primary_key, auto_increment = true)] pub customer_id: i32, pub name: String, pub address: String, pub phone_number: String, } #[derive(Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::order::Entity")] Order, } impl Related for Entity { fn to() -> RelationDef { Relation::Order.def() } } impl ActiveModelBehavior for ActiveModel {}

Slide 18

Slide 18 text

エンティティとモデル // DeriveEntityModelでカラムやプライマリキーに関する情報が埋め合わせされる。 #[derive(Clone, Debug, DeriveEntityModel)] #[sea_orm(table_name = "customers")] // 構造体の名前は`Model`じゃないとダメ。 pub struct Model { #[sea_orm(primary_key, auto_increment = true)] pub customer_id: i32, pub name: String, pub address: String, pub phone_number: String, } // insertやupdateなどのメソッドを生やしてくれる`ActiveModel`。 impl ActiveModelBehavior for ActiveModel { // ActiveModelBehaviorを使うと、保存や削除処理をする直前に、 // たとえばバリデーションチェックなどの機構を挟み込むことができる。 // 何もしない場合は空にしておく(飛ばすことはできない) 。 }

Slide 19

Slide 19 text

モデル定義 : order モデルの定義 // src/order.rs use sea_orm::entity::prelude::*; #[derive(Clone, Debug, DeriveEntityModel)] #[sea_orm(table_name = "orders")] pub struct Model { #[sea_orm(primary_key, auto_increment = true)] pub order_id: i32, pub ordered_at: DateTime, pub customer_id: i32, pub item_id: i32, pub amount: u32, } #[derive(Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::customer::Entity", from = "Column::CustomerId", to = "super::customer::Column::CustomerId" )] Customer, #[sea_orm( belongs_to = "super::item::Entity", from = "Column::ItemId", to = "super::item::Column::ItemId" )] Item, } impl Related for Entity { fn to() -> RelationDef { Relation::Customer.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::Item.def() } } impl ActiveModelBehavior for ActiveModel {}

Slide 20

Slide 20 text

モデルの定義 : item モデルの定義 // src/item.rs use sea_orm::entity::prelude::*; #[derive(Clone, Debug, DeriveEntityModel)] #[sea_orm(table_name = "items")] pub struct Model { #[sea_orm(primary_key, auto_increment = true)] pub item_id: i32, pub name: String, pub price: u32, } #[derive(Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::order::Entity")] Order, } impl Related for Entity { fn to() -> RelationDef { Relation::Order.def() } } impl ActiveModelBehavior for ActiveModel {}

Slide 21

Slide 21 text

リレーションの定義 customer と order は one-to-many の関係性にする。 item と order は one-to-many の関係性にする。 これを SeaORM 上で定義する。

Slide 22

Slide 22 text

SeaORM でのリレーションの定義 Relation という enum を定義する。 Related トレイトを実装する。

Slide 23

Slide 23 text

customer に実装する #[derive(Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { // customer -> orderに対する関係性を定義する。 #[sea_orm(has_many = "super::order::Entity")] Order, } // customer::Entityに対して関係性情報を追加する。 impl Related for Entity { fn to() -> RelationDef { Relation::Order.def() } }

Slide 24

Slide 24 text

item に実装する customer と同様。 #[derive(Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::order::Entity")] Order, } impl Related for Entity { fn to() -> RelationDef { Relation::Order.def() } }

Slide 25

Slide 25 text

order に実装する #[derive(Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { // どのカラムをキーとしてマッピングさせるかを定義する。 // itemとcustomerに対して定義する。 #[sea_orm( belongs_to = "super::customer::Entity", from = "Column::CustomerId", to = "super::customer::Column::CustomerId" )] Customer, #[sea_orm( belongs_to = "super::item::Entity", from = "Column::ItemId", to = "super::item::Column::ItemId" )] Item, } // order::Entityとcustomer/item::Entityの関係性を定義する。 impl Related for Entity { fn to() -> RelationDef { Relation::Customer.def() } } impl Related for Entity { fn to() -> RelationDef { Relation::Item.def() } }

Slide 26

Slide 26 text

テストを通じて使い方を確認する 本来は実データベースに接続すべきですが、時間と説明の都合モックの話を優先します 生成された Entity から find_by_id などのデータベース操作関連の関数が生えている のを確認する。 モックを使ったテストの仕方を確認する。

Slide 27

Slide 27 text

テストコード #[cfg(test)] mod tests { use sea_orm::{entity::prelude::*, DatabaseBackend, MockDatabase}; use crate::customer::Entity as CustomerEntity; use crate::{ customer::Model as CustomerModel, item::Model as ItemModel, order::Model as OrderModel, }; #[tokio::test] async fn test_find_item() -> Result<(), DbErr> { let db = MockDatabase::new(DatabaseBackend::Postgres) .append_query_results([vec![CustomerModel { /* 省略 */ }]]) .append_query_results([vec![ItemModel { /* 省略 */ }]]) .append_query_results([vec![OrderModel { /* 省略 */ }]]) .into_connection(); assert_eq!( CustomerEntity::find_by_id(1).one(&db).await?, Some(CustomerModel { /* 省略 */ }) ); Ok(()) } }

Slide 28

Slide 28 text

テストコード ポイントは次の通り。 Entity から生えた find_by_id などの関数が見られる。 MockDatabase 、ならびに append_query_results を使ってテストデータをセットで きる。

Slide 29

Slide 29 text

CRUD 関数 生成した Entity に対して、 find 、 insert 、 update 、 delete などの関数が生え てくる。 これを使ってデータベース操作を行う。 CustomerEntity::find_by_id(1).one(&db).await?, Some(CustomerModel { /* 省略 */ })

Slide 30

Slide 30 text

モックデータベースの定義 MockDatabase ならびに付随する関数を利用して、モック用のデータを結構手軽にセッ トできる。便利。 let db = MockDatabase::new(DatabaseBackend::Postgres) .append_query_results([vec![CustomerModel { /* 省略 */ }]]) .append_query_results([vec![ItemModel { /* 省略 */ }]]) .append_query_results([vec![OrderModel { /* 省略 */ }]]) .into_connection();

Slide 31

Slide 31 text

モックデータベースの定義 読み出しの場合は append_query_results を用いたが、書き込みの結果をモックさせ る場合は append_exec_results という関数が使える。 書き込み時には、その他トランザクションログをテストする機構も用意されている。 詳しくはドキュメント https://www.sea-ql.org/SeaORM/docs/write-test/mock/ を参照 できる。

Slide 32

Slide 32 text

sea-orm-cliの機能 実務上便利かもしれない機能として下記がある。 データベースマイグレーションをできる。 実テーブルの情報を読んでエンティティのコードを自動生成させられる( sea-orm- cli generate entity ) 。

Slide 33

Slide 33 text

その他の Sea系クレートとの連携 Seaography: SeaORMで定義したモデルを使って、 seaography::register_entities! マクロ経由で GraphQLスキーマを定義できる。 SeaStreamer: SeaORMで定義したモデルを、ストリーム処理のシンクの対象(コンシュ ーマ、あるいはデータの保存先)とすることができる。

Slide 34

Slide 34 text

まとめ SeaXXX というクレートがある。 SeaORM について今回はメインで解説した。