Skip to content

Refactor DTO Parameter Lists

Priority: 🟠 HIGH Status: Planning Related QA Analysis: qa-analysis-overview.md

Problem Statement

Multiple API DTOs have excessive constructor parameters, making them error-prone and difficult to maintain:

Violations

  1. CoffeeBeanDTO - DTO/Api/CoffeeBeanDTO.php:17

    • 22 parameters (175% over limit of 8)
    • MOST SEVERE violation
  2. ProcessingMethodDTO - DTO/Api/ProcessingMethodDTO.php:12

    • 9 parameters (12% over limit)
  3. RoasterDTO - DTO/Api/RoasterDTO.php:9

    • 10 parameters (25% over limit)
  4. VarietyDTO - DTO/Api/VarietyDTO.php:9

    • 10 parameters (25% over limit)

Impact

  • Error-Prone: Easy to swap parameter positions when constructing
  • Maintainability: Adding/removing fields requires many call-site changes
  • Readability: Constructor calls are difficult to read and understand
  • API Evolution: Difficult to extend API without breaking changes
  • Poor Ergonomics: Painful developer experience

Guideline Violations

  • Design Best Practices: Excessive parameters harm readability
  • Maintainability: High change amplification

Root Cause Analysis

DTOs likely have excessive parameters because:

  1. Mapping directly from complex domain entities
  2. No grouping of related fields
  3. No use of builder pattern or factory methods
  4. Attempting to be fully immutable via constructor

Proposed Refactoring Strategy

Implement builder pattern for DTOs with many parameters:

Advantages:

  • Readable construction with named methods
  • Optional parameters with defaults
  • Immutable DTOs after building
  • Easy to extend without breaking changes
  • Better IDE support and autocomplete

Example:

$coffeeBean = CoffeeBeanDTO::builder()
    ->withId($id)
    ->withName($name)
    ->withRoaster($roaster)
    ->withOrigin($origin)
    // ... other fields
    ->build();

Option B: Parameter Objects

Group related parameters into cohesive objects:

Example:

// Instead of: name, street, city, country, postalCode
// Use: LocationInfo $location

class LocationInfo {
    public function __construct(
        public readonly string $city,
        public readonly string $country,
        public readonly ?string $region = null,
    ) {}
}

Option C: Factory Methods

Create named factory methods for common construction patterns:

Example:

CoffeeBeanDTO::fromEntity(CoffeeBean $entity);
CoffeeBeanDTO::fromArray(array $data);
CoffeeBeanDTO::minimal($id, $name, $roaster); // Minimal DTO

Combine builder pattern with parameter objects:

  • Use builder for primary construction
  • Group related fields into parameter objects
  • Provide factory methods for common cases

Refactoring Plan by DTO

1. CoffeeBeanDTO (22 parameters) - PRIORITY

Approach: Hybrid (Builder + Parameter Objects + Factories)

Steps:

  1. Analyze 22 parameters and group into logical clusters
  2. Create parameter objects for clusters (e.g., PriceInfo, LocationInfo, FlavorProfile)
  3. Implement builder pattern
  4. Add factory methods: fromEntity(), fromArray()
  5. Update all construction call-sites
  6. Deprecate old constructor (if backward compatibility needed)

Estimated Complexity: High (22 params → needs careful design)

2. RoasterDTO & VarietyDTO (10 parameters each)

Approach: Builder Pattern

Steps:

  1. Analyze parameters for grouping opportunities
  2. Implement simple builder
  3. Update call-sites
  4. Add factory methods if beneficial

Estimated Complexity: Medium

3. ProcessingMethodDTO (9 parameters)

Approach: Simple Builder or keep as-is

Steps:

  1. Evaluate if 9 parameters justifies refactoring
  2. If yes, implement simple builder
  3. Otherwise, document and monitor

Estimated Complexity: Low-Medium

Success Criteria

  • All DTOs have ≤8 constructor parameters (or use builder pattern)
  • Construction is readable and self-documenting
  • Easy to add new fields without breaking changes
  • Improved developer experience
  • No loss of type safety or immutability
  • All API tests pass

Migration Strategy

Phase 1: Create Builders

  • Implement builder classes for each DTO
  • Keep existing constructors for backward compatibility
  • Add deprecation notices

Phase 2: Migrate Call-Sites

  • Update internal code to use builders
  • Update tests
  • Verify API responses unchanged

Phase 3: Cleanup

  • Remove deprecated constructors (breaking change)
  • Update documentation
  • Add examples to API docs

Risk Assessment

Medium Risk:

  • API DTOs used throughout codebase
  • Many call-sites to update
  • Must maintain API contract

Mitigation:

  • Comprehensive API contract tests
  • Maintain backward compatibility initially
  • Update call-sites incrementally
  • Thorough testing of serialization

Estimated Effort

Medium:

  • CoffeeBeanDTO: 2-3 days (most complex)
  • RoasterDTO & VarietyDTO: 1-2 days each
  • ProcessingMethodDTO: 0.5-1 day (evaluate first)
  • Migration & testing: 2 days
  • Total: 6-9 days

Dependencies

  • H1: Simplify Domain Entities - May reveal better DTO structure
  • Consider doing after or alongside entity refactoring
  • StepOrchestrator also has 9 parameters (service, not DTO) - different approach needed

Notes

  • CoffeeBeanDTO (22 params) is the worst violator - start here
  • Builder pattern is well-established in PHP community
  • Consider using a builder library or code generation
  • This refactoring will improve API evolution velocity
  • May discover opportunities to simplify API responses