toastyは先日tokioから発表されたORMです。
このORMは現状開発段階のもので、まだ実用に耐えうる段階にはないとGitHubには書かれています。というか、crates.ioにはダミー用関数が用意されているだけで、プロジェクトの依存に追加したとしてもまだ何もできません。現時点で対応しているのはsqliteとDynamoDBのようで、他のデータベースないしはCassandraなどには今後対応予定とのことです。async対応しています。また、SQLとNoSQL対応しているとなると、両者を抽象してくれるなにかかと思われるかもしれませんが、両者に対する操作を抽象してくれるわけではありません。
toastyの特徴
特徴としては、toasty
というファイルにスキーマ定義を書き、toasty cliを実行すると、専用のRustコードが生成されるという点でしょうか。そして、生成されたコードにあるメソッドを呼び出し、データベースに対する操作を行うという流れになります。作者の方もRedditで言及していますが、Prismaが念頭にあるようです。
toastyの記法は専用に開発されたもののようで、今のところone-to-manyをはじめとするORMらしい記述をexamplesで見ることができます。今後どんどん機能が増えていくでしょうね。
toasty
ファイルに書かれた内容を元に、Rustのコードが生成されます。このコードは新規のRustファイルが複数生成される形式になっています。つまり、アプリケーション本体側には手続き的マクロを使用することなく、toasty
に書かれた情報を元にcliを経由してコードが生成されるということです[*1]。
toastyは、今のところは手続き的マクロを使用しない方針を採っているというのが大きな特徴になると思っています。そして、この方式を採用するメリットは次のものが考えられます:
- コンパイルが遅くなりにくい: アプリケーションのコンパイルとORM向けのコード生成のコンパイルが切り離されるようになります。その分だけコンパイル時間を外出しできることになり、短縮されます。それ以外にも、内部実装を軽く読んだ限りでは、ORMの機能を呼び出す側においてはトレイトを必要とせず、単に構造体に紐づくメソッドを呼び出すだけでよいようです。これは単純に使う側の使いやすさに直結するのと、コード生成量を減らせるのでコンパイル時間を悪化させずに済むように思われます。
- 生成されたコードを簡単に読める、つまりGitHub上などでレビュー可能な状態に置かれる: 手続き的マクロで生成されたコードを見るためにはcargo-expandなどでひと手間かけて閲覧する必要がありますが、toastyの場合生のRustコードが所定のディレクトリに生成される関係で、実装中に生成されたコードを最悪見に行けますし、GitHub上でレビューできます。
- 生成コードがバージョン管理される: 手続き的マクロで生成されるコードはバージョン管理できません。なので、たとえばクレートのバージョンを入れ替えたときなどに内部実装が変わっていた場合、どこに変更があったか追いにくくデバッグを困難にするかもしれません。生のRustコードの場合、これは非常に容易です。
Rustには、dieselや、Active RecordインスパイアなORMのSeaORMなどがあります。しかしこれらに共通する問題として、手続き的マクロを使用しているのでコンパイル時間を悪化させる要因になりがちというものがあります。dieselは私も業務利用経験がありますが、プロジェクトの規模が大きくなるにつれて本当にビルド時間時間がかかるようになります。ビルドが遅いのは、rust-analyzerを止めるので割と開発時間にも影響が出てきます。また、同じ問題はsqlxでマクロ側を使用した場合にも同様に発生するのではないかと思われます。toastyの場合は、まだ軽く触った限りなので何もわかりませんが、理論上はこうした問題を軽減できるはずです。
この手法のデメリットも少し考えてみましょう。一例をあげると、スキーマ定義がRustでは完結せず、toastyというある種の「設定ファイル」を書くことになる点はデメリットといえるかもしれません。たとえば上手な共通化や抽象化はRustコードでスキーマ定義を書く場合と比べるとできなくなります。しかし、こうした定義ファイルに対する共通化や抽象化はどこまで必要でしょうか?好みの問題は依然残りそうですが、実務上は同じ内容を繰り返して書いてもそこまで大きな問題は生じないと思います。
dieselではマクロでスキーマ定義を書きますが、マクロで書くので抽象化や共通化が効くのではと思いきや、ダーティハックしようとするとすぐにマクロの解決ができなくなります。マクロで書いたとしても共通化や抽象化できないとなると、単にコンパイル時間の延長というペインを引き受けるだけになります。
もうひとつ考えられることがあるとすれば、Rustコードの管理とtoastyファイルの管理が必要になるということでしょうか。これにより、Rustのコンパイルを行っただけでは最新のスキーマ定義に基づくコードが生成されないケースがある、という管理の煩雑さの増加が考えられます。Rustのコンパイルでスキーマ情報が最新化されるわけではないので、もしかするとtoasty側の更新を忘れてそれに気づかずハマる、といったことが起こるかもしれません。これは些細なように見えて、意外に開発中に気づきにくいミスなような気がしています。私だけかもしれませんが。
シンタックスハイライトや補完などは、新しいファイル形式なため当然別途対応する必要があります。ただこれについては、Language Serverを軽く実装するか、もしくはRustRoverなどはそれ向けの専用のプラグインを用意すれば回避可能な問題なので、開発が進むにつれて改善されている話なように思われます。
気になるポイントとしては、toastyファイル内で起こった定義の不整合に対する出力方法です。現状のtoastyのコード生成部分の実装を確認したり、実際に異常系の挙動をさせたりした限りだと、あまりエラーを親切に出せる感じにはなっていません。おそらく今後大幅な改訂が入るのではないかと思われます。Rustコンパイラ級のわかりやすいエラーメッセージを出すように実装されるともちろん嬉しいので、今後の開発に期待しています。
動かしてみる
toastyそれ自体はまだcrates.ioに公開されていないようです。crates.ioに名前自体は登録されていますが、cargo add
などで追加したとしても機能を使うことはまだできません。
https://crates.io/crates/toasty
それでも少し試してみたいと思った場合、リポジトリをforkすることで利用できます。私が興味を持ったのは実際のコード生成の部分なので、それを確認してみます。試したのは記事を書いている時点で最新のコミットである 2a4f7175e9cf2f7e56cb545a5a9e9ffbbc291fb0
です。
scripts
を見ていると、gen-examples
という意味ありげなスクリプトが見つかりました。この後半に、
# Main loop to execute the command for each directory for dir in "${directories[@]}"; do cargo run -p toasty-cli -- gen --schema "examples/$dir/schema.toasty" "examples/$dir/src/db" cargo check -p example-$dir done
などという記述があります。examplesには現状、composite-key
、cratehub
、hello-toasty
、user-has-one-profile
のサンプルがあります。hello-toasty
の生成されたコードを試しに再生成させてみます。
まず、db
ディレクトリを削除します。次に、下記のコマンドを実行してtoasty-cli
を動かします。すると、コードが確かに生成されているのを確認できるはずです。
$ cargo run -q -p toasty-cli -- gen --schema "examples/hello-toasty/schema.toasty" "examples/hello-toasty/src/db" writing examples/hello-toasty/src/db/mod.rs writing examples/hello-toasty/src/db/user.rs writing examples/hello-toasty/src/db/todo.rs
確認すると、たとえばuser.rs
には次のようなコードが生成されていることを確認できるはずです(長いのでRust Playgroundに置いています)。
生成済みコードでは、ORMに利用できる関数が逐次生成されている様子を伺うことができます。実際にLSPのGo To Definition機能で関数に入るとどう実装されているのかを見られてよいなと感じました。
実際の使い心地側は今回は説明しませんが、下記のリポジトリ上のコードを見てもらうとわかりやすいかと思います。
まとめ
tokioが先日発表したORM「toasty」を簡単に説明しました。コンパイル時間を伸ばしにくそうな設計になっているのはよかったなと思うので、今後に期待しています。
サーバーサイドの開発においては、サーバーサイド用のライブラリとORMを用意すれば一旦開発をはじめられることが多いように思います。たとえば、KotlinだとktorとExposedが開発されているように、です。なので、tokioがこれを作っているのは理にかなった話だと考えています。
余談ですが、まだ規模が小さくコードリーディングに良さそうです。ORMってどうやって作ってるんだろうなと前から思ってたんですが、sqlxもdieselも規模は大きいしコードは複雑で読むのが大変でやめてました。toastyはコードが読みやすいというかシンプルな作りになっているのと、単純に規模が小さいので読みやすかったです。暇を見つけてもう少しじっくりいろいろ読んでおきたい。