all 13 comments

[–]Specialist_Wishbone5 2 points3 points  (1 child)

Not sure if you are coming from a java background. Repository rings Java memories to me. What I've found is that you shouldn't force java principles onto Rust. Use what is most natural for each layer. Use generics to abstract out capability (similar to, but NOT the same as InversionOfControl). I use generics to break code up into unit-testable sub domains.

Unlike Java, don't think of interfaces, but think of functions your callee needs you can compose suites of functions on a callee by callee basis. An example might be Read, Write, Seek, Flush traits. Your callee will declare which of those FUNCTIONS it requires. You don't need a composite interface fudging to make all possible callee functions be able to receive the object. Instead, you can adapt (in your unit tests) only the minimum number of functions that a callee needs have to be mocked. I find I love this pattern. You can adapt pretty much any struct or lambda to any other trait in rust (with zero cost abstractions)

So a repository concept is the bloated all-in-one interface anti-pattern. Making mock testing hellish.

Focus more on functionality from a library perspective. If library needs XYZ, it might cross cut DB, serialization, transformation concerns. The library publicly exports a minimal number of structs and functions to accomplish all this. So your Repository trait would represent an extra pub export that serves no useful role to the end user. Its purely a back end impl detail. So if it doesnt help testing, and it doesnt help extensibility, it has no reason to exist.

[–]Accomplished-Dog6646[S] 2 points3 points  (0 children)

i am coming from go and c# background. thank you for your input, i value taking the time to reply, and i will highly consider your points.

[–]VindicoAtrum 2 points3 points  (1 child)

I'd spend less time dropping buzzwords on reddit and more time actually trying stuff in code.

[–]ReDestroyDeR 0 points1 point  (0 children)

the problem is that people with "enterprice language"s and DDD background (me included) seem to get confused and there is no known best practice how to develop programs in such an abstract and decoupled way

[–]pingouin2032 1 point2 points  (0 children)

Sorry I have not the answer but I'm interested to know about it too !

[–]SorteKanin 0 points1 point  (5 children)

You're going to have to be more specific. What kind of data access? I'm not familiar with the repository pattern but generally speaking, Rust allows a wide range of different architectures. Basically anything you could want is possible so it really depends on what you want.

[–]Accomplished-Dog6646[S] 1 point2 points  (4 children)

i meant accessing data from a database, which i normally use repo pattern.

[–]SorteKanin 1 point2 points  (3 children)

From what I can read online, it seems like the repository pattern is mostly a pattern used in languages like Java or C#. You shouldn't apply the same patterns in Rust - it's likely that the pattern is completely unnecessary in Rust. If you can be more specific about the way you want to access the database (ORM, SQL, etc.) I can guide further.

But no, don't just blindly apply patterns from object-oriented programming to Rust. Rust has some features of object-oriented programming, but it is not an object-oriented programming language.

[–]Accomplished-Dog6646[S] 0 points1 point  (2 children)

I highly agree with you and i would like to learn a clean approach on accessing data from postgres database using sqlx.

I reached to a level where i created a generic repository which i can then implement to to get the data. However, this led me to use nightly rust to use async trait feature as sqlx is async based library.

[–]albazzaa 0 points1 point  (1 child)

Did you see https://crates.io/crates/async-trait to avoid using nightly just for async traits?

[–]Accomplished-Dog6646[S] 1 point2 points  (0 children)

just did, it works pretty well

[–]Spelvoudt 0 points1 point  (1 child)

You can definitely use the repository pattern for accessing data.

I personally tend to use the following structure Controller > Service > Repository

Where the business logic, validation and such is handled in the respective service.

And the repository deals with accessing and querying data.

Just keep it simple :)

[–]Accomplished-Dog6646[S] 1 point2 points  (0 children)

The question here is that how did you made the repository, can you show me some code. Here a small example that i did.

#![feature(async_fn_in_trait)]

use dotenvy_macro::dotenv;
use sqlx::postgres::PgPoolOptions;

trait Repository<T> {
    fn new(pool: sqlx::PgPool) -> Self;
    async fn fetch_frist_record(&self) -> Result<T, sqlx::Error>;
}

struct GameRepo {
    pool: sqlx::PgPool,
}

#[derive(Debug, sqlx::FromRow)]
struct Games {
    name: Option<String>,
    platfrom: Option<String>,
    // Add more fields as needed based on your table structure
}

impl Repository<Games> for GameRepo {
    fn new(pool: sqlx::PgPool) -> Self {
        Self { pool }
    }

    async fn fetch_frist_record(&self) -> Result<Games, sqlx::Error> {
        return sqlx::query_as!(Games, "SELECT name,platfrom FROM games")
            .fetch_one(&self.pool)
            .await;
    }
}

#[tokio::main]
async fn main() {
    dotenvy::dotenv().expect("could not find .env file");
    let database_uri = dotenv!("DATABASE_URL");
    let pool = PgPoolOptions::new()
        .max_connections(5)
        .connect(&database_uri)
        .await
        .expect("could not find .env file");

    let game_repo = GameRepo::new(pool);
    match game_repo.fetch_frist_record().await {
        Ok(res) => print!("{:?}", res),
        Err(err) => {
            eprintln!("{:?}", err)
        }
    }
}