RUST WEB APP
PRODUCTION BLUEPRINT
The Rust Web Application blueprint provides a straightforward yet scalable foundation for building robust Web applications or Web services using the Rust language.
YouTube Videos
- Episode 01: Rust Axum Production Coding - Rust Web Development
- Episode 02: Rust sea-query for Production coding - SeaQuery + ModQL
- Episode 03: Rust Workspace for Production Coding By Example
- Episode 04: Multi-Scheme Password Hashing with Argon 2
- Episode 05.1: Rust RPC Router - Axume/Tower/Bevy Style
DISCORD: #Rust10x-Blueprint
GitHub Info
- GitHub URL: https://github.com/rust10x/rust-web-app
git clone https://github.com/rust10x/rust-web-app.git
- Tags:
E01-C09, E01-END
E01-C08
E01-C07
E01-C06
E01-C05
E01-C04
E01-C03
E01-C02
E01-C01, E01-START
- Clone at start at
E01-START
git clone https://github.com/rust10x/rust-web-app.git
cd rust-web-app
git checkout -b my-start E01-START
Design goals
-
Secure: A primary design objective of the Rust10x (aka RX) blueprint family is security by design. Many of our best practices are derived from experience in crafting cloud applications for highly compliant environments (e.g., medical cloud applications).
-
Scalable: The second priority of RX is managing code complexity. This means the architecture is thoughtfully designed so that code complexity does not skyrocket but remains linear and relatively flat, even as overall system requirements grows significantly.
-
Modular: A central tenet of the RX design is to prioritize smaller components over monolithic, all-in-one frameworks or solutions. This strategy enhances modularity, rendering the overall system design more future-proof as various components can be replaced as required.
-
Shapeable: While the blueprint integrates each component cohesively, its overarching design allows individual components to be interchanged to better align with the developer/team's preferences and best practices.
Architecture
The Rust10x backend architecture consists of the following five main layers and is designed to align with what one would expect from an on-device backend service as well.
The five main layers include:
Web
- The Web layer serves as the IPC (Inter-Process Communication) part of the web application backend.
- Within a web application, this layer handles the HTTP request flow.
- We've chosen Axum because of its modern design, robust features, and consistent maintenance. As a Rust Web Framework, Axum provides all the necessary features expected from a polished Rust framework and also showcases a modular architecture, exemplified by the Tower model.
- It's worth noting that only the Web layer interacts deeply with the Web domain, making it the exclusive section using the Axum constructs such as Routing, Extractors, and others.
- This distinction is central to the Rust10x design. It enables other layers to be repurposed for varied backend contexts, such as Desktop or Mobile application
backend
using tools like Tauri where HTTP Request becomes Tauri commands.
Context
- The primary role of the Context layer is to carry essential request/command details throughout various stages of a request/command lifecycle.
- Even though it's often initiated at the IPC layer (the Web layer in a web app backend scenario), it's intentionally kept ignorant of IPC layer details.
- In a Web Application scenario, this Context encapsulates elements like authentication, as seen in the early iterations of rust-web-app. But its scope is broader, soon encompassing elements like authorization caching and other request/comand specific data that must be preserved throughout the request/command flow.
- Within the Rust10x codebase, this is housed under the
ctx::
module, with the primary struct namedCtx
. This naming strategy was adopted to prevent overlap since many libraries gravitate towards the term "Context".
Event
- The Event layer stands as the foundational infrastructure for managing service events, encompassing IPC, external queue services, and data-driven events.
- This layer leverages Rust's MPSC (Multiple Producers, Single Consumer) along with other Producer/Consumer models to transition the service towards an event-based architecture.
- In the initial versions of the
rust-web-app
blueprint, this layer won't be present. However, as the application progresses towards an event-centric design, this layer will be introduced. - This becomes especially vital for a
rust-cloud-app
(coming later). As the system architecture transitions from a single-service (web-app) setup to a multi-service layout orchestrated by Kubernetes, the value of a event based architecture will become significant. Instead of services directly communicating, they'll rely on subscribing/publishing to event buses to coordinate their workflows.
Model
- The Model layer is arguably the largest layer in the rust-web-app AA, as it normalizes all the application's data type structures and access.
- All application code data accesses must go through the Model layer to access any type of data.
- In the Rust10x design, for maximum security and portability, the Authorization logic is managed at the model layer and not at the Web layer.
- While the Web layer is responsible for authenticating the request and creating the Ctx, the Model layer handles all data and access logic post-routing.
- The Model layer is the only layer that have access to the Store layer.
- See lib-core > src/model/mod.rs module comments for more design details.
Store
- The Store layer exposes generic lower level API to the specific store for the Model layer.
- Notably, this layer's objective isn't to craft a generic store API across multiple backend store.
- Instead, it's designed to offer application-specific implementations for particular stores, simplifying and normalizing the Model layer implementation when necessary.
- This layer can be minimalistic if the store library/API already aligns closely with the application's requirements.
- For instance, in the context of
PostgreSQL
, this layer is currently utilized primarily to establish thesqlx
database connection, given that thesqlx
/sqlb
libraries are succinct and expressive enough for direct use within the Model Layer.
FAQ
Why distinguish between the ModelManager and ModelController?
The short answer is because it makes each component much simpler and more focused on executing its core function.
-
For example, now the ModelControllers do not need any states, and therefore, their design becomes simpler as they are merely a set of functions with some common contracts.
-
We do not have to worry about ModelControllers' instantiation, ownership, lifetime, or anything of this nature in our design.
-
So, we know that everything that would be state-related will be passed as an argument in a consistent manner.
-
And in fact, that goes for the Ctx as well,
-
And that simplicity allowed us to design the base shared CRUD implementation just as a set of static functions.
-
Conversely, the ModelManager now can just focus on holding and managing the appropriate resources that might be needed by the various
-
ModelControllers, irrespective of the specific operations.
-
Right now, it is a very simple implementation, but it will become richer when we add more client types and database transaction support.
As the application's needs grow, these patterns can evolve, and further separation of concerns may be needed. However, this is a good base to start with, balancing simplicity and future code scalability.