0

I am trying to create a clean architecture structure in Rust with some structs using traits for dependency inversion.

My idea is basically to have:

  • A User model with one field only.
  • A repository that complies with a repository trait/interface holding a method to retrieve users from a MySQL database.
  • A use case that depends on the repository trait/interface, and receives an instance of this repository on instantiation with a new method. I also holds an execute method to trigger the repository action.
  • A controller that depends on the use case trait/interface, and receives an instance of this use case when instantiating it with a new method. It also holds an execute method to trigger the use case action.

  User:
  + id

  UserRepository complies with IUserRepository:
  - get_all_users: () -> Vec<Users>

  GetUsersUseCase complies with IGetUsersUseCase:
  + user_repository: IUserRepository
  - new: (user_repository: IUserRepository) -> GetUsersUseCase
  - execute: () -> Vec<Users>

  GetUsersController:
  + get_users_use_case: IGetUsersUseCase
  - new: (user_use_case: IGetUsersUseCase) -> GetUsersController
  - execute: () -> Vec<Users>

I have an implementation of this, but I have a problem with the generics. The basic code:

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ca1f4f9d6cfe4df2864d7e574966ad6b.

Code:

// MODEL
#[derive(Debug)]
struct User {
  id: i8,
}

// REPOSITORY for DB
trait IUserRepository {
  fn get_users(&self) -> Vec<User>;
}

struct MySQLUserRepository;

impl IUserRepository for MySQLUserRepository {
  fn get_users(&self) -> Vec<User> {
    let mock_user1 = User { id: 1 };
    let mock_user2 = User { id: 2 };
    let mock_users = vec![mock_user1, mock_user2];

    mock_users
  }
}

// USE CASE
trait IGetUsersUseCase {
  fn new<T: IUserRepository>(repository: T) -> GetUsersUseCase<T>;
  fn execute(&self) -> Vec<User>;
}

struct GetUsersUseCase<T> {
  user_repo: T,
}

impl<T: IUserRepository> IGetUsersUseCase for GetUsersUseCase<T> {
  fn new<K: IUserRepository>(user_repo: K) -> GetUsersUseCase<K> {
    GetUsersUseCase { user_repo }
  }

  fn execute(&self) -> Vec<User> {
    let users = self.user_repo.get_users();
    users
  }
}

// CONTROLLER for HTTP requests
struct GetUsersController<T> {
  get_users_use_case: T,
}

impl<T: IGetUsersUseCase> GetUsersController<T> {
  fn new(get_users_use_case: T) -> GetUsersController<T> {
    GetUsersController { get_users_use_case }
  }

  fn execute(&self) -> Vec<User> {
    let users = self.get_users_use_case.execute();
    users
  }
}

fn main() {
  // Lets imagine we are handling an HTTP request
  let mysql_repo = MySQLUserRepository {};
  // Error here: cannot infer type for type parameter `T` declared on the struct `GetUsersUseCase`
  let get_users_use_case = GetUsersUseCase::new(mysql_repo);
  let get_users_controller = GetUsersController::new(get_users_use_case);
  let users = get_users_controller.execute();
  println!("{:?}", users);
}

As you may see, the issue is at the implementation for GetUsersUseCase —impl<T: IUserRepository> IGetUsersUseCase for GetUsersUseCase<T>—. As the implementation receives two generic parameters, when constructing GetUsersUseCase::new(mysql_repo) I receive the following error:

cannot infer type for type parameter `T` declared on the struct `GetUsersUseCase`rustc(E0282)

A workaround would be to make user_repo at GetUsersUseCase public, and instead of using GetUsersUseCase::new(mysql_repo) instantiate it as usual:

[…]
struct GetUsersUseCase<T> {
  pub user_repo: T,
}
[…]
let get_users_use_case = GetUsersUseCase {
  user_repo: mysql_repo,
};
[…]

This works, but I really would like to find out how to construct structs using a public function without exposing private fields.

Here is the playground making this field public: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=917cee9d969dccd08c4e27753d04994f

Any idea will be welcome!

