Expand description
Controllers Layer - Presentation Layer Component
The Controllers Layer handles command execution and business logic coordination. This is the third layer in the presentation layerβs four-layer architecture: Input β Dispatch β Controllers β Views.
Β§π§ Current Status: Proposal #3 In Progress
This layer is being refactored to establish consistent controller patterns. The goal is to align all command controllers with the clean architecture established by the destroy command controller.
Β§Command Architecture Types
There are two types of commands in the system, each with different internal structure:
Β§1. Single Commands (Direct Execution)
Commands that execute directly without subcommands (e.g., destroy).
These follow the clean handler pattern with direct execution.
Β§2. Commands with Subcommands (Router + Subcontrollers)
Commands that route to multiple subcommands (e.g., create environment, create template).
These have an extra routing layer but subcommands maintain the same internal
structure as single commands.
Key Principle: Subcommands should have internally the same structure as normal commands, but with an additional routing layer to dispatch between subcommands.
Β§Controller Maturity Levels
Β§β Reference Implementation: Destroy Controller (Single Command)
The destroy controller demonstrates the target architecture for single commands:
- Clean Handler Pattern: Single
handler.rswith focused responsibility - Dedicated Error Types: Command-specific errors with help methods
- Minimal Dependencies: Takes
ExecutionContext, delegates to application layer - Comprehensive Tests: Full test coverage with clear test organization
destroy/
βββ handler.rs # Main command handler function
βββ errors.rs # DestroySubcommandError with help methods
βββ tests/ # Command-specific tests
βββ mod.rs # Module exportsΒ§π§ Needs Refactoring: Create Controller (Command with Subcommands)
The create controller needs refactoring to match the target pattern for commands
with subcommands. It demonstrates the router + subcontroller pattern but needs
architectural cleanup.
Current Structure (Transitional):
create/
βββ router.rs # Routes between environment and template subcommands
βββ errors.rs # Unified CreateCommandError wrapper
βββ subcommands/ # Temporary: should become separate controllers
β βββ environment/ # Environment creation logic
β βββ template/ # Template generation logic
βββ tests/ # Tests organized by function
β βββ environment.rs # Environment creation tests
β βββ template.rs # Template generation tests
βββ mod.rs # Module exportsTarget Structure (After Refactoring):
The refactoring will split this into separate controllers for each subcommand, each following the same clean structure as single commands:
create_environment/ # NEW: Dedicated environment controller
βββ handler.rs # Environment creation handler (same structure as destroy)
βββ errors.rs # Environment-specific errors
βββ tests/ # Environment tests
βββ mod.rs # Module exports
create_template/ # NEW: Dedicated template controller
βββ handler.rs # Template generation handler (same structure as destroy)
βββ errors.rs # Template-specific errors
βββ tests/ # Template tests
βββ mod.rs # Module exportsKey Insight: Each subcommand becomes a separate controller with the same internal structure as single commands. The routing is handled at the dispatch layer, not within controllers.
Β§π― Controller Design Principles
Based on the destroy controller reference implementation:
Β§1. Single Responsibility
- Each controller handles one specific command (destroy, create environment, create template)
- No routing logic within controllers - routing handled by dispatch layer for subcommands
- No presentation logic - controllers coordinate, views handle output
Β§2. Clean Handler Pattern (Universal for All Controllers)
- Main logic in
handler.rswith descriptive function name (handle_destroy_command) - Take
ExecutionContextfor dependencies - Return command-specific error types
- Focus on orchestrating application layer services
Note: This pattern applies to both single commands and subcommand controllers. Subcommand controllers have the same internal structure, just different routing.
Β§3. Dedicated Error Types
- Command-specific error enums (e.g.,
DestroySubcommandError) - Use thiserror for structured errors
- Implement
.help()method for detailed troubleshooting - Include context and actionable guidance
Β§4. Application Layer Integration
- Controllers call application layer command handlers
- Pass through domain entities and value objects
- Handle application errors and convert to presentation errors
- Donβt contain business logic - delegate to application layer
Β§π Subcommand Routing Architecture
For commands with subcommands (like create), the current architecture uses:
- Dispatch Layer: Receives full command (e.g.,
create environment) - Router: Routes to appropriate subcommand handler based on action type
- Subcommand Handler: Executes specific logic (environment creation or template generation)
Current Implementation Example (from create/router.rs):
use std::path::Path;
use torrust_tracker_deployer_lib::domain::provider::Provider;
use torrust_tracker_deployer_lib::presentation::cli::input::cli::commands::CreateAction;
use torrust_tracker_deployer_lib::presentation::cli::dispatch::context::ExecutionContext;
use torrust_tracker_deployer_lib::presentation::cli::controllers::create::errors::CreateCommandError;
use torrust_tracker_deployer_lib::presentation::cli::controllers::create::subcommands;
pub async fn route_command(
action: CreateAction,
working_dir: &Path,
context: &ExecutionContext,
) -> Result<(), CreateCommandError> {
match action {
CreateAction::Environment { env_file } => {
context
.container()
.create_environment_controller()
.execute(&env_file, working_dir)
.await
.map(|_| ()) // Convert Environment<Created> to ()
.map_err(CreateCommandError::Environment)
}
CreateAction::Template { output_path, provider } => {
let template_path = output_path.unwrap_or_else(CreateAction::default_template_path);
context
.container()
.create_template_controller()
.execute(&template_path, provider)
.await
.map_err(CreateCommandError::Template)
}
CreateAction::Schema { output_path } => {
context
.container()
.create_schema_controller()
.execute(output_path.as_ref())
.map_err(CreateCommandError::Schema)
}
}
}Target Architecture: Move routing to dispatch layer, make each subcommand a separate controller with the same structure as single commands.
Β§π Refactoring Plan for Create Controller
To complete Proposal #3, the create controller needs to be split into separate controllers that each follow the same clean structure as single commands:
Β§Current Challenge
The create command currently uses internal routing with subcommands. This needs
to be refactored so each subcommand becomes a separate controller with the same
internal structure as single commands.
Β§Step 1: Create Environment Controller
- Create
src/presentation/controllers/create_environment/ - Move environment logic from
create/subcommands/environment/ - Create clean handler following destroy pattern (same structure)
- Move environment tests to new controller
Β§Step 2: Create Template Controller
- Create
src/presentation/controllers/create_template/ - Move template logic from
create/subcommands/template/ - Create clean handler following destroy pattern (same structure)
- Move template tests to new controller
Β§Step 3: Update Dispatch Router
- Remove create controller router (no more internal subcommand routing)
- Update dispatch layer to route directly to separate controllers
- Update error handling for separate error types
- Each subcommand treated as independent controller
Β§Step 4: Remove Old Create Structure
- Remove
create/directory entirely - Update imports throughout codebase
- Update documentation and tests
Result: Both create environment and create template will be handled by
separate controllers with identical structure to destroy - just different routing
at the dispatch level.
Β§π§ͺ Testing Strategy
Each controller should have comprehensive test coverage:
- Unit Tests: Handler function behavior with various inputs
- Error Tests: All error variants and help text validation
- Integration Tests: End-to-end command execution with
ExecutionContext
Tests should be isolated - each controllerβs tests run independently without depending on other controllers or external state.
Β§π Future Controllers
After establishing the controller pattern, additional commands will follow the same structure:
controllers/
βββ create_environment/ # Environment creation
βββ create_template/ # Template generation
βββ destroy/ # Environment destruction β
βββ provision/ # Future: Infrastructure provisioning
βββ configure/ # Future: Software configuration
βββ release/ # Future: Application deployment
βββ run/ # Future: Service managementEach controller will:
- Follow the established handler pattern
- Have dedicated error types
- Integrate cleanly with the dispatch layer
- Maintain comprehensive test coverage
ModulesΒ§
- configure
- Configure Command Presentation Module
- constants
- Constants for command handlers
- create
- Create Command Presentation Module
- destroy
- Destroy Command Presentation Module
- docs
- Docs Command Controller (Presentation Layer)
- exists
- Exists Command Presentation Module
- list
- List Command Presentation Module
- provision
- Provision Command Presentation Module
- purge
- Purge Command Presentation Module
- register
- Register Command Presentation Module
- release
- Release Command Presentation Module
- render
- Render Command Controller Module
- run
- Run Command Presentation Module
- show
- Show Command Presentation Module
- test
- Test Command Presentation Module
- validate
- Validate Command Controller