Simple Domain-Driven Design Refactoring Approach for PRS Project¶
This document outlines a practical, minimalist approach to refactoring the PRS codebase to better align with Domain-Driven Design (DDD) principles while maintaining the existing architecture. This approach is specifically designed for teams with junior and mid-level developers, focusing on gradual improvements without overwhelming complexity.
Current Challenges¶
The PRS codebase currently faces several challenges that this refactoring aims to address:
- Business Logic Scattered: Business rules are spread across controllers and services
- Duplicate Logic: The same business rules are implemented in multiple places
- Difficult Maintenance: When business rules change, updates are needed in multiple files
- Testing Challenges: Business logic is tightly coupled with infrastructure code
- Onboarding Difficulties: New developers struggle to understand where business rules are implemented
Goals¶
- Improve Code Organization: Move business logic to domain entities
- Reduce Duplication: Centralize business rules in one place
- Enhance Maintainability: Make business rules easier to find and update
- Simplify Testing: Enable testing of business logic in isolation
- Minimize Disruption: Keep changes small and focused
- Support Junior Developers: Make the codebase more accessible to less experienced team members
Approach¶
Our approach focuses on making minimal changes to the existing architecture while still gaining the benefits of DDD principles. We'll keep the current project structure but enhance it with better organization of business logic.
1. Add Business Logic to Domain Entities¶
We're enhancing the existing domain entities by adding business logic methods while keeping the validation schemas. This allows us to centralize business rules without disrupting the current structure.
Before Refactoring: Business logic is scattered across controllers and services:
After Refactoring: Business logic is centralized in domain entities:
2. Refactor Controllers and Services to Use Domain Logic¶
Update controllers and services to use the business logic from domain entities. This separates the "what" (business rules) from the "how" (implementation details).
Before Refactoring: Controllers and services contain both business logic and implementation details:
After Refactoring: Controllers and services use domain logic from entities:
Benefits for the PRS Project¶
1. Clear Business Rules¶
Business rules are clearly defined in one place, making them easier to understand and maintain. This is especially important for the PRS project where business rules around requisition approval, purchase orders, and payments are complex.
2. Improved Maintainability¶
When business rules change (which happens frequently in the PRS project), you only need to update them in one place. This reduces the risk of inconsistent implementations.
3. Better Testability¶
Business logic can be tested independently of controllers and services. This makes it easier to write unit tests for critical business rules like approval workflows and payment validations.
4. Reduced Duplication¶
Business rules are defined once and reused across the application. This is particularly valuable for rules that are applied in multiple contexts, such as validation rules for requisitions.
5. Minimal Disruption¶
This approach doesn't require major architectural changes, making it easier to implement incrementally. The team can continue working on features while gradually improving the codebase.
6. Easier Onboarding¶
New developers can more easily understand the business rules by looking at the domain entities, rather than having to search through multiple controllers and services.
Implementation Strategy for PRS Project¶
1. Start with Core Entities¶
Begin with the most important entities in the PRS system:
- Requisition: The central entity in the purchase requisition process
- PurchaseOrder: Critical for vendor management and procurement
- User: Important for approval workflows and permissions
- Supplier: Essential for vendor management
2. Identify Business Rules¶
For each entity, identify the business rules that should be moved to the domain layer:
- Validation rules beyond simple data validation (e.g., a requisition must have items before submission)
- State transition rules (e.g., when a requisition can be approved, rejected, or closed)
- Business calculations (e.g., calculating total amounts, taxes, or discounts)
- Domain-specific constraints (e.g., approval hierarchies, budget limits)
3. Implement Incrementally¶
Refactor one entity at a time, and within each entity, refactor one method at a time:
- Start with the
Requisitionentity and its submission workflow - Move to approval and rejection workflows
- Continue with
PurchaseOrdercreation and approval - Implement
SupplierandUserbusiness rules
4. Add Tests¶
Write tests for the business logic to ensure it works correctly:
5. Update Documentation¶
Keep documentation up-to-date with the changes:
- Update code comments to explain business rules
- Maintain this refactoring guide
- Create examples for the team to follow
Example: Requisition Entity in PRS¶
We've added the following business logic methods to the Requisition entity in the PRS project:
canBeSubmitted: Checks if a requisition can be submitted (must be in DRAFT status and have items)canBeApprovedBy: Checks if a requisition can be approved by a specific user (based on approval workflow)canBeRejectedBy: Checks if a requisition can be rejected by a specific user (based on approval workflow)getNextStatusAfterApproval: Determines the next status after approval (PARTIALLY_APPROVED or APPROVED)canBeClosed: Checks if a requisition can be closed (based on delivery status)canBeAssigned: Checks if a requisition can be assigned to a user (based on permissions)hasItemSufficientQuantity: Checks if an item has sufficient quantity (for inventory management)
These methods encapsulate critical business rules for the requisition process, which is the core workflow in the PRS system.
Guidelines for Junior and Mid-Level Developers¶
When to Add Business Logic to Domain Entities¶
Add business logic to domain entities when:
- The logic represents a business rule (not just data manipulation)
- The logic might be reused in multiple places
- The logic is specific to a particular entity
- The rule would otherwise be duplicated in multiple controllers or services
How to Use Business Logic in Controllers and Services¶
-
Import the entity module:
JavaScript -
Use the business logic methods:
-
Keep controllers focused on HTTP concerns:
What Not to Put in Domain Entities¶
- Database access code (use repositories instead)
- HTTP request/response handling (keep in controllers)
- File I/O (use services for this)
- External API calls (use services for this)
- Infrastructure concerns (logging, caching, etc.)
Conclusion¶
This simple approach to DDD refactoring allows us to improve the PRS codebase incrementally without major architectural changes. By moving business logic to domain entities, we make the code more maintainable, testable, and easier to understand.
The approach is particularly well-suited for the PRS project because:
- It maintains the existing architecture that developers are familiar with
- It addresses the specific challenges of scattered business logic
- It's accessible to junior and mid-level developers
- It can be implemented gradually alongside regular feature development
- It provides immediate benefits without requiring a complete rewrite
By following this approach, the PRS team can gradually improve the codebase while continuing to deliver value to users.