miravelardo
  • 75
  • 1
  • 9
  • You can help compile like this, line 64 `let get_users_use_case = GetUsersUseCase::::new(mysql_repo);` – Zeppi Nov 17 '21 at 13:14
  • Lots of thanks. It works, and dependencies directions are maintained. But, in terms of good practices… does it mean that my structure is convoluted or impractical? – miravelardo Nov 17 '21 at 13:34
  • Your structure is clearly inspired by the OOP MVC models and Rust have some path to do thath. You can read this doc (https://doc.rust-lang.org/book/ch17-00-oop.html) But, my point of view it could be difficult to try to use the OOP paradigme with Rust who is more functional oriented. You can found inspiration in some crate like Diesel (https://diesel.rs/) – Zeppi Nov 17 '21 at 13:51
  • 1
    A note: the structure I use is not related to MVC, but to clean architecture, hexagonal, onion or ports/adapters. Basically decoupling the external dependencies from the domain logic via dependency inversion. But I agree that Rust is not pure OOP, although I see there are ways to compose; the only thing that doesnt provide is inheritance, which I don't miss at all. – miravelardo Nov 17 '21 at 14:27
  • 1
    One minor comment: It's syntactically not possible in Rust to use a trait where a struct was expected, or vice versa. That means you don't need to do that Java (and C#) thing of prefacing all your traits with an I (for Interface). – cadolphs Nov 17 '21 at 16:43
  • Didn't know it, thanks a lot. Anyway, I find it easier to distinguish interfaces/traits or actual implementations by their names – miravelardo Nov 18 '21 at 06:55
  • @miravelardo, how can I contact you to ask about this approach of yours which is also my favorite? – Fred Hors Aug 27 '22 at 13:54

1 Answers1

1

A quick fix of your code:

// No more error here
let get_users_use_case = GetUsersUseCase::<MySQLUserRepository>::new(mysql_repo);

Why so?

It starts from trait IGetUsersUseCase: it doesn't require a generic type, however, its method fn new<T>() does, so as struct GetUsersUseCase<T>. And look here:

impl<T: IUserRepository> IGetUsersUseCase for GetUsersUseCase<T> {
  fn new<K: IUserRepository>(user_repo: K) -> GetUsersUseCase<K> {
    GetUsersUseCase { user_repo }
  }
}

It's a correct implementation having such traits, and it actually shows that T is not referred to K, it's like they are "different" (K is defined for the function, and T is defined for the struct, both are not defined for the trait). Therefore, you need to help the compiler with the turbofish operator (GetUsersUseCase::<MySQLUserRepository>).

It's not the only way. For instance, you can move generics from the method level to the trait level.

// BEFORE
trait IGetUsersUseCase {
  fn new<T: IUserRepository>(repository: T) -> GetUsersUseCase<T>;
  fn execute(&self) -> Vec<User>;
}

// AFTER
trait IGetUsersUseCase<T: IUserRepository> {
  fn new(repository: T) -> GetUsersUseCase<T>;
  fn execute(&self) -> Vec<User>;
}

However, you will see that things become quite complicated at the next trait declaration. (See the playground https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=38f41d8b50464acc28c7dc0c70842e6e).

I would recommend you to review your design of "interfaces" (of traits). Now, you have a kind of "inheritance" Repo -> UseCase -> Controller, however, for example, you can implement a controller as a set of simple functions, and maybe you don't need generic types everywhere.

Alexander Fadeev
  • 2,110
  • 14
  • 16
  • Thanks a lot, best answer ever, it is difficult to quantify your help. I tried to pass the generic at a trait level instead of a method level, but didn't know about `PhantomData` to force usage of a generic param. – miravelardo Nov 18 '21 at 06:59
  • About the dependencies direction: the core of the program is the model `User`. In an outer layer we have `GetUsersUseCase` and `IUserRepository`, which depends on it. In the outer layer, at one edge, we have `MySQLUserRepository` depending on `IUserRepository` and the entity `User`. Also in the outer layer, at the other edge, we have `GetUsersController`, which depends on `IUserRepository` and `IUserUseCase`, both in an inner layer. So the dependencies direction is correct AFAIK. – miravelardo Nov 18 '21 at 07:07
  • Here is a diagram describing the dependencies directions: https://i.stack.imgur.com/1gHka.jpg – miravelardo Nov 18 '21 at 07:29
  • I understand what you're doing, and here is the thing: 1. Controller doesn't have to have a trait. It's absent on you diagram, either you won't find it in the Clean Architecture.2. Controller can be a simple function. 3."Get users controller"? Controller doesn't carry out such narrow function, it should just be "Controller". 4. Problems start with the controller's trait: remove it. 5. I doubt about HTTP -> Controller flow, because the Clean Architecture shows HTTP -> UI/Presenter flow, and controller stays aside. – Alexander Fadeev Nov 18 '21 at 11:18