Structuring a Rust Backend: Feature-Based Architecture That Scales

Hello everyone,

When I started with Rust and Web developement, I had quite a few questions about the frameworks, such as:

  • How do I organize my code (handlers, models, routes, ... )?
  • Which architecture to follow?
  • How to manage the integration of an ORM?

Because unlike some Backend frameworks such as Spring boot, Django, Rails, ... Rust web frameworks (Actix Web, Axum, Rocket,... ) does not offer a default structure, we have the freedom to structure it as we want.

So I wrote this article to propose a feature-based architecture because there aren't many of them in the Rust community.

Happy reading.

Nice read, your structure looks nice.

What is your opinion on using hexagonal architecture? It seems that in your current setup there is no way to e.g switch up the type of database.

In one of my Axum-projects I made interfaces (traits) for the repositories. I can have different implementations for those repos (Diesel, sqlite, even text files if somehow necessary). For each 'feature' I made use-cases.

E.g struct UserUseCases contains structs RegisterUser and LoginUser. RegisterUser takes the user repo + additional required services. In my main.rs I construct a AppState with all the use-cases which I can call from all my handlers.

This way the actual implementation the handlers use is fully abstracted from a specific choice like Diesel.

What is your opinion on that?

PS: Typo in article "notificqtions".

Hi ONeil!

Thanks a lot for the feedback, I really appreciate it.

Regarding hexagonal architecture: I have a very positive opinion of it. Conceptually, it’s a strong approach that enforces clear boundaries and keeps business logic independent from infrastructure concerns like the database or ORM. What you describe with repository traits, multiple implementations, and use-cases is very much aligned with that philosophy, and it’s something I find very clean and elegant.

In this article, the choice was intentional to keep things simpler. The goal was to provide a first solid mental model for structuring an Actix Web project, especially for people who are new to Rust backend or coming from frameworks that impose conventions by default. Introducing hexagonal architecture from the start can be overwhelming, so I focused on a pragmatic, feature-based structure as a foundation.

That said, I really like your proposal. Abstracting the database behind traits and organizing logic around use-cases is a natural and very appealing next step. This article is meant as a first iteration, not a final destination, and your feedback fits perfectly with how I plan to evolve it.

I’m planning to implement this kind of abstraction in a follow-up, show how to transition toward a more hexagonal approach, and share the results. Thanks again for the thoughtful comment — it’s exactly the kind of discussion I was hoping this article would spark.

Hi,

I'm trying to go on that way (repository trait, ability to change underlaying database) but I've reached on caveat. Everything works very well until I do need to support transactions that require functions from multiple repositories (let's say create a user with its identity provider, email verification token and credentials) .

Email verification token can be generated from multiple calling sites, some requiring to include that in a transaction, some only needing a single write operation. While I could force the use of transactions, I would like to avoid it (like starting one transaction for one operation).
I tried the UnitOfWork approach but I'm ending having some functions requiring repositories and some requiring the unit of work instance (not consistent).

Do you have any advice on how to deal with that ?

You have touched a topic of my high interest, so I read your work with big interest. Indeed, when you start using Rust, you question yourself - how can I create web applications in the language? There is no such technology as servlets, or PHP scripting. You need to rebuild a web server with every app change. After some thinking, I selected the technology I never used before. Surprisingly, it works with Rust very well. I do not need to rebuild a web server anymore, and every new request can use already updated code, so CD/CI works in snap. Probably such approach won't work well in a large scale deployment with millions request per a second, but for medium size systems with thousand requests in a second it works fairly well. Using websocket gives even more advantages, because doesn't require to reload Rust executable for every request. I even did several implementations with zero HTTP requests relaying completely on the websocket. So, I am very pleasant with Rust for developing web applications.

I encountered a similar problem also.

To implement transactions, while decoupling the backend from the application, I introduced a trait for each repository, and the backend implements each repository trait and also allows using a transaction.

But the transaction works with a closure, inside of which you can call only the methods of the corresponding repository trait, because of type erasure.

This works well if you have only one repository trait. But as the application scales, you usually need to split into several repository traits, because having everything in one trait is too cumbersome (and also if there are several repositories).

I am curious if there are general solutions to the problem of data consistency across multiple repositories (for example if you open two transactions in two repositories, one fails and you need to revert the other one so that the whole stays in consistent state), other than custom cleanup jobs that run periodically.

I use this architecture too since I know it from .Net discussion group. It is named Vertical Slice architecture. Previously I tried to learn Clean Architecture, it turned out not easy for me until I found this Vertical Slice :v

Sadly, architecture like this is not popular in Rust yet I think, because I got task some months ago, it was AI chat backend using Actix Web. The project is already big but it doesn't has well structured, consistent architecture, it was hard to navigate through the codebase, and often confusing ;(

Mine is like this

main.rs

user/mod.rs
user/model.rs
user/interface.rs
user/repo.rs
user/validation.rs       // if needed, mostly the validation is already in the model with macro
user/controller.rs

db/db files

shared/shared code