I'm trying to create a minimal clean architecture structure with Rust. Basically a controller that receives a request, a use case that handles the logic, an entitiy that holds the data model, and a repository that actually retrieves the data. Here is a diagram of the structure I'm using: diagram.
A synchronous version of this can be see at Generics and dependency inversion with multiple structs now I'm trying to do it asynchronously with Tokio: https://crates.io/crates/tokio. For starters I wont set the repository, so will pull the data directly from the use case.
Here is a playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ed9c8c0e12c043ad4e3e4bea956fa0cc
Here same code for reference:
#![allow(dead_code)]
use async_trait::async_trait;
use std::io::Error;
use tokio::time;
// CUSTOM RETURN TYPE - - - - - - - - - - -
type AsyncResult<T> = Result<T, Error>;
// ENTITY - - - - - - - - - - - - - - - - -
#[derive(Debug)]
struct User {
pub id: i32,
pub name: String,
}
// USE CASE - - - - - - - - - - - - - - - - -
#[async_trait]
trait IUserGetAllUseCase {
fn new() -> UserGetAllUseCase;
async fn execute(&self) -> AsyncResult<Vec<User>>;
}
struct UserGetAllUseCase;
#[async_trait]
impl IUserGetAllUseCase for UserGetAllUseCase {
fn new() -> UserGetAllUseCase {
UserGetAllUseCase {}
}
async fn execute(&self) -> AsyncResult<Vec<User>> {
// Simulating async data retrieval from persistence
time::sleep(std::time::Duration::from_secs(3)).await;
let user_1 = User {
id: 1,
name: String::from("user_1"),
};
let user_2 = User {
id: 2,
name: String::from("user_2"),
};
let users = vec![user_1, user_2];
Ok(users)
}
}
// CONTROLLER - - - - - - - - - - - - - - - - -
struct UserGetAllController<T> {
user_get_all_use_case: T,
}
impl<T: IUserGetAllUseCase> UserGetAllController<T> {
fn new(user_get_all_use_case: T) -> UserGetAllController<T> {
UserGetAllController {
user_get_all_use_case,
}
}
async fn execute(&self) -> AsyncResult<Vec<User>> {
let users = self.user_get_all_use_case.execute().await;
users
}
}
// MOCK SERVER - - - - - - - - - - - - - - - - -
#[tokio::main]
async fn main() -> AsyncResult<()> {
let user_get_all_use_case = UserGetAllUseCase::new();
let user_get_all_controller = UserGetAllController::new(user_get_all_use_case);
let users = user_get_all_controller.execute().await?;
println!("{:#?}", users);
Ok(())
}
Ok, this works, is asynchronous.
Now I want to add a repository trait to be able to create any repositories I may need to retrieve data from different persistence systems: PostgreSQL, filesystem, etc.
To do this I create IUserRepository
, and the implement in in PostgreSQLUserRepository
.
Here is a playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=8758491ba16eee3821df8d1f800c9c85. And here the same code for reference:
#![allow(dead_code)]
use async_trait::async_trait;
use std::io::Error;
use tokio::time;
// CUSTOM RETURN TYPE - - - - - - - - - - -
type AsyncResult<T> = Result<T, Error>;
// ENTITY - - - - - - - - - - - - - - - - -
#[derive(Debug)]
pub struct User {
pub id: i32,
pub name: String,
}
// REPOSITORY - - - - - - - - - - - - - - - - -
#[async_trait]
pub trait IUserRepository {
async fn company_get_all(&self) -> AsyncResult<Vec<User>>;
}
pub struct UserRepository;
#[async_trait]
impl IUserRepository for UserRepository {
async fn company_get_all(&self) -> AsyncResult<Vec<User>> {
// Simulating async data retrieval from persistence
time::sleep(std::time::Duration::from_secs(3)).await;
let user_1 = User {
id: 1,
name: String::from("user_1"),
};
let user_2 = User {
id: 2,
name: String::from("user_2"),
};
let users = vec![user_1, user_2];
Ok(users)
}
}
// USE CASE - - - - - - - - - - - - - - - - -
#[async_trait]
trait IUserGetAllUseCase {
fn new<T: IUserRepository>(user_repository: T) -> UserGetAllUseCase<T>;
async fn execute(&self) -> AsyncResult<Vec<User>>;
}
struct UserGetAllUseCase<T> {
user_repository: T,
}
#[async_trait]
impl<T: IUserRepository> IUserGetAllUseCase for UserGetAllUseCase<T> {
fn new<K: IUserRepository>(user_repository: K) -> UserGetAllUseCase<K> {
UserGetAllUseCase { user_repository }
}
async fn execute(&self) -> AsyncResult<Vec<User>> {
let users = self.user_repository.company_get_all().await;
users
// Two errors here:
// «future cannot be sent between threads safely»
// «future created by async block is not `Send`»
}
}
// CONTROLLER - - - - - - - - - - - - - - - - -
struct UserGetAllController<T> {
user_get_all_use_case: T,
}
impl<T: IUserGetAllUseCase> UserGetAllController<T> {
fn new(user_get_all_use_case: T) -> UserGetAllController<T> {
UserGetAllController {
user_get_all_use_case,
}
}
async fn execute(&self) -> AsyncResult<Vec<User>> {
let users = self.user_get_all_use_case.execute().await;
users
}
}
// MOCK SERVER - - - - - - - - - - - - - - - - -
#[tokio::main]
async fn main() -> AsyncResult<()> {
let user_repository = UserRepository {};
let user_get_all_use_case = UserGetAllUseCase::<UserRepository>::new(user_repository);
let user_get_all_controller = UserGetAllController::new(user_get_all_use_case);
let users = user_get_all_controller.execute().await?;
println!("{:#?}", users);
Ok(())
}
As you see, I get two errors:
«future cannot be sent between threads safely»
«future created by async block is not `Send`»
I'm not sure why this wont compile while my previous structure was similar, and it was compiled and run without issues.
Any help will be welcome ))