Design Principles
This document outlines the core architectural principles and design patterns that guide the development of the Navius framework. These principles ensure the framework remains maintainable, extensible, and performant as it evolves.
Core Principles
1. Modular Design
Navius is built around a modular architecture that separates concerns and enables components to evolve independently.
Key aspects:
- Self-contained modules with clearly defined responsibilities
- Minimal dependencies between modules
- Ability to replace or upgrade individual components without affecting others
- Configuration-driven composition of modules
2. Explicit Over Implicit
Navius favors explicit, clear code over "magic" behavior or hidden conventions.
Key aspects:
- Explicit type declarations and function signatures
- Clear error handling paths
- Minimal use of macros except for well-defined, documented purposes
- No "convention over configuration" that hides important behavior
3. Compile-Time Safety
Navius leverages Rust's type system to catch errors at compile time rather than runtime.
Key aspects:
- Strong typing for all API interfaces
- Use of enums for representing states and variants
- Avoiding dynamic typing except when necessary for interoperability
- Proper error type design for comprehensive error handling
4. Performance First
Performance is a primary design goal, not an afterthought.
Key aspects:
- Minimal runtime overhead
- Efficient memory usage
- Asynchronous by default
- Careful consideration of allocations and copying
- Benchmarking as part of the development process
5. Developer Experience
The framework prioritizes developer experience and productivity.
Key aspects:
- Intuitive API design
- Comprehensive documentation
- Helpful error messages
- Testing utilities and patterns
- Minimal boilerplate code
Architectural Patterns
Clean Architecture
Navius follows a modified Clean Architecture pattern with distinct layers:
┌─────────────────┐
│ Controllers │
│ (HTTP Layer) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Services │
│ (Business Logic)│
└────────┬────────┘
│
▼
┌─────────────────┐
│ Repositories │
│ (Data Access) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Data Store │
│ (DB, Cache, etc)│
└─────────────────┘
Principles applied:
- Dependencies point inward
- Inner layers know nothing about outer layers
- Domain models are independent of persistence models
- Business logic is isolated from I/O concerns
Dependency Injection
Navius uses a trait-based dependency injection pattern to enable testability and flexibility:
#![allow(unused)] fn main() { // Define a service that depends on a repository trait pub struct UserService<R: UserRepository> { repository: R, } impl<R: UserRepository> UserService<R> { pub fn new(repository: R) -> Self { Self { repository } } pub async fn get_user(&self, id: Uuid) -> Result<User, Error> { self.repository.find_by_id(id).await } } // In production code let db_repository = PostgresUserRepository::new(db_pool); let service = UserService::new(db_repository); // In test code let mock_repository = MockUserRepository::new(); let service = UserService::new(mock_repository); }
Error Handling
Navius uses a centralized error handling approach:
#![allow(unused)] fn main() { // Core error type pub enum AppError { NotFound, Unauthorized, BadRequest(String), Validation(Vec<ValidationError>), Internal(anyhow::Error), } // Converting from domain errors impl From<DatabaseError> for AppError { fn from(error: DatabaseError) -> Self { match error { DatabaseError::NotFound => AppError::NotFound, DatabaseError::ConnectionFailed(e) => AppError::Internal(e.into()), // Other conversions... } } } // Converting to HTTP responses impl IntoResponse for AppError { fn into_response(self) -> Response { let (status, error_message) = match self { AppError::NotFound => (StatusCode::NOT_FOUND, "Resource not found"), AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"), // Other mappings... }; // Create response (status, Json(ErrorResponse { message: error_message.to_string() })).into_response() } } }
Middleware Pipeline
Navius uses a middleware-based pipeline for processing HTTP requests:
#![allow(unused)] fn main() { let app = Router::new() .route("/api/users", get(list_users).post(create_user)) .layer(TracingLayer::new_for_http()) .layer(CorsLayer::permissive()) .layer(AuthenticationLayer::new()) .layer(CompressionLayer::new()) .layer(TimeoutLayer::new(Duration::from_secs(30))); }
API Design Principles
Resource-Oriented
APIs are structured around resources and their representations.
Consistent Error Handling
A standardized error response format is used across all API endpoints.
Proper HTTP Method Usage
HTTP methods match their semantic meaning (GET, POST, PUT, DELETE, etc.).
Versioning Support
APIs support versioning to maintain backward compatibility.
Database Access Principles
Repository Pattern
Data access is encapsulated behind repository interfaces.
Transaction Management
Explicit transaction boundaries with proper error handling.
Migration-Based Schema Evolution
Database schemas evolve through explicit migrations.