Building a Clean Architecture with Rust's Rocket and sqlx
Tags: rust, rocket, sqlx, cleanarchitecture
Published: 2023-09-25 06:33:23

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.

## We've done something similar before at Axum

I used to try to do the same thing with Axum. The repository with the highest number of stars in my history is below.

{% embed https://github.com/net3i/rust-axum-sqlx-sample %}

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.

## Repository of the Rocket version we made this time

The repository of this Rocket version is below. Please star it if you like!

{% embed https://github.com/net3i/rust-rocket-sqlx-sample %}


### Features
- 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).

### Points that annoyed me
- Transaction handling
  - [Transaction](https://docs.rs/sqlx/0.6.3/sqlx/struct.Transaction.html#) and [PoolConnection](https://docs.rs/sqlx/0.6.3/sqlx/pool/ PoolConnection.html), 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.

### What I want to do in the future
- 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.

## Summary
- I thought Axum and Rocket were nice.
- I thought it was transaction complicated.
- Please star [repository](https://github.com/net3i/rust-rocket-sqlx-sample) if you like!