I tried my best to create a clean architecture with Rust Rocket and sqlx.
I tried my best to create a state that is easy to test and mock, while also separating modules in a nice way.
I used to try to do the same thing with Axum. The repository with the highest number of stars in my history is below.
git clone https://github.com/web3ten0/rust-axum-sqlx-1.git
docker-compose up -d
sh migrate add -r users
sh migrate run
sh migrate revert
The version of Axum in this repository is already quite old, so I think the whole thing needs to be modified if you try to do it now. Also, use_case is not a Trait, so it may be difficult to create mocks.
However, the code is rather simple (in my opinion), so I think it is good in terms of simplicity.
However, the DB Pool is passed to the repository, so each time a function in the repository is executed, it gets a connection from the Pool. I wonder if this is the difference between this version of Rocket and the current version.
I wonder if getting and destroying a connection every time a function is executed is relatively inefficient compared to continuing to use the same connection for a single request. (I have not measured this, but I asked ChatGPT and they said yes. I think it is basically impossible (and complicated) to do things such as crossing multiple repositories or waiting for a response from an external service before committing.
The repository of this Rocket version is below. Please star it if you like!
A clean architecture style Sample using Rust's Rocket, sqlx and PostgreSQL.
git clone https://github.com/net3i/rust-rocket-sqlx-sample
docker-compose up -d
cp .env.example .env
cargo test -- --test-threads=1
Use Rocket's rocket_db_pools to retrieve DB Pool connections from Controller arguments. This is passed as an argument to use_case, which passes the connection as an argument to each function in the repository. Only one connection is used per request, and it is returned to the Pool as soon as the request is processed.
Since use_case and repository are Trait, mock can be easily created by mockall when testing.
DB transactions are basically assumed to be used only within each function of repository, but transactions can be generated from DB Pool connections on the use_case side if necessary. However, since the functions in repository do not accept…
- Both use_case and repository are Trait, so it is easier to create mocks.
- The repository functions are now passed only references to connections obtained from the DB Pool, which I think is a bit more efficient than the Axum version above. I think it is a little more efficient than the Axum version above.
- A connection is obtained from the Pool for each request, and that connection is used throughout the same request. The connection is returned (discarded) when the request is finished.
- Since the connection is passed to each function of repository, it is possible to create a transaction on the use_case side and have it commit after multiple repository functions are executed, if you want to do so.
- It is easy to do controller, use_case, and repository (integration test).
- Transaction handling
- Transaction and PoolConnection, it was difficult to use mockall.automock while using a repository function that can receive both (I eventually stopped using a function that can receive both).
- As a result of the above, I can no longer use Transaction's Rollback to clear the table state when testing repository. (I now truncate before testing).
- Compared to the Axum version, the code around DB connection is a little less simple.
- But it is inefficient to pass a Pool to a function, and it is difficult to use transactions, so I thought it was good.
- I would like to introduce SeaORM.
- I want to make it easy to use transactions in testing.
- Currently, Rocket's sqlx is 0.6, but SeaORM is 0.7. Maybe we will stop using Rocket's db related libraries.
- I thought Axum and Rocket were nice.
- I thought it was transaction complicated.
- Please star repository if you like!