- Add a comprehensive .gitignore to manage project files. - Create the basic project structure including Cargo.toml, LICENSE, and README.md. - Add basic project documentation.
8.8 KiB
MVC Architecture in Hostbasket
This document explains the Model-View-Controller (MVC) architecture used in the Hostbasket application.
Table of Contents
- Introduction to MVC
- MVC Components in Hostbasket
- Data Flow in MVC
- Benefits of MVC
- Best Practices
- Examples
Introduction to MVC
Model-View-Controller (MVC) is a software architectural pattern that separates an application into three main logical components:
- Model: Represents the data and business logic of the application
- View: Represents the user interface
- Controller: Acts as an intermediary between Model and View
This separation helps in organizing code, making it more maintainable, testable, and scalable.
MVC Components in Hostbasket
Model
In Hostbasket, models are defined in the src/models
directory. They represent the data structures and business logic of the application.
// src/models/user.rs
pub struct User {
pub id: Option<i32>,
pub name: String,
pub email: String,
pub password_hash: Option<String>,
pub role: UserRole,
pub created_at: Option<DateTime<Utc>>,
pub updated_at: Option<DateTime<Utc>>,
}
Models are responsible for:
- Defining data structures
- Implementing business logic
- Validating data
- Interacting with the database (in a full implementation)
View
Views in Hostbasket are implemented using Tera templates, located in the src/views
directory. They are responsible for rendering the user interface.
<!-- src/views/home/index.html -->
{% extends "base.html" %}
{% block title %}Home - Hostbasket{% endblock %}
{% block content %}
<div class="container">
<h1>Welcome to Hostbasket!</h1>
<p>A web application framework built with Actix Web and Rust.</p>
</div>
{% endblock %}
Views are responsible for:
- Presenting data to the user
- Capturing user input
- Providing a user interface
- Implementing client-side logic (with JavaScript)
Controller
Controllers in Hostbasket are defined in the src/controllers
directory. They handle HTTP requests, process user input, interact with models, and render views.
// src/controllers/home.rs
pub struct HomeController;
impl HomeController {
pub async fn index(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let mut ctx = tera::Context::new();
ctx.insert("active_page", "home");
// Add user to context if available
if let Ok(Some(user)) = session.get::<String>("user") {
ctx.insert("user_json", &user);
}
render_template(&tmpl, "home/index.html", &ctx)
}
}
Controllers are responsible for:
- Handling HTTP requests
- Processing user input
- Interacting with models
- Preparing data for views
- Rendering views
Data Flow in MVC
The typical data flow in an MVC application like Hostbasket is as follows:
- The user interacts with the view (e.g., submits a form)
- The controller receives the request
- The controller processes the request, often interacting with models
- The controller prepares data for the view
- The controller renders the view with the prepared data
- The view is displayed to the user
This flow ensures a clear separation of concerns and makes the application easier to maintain and extend.
Benefits of MVC
Using the MVC architecture in Hostbasket provides several benefits:
- Separation of Concerns: Each component has a specific responsibility, making the code more organized and maintainable.
- Code Reusability: Models and controllers can be reused across different views.
- Parallel Development: Different team members can work on models, views, and controllers simultaneously.
- Testability: Components can be tested in isolation, making unit testing easier.
- Flexibility: Changes to one component have minimal impact on others.
- Scalability: The application can grow without becoming unwieldy.
Best Practices
When working with the MVC architecture in Hostbasket, follow these best practices:
Models
- Keep models focused on data and business logic
- Implement validation in models
- Use traits to define common behavior
- Keep database interactions separate from business logic
Views
- Keep views simple and focused on presentation
- Use template inheritance to avoid duplication
- Minimize logic in templates
- Use partials for reusable components
Controllers
- Keep controllers thin
- Focus on request handling and coordination
- Delegate business logic to models
- Use dependency injection for services
Examples
Complete MVC Example
Here's a complete example of how the MVC pattern works in Hostbasket for a user registration flow:
Model (src/models/user.rs)
impl User {
pub fn new_with_password(name: String, email: String, password: &str) -> Result<Self, bcrypt::BcryptError> {
let password_hash = hash(password, DEFAULT_COST)?;
Ok(Self {
id: None,
name,
email,
password_hash: Some(password_hash),
role: UserRole::User,
created_at: Some(Utc::now()),
updated_at: Some(Utc::now()),
})
}
pub fn verify_password(&self, password: &str) -> Result<bool, bcrypt::BcryptError> {
match &self.password_hash {
Some(hash) => verify(password, hash),
None => Ok(false),
}
}
}
View (src/views/auth/register.html)
{% extends "base.html" %}
{% block title %}Register - Hostbasket{% endblock %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card mt-5">
<div class="card-header">
<h2>Register</h2>
</div>
<div class="card-body">
<form method="post" action="/register">
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="mb-3">
<label for="password_confirmation" class="form-label">Confirm Password</label>
<input type="password" class="form-control" id="password_confirmation" name="password_confirmation" required>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
Controller (src/controllers/auth.rs)
pub async fn register(
form: web::Form<RegistrationData>,
session: Session,
_tmpl: web::Data<Tera>
) -> Result<impl Responder> {
// Create a new user with the form data
let user = match User::new_with_password(
form.name.clone(),
form.email.clone(),
&form.password
) {
Ok(user) => user,
Err(_) => return Err(actix_web::error::ErrorInternalServerError("Failed to create user")),
};
// In a real application, you would save the user to a database here
// Generate JWT token
let token = Self::generate_token(&user.email, &user.role)
.map_err(|_| actix_web::error::ErrorInternalServerError("Failed to generate token"))?;
// Store user data in session
let user_json = serde_json::to_string(&user).unwrap();
session.insert("user", &user_json)?;
session.insert("auth_token", &token)?;
// Create a cookie with the JWT token
let cookie = Cookie::build("auth_token", token)
.path("/")
.http_only(true)
.secure(false) // Set to true in production with HTTPS
.max_age(actix_web::cookie::time::Duration::hours(24))
.finish();
// Redirect to the home page with JWT token in cookie
Ok(HttpResponse::Found()
.cookie(cookie)
.append_header((header::LOCATION, "/"))
.finish())
}
This example demonstrates how the MVC pattern separates concerns while allowing the components to work together to handle user registration.