A web service typically consists of multiple layers: A controller layer for the request handling, a service layer for the logic and a database layer for the database access.
When building a web service with Rust, I see two options for organizing the project structure:
Approach 1: Organizing per domain
|-> src
|-> users/
|-> mod.rs
|-> models.rs
|-> controller.rs
|-> service.rs
|-> db.rs
|-> teams/
|-> mod.rs
|-> models.rs
|-> controller.rs
|-> service.rs
|-> db.rs
Approach 2: Organizing per application layer
|-> src
|-> controller/
|-> mod.rs
|-> user.rs
|-> team.rs
|-> service/
|-> mod.rs
|-> user.rs
|-> team.rs
|-> db/
|-> mod.rs
|-> user.rs
|-> team.rs
|-> models/
|-> mod.rs
|-> user.rs
|-> team.rs
In both cases, the mod.rs
would publicly re-export the child modules so that the module structure is flattened from the user's perspective.
The advantage with approach 1 is that all code that is logically related resides in the same module. But what if I have an API endpoint or service function called get_user_with_teams
that fetches both users with their teams from the database? This function would logically belong to both the users
and the teams
module.
With approach 2, this problem doesn't arise because the get_user_with_teams
function would be re-exported in the respective modules and simply be available via crate::service::get_user_with_teams
for example. Also, different layers often work on different models - so a User would have a separate struct for the DTO, database fetching, and application logic. I feel that separating by layer makes it clearer what logic belongs to which version of a struct. However, the downside is that related logic for the same domain is now split over multiple unrelated modules.
What is the recommended approach, and how do I deal with cross-domain functions if I choose approach 1 (domain driven structure)?