Skip to content

Extract CommandHandlerFactory to centralize handler creation#91

Merged
josecelano merged 4 commits intomainfrom
copilot/extract-command-handler-factory
Oct 29, 2025
Merged

Extract CommandHandlerFactory to centralize handler creation#91
josecelano merged 4 commits intomainfrom
copilot/extract-command-handler-factory

Conversation

Copy link
Contributor

Copilot AI commented Oct 29, 2025

Each presentation command handler was manually constructing application command handlers with duplicate initialization code, making it difficult to modify handler creation logic or add new commands consistently.

Changes

Created CommandHandlerFactory

  • Centralizes creation of CreateCommandHandler and DestroyCommandHandler
  • Uses RepositoryFactory to manage lock timeout configuration
  • Provides create_context() to build CommandContext with shared dependencies
  • Adds new_for_testing() for custom test configurations

Updated command handlers

  • destroy/handler.rs: Use factory instead of manual DestroyCommandHandler::new()
  • create/subcommands/environment.rs: Use factory instead of manual CreateCommandHandler::new()
  • Removed 12 lines of duplicate initialization code across handlers

Added CommandContext::new_with_factory()

  • Allows creating context from existing RepositoryFactory instance
  • Enables factory to create multiple contexts with consistent configuration

Example

Before:

pub fn handle_destroy_command(environment_name: &str, working_dir: &Path) -> Result<()> {
    let mut ctx = CommandContext::new(working_dir.to_path_buf());
    let command_handler = DestroyCommandHandler::new(
        ctx.repository().clone(),
        ctx.clock().clone()
    );
    // ...
}

After:

pub fn handle_destroy_command(environment_name: &str, working_dir: &Path) -> Result<()> {
    let factory = CommandHandlerFactory::new();
    let mut ctx = factory.create_context(working_dir.to_path_buf());
    let command_handler = factory.create_destroy_handler(&ctx);
    // ...
}
Original prompt

This section details on the original issue you should resolve

<issue_title>Extract Command Handler Factory</issue_title>
<issue_description>Parent Issue: #63
Type: 🚀 Advanced Pattern
Impact: 🟢🟢🟢 High
Effort: 🔵🔵🔵🔵 Very High
Priority: P2

Problem

Each command handler manually constructs all its dependencies and output handling, leading to:

  • Duplicate initialization code across commands
  • Inconsistent dependency setup patterns
  • Difficult to test different dependency configurations
  • Hard to add new commands consistently

Current pattern (repeated in every command):

pub fn handle_create_command(action: CreateAction, working_dir: &Path) {
    let mut output = output::create_default();
    let repository_factory = RepositoryFactory::new(DEFAULT_LOCK_TIMEOUT);
    let repository = repository_factory.create(working_dir.to_path_buf());
    let clock = Arc::new(SystemClock);
    
    // Use dependencies...
}

Proposed Solution

Create a CommandHandlerFactory that centralizes command handler creation:

//! Command Handler Factory
//!
//! Provides centralized creation of command handlers with consistent
//! dependency injection and output management.

pub struct CommandHandlerFactory {
    repository_factory: RepositoryFactory,
}

impl CommandHandlerFactory {
    pub fn new() -> Self {
        let repository_factory = RepositoryFactory::new(DEFAULT_LOCK_TIMEOUT);
        Self { repository_factory }
    }

    /// Create a command context for the given working directory
    pub fn create_context(&self, working_dir: PathBuf) -> CommandContext {
        CommandContext::new_with_factory(&self.repository_factory, working_dir)
    }

    /// Create a create command handler
    pub fn create_create_handler(
        &self,
        context: &CommandContext,
    ) -> CreateCommandHandler {
        CreateCommandHandler::new(
            context.repository().clone(),
            context.clock().clone(),
        )
    }

    /// Create a destroy command handler
    pub fn create_destroy_handler(
        &self,
        context: &CommandContext,
    ) -> DestroyCommandHandler {
        DestroyCommandHandler::new(
            context.repository().clone(),
            context.clock().clone(),
        )
    }
}

#[cfg(test)]
impl CommandHandlerFactory {
    /// Create a factory for testing with custom repository factory
    pub fn new_for_testing(repository_factory: RepositoryFactory) -> Self {
        Self { repository_factory }
    }
}

Then simplify command handlers:

pub fn handle_create_command(action: CreateAction, working_dir: &Path) 
    -> Result<(), CreateSubcommandError> 
{
    let factory = CommandHandlerFactory::new();
    let mut context = factory.create_context(working_dir.to_path_buf());

    match action {
        CreateAction::Environment { env_file } => {
            let handler = factory.create_create_handler(&context);
            // Use handler...
        }
        CreateAction::Template { output_path } => {
            // Simple operation, no handler needed
        }
    }
}

Benefits

  • ✅ Centralized command handler creation
  • ✅ Consistent dependency injection across all commands
  • ✅ Easier to add new command handlers
  • ✅ Better testability with factory customization
  • ✅ Single place to modify initialization logic

Implementation Checklist

Phase 1: Create factory

  • Create src/presentation/commands/factory.rs
  • Implement CommandHandlerFactory struct
  • Add new() constructor
  • Add create_context() method
  • Add create_create_handler() method
  • Add create_destroy_handler() method
  • Add new_for_testing() for test support
  • Add comprehensive documentation

Phase 2: Update command handlers

  • Update create command handler to use factory
  • Update destroy command handler to use factory
  • Remove duplicate initialization code
  • Simplify dependency setup

Phase 3: Update tests

  • Update tests to use factory
  • Verify all tests still pass
  • Add factory-specific tests

Phase 4: Documentation

  • Document factory pattern usage
  • Add examples for adding new commands
  • Update contributing guide

Phase 5: Validation

  • Run all tests
  • Run linter
  • Verify no behavioral changes

Acceptance Criteria

  • CommandHandlerFactory exists and is documented
  • All command handlers use the factory
  • No duplicate initialization code remains
  • Factory is testable and has tests
  • Pattern is documented for future commands
  • All tests pass: cargo test presentation::commands
  • Pre-commit checks pass: ./scripts/pre-commit.sh
  • Code follows project conventions

Related Documentation


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] Extract command handler factory to reduce code duplication Extract CommandHandlerFactory to centralize handler creation Oct 29, 2025
Copilot AI requested a review from josecelano October 29, 2025 18:26
@josecelano josecelano marked this pull request as ready for review October 29, 2025 18:33
Copy link
Member

@josecelano josecelano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 8a258d9

@josecelano josecelano merged commit ae9a59e into main Oct 29, 2025
48 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extract Command Handler Factory

2 participants