Style question: Many functions for one struct or member structs?

I'm currently writing a wrapper struct for my database (a BonsaiDB, highly recommend checking it out!), and it's getting large.

Right now, I have split up the implementation of the wrapping struct in multiple impl blocks, each in a different file and roughly sorted by what data they are accessing. Kind-of-fine in terms of code navigability, but an instance of this struct will have an insane amount of functions, full of get_this, get_that, etc. Also, naming these functions becomes hard - should their prefix be the action (get_this, set_this) or the "group" or data type (this_get, this_set)?

One other solution would be to expose sub-members of the wrapper which then house a group of functions related to each other, access would e.g. look like db.this().get(id). Certainly more structure, but calls become longer and there's not always only one group to put a function into. Implementing it twice is madness - so rather take the risk of a dev not finding the function because he looks at the wrong group?

I'm sure this has been solved (in rust) a million times before. How do you do this? Any hints?

Sorry, but it's hard to understand what you're trying to say. Try to be a bit more pragmatic when creating a new topic by adding code snippets and examples for every point that you want to make. This will make it easier for others to help you!

Alright, sorry it wasn't clear enough. So I have implemented a database handle

struct Database {
   conn: DBConnection
}

, which can, say, store books and newspapers. Of course a lot more, but for the example this is fine. Currently, to have some structure in my code, I divided the impls as follows:

// book.rs
impl Database {
  pub fn get_book(...) {...}
  pub fn set_book_name(...) {...}
}
// newspaper.rs
impl Database {
  pub fn get_newspaper(...) {...}
  pub fn set_newspaper_name(...) {...}
}

That is okay for now, but it results in Database objects having a LOT of member functions, and function names become rather long since they need to describe both what data they work with and the action (get_book instead of e.g. Book::get(...))

An other option would be to have structure not only in file names, but in types:

struct Database {...}
struct BookDb {
  inner: Database
}
impl BookDb {
  pub fn get(...) {...}
  pub fn set_name(...) {...}
}

struct NewspaperDb {
  inner: Database
}
impl NewspaperDb {
  pub fn get(...) {...}
  pub fn set_name(...) {...}
}

Once you need a query pertaining to two types, this becomes awkward. Should I put a query books_presented_in_newspaper(newspaperId) in the BookDb struct or the NewspaperDb struct? Hard enough with the first example (deciding on a file to put it into), but in the end it's a member function of Database - whereas here the developer needs to find the correct struct to find the member function.

So, the question is: Is the first example considered to be an idiomatic and readable way to structure the code for such a use case? Is there a common pattern / way of doing this in Rust?

Got it. Let's clarify a few things:

Your models should be as lean as possible; this is what people mean by SRP (Single Responsibility Principle) or do one thing, and do it right.

Which is exactly what you did by removing the logic away from Database and into the other models.

Now, what you did there i.e. with BookDb is what is commonly referred to as a DAO (Database Access Object), and are usually named after the database resource they access. For example, if you have a table named users where you store records of the User resource, the DAO would be named UsersDao. It's a better practice for DAOs to access only one table, for the same reasons aforementioned.

What to do if a query spans across multiple tables?

Well, it depends on in which direction did you perform the join (using SQL terms); i.e. if you are joining some data to the User resource to form the compound type UserWithRoles where you do a join with the users and user_roles tables, then the method that performs such query should naturally live within the UsersDao.

Btw, Database should be a singleton. The way you are passing it by value will make your code inefficient and could potentially fail in production since you'll not be reusing connections to the database efficiently.

Got it, thanks! I wanted to reduce coupling between other parts of the code and the Database mod by only exposing one interface.

It also made using the database a lot easier, since e.g. in an API module which accesses the database, I can just store one instance of Database and can access everything through this one object. Maybe it would be a solution to split it up as shown in example 2, but have all instances of these more specific structs be members of Database. Usage would e.g. be self.db.books().get(name)..

As for the tables, that's not so easy when working with a NoSQL DB. I'm new to them, thus not familiar with what code looks like which uses them, but dividing by table name is hardly possible :confused:

It's the same. Just replace tables with collections.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.