Layered Architecture
Implementing layered architecture pattern in Rust applications.
Layered Architecture
Layered Architecture separates concerns into distinct layers, each with specific responsibilities.
Architecture Layers
1. Presentation Layer (API/Controllers)
Responsibility: Handle HTTP requests, validate input, format responses
RUST// src/api/mod.rs pub mod routes; pub mod handlers; pub mod middleware; // src/api/handlers.rs use crate::services::UserService; use crate::models::User; pub struct UserHandler { user_service: UserService, } impl UserHandler { pub async fn create_user(&self, data: CreateUserRequest) -> Result<User, Error> { // Validate input // Call service layer // Format response self.user_service.create_user(data).await } }
2. Service Layer (Business Logic)
Responsibility: Implement business rules, orchestrate operations
RUST// src/services/mod.rs pub mod user_service; pub mod transaction_service; // src/services/user_service.rs use crate::repositories::UserRepository; use crate::models::User; pub struct UserService { user_repo: UserRepository, } impl UserService { pub async fn create_user(&self, data: CreateUserRequest) -> Result<User, Error> { // Business logic // Validation rules // Call repository self.user_repo.create(data).await } pub async fn transfer_funds( &self, from: UserId, to: UserId, amount: u64 ) -> Result<(), Error> { // Complex business logic // Multiple repository calls // Transaction management } }
3. Repository Layer (Data Access)
Responsibility: Abstract database operations, data persistence
RUST// src/repositories/mod.rs pub mod user_repository; pub mod transaction_repository; // src/repositories/user_repository.rs use crate::models::User; use crate::database::Connection; pub struct UserRepository { db: Connection, } impl UserRepository { pub async fn create(&self, data: CreateUserData) -> Result<User, Error> { // Database operations // SQL queries // Data mapping } pub async fn find_by_id(&self, id: UserId) -> Result<Option<User>, Error> { // Query database } }
4. Domain/Model Layer
Responsibility: Define data structures, domain entities
RUST// src/models/mod.rs pub mod user; pub mod transaction; // src/models/user.rs use serde::{Serialize, Deserialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct User { pub id: UserId, pub email: String, pub balance: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateUserRequest { pub email: String, pub password: String, }
Complete Example Structure
src/
āāā main.rs
āāā lib.rs
āāā api/ # Presentation Layer
ā āāā mod.rs
ā āāā routes.rs # Route definitions
ā āāā handlers.rs # Request handlers
ā āāā middleware.rs # Auth, logging, etc.
āāā services/ # Service Layer
ā āāā mod.rs
ā āāā user_service.rs
ā āāā transaction_service.rs
āāā repositories/ # Repository Layer
ā āāā mod.rs
ā āāā user_repository.rs
ā āāā transaction_repository.rs
āāā models/ # Domain Layer
ā āāā mod.rs
ā āāā user.rs
ā āāā transaction.rs
āāā database/ # Infrastructure
ā āāā mod.rs
ā āāā connection.rs
āāā config/ # Configuration
āāā mod.rs
Dependency Flow
API Layer
ā (depends on)
Service Layer
ā (depends on)
Repository Layer
ā (depends on)
Database/Infrastructure
Key Principle: Dependencies flow downward. Upper layers depend on lower layers, but not vice versa.
Benefits
- Separation of concerns: Each layer has clear responsibility
- Testability: Easy to mock dependencies
- Maintainability: Changes isolated to specific layers
- Reusability: Services can be used by different APIs
- Scalability: Layers can scale independently
Code Examples
Layered Architecture Example
Complete layered architecture structure
struct ApiHandler {
service: UserService,
}
impl ApiHandler {
fn handle_request(&self) {
self.service.process();
}
}
struct UserService {
repo: UserRepository,
}
impl UserService {
fn process(&self) {
self.repo.save();
}
}
struct UserRepository {
db: Database,
}
impl UserRepository {
fn save(&self) {
}
}
struct User {
id: u64,
name: String,
}
fn main() {
println!("Layered Architecture:");
println!("1. API Layer: HTTP handling");
println!("2. Service Layer: Business logic");
println!("3. Repository Layer: Data access");
println!("4. Domain Layer: Models");
}Explanation:
Layered architecture separates concerns into distinct layers. Each layer has a specific responsibility and depends only on layers below it.
Service Layer Pattern
Service layer with business logic
struct TransferService {
user_repo: UserRepository,
tx_repo: TransactionRepository,
}
impl TransferService {
fn transfer_funds(
&self,
from: u64,
to: u64,
amount: u64
) -> Result<(), String> {
if amount == 0 {
return Err(String::from("Amount must be positive"));
}
Ok(())
}
}
fn main() {
println!("Service layer contains:");
println!("- Business rules");
println!("- Validation logic");
println!("- Orchestration");
}Explanation:
The service layer contains business logic and orchestrates operations across multiple repositories. It's where complex business rules are implemented.
Exercises
Create Service Layer
Create a service struct with a method that uses a repository!
Starter Code:
struct UserService {
repo: UserRepository,
}
struct UserRepository;
impl UserRepository {
fn find_by_id(&self, id: u64) -> Option<String> {
Some(format!("User {}", id))
}
}
fn main() {
let service = UserService::new(UserRepository);
let user = service.get_user(1);
println!("{:?}", user);
}