🚀
RustのRocketとSqlxでクリーンアーキテクチャっぽいやつを作ってみました
RustのRocketとsqlxでクリーンアーキテクチャっぽくして、ディレクトリとか
モジュールもなるべくいい感じに分けつつ、テストもしやすい・モックも作りやすい状態を、頑張って作ってみました。
以前Axumでも同じようなことをやりました
以前Axumでも同じようなことをやってみていました。ワイの歴代最多スター数を誇るリポジトリが下記です。
このリポジトリのAxumのバージョンはもう結構古いですので、今やろうとしたら全体的に修正が必要だとは思います。あとは、use_caseがTraitになっていないので、モックが作りづらいとかはあるかなあと思います。
ただ、コードは、(私的にはですが)割とシンプルな感じなので、シンプルさ的にはいいかなあと思っています。
ただ、リポジトリに渡しているのが、DB Poolなので、リポジトリの関数を実行する度にPoolからコネクションをとってきていると思います。これが、今回のRocketのバージョンとの違いかなあと思っております。
毎回関数実行時にconnectionを取得・破棄するというのは、1リクエストで同じconnectionを使い続ける場合と比べて、相対的に非効率になるのかなと思ってます。(計測したりしていませんが、ChatGPTに聞いたら、そうだよ、と言っていました)また、トランザクションも基本的にはrepository関数内で完結させるしかないので、複数のrepositoryをまたいだり、外部サービスのレスポンスを待ってからcommitしたりということが、基本できない(ややこしい)のかなと思っています。
今回作ったRocketバージョンのリポジトリ
今回のRocketバージョンのリポジトリが下記です。よかったらスターをお願いします!
特徴
- 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もいい感じだと思いました
- トランザクションややこしいと思いました
- よかったらリポジトリのスターをお願いします!
Discussion