Skip to content

Centralize UserOutput via Dependency Injection #107

@josecelano

Description

@josecelano

Overview

Currently, the UserOutput type is instantiated in multiple places throughout the presentation layer, which can lead to inconsistent user-facing messages with different verbosity levels, styles, or formatting. This task centralizes UserOutput instantiation in the application bootstrap phase and passes it down through the command execution chain using dependency injection via Arc<UserOutput>.

Problem: Decentralized construction creates inconsistency risk in:

  • src/presentation/commands/mod.rs - Error handler creates its own instance
  • src/presentation/commands/create/subcommands/template.rs - Template generation creates its own instance
  • src/presentation/commands/context.rs - Command context creates its own instance

Solution: Bootstrap a single UserOutput instance during application initialization and pass it down using Arc<UserOutput> for shared ownership.

Specification

See detailed specification: docs/issues/107-centralize-user-output-via-dependency-injection.md

🏗️ Architecture Requirements

DDD Layer: Infrastructure (Container), Presentation (usage)
Module Path:

  • Container: src/bootstrap/container.rs (new module)
  • Bootstrap integration: src/bootstrap/app.rs (existing)
  • Presentation layer: Multiple files

Pattern: Dependency Injection Container + Function Parameter Threading

Module Structure Requirements

  • Create Container type in src/bootstrap/container.rs for centralized service initialization
  • Bootstrap container in src/bootstrap/app.rs after logging initialization
  • Thread Arc<UserOutput> through presentation layer function signatures
  • Update existing UserOutput construction sites to use injected dependency
  • Respect layering: Container in bootstrap, usage in presentation

Architectural Constraints

  • No global state - Use Arc<UserOutput> for shared ownership, not global variables
  • Thread-safe - UserOutput must be thread-safe (already is with Arc)
  • Bootstrap only - Container initialization happens once during app startup
  • Dependency flow - Bootstrap → Presentation layer only (no reverse dependency)
  • Maintain existing behavior - User-facing output behavior must remain unchanged

Anti-Patterns to Avoid

  • Global mutable state - Don't use static mut or lazy_static! for UserOutput
  • Service locator pattern - Don't create a global registry for dependency lookup
  • Hidden dependencies - All functions should explicitly declare Arc<UserOutput> parameter
  • Layer violations - Don't pass container into domain or application layers

Implementation Plan

Phase 1: Create Bootstrap Container (1-2 hours)

  • Create src/bootstrap/container.rs with Container type
  • Add Arc<UserOutput> field to Container
  • Implement new() and user_output() methods
  • Update src/bootstrap/mod.rs to export Container
  • Add unit tests for Container construction and user_output() access

Phase 2: Integrate Container in Bootstrap (1 hour)

  • Create Container in src/bootstrap/app.rs after logging initialization
  • Pass container.user_output() to presentation::execute()
  • Pass container.user_output() to presentation::handle_error()

Phase 3: Update Presentation Layer Signatures (2-3 hours)

  • Update presentation::execute() signature to accept Arc<UserOutput>
  • Update presentation::handle_error() signature to accept Arc<UserOutput>
  • Update create::handle_create_command() signature
  • Update destroy::handle_destroy_command() signature
  • Update all subcommand handlers in create/subcommands/ and destroy/subcommands/

Phase 4: Remove Local UserOutput Constructions (1-2 hours)

  • Remove UserOutput::new() call in presentation::handle_error()
  • Remove UserOutput::new() call in create/subcommands/template.rs
  • Update CommandContext::new() to accept user_output: Arc<UserOutput> parameter
  • Update all CommandContext::new() call sites to pass injected user_output
  • Search codebase for remaining UserOutput::new() calls and update them

Phase 5: Update Tests (2-3 hours)

  • Update all presentation layer tests to create and pass Arc<UserOutput>
  • Update command handler tests
  • Update subcommand tests
  • Verify E2E tests still pass with new signatures
  • Add integration test verifying consistent output configuration

Phase 6: Documentation and Cleanup (1 hour)

  • Update module documentation in src/bootstrap/container.rs
  • Update function documentation for all changed signatures
  • Add architectural note to docs/codebase-architecture.md about centralized services
  • Run pre-commit checks: ./scripts/pre-commit.sh

Acceptance Criteria

Note for Contributors: These criteria define what the PR reviewer will check. Use this as your pre-review checklist before submitting the PR to minimize back-and-forth iterations.

Quality Checks:

  • Pre-commit checks pass: ./scripts/pre-commit.sh
  • All linters pass (markdown, yaml, toml, clippy, rustfmt, shellcheck)
  • All unit tests pass
  • All E2E tests pass

Architecture Checks:

  • Container type exists in src/bootstrap/container.rs
  • Container is bootstrapped in src/bootstrap/app.rs after logging initialization
  • No UserOutput::new() calls exist in presentation layer (except in tests)
  • All presentation layer functions accept Arc<UserOutput> parameter
  • CommandContext requires Arc<UserOutput> in constructor and stores it as a field
  • CommandContext::new() accepts user_output: Arc<UserOutput> parameter
  • No global mutable state used for UserOutput
  • No service locator pattern used

Behavior Checks:

  • User-facing output behavior remains unchanged (no visual differences)
  • Default verbosity level (DEFAULT_VERBOSITY) is used consistently
  • Error messages still display correctly with help text
  • Progress messages still display correctly
  • All commands produce consistent output style

Testing Checks:

  • Container construction is tested
  • Presentation layer functions are tested with injected UserOutput
  • Integration test verifies consistent output configuration
  • E2E tests pass without modification

Documentation Checks:

  • Module documentation explains container purpose and usage
  • Function documentation updated for new signatures
  • Architecture documentation mentions centralized service pattern

Related

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions