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¶
-
CoffeeBeanDTO -
DTO/Api/CoffeeBeanDTO.php:17- 22 parameters (175% over limit of 8)
- MOST SEVERE violation
-
ProcessingMethodDTO -
DTO/Api/ProcessingMethodDTO.php:12- 9 parameters (12% over limit)
-
RoasterDTO -
DTO/Api/RoasterDTO.php:9- 10 parameters (25% over limit)
-
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:
- Mapping directly from complex domain entities
- No grouping of related fields
- No use of builder pattern or factory methods
- Attempting to be fully immutable via constructor
Proposed Refactoring Strategy¶
Option A: Builder Pattern (Recommended)¶
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
Option D: Hybrid Approach (Recommended for CoffeeBeanDTO)¶
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:
- Analyze 22 parameters and group into logical clusters
- Create parameter objects for clusters (e.g., PriceInfo, LocationInfo, FlavorProfile)
- Implement builder pattern
- Add factory methods:
fromEntity(),fromArray() - Update all construction call-sites
- 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:
- Analyze parameters for grouping opportunities
- Implement simple builder
- Update call-sites
- Add factory methods if beneficial
Estimated Complexity: Medium
3. ProcessingMethodDTO (9 parameters)¶
Approach: Simple Builder or keep as-is
Steps:
- Evaluate if 9 parameters justifies refactoring
- If yes, implement simple builder
- 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
Related Issues¶
- 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