Logicky Blog

Logickyの開発ブログです

RustのRocketとSqlxでクリーンアーキテクチャっぽいやつを作ってみました

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(統合テスト)が簡単に出来るようになっています。

悩ましかった点

  • トランザクションの扱い
    • TransactionPoolConnectionの両方を受け取れる repository 関数を使いながら mockall.automock を使うのが難しかった(結局両方受け取れる関数にするのをやめた)
    • 上記の結果、repository のテスト時に Transaction の Rollback を使ってテーブル状態をクリアするというのが使えなくなった。(今はテスト前に truncate している)
  • Axum バージョンと比べると DB コネクション周りのコードが若干シンプルではなくなった。
    • まあでも関数に Pool を渡すと非効率だし、トランザクションも使いづらいのでよいのかなと思いました。

将来やりたいこと

  • SeaORM を導入したい。
  • テストでトランザクションを簡単に使えるようにしたい。
  • 現在、Rocket の sqlx は 0.6 ですが、SeaORM は 0.7 です。もしかしたら、Rocket の db 関連ライブラリを使うのをやめるかも。

まとめ

  • Axum も Rocket もいい感じだと思いました
  • トランザクションややこしいと思いました
  • よかったらリポジトリのスターをお願いします!