Rust の Rocket と sqlx でクリーンアーキテクチャっぽくして、ディレクトリとか モジュールもなるべくいい感じに分けつつ、テストもしやすい・モックも作りやすい状態を、頑張って作ってみました。
目次
以前 Axum でも同じようなことをやりました
以前 Axum でも同じようなことをやってみていました。ワイの歴代最多スター数を誇るリポジトリが下記です。
https://github.com/edo1z/rust-axum-sqlx-sample
このリポジトリの Axum のバージョンはもう結構古いですので、今やろうとしたら全体的に修正が必要だとは思います。あとは、use_case が Trait になっていないので、モックが作りづらいとかはあるかなあと思います。
ただ、コードは、(私的にはですが)割とシンプルな感じなので、シンプルさ的にはいいかなあと思っています。
ただ、リポジトリに渡しているのが、DB Pool なので、リポジトリの関数を実行する度に Pool からコネクションをとってきていると思います。これが、今回の Rocket のバージョンとの違いかなあと思っております。
毎回関数実行時に connection を取得・破棄するというのは、1 リクエストで同じ connection を使い続ける場合と比べて、相対的に非効率になるのかなと思ってます。(計測したりしていませんが、ChatGPT に聞いたら、そうだよ、と言っていました)また、トランザクションも基本的には repository 関数内で完結させるしかないので、複数の repository をまたいだり、外部サービスのレスポンスを待ってから commit したりということが、基本できない(ややこしい)のかなと思っています。
今回作った Rocket バージョンのリポジトリ
今回の Rocket バージョンのリポジトリが下記です。よかったらスターをお願いします!
https://github.com/edo1z/rust-rocket-sqlx-sample
特徴
- use_case も repository も Trait にしたので、モックが作りやすくなりました。
- repository の各関数に渡すのは、DB Pool から取得したコネクションの参照になっていますので、上記の Axum バージョンより、ちょっと効率がよいのではないか?と思っています。
- 1 リクエスト毎に Pool からコネクションを取得して、同じリクエスト内ではずっとそのコネクションを使います。リクエストの処理が終わったら返却(破棄)されます。
- repository の各関数に connection を渡しますので、use_case 側でトランザクションを作って、複数の repository 関数の実行後に commit させるというのも、やろうと思えばできます。
- controller, use_case, repository(統合テスト)が簡単に出来るようになっています。
悩ましかった点
- トランザクションの扱い
- Transaction とPoolConnection の両方を受け取れる repository 関数を使いながら mockall.automock を使うのが難しかった(結局両方受け取れる関数にするのをやめた)
- 上記の結果、repository のテスト時に Transaction の Rollback を使ってテーブル状態をクリアするというのが使えなくなった。(今はテスト前に truncate している)
- Axum バージョンと比べると DB コネクション周りのコードが若干シンプルではなくなった。
- まあでも関数に Pool を渡すと非効率だし、トランザクションも使いづらいのでよいのかなと思いました。
将来やりたいこと
- SeaORM を導入したい。
- テストでトランザクションを簡単に使えるようにしたい。
- 現在、Rocket の sqlx は 0.6 ですが、SeaORM は 0.7 です。もしかしたら、Rocket の db 関連ライブラリを使うのをやめるかも。
まとめ
- Axum も Rocket もいい感じだと思いました
- トランザクションややこしいと思いました
- よかったらリポジトリ のスターをお願いします!