【发布时间】:2021-12-28 10:24:42
【问题描述】:
我正在尝试在 Rust 中创建一个干净的架构结构,其中一些结构使用特征进行依赖倒置。
我的想法基本上是:
- 只有一个字段的
User模型。 - 符合存储库特征/接口的存储库,其中包含从 MySQL 数据库检索用户的方法。
- 一个依赖于存储库特征/接口的用例,并在使用
new方法实例化时接收此存储库的实例。我还持有一个execute方法来触发存储库操作。 - 依赖于用例特征/接口的控制器,并在使用
new方法对其进行实例化时接收此用例的实例。它还包含一个execute方法来触发用例操作。
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>
我有一个实现,但是泛型有问题。基本代码:
代码:
// 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);
}
如您所见,问题在于 GetUsersUseCase —impl<T: IUserRepository> IGetUsersUseCase for GetUsersUseCase<T>- 的实现。由于实现接收两个泛型参数,在构造GetUsersUseCase::new(mysql_repo) 时收到以下错误:
cannot infer type for type parameter `T` declared on the struct `GetUsersUseCase`rustc(E0282)
解决方法是将user_repoGetUsersUseCase 公开,而不是像往常一样使用GetUsersUseCase::new(mysql_repo) 实例化它:
[…]
struct GetUsersUseCase<T> {
pub user_repo: T,
}
[…]
let get_users_use_case = GetUsersUseCase {
user_repo: mysql_repo,
};
[…]
这可行,但我真的很想知道如何使用公共函数构造结构而不暴露私有字段。
欢迎任何想法!
【问题讨论】:
-
你可以这样帮忙编译,第64行
let get_users_use_case = GetUsersUseCase::<MySQLUserRepository>::new(mysql_repo); -
非常感谢。它可以工作,并且保持依赖关系方向。但是,就良好实践而言……这是否意味着我的结构复杂或不切实际?
-
您的结构显然受到 OOP MVC 模型的启发,而 Rust 有一些方法可以做到这一点。您可以阅读此文档 (doc.rust-lang.org/book/ch17-00-oop.html) 但是,我的观点是,尝试将 OOP 范式与更面向功能的 Rust 一起使用可能会很困难。您可以在 Diesel (diesel.rs) 等板条箱中找到灵感
-
注意:我使用的结构与 MVC 无关,而是与清洁架构、六边形、洋葱或端口/适配器有关。基本上通过依赖倒置将外部依赖与域逻辑解耦。但我同意 Rust 不是纯粹的 OOP,尽管我看到有一些组合方法;唯一没有提供的是继承,我一点也不怀念。
-
一个小评论:在 Rust 中,在语法上不可能使用预期结构的特征,反之亦然。这意味着您不需要做 Java(和 C#)的事情,即在所有特征前加上 I(用于接口)。
标签: struct rust clean-architecture