Expand description
Dispatch Layer - Presentation Layer Component
The Dispatch Layer is responsible for routing parsed commands to their appropriate handlers and providing execution context for command processing. This is the second layer in the presentation layer’s four-layer architecture: Input → Dispatch → Controllers → Views.
§Purpose
The Dispatch Layer establishes clear separation between:
- Command routing (determining which handler to execute)
- Execution context (providing dependencies to handlers)
- Command execution (actual command logic in Controllers layer)
- Result presentation (handled by Views layer)
This separation provides several benefits:
- Single Responsibility: Routing logic isolated from command execution
- Testability: Router can be tested independently of command handlers
- Dependency Injection: Clean pattern for providing services to commands
- Scalability: Easy to add new commands without modifying existing handlers
§Module Structure
dispatch/
├── mod.rs # This file - layer exports and documentation
├── router.rs # Command routing logic (route_command function)
└── context.rs # ExecutionContext wrapper around Container§ExecutionContext Design
The Dispatch Layer uses an ExecutionContext wrapper around the Container rather than
passing the Container directly to command handlers. This design choice provides several
important benefits:
§1. Future-Proof Command Signatures
By using ExecutionContext, we can extend execution context in the future without
breaking existing command handler signatures:
use std::sync::Arc;
use torrust_tracker_deployer_lib::bootstrap::Container;
pub struct ExecutionContext {
container: Arc<Container>,
// Future additions without breaking changes:
// request_id: RequestId,
// execution_metadata: ExecutionMetadata,
// tracing_context: TracingContext,
// user_permissions: UserPermissions,
}§2. Clear Abstraction and Intent
ExecutionContext represents “everything a command needs to execute” rather than
exposing dependency injection mechanics directly:
use torrust_tracker_deployer_lib::bootstrap::Container;
use torrust_tracker_deployer_lib::presentation::cli::dispatch::ExecutionContext;
// Clear: This is specifically for command execution
fn handle_command(context: &ExecutionContext) {
// Command execution logic
}
// Less clear: Could be for bootstrapping, testing, or execution
fn handle_command_old(container: &Container) {
// Generic container usage
}§3. Command-Specific Service Access
ExecutionContext can provide command-specific convenience methods and service
aggregations without exposing the entire Container interface.
For the complete rationale, see the architectural decision record:
docs/decisions/execution-context-wrapper.md
§Design Principles
- Route, Don’t Execute: This layer only routes commands, doesn’t execute them
- Dependency Injection: Provide clean access to services via
ExecutionContext - Type Safety: Use strongly-typed routing with match statements
- Error Propagation: Pass routing errors up to caller
§Integration with Presentation Layer
The Dispatch Layer integrates with the broader presentation layer architecture:
- Input Layer (
input/) - Parses user input into Commands enum - Dispatch Layer (this module) - Routes commands and provides context
- Controller Layer (
commands/) - Executes command logic with context - View Layer (
user_output/,progress.rs) - Presents results to users
§Usage Pattern
use std::path::Path;
use std::sync::Arc;
use torrust_tracker_deployer_lib::bootstrap::Container;
use torrust_tracker_deployer_lib::presentation::cli::dispatch::{route_command, ExecutionContext};
use torrust_tracker_deployer_lib::presentation::cli::views::VerbosityLevel;
// Note: Commands enum requires specific action parameters in practice
let container = Container::new(VerbosityLevel::Normal);
let context = ExecutionContext::new(Arc::new(container), global_args);
let working_dir = Path::new(".");
// Execute a command through the dispatch layer
// Note: route_command is synchronous, not async
// Commands require proper construction with actionsRe-exports§
pub use context::ExecutionContext;pub use router::route_command;