0

This is just an example of an (still incomplete) real-world project written in Rust using a clean architecture: https://github.com/frederikhors/rust-clean-architecture-with-db-transactions.

Goals

My intent is to have an app build in 4 layers:

  • entities:

    • some call this layer "domain", not important for now, just the minimum
  • services:

    • some call this layer "use cases", this is where business logic lives (just CRUD methods for now)
  • repositories:

    • some call this layer "adapters", this is where concrete implementation of DB/cache/mail drivers lives
  • ports:

    • some call this layer "controllers or presenters", still not present and not important for now, I'm using main.rs for this

Reproduction

https://codesandbox.io/p/github/frederikhors/rust-clean-architecture-with-db-transactions/main

The issue

If you open the main.rs file you can see the issue:

// This obviously works if alone:
// let db_repo = Arc::new(repositories::in_memory::Repo::new());

// This obviously works if alone:
// let pg_pool = Arc::new(sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres").await.unwrap());
// let db_repo = Arc::new(repositories::postgres::Repo::new(pg_pool));

// This doesn't work instead:
let db_repo = if use_postgres {
    let pg_pool = Arc::new(sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres").await.unwrap());

    Arc::new(repositories::postgres::Repo::new(pg_pool))
} else {
    Arc::new(repositories::in_memory::Repo::new())
};

My intent here is to change repository in use based on a variable, but Rust doesn't like it, this is the error:

Expand the error
error[E0308]: `if` and `else` have incompatible types
--> src\main.rs:37:9
|
28 |       let db_repo = if use_postgres {
|  ___________________-
29 | |         let pg_pool = Arc::new(
30 | |             sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres")
31 | |                 .await
...  |
35 | |         Arc::new(repositories::postgres::Repo::new(pg_pool))
| |         ---------------------------------------------------- expected because of this
36 | |     } else {
37 | |         Arc::new(repositories::in_memory::Repo::new())
| |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `repositories::postgres::Repo`, found struct `in_memory::Repo`
38 | |     };
| |_____- `if` and `else` have incompatible types
|
= note: struct `in_memory::Repo` and struct `repositories::postgres::Repo` have similar names, but are actually distinct types
note: struct `in_memory::Repo` is defined in module `crate::repositories::in_memory` of the current crate
--> src\repositories\in_memory\mod.rs:6:1
|
6  | pub struct Repo {
| ^^^^^^^^^^^^^^^
note: struct `repositories::postgres::Repo` is defined in module `crate::repositories::postgres` of the current crate
--> src\repositories\postgres\mod.rs:6:1
|
6  | pub struct Repo {
| ^^^^^^^^^^^^^^^

How can I fix this?

1 Answer 1

2

Like always with different types you have essentialy 2 options, static or dynamic dispatch.

For dynamic dispatch both Repo types have to implement a common trait that encompasses all necessary functionality and is object safe:

pub trait Repo {
    fn do_stuff_with_repo(&self);
    // etc
}

impl Repo for postgres::Repo {
    fn do_stuff_with_repo(&self) {
        //...
    }
}

impl Repo for in_memory::Repo {
    fn do_stuff_with_repo(&self) {
        //...
    }
}

let db_repo: Arc<dyn Repo> = if use_postgres {
    let pg_pool = Arc::new(sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres").await.unwrap());

    Arc::new(repositories::postgres::Repo::new(pg_pool))
} else {
    Arc::new(repositories::in_memory::Repo::new())
};

or for static dispatch create an enum and everytime you have to deal with it you pattern match:

enum Repo {
    Memory(in_memory::Repo),
    Postgres(postgres::Repo),
}
let db_repo = if use_postgres {
    let pg_pool = Arc::new(sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres").await.unwrap());

    Arc::new(Repo::Postgres(repositories::postgres::Repo::new(pg_pool)))
} else {
    Arc::new(Repo::Memory(repositories::in_memory::Repo::new()))
};
Sign up to request clarification or add additional context in comments.

11 Comments

Thanks. The issue with this project using dynamic dispatch is that I'm using pub commands_repo: Arc<C> which comes from pub fn new_executor<C: RepoTrait + Send + Sync + 'static>(deps: Arc<Deps<C>>) -> Box<dyn Executor> {...}. I cannot use as you suggest let db_repo: Arc<dyn Repo>. Or at least I don't know how to do. I'm fixing the codesandbox reproduction so you can execute the code there easily.
This is the branch with static dispatch, also in error with your suggestions: codesandbox.io/p/github/frederikhors/…. I do not understand the error.
Even if I change pub queries_repo: Arc<dyn queries::RepoTrait> to pub queries_repo: Arc<C> I still get many errors like: the trait bound Repo: services::commands::RepoPlayer is not satisfied. Why @cafce25?
Probably because the trait bound RepoPlayer is not satisfied. Or in other words you did not implement RepoPlayer for whatever type you get that error on or did not specify that bound on the trait object.
This is all implemented. In fact if you use them alone they work!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.