Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
1088 lines
33 KiB
Markdown
1088 lines
33 KiB
Markdown
# Architecture Decision: Layer-Based vs Module-Based Organization
|
||
|
||
**Decision Date**: 2025-01-28
|
||
**Status**: Recommended
|
||
**Deciders**: Development Team
|
||
**Related Documents**:
|
||
- `SRC-ARCHITECTURE-ANALYSIS.md` - Current architecture analysis
|
||
- `DOCUMENTATION-ANALYSIS.md` - Documentation structure analysis
|
||
|
||
---
|
||
|
||
## Executive Summary
|
||
|
||
**Decision**: Maintain 4-layer Clean Architecture with enhanced modular organization within each layer (Hybrid Approach)
|
||
|
||
**Rationale**:
|
||
- Framework layer constitutes 85.3% of codebase (3,883 files)
|
||
- Pure module-based approach would duplicate this massive framework across modules
|
||
- Hybrid approach provides modular benefits while preserving shared framework infrastructure
|
||
- Estimated effort: 2-3 months (vs 6-12 months for pure module approach)
|
||
|
||
**Impact**:
|
||
- ✅ Maintains Clean Architecture compliance
|
||
- ✅ Achieves Domain-Driven Design benefits
|
||
- ✅ Avoids massive code duplication
|
||
- ✅ Minimizes refactoring effort
|
||
- ✅ Preserves existing framework investments
|
||
|
||
---
|
||
|
||
## Context & Problem Statement
|
||
|
||
### Current Architecture State
|
||
|
||
**Analyzed Structure** (from `SRC-ARCHITECTURE-ANALYSIS.md`):
|
||
```
|
||
Total Files: 4,551 PHP files
|
||
Total Directories: 1,052
|
||
|
||
Layer Distribution:
|
||
├── Framework: 3,883 files (85.3%) - TOO LARGE
|
||
├── Application: 420 files (9.2%) - Below target
|
||
├── Domain: 184 files (4.0%) - TOO THIN
|
||
└── Infrastructure: 64 files (1.5%) - Below target
|
||
```
|
||
|
||
**Current Grade**: C+ (75%) - "Good foundation with critical architectural flaws"
|
||
|
||
### Identified Problems
|
||
|
||
**3 Critical Architectural Violations**:
|
||
|
||
1. **HTTP Layer in Domain** (`src/Domain/Meta/Http/`)
|
||
- Controllers with `#[Route]` attributes in Domain layer
|
||
- 406 lines of HTTP-specific code misplaced
|
||
|
||
2. **DI Container Setup in Domain** (5 domains affected)
|
||
- Framework-specific dependency injection in Domain
|
||
- Violates Dependency Rule (Domain depends on Framework)
|
||
|
||
3. **Infrastructure Nested in Application** (`src/Application/Website/Infrastructure/GeoIp/`)
|
||
- Wrong dependency direction
|
||
- Confusing architecture
|
||
|
||
### Strategic Question
|
||
|
||
**"Should we maintain 4-layer architecture or switch to pure module-based organization?"**
|
||
|
||
This decision has fundamental implications:
|
||
- Code organization strategy
|
||
- Framework reusability
|
||
- Refactoring effort (months vs years)
|
||
- Team cognitive load
|
||
- Maintainability long-term
|
||
|
||
---
|
||
|
||
## Decision Drivers
|
||
|
||
### Key Factors Influencing Decision
|
||
|
||
1. **Framework Size**: 85.3% of codebase is framework infrastructure
|
||
2. **Code Duplication Risk**: Pure modules would replicate framework per module
|
||
3. **Clean Architecture Benefits**: Dependency inversion, testability, maintainability
|
||
4. **Domain-Driven Design Goals**: Bounded contexts, domain focus
|
||
5. **Refactoring Effort**: Time and risk considerations
|
||
6. **Team Familiarity**: Existing knowledge of current structure
|
||
7. **Framework Reusability**: Shared infrastructure across all modules
|
||
|
||
### Stakeholder Concerns
|
||
|
||
- **Development Team**: Minimize disruption, preserve framework investments
|
||
- **Architecture**: Maintain Clean Architecture principles
|
||
- **Business**: Minimize refactoring time, maintain velocity
|
||
- **Quality**: Improve testability, reduce coupling
|
||
|
||
---
|
||
|
||
## Considered Options
|
||
|
||
### Option 1: Pure Module-Based (Vertical Slicing)
|
||
|
||
**Structure**:
|
||
```
|
||
src/
|
||
├── User/ # Self-contained module
|
||
│ ├── Domain/
|
||
│ ├── Application/
|
||
│ ├── Infrastructure/
|
||
│ └── Framework/ # Duplicated framework code!
|
||
├── Order/ # Self-contained module
|
||
│ ├── Domain/
|
||
│ ├── Application/
|
||
│ ├── Infrastructure/
|
||
│ └── Framework/ # Duplicated framework code!
|
||
└── Product/ # Self-contained module
|
||
├── Domain/
|
||
├── Application/
|
||
├── Infrastructure/
|
||
└── Framework/ # Duplicated framework code!
|
||
```
|
||
|
||
**Advantages**:
|
||
- ✅ True bounded contexts - modules are completely independent
|
||
- ✅ Team autonomy - each module owned by dedicated team
|
||
- ✅ Deploy modules independently (microservices-ready)
|
||
- ✅ Pure Domain-Driven Design implementation
|
||
- ✅ No cross-module dependencies
|
||
|
||
**Disadvantages**:
|
||
- ❌ **CRITICAL**: Framework duplication (85.3% × modules = massive overhead)
|
||
- ❌ Framework updates must propagate to ALL modules
|
||
- ❌ Code duplication: HTTP layer, Database, Cache, Queue, Events
|
||
- ❌ Increased maintenance burden (N frameworks to update)
|
||
- ❌ Inconsistency risk across modules
|
||
- ❌ Massive refactoring effort (6-12 months)
|
||
- ❌ Loss of framework investment (3,883 files built over time)
|
||
|
||
**Estimated Effort**: 6-12 months
|
||
**Risk Level**: HIGH
|
||
|
||
---
|
||
|
||
### Option 2: Hybrid Approach (Layers + Modules)
|
||
|
||
**Recommended Structure**:
|
||
```
|
||
src/
|
||
├── Domain/ (20-30% - business logic)
|
||
│ ├── User/ (Module - bounded context)
|
||
│ │ ├── Entity/
|
||
│ │ ├── ValueObject/
|
||
│ │ ├── Event/
|
||
│ │ ├── Repository/ (Interfaces only!)
|
||
│ │ └── Service/
|
||
│ ├── Order/ (Module - bounded context)
|
||
│ │ ├── Entity/
|
||
│ │ ├── ValueObject/
|
||
│ │ ├── Event/
|
||
│ │ ├── Repository/
|
||
│ │ └── Service/
|
||
│ └── Product/ (Module - bounded context)
|
||
│
|
||
├── Application/ (25-35% - use cases)
|
||
│ ├── User/ (Module - aligned with domain)
|
||
│ │ ├── Controller/
|
||
│ │ ├── Command/
|
||
│ │ └── Query/
|
||
│ ├── Order/ (Module - aligned with domain)
|
||
│ │ ├── Controller/
|
||
│ │ ├── Command/
|
||
│ │ └── Query/
|
||
│ └── Admin/ (Feature module - cross-cutting)
|
||
│ ├── Analytics/
|
||
│ ├── Content/
|
||
│ └── System/
|
||
│
|
||
├── Framework/ (40-50% - SHARED infrastructure)
|
||
│ ├── Core/ # Shared by ALL modules
|
||
│ ├── Database/ # Shared by ALL modules
|
||
│ ├── Http/ # Shared by ALL modules
|
||
│ ├── Cache/ # Shared by ALL modules
|
||
│ ├── Queue/ # Shared by ALL modules
|
||
│ ├── Events/ # Shared by ALL modules
|
||
│ └── [32 more shared components]
|
||
│
|
||
└── Infrastructure/ (5-10% - external integrations)
|
||
└── ServiceProviders/ # Module-specific DI setup
|
||
├── UserServiceProvider.php
|
||
├── OrderServiceProvider.php
|
||
└── ProductServiceProvider.php
|
||
```
|
||
|
||
**Advantages**:
|
||
- ✅ Clean Architecture compliance maintained
|
||
- ✅ **Framework shared across all modules** (no duplication!)
|
||
- ✅ Modular organization where beneficial (Domain, Application)
|
||
- ✅ Domain-Driven Design benefits (bounded contexts)
|
||
- ✅ Reduced refactoring effort (2-3 months)
|
||
- ✅ Framework investment preserved
|
||
- ✅ Single framework to maintain and update
|
||
- ✅ Consistency across modules (shared patterns)
|
||
- ✅ Testability (clear boundaries, mocking framework components)
|
||
|
||
**Disadvantages**:
|
||
- ⚠️ Framework is shared dependency (not pure bounded contexts)
|
||
- ⚠️ Module deployment still coupled via framework
|
||
- ⚠️ Requires discipline to avoid cross-domain dependencies
|
||
|
||
**Estimated Effort**: 2-3 months
|
||
**Risk Level**: LOW-MEDIUM
|
||
|
||
---
|
||
|
||
### Option 3: Keep Current Structure (Do Nothing)
|
||
|
||
**Structure**: Current 4-layer architecture without modular organization
|
||
|
||
**Advantages**:
|
||
- ✅ Zero refactoring effort
|
||
- ✅ No disruption to development
|
||
|
||
**Disadvantages**:
|
||
- ❌ Architectural violations remain (HTTP in Domain, DI in Domain)
|
||
- ❌ Poor domain organization (Domain layer too thin at 4%)
|
||
- ❌ Framework bloat (85.3% - too large)
|
||
- ❌ Technical debt accumulation
|
||
- ❌ Reduced maintainability over time
|
||
|
||
**Estimated Effort**: 0 months (no change)
|
||
**Risk Level**: HIGH (accumulating technical debt)
|
||
|
||
---
|
||
|
||
## Decision Outcome
|
||
|
||
### Chosen Option: **Option 2 - Hybrid Approach (Layers + Modules)**
|
||
|
||
**Rationale**:
|
||
|
||
1. **Framework Reusability**: 85.3% of codebase is framework infrastructure that SHOULD be shared
|
||
- Database layer (387 files) - shared across all modules
|
||
- HTTP layer (156 files) - shared routing, middleware
|
||
- Queue system (89 files) - shared background processing
|
||
- Cache layer (67 files) - shared caching infrastructure
|
||
- 40 more framework components - all reusable
|
||
|
||
2. **Clean Architecture Preserved**: Maintains dependency rule while adding modularity
|
||
- Domain depends on nothing
|
||
- Application depends on Domain
|
||
- Framework provides infrastructure
|
||
- Infrastructure implements interfaces
|
||
|
||
3. **Domain-Driven Design Benefits**: Modules represent bounded contexts
|
||
- `Domain/User/` - user bounded context
|
||
- `Domain/Order/` - order bounded context
|
||
- `Domain/Product/` - product bounded context
|
||
- Clear boundaries, no cross-domain dependencies
|
||
|
||
4. **Realistic Refactoring Timeline**: 2-3 months vs 6-12 months
|
||
- Phase 1: Priority 1 Fixes (2-3 days)
|
||
- Phase 2: Domain Layer Modularization (1-2 weeks)
|
||
- Phase 3: Application Layer Modularization (1-2 weeks)
|
||
- Phase 4: Infrastructure Consolidation (1-2 weeks)
|
||
|
||
5. **Risk Mitigation**: Incremental changes, testable at each phase
|
||
|
||
---
|
||
|
||
## Implementation Strategy
|
||
|
||
### Phase 1: Priority 1 Fixes (2-3 days) - CRITICAL
|
||
|
||
**Objective**: Fix architectural violations
|
||
|
||
**Tasks**:
|
||
|
||
1. **Move HTTP out of Domain**
|
||
```bash
|
||
# Before: src/Domain/Meta/Http/Controller/MetaAdminController.php
|
||
# After: src/Application/Admin/Controllers/Meta/MetaAdminController.php
|
||
|
||
mv src/Domain/Meta/Http/Controller/ src/Application/Admin/Controllers/Meta/
|
||
mv src/Domain/Meta/Http/Middleware/ src/Framework/Http/Middlewares/
|
||
```
|
||
|
||
2. **Move DI to Infrastructure**
|
||
```bash
|
||
mkdir -p src/Infrastructure/ServiceProviders
|
||
|
||
# Transform Initializers to ServiceProviders
|
||
mv src/Domain/Asset/DI/AssetServiceInitializer.php \
|
||
src/Infrastructure/ServiceProviders/AssetServiceProvider.php
|
||
|
||
mv src/Domain/Cms/DI/CmsServiceInitializer.php \
|
||
src/Infrastructure/ServiceProviders/CmsServiceProvider.php
|
||
|
||
# Repeat for User, Order, Console domains
|
||
```
|
||
|
||
3. **Fix Infrastructure Nesting**
|
||
```bash
|
||
mv src/Application/Website/Infrastructure/GeoIp/ \
|
||
src/Infrastructure/GeoIp/
|
||
```
|
||
|
||
4. **Update Namespaces and Imports**
|
||
- Run PSR-4 compliance check
|
||
- Update all references to moved files
|
||
- Test all affected functionality
|
||
|
||
**Success Criteria**:
|
||
- ✅ Zero architectural violations
|
||
- ✅ All tests pass
|
||
- ✅ PSR-4 compliance: 100%
|
||
|
||
**Code Example - ServiceProvider Transformation**:
|
||
|
||
```php
|
||
// Before: src/Domain/Asset/DI/AssetServiceInitializer.php
|
||
namespace App\Domain\Asset\DI;
|
||
|
||
use App\Framework\DI\Container;
|
||
use App\Framework\DI\Initializer;
|
||
|
||
final readonly class AssetServiceInitializer
|
||
{
|
||
#[Initializer]
|
||
public function __invoke(Container $container): void
|
||
{
|
||
$container->singleton(AssetRepository::class, ...);
|
||
$container->singleton(AssetService::class, ...);
|
||
}
|
||
}
|
||
|
||
// After: src/Infrastructure/ServiceProviders/AssetServiceProvider.php
|
||
namespace App\Infrastructure\ServiceProviders;
|
||
|
||
use App\Framework\DI\Container;
|
||
use App\Framework\DI\ServiceProvider;
|
||
|
||
final readonly class AssetServiceProvider implements ServiceProvider
|
||
{
|
||
public function register(Container $container): void
|
||
{
|
||
$container->singleton(AssetRepository::class, ...);
|
||
$container->singleton(AssetService::class, ...);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Phase 2: Domain Layer Modularization (1-2 weeks)
|
||
|
||
**Objective**: Organize Domain layer by bounded contexts
|
||
|
||
**Current State**:
|
||
```
|
||
src/Domain/
|
||
├── User/ (23 files) - mixed structure
|
||
├── Asset/ (18 files) - mixed structure
|
||
├── Cms/ (15 files) - mixed structure
|
||
├── Order/ (29 files) - mixed structure
|
||
└── Product/ (12 files) - mixed structure
|
||
```
|
||
|
||
**Target State**:
|
||
```
|
||
src/Domain/
|
||
├── User/ # User bounded context
|
||
│ ├── Entity/
|
||
│ │ └── User.php
|
||
│ ├── ValueObject/
|
||
│ │ ├── UserId.php
|
||
│ │ ├── Email.php
|
||
│ │ └── UserName.php
|
||
│ ├── Event/
|
||
│ │ ├── UserRegisteredEvent.php
|
||
│ │ └── UserUpdatedEvent.php
|
||
│ ├── Repository/
|
||
│ │ └── UserRepositoryInterface.php # Interface only!
|
||
│ └── Service/
|
||
│ └── UserService.php
|
||
│
|
||
├── Order/ # Order bounded context
|
||
│ ├── Entity/
|
||
│ │ ├── Order.php
|
||
│ │ └── OrderItem.php
|
||
│ ├── ValueObject/
|
||
│ │ ├── OrderId.php
|
||
│ │ ├── OrderStatus.php
|
||
│ │ └── Money.php
|
||
│ ├── Event/
|
||
│ │ ├── OrderCreatedEvent.php
|
||
│ │ └── OrderCompletedEvent.php
|
||
│ ├── Repository/
|
||
│ │ └── OrderRepositoryInterface.php
|
||
│ └── Service/
|
||
│ └── OrderService.php
|
||
│
|
||
└── Product/ # Product bounded context
|
||
├── Entity/
|
||
├── ValueObject/
|
||
├── Event/
|
||
├── Repository/
|
||
└── Service/
|
||
```
|
||
|
||
**Benefits**:
|
||
- Clear bounded contexts
|
||
- Self-documenting structure
|
||
- Easy to locate domain logic
|
||
- No cross-domain dependencies
|
||
- Repository interfaces (implementations in Infrastructure)
|
||
|
||
---
|
||
|
||
### Phase 3: Application Layer Modularization (1-2 weeks)
|
||
|
||
**Objective**: Align Application layer with Domain modules
|
||
|
||
**Current State**:
|
||
```
|
||
src/Application/
|
||
├── Website/ (89 files) - feature-based
|
||
├── Admin/ (234 files) - feature-based
|
||
├── Api/ (67 files) - feature-based
|
||
├── Contact/ (8 files) - feature-based
|
||
└── Console/ (22 files) - feature-based
|
||
```
|
||
|
||
**Target State**:
|
||
```
|
||
src/Application/
|
||
├── User/ # Aligned with Domain/User
|
||
│ ├── Controller/
|
||
│ │ └── UserController.php
|
||
│ ├── Command/
|
||
│ │ ├── CreateUserCommand.php
|
||
│ │ └── CreateUserHandler.php
|
||
│ └── Query/
|
||
│ ├── GetUserQuery.php
|
||
│ └── GetUserHandler.php
|
||
│
|
||
├── Order/ # Aligned with Domain/Order
|
||
│ ├── Controller/
|
||
│ ├── Command/
|
||
│ └── Query/
|
||
│
|
||
├── Product/ # Aligned with Domain/Product
|
||
│ ├── Controller/
|
||
│ ├── Command/
|
||
│ └── Query/
|
||
│
|
||
└── Admin/ # Cross-cutting feature module
|
||
├── Analytics/
|
||
├── Content/
|
||
├── Infrastructure/
|
||
├── Notifications/
|
||
└── System/
|
||
```
|
||
|
||
**Benefits**:
|
||
- Clear mapping: Application/User → Domain/User
|
||
- CQRS patterns (Command/Query separation)
|
||
- Feature-based organization where appropriate (Admin)
|
||
- Reduced cognitive load
|
||
|
||
---
|
||
|
||
### Phase 4: Infrastructure Consolidation (1-2 weeks)
|
||
|
||
**Objective**: Centralize infrastructure implementations
|
||
|
||
**Target State**:
|
||
```
|
||
src/Infrastructure/
|
||
├── ServiceProviders/ # DI setup per module
|
||
│ ├── UserServiceProvider.php
|
||
│ ├── OrderServiceProvider.php
|
||
│ └── ProductServiceProvider.php
|
||
│
|
||
├── Persistence/ # Database implementations
|
||
│ ├── User/
|
||
│ │ └── DatabaseUserRepository.php # Implements Domain/User/Repository/UserRepositoryInterface
|
||
│ ├── Order/
|
||
│ │ └── DatabaseOrderRepository.php
|
||
│ └── Product/
|
||
│ └── DatabaseProductRepository.php
|
||
│
|
||
├── GeoIp/ # External service integrations
|
||
│ ├── GeoIpService.php
|
||
│ └── MaxMindProvider.php
|
||
│
|
||
└── Email/ # External service integrations
|
||
├── EmailService.php
|
||
└── SmtpProvider.php
|
||
```
|
||
|
||
**Benefits**:
|
||
- Infrastructure implementations separated from Domain interfaces
|
||
- Clear dependency inversion (Domain → Interface ← Infrastructure)
|
||
- Easy to swap implementations (testing, different databases)
|
||
- ServiceProviders wire up dependencies
|
||
|
||
---
|
||
|
||
## Consequences
|
||
|
||
### Positive Consequences
|
||
|
||
1. **Clean Architecture Compliance**
|
||
- All layers respect Dependency Rule
|
||
- Domain layer pure (no framework dependencies)
|
||
- Testability improved (mock infrastructure easily)
|
||
|
||
2. **Domain-Driven Design Benefits**
|
||
- Clear bounded contexts (User, Order, Product)
|
||
- Domain logic focused and cohesive
|
||
- Business rules visible and maintainable
|
||
|
||
3. **Framework Preservation**
|
||
- No code duplication (Framework shared)
|
||
- Single framework to maintain
|
||
- Consistency across all modules
|
||
- Framework investment preserved (3,883 files)
|
||
|
||
4. **Maintainability**
|
||
- Clear structure reduces cognitive load
|
||
- Easy to locate code (Domain/User/Entity/User.php)
|
||
- Self-documenting architecture
|
||
- Onboarding new developers easier
|
||
|
||
5. **Realistic Timeline**
|
||
- 2-3 months total effort
|
||
- Incremental changes (low risk)
|
||
- Testable at each phase
|
||
|
||
### Negative Consequences (Mitigated)
|
||
|
||
1. **Framework is Shared Dependency**
|
||
- **Mitigation**: Framework is stable, well-tested infrastructure
|
||
- **Trade-off**: Accepting this trade-off for massive code reuse benefit
|
||
|
||
2. **Module Deployment Coupling**
|
||
- **Mitigation**: Not pursuing microservices architecture (monolith is appropriate)
|
||
- **Trade-off**: Deployment simplicity vs independent module deployment
|
||
|
||
3. **Discipline Required**
|
||
- **Mitigation**: Code review process, automated PSR-4 checks, architectural tests
|
||
- **Trade-off**: Team training vs long-term maintainability
|
||
|
||
---
|
||
|
||
## Comparison Matrix
|
||
|
||
| Aspect | Pure Modules | Hybrid (Recommended) | Keep Current |
|
||
|--------|-------------|---------------------|--------------|
|
||
| **Clean Architecture** | ✅ Yes | ✅ Yes | ❌ Violations exist |
|
||
| **DDD Bounded Contexts** | ✅ Pure | ✅ Practical | ❌ Weak |
|
||
| **Framework Duplication** | ❌ Massive (85.3% × N) | ✅ None (shared) | ✅ None |
|
||
| **Code Reuse** | ❌ Low (per-module) | ✅ High (shared) | ✅ High |
|
||
| **Refactoring Effort** | ❌ 6-12 months | ✅ 2-3 months | ✅ 0 months |
|
||
| **Risk Level** | ❌ HIGH | ✅ LOW-MEDIUM | ❌ HIGH (debt) |
|
||
| **Team Autonomy** | ✅ Full (per module) | ⚠️ Partial | ❌ None |
|
||
| **Deploy Independence** | ✅ Yes (microservices) | ❌ No (monolith) | ❌ No |
|
||
| **Maintenance Burden** | ❌ High (N frameworks) | ✅ Low (1 framework) | ⚠️ Medium |
|
||
| **Architectural Quality** | ✅ A+ (pure DDD) | ✅ A (pragmatic) | ❌ C+ (violations) |
|
||
|
||
**Scoring**:
|
||
- Pure Modules: 5/10 (ideal theory, impractical for this project)
|
||
- **Hybrid: 9/10 (recommended - pragmatic and effective)**
|
||
- Keep Current: 3/10 (accumulating technical debt)
|
||
|
||
---
|
||
|
||
## Migration Risks & Mitigation
|
||
|
||
### Risk 1: Breaking Changes During Refactoring
|
||
|
||
**Probability**: MEDIUM
|
||
**Impact**: HIGH
|
||
|
||
**Mitigation**:
|
||
- Comprehensive test suite BEFORE starting
|
||
- Feature flags for gradual rollout
|
||
- Incremental changes with continuous testing
|
||
- Rollback plan for each phase
|
||
|
||
### Risk 2: Team Resistance to Change
|
||
|
||
**Probability**: LOW
|
||
**Impact**: MEDIUM
|
||
|
||
**Mitigation**:
|
||
- Document rationale (this document)
|
||
- Training sessions on new structure
|
||
- Gradual transition (4 phases over 2-3 months)
|
||
- Team involvement in decision-making
|
||
|
||
### Risk 3: Overlooked Dependencies
|
||
|
||
**Probability**: MEDIUM
|
||
**Impact**: MEDIUM
|
||
|
||
**Mitigation**:
|
||
- Automated dependency analysis
|
||
- PSR-4 compliance checks
|
||
- Static analysis tools (PHPStan)
|
||
- Code review process for all moves
|
||
|
||
### Risk 4: Timeline Overrun
|
||
|
||
**Probability**: LOW
|
||
**Impact**: MEDIUM
|
||
|
||
**Mitigation**:
|
||
- Conservative time estimates (2-3 months)
|
||
- Phase-based approach (can pause between phases)
|
||
- Automated tooling for bulk operations
|
||
- Dedicated refactoring time (not mixed with feature work)
|
||
|
||
---
|
||
|
||
## Success Metrics
|
||
|
||
### Phase 1 Completion (Priority 1 Fixes)
|
||
- ✅ Zero architectural violations
|
||
- ✅ All tests pass (100%)
|
||
- ✅ PSR-4 compliance: 100%
|
||
- ✅ No HTTP code in Domain layer
|
||
- ✅ No DI code in Domain layer
|
||
- ✅ Infrastructure at top level (not nested)
|
||
|
||
### Phase 2-4 Completion (Full Migration)
|
||
- ✅ Domain layer: 15-20% of codebase (from 4%)
|
||
- ✅ Framework layer: 40-50% of codebase (from 85.3%)
|
||
- ✅ Clear bounded contexts in Domain layer
|
||
- ✅ Application layer aligned with Domain modules
|
||
- ✅ Infrastructure ServiceProviders per module
|
||
- ✅ Overall Architecture Grade: A- (90%) (from C+ 75%)
|
||
|
||
### Long-Term Success
|
||
- ✅ Reduced time to locate code (faster development)
|
||
- ✅ Easier onboarding (clear structure)
|
||
- ✅ Fewer architectural violations (ongoing)
|
||
- ✅ Higher test coverage (better boundaries)
|
||
- ✅ Improved maintainability (team feedback)
|
||
|
||
---
|
||
|
||
## Code Examples
|
||
|
||
### Example 1: Domain Module (User Bounded Context)
|
||
|
||
**Before Migration**:
|
||
```
|
||
src/Domain/User/
|
||
├── User.php # Mixed: Entity + logic
|
||
├── UserRepository.php # Confusion: Interface or implementation?
|
||
├── UserService.php # Service
|
||
└── DI/
|
||
└── UserServiceInitializer.php # WRONG: DI in Domain!
|
||
```
|
||
|
||
**After Migration**:
|
||
```
|
||
src/Domain/User/
|
||
├── Entity/
|
||
│ └── User.php # Pure entity
|
||
├── ValueObject/
|
||
│ ├── UserId.php # Type-safe ID
|
||
│ ├── Email.php # Validated email
|
||
│ └── UserName.php # Validated name
|
||
├── Event/
|
||
│ └── UserRegisteredEvent.php # Domain event
|
||
├── Repository/
|
||
│ └── UserRepositoryInterface.php # Interface only!
|
||
└── Service/
|
||
└── UserService.php # Domain service
|
||
```
|
||
|
||
**Domain Entity** (Pure business logic):
|
||
```php
|
||
// src/Domain/User/Entity/User.php
|
||
namespace App\Domain\User\Entity;
|
||
|
||
use App\Domain\User\ValueObject\{UserId, Email, UserName};
|
||
use App\Domain\User\Event\UserRegisteredEvent;
|
||
|
||
final readonly class User
|
||
{
|
||
public function __construct(
|
||
public UserId $id,
|
||
public Email $email,
|
||
public UserName $name,
|
||
public bool $active = true
|
||
) {}
|
||
|
||
public static function register(Email $email, UserName $name): self
|
||
{
|
||
$user = new self(
|
||
id: UserId::generate(),
|
||
email: $email,
|
||
name: $name
|
||
);
|
||
|
||
// Domain event - no framework dependency!
|
||
DomainEvents::raise(new UserRegisteredEvent($user));
|
||
|
||
return $user;
|
||
}
|
||
|
||
public function deactivate(): self
|
||
{
|
||
return new self(
|
||
id: $this->id,
|
||
email: $this->email,
|
||
name: $this->name,
|
||
active: false
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
**Repository Interface** (Domain layer):
|
||
```php
|
||
// src/Domain/User/Repository/UserRepositoryInterface.php
|
||
namespace App\Domain\User\Repository;
|
||
|
||
use App\Domain\User\Entity\User;
|
||
use App\Domain\User\ValueObject\{UserId, Email};
|
||
|
||
interface UserRepositoryInterface
|
||
{
|
||
public function find(UserId $id): ?User;
|
||
public function findByEmail(Email $email): ?User;
|
||
public function save(User $user): void;
|
||
public function delete(User $user): void;
|
||
}
|
||
```
|
||
|
||
**Repository Implementation** (Infrastructure layer):
|
||
```php
|
||
// src/Infrastructure/Persistence/User/DatabaseUserRepository.php
|
||
namespace App\Infrastructure\Persistence\User;
|
||
|
||
use App\Domain\User\Repository\UserRepositoryInterface;
|
||
use App\Domain\User\Entity\User;
|
||
use App\Framework\Database\EntityManager;
|
||
|
||
final readonly class DatabaseUserRepository implements UserRepositoryInterface
|
||
{
|
||
public function __construct(
|
||
private EntityManager $entityManager
|
||
) {}
|
||
|
||
public function find(UserId $id): ?User
|
||
{
|
||
return $this->entityManager->find(User::class, $id->value);
|
||
}
|
||
|
||
public function save(User $user): void
|
||
{
|
||
$this->entityManager->save($user);
|
||
}
|
||
}
|
||
```
|
||
|
||
**ServiceProvider** (Infrastructure layer - wires dependencies):
|
||
```php
|
||
// src/Infrastructure/ServiceProviders/UserServiceProvider.php
|
||
namespace App\Infrastructure\ServiceProviders;
|
||
|
||
use App\Framework\DI\Container;
|
||
use App\Domain\User\Repository\UserRepositoryInterface;
|
||
use App\Infrastructure\Persistence\User\DatabaseUserRepository;
|
||
|
||
final readonly class UserServiceProvider
|
||
{
|
||
public function register(Container $container): void
|
||
{
|
||
// Wire Domain interface → Infrastructure implementation
|
||
$container->singleton(
|
||
UserRepositoryInterface::class,
|
||
fn() => new DatabaseUserRepository(
|
||
$container->get(EntityManager::class)
|
||
)
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Example 2: Application Layer (CQRS Pattern)
|
||
|
||
**Structure**:
|
||
```
|
||
src/Application/User/
|
||
├── Controller/
|
||
│ └── UserController.php
|
||
├── Command/ # Write operations
|
||
│ ├── CreateUserCommand.php
|
||
│ └── CreateUserHandler.php
|
||
└── Query/ # Read operations
|
||
├── GetUserQuery.php
|
||
└── GetUserHandler.php
|
||
```
|
||
|
||
**Controller** (Thin - delegates to Command/Query handlers):
|
||
```php
|
||
// src/Application/User/Controller/UserController.php
|
||
namespace App\Application\User\Controller;
|
||
|
||
use App\Framework\Attributes\Route;
|
||
use App\Framework\Http\{Method, JsonResult, Status};
|
||
use App\Application\User\Command\{CreateUserCommand, CreateUserHandler};
|
||
use App\Application\User\Query\{GetUserQuery, GetUserHandler};
|
||
|
||
final readonly class UserController
|
||
{
|
||
public function __construct(
|
||
private CreateUserHandler $createHandler,
|
||
private GetUserHandler $getHandler
|
||
) {}
|
||
|
||
#[Route(path: '/api/users', method: Method::POST)]
|
||
public function create(CreateUserRequest $request): JsonResult
|
||
{
|
||
$command = new CreateUserCommand(
|
||
$request->email,
|
||
$request->name
|
||
);
|
||
|
||
$user = $this->createHandler->handle($command);
|
||
|
||
return new JsonResult(
|
||
['id' => $user->id->value],
|
||
status: Status::CREATED
|
||
);
|
||
}
|
||
|
||
#[Route(path: '/api/users/{id}', method: Method::GET)]
|
||
public function get(string $id): JsonResult
|
||
{
|
||
$query = new GetUserQuery(new UserId($id));
|
||
$user = $this->getHandler->handle($query);
|
||
|
||
return new JsonResult($user->toArray());
|
||
}
|
||
}
|
||
```
|
||
|
||
**Command Handler** (Business logic):
|
||
```php
|
||
// src/Application/User/Command/CreateUserHandler.php
|
||
namespace App\Application\User\Command;
|
||
|
||
use App\Domain\User\Entity\User;
|
||
use App\Domain\User\Repository\UserRepositoryInterface;
|
||
|
||
final readonly class CreateUserHandler
|
||
{
|
||
public function __construct(
|
||
private UserRepositoryInterface $repository
|
||
) {}
|
||
|
||
public function handle(CreateUserCommand $command): User
|
||
{
|
||
// Domain logic
|
||
$user = User::register($command->email, $command->name);
|
||
|
||
// Persistence via Domain interface
|
||
$this->repository->save($user);
|
||
|
||
return $user;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Alternative Approaches Considered
|
||
|
||
### Alternative 1: Microservices Architecture
|
||
|
||
**Why Rejected**:
|
||
- Premature optimization for current scale
|
||
- Operational complexity (deployment, monitoring, debugging)
|
||
- Network latency overhead
|
||
- Distributed transaction challenges
|
||
- Team not structured for microservices (no dedicated platform team)
|
||
|
||
### Alternative 2: Event Sourcing + CQRS (Full)
|
||
|
||
**Why Rejected**:
|
||
- Significant complexity increase
|
||
- Learning curve for team
|
||
- Infrastructure requirements (event store)
|
||
- Eventual consistency challenges
|
||
- Overkill for current business requirements
|
||
|
||
### Alternative 3: Module-per-Repository (Monorepo)
|
||
|
||
**Why Rejected**:
|
||
- Still duplicates Framework code per repository
|
||
- Tooling complexity (cross-repo dependencies)
|
||
- Versioning challenges
|
||
- Overhead not justified for current team size
|
||
|
||
---
|
||
|
||
## Related Decisions
|
||
|
||
- **ADR-001**: Use Custom PHP Framework (already decided)
|
||
- **ADR-002**: Adopt Clean Architecture principles (already decided)
|
||
- **ADR-003**: Use Composition over Inheritance (already decided)
|
||
- **ADR-004**: Value Objects over Primitives (already decided)
|
||
- **ADR-005**: Attribute-based Discovery (already decided)
|
||
|
||
**This Decision**: **ADR-006 - Hybrid Layer-Module Architecture**
|
||
|
||
---
|
||
|
||
## References
|
||
|
||
### Internal Documents
|
||
- `docs/SRC-ARCHITECTURE-ANALYSIS.md` - Current architecture analysis
|
||
- `docs/DOCUMENTATION-ANALYSIS.md` - Documentation structure
|
||
- `docs/claude/architecture.md` - Framework architecture guide
|
||
- `docs/claude/guidelines.md` - Development guidelines
|
||
|
||
### Clean Architecture
|
||
- "Clean Architecture" by Robert C. Martin
|
||
- Dependency Rule: Dependencies point inward (Domain → Application → Framework)
|
||
- Testability: Inner layers testable without outer layers
|
||
|
||
### Domain-Driven Design
|
||
- "Domain-Driven Design" by Eric Evans
|
||
- Bounded Contexts: Clear boundaries between domains
|
||
- Ubiquitous Language: Domain-specific terminology
|
||
- Aggregates: Consistency boundaries
|
||
|
||
### Framework Patterns
|
||
- Martin Fowler's Enterprise Application Patterns
|
||
- Repository Pattern: Data access abstraction
|
||
- CQRS: Command-Query Responsibility Segregation
|
||
- Event-Driven Architecture: Loose coupling via events
|
||
|
||
---
|
||
|
||
## Timeline & Next Steps
|
||
|
||
### Immediate Next Steps (Week 1)
|
||
|
||
**Day 1-2**: Phase 1 - Priority 1 Fixes
|
||
- Move HTTP out of Domain (2 hours)
|
||
- Move DI to Infrastructure (4 hours)
|
||
- Fix Infrastructure nesting (1 hour)
|
||
- Update namespaces (3 hours)
|
||
- **Total**: 10 hours
|
||
|
||
**Day 3**: Testing & Validation
|
||
- Run full test suite
|
||
- PSR-4 compliance check
|
||
- Architectural violation check
|
||
- **Total**: 4 hours
|
||
|
||
### Short-Term (Weeks 2-4): Phase 2 - Domain Modularization
|
||
- Organize User domain (1 week)
|
||
- Organize Order domain (1 week)
|
||
- Organize Product, Asset, Cms domains (1 week)
|
||
|
||
### Mid-Term (Weeks 5-8): Phase 3 - Application Modularization
|
||
- Restructure User application layer (1 week)
|
||
- Restructure Order application layer (1 week)
|
||
- Restructure Admin feature module (2 weeks)
|
||
|
||
### Long-Term (Weeks 9-12): Phase 4 - Infrastructure Consolidation
|
||
- Create ServiceProviders (1 week)
|
||
- Organize Persistence layer (1 week)
|
||
- Migrate external service integrations (1 week)
|
||
- Final testing & documentation (1 week)
|
||
|
||
**Total Timeline**: 12 weeks (3 months) - conservative estimate
|
||
|
||
---
|
||
|
||
## Approval & Sign-Off
|
||
|
||
**Decision Maker**: Development Team Lead
|
||
**Date**: 2025-01-28
|
||
**Status**: Recommended for Approval
|
||
|
||
**Review Required By**:
|
||
- [ ] Technical Architect
|
||
- [ ] Development Team
|
||
- [ ] Product Owner
|
||
- [ ] CTO
|
||
|
||
**Implementation Start**: After approval
|
||
**Estimated Completion**: 3 months from start
|
||
|
||
---
|
||
|
||
## Appendix A: Framework Layer Breakdown
|
||
|
||
**Why Framework Must Be Shared** (3,883 files - 85.3%):
|
||
|
||
```
|
||
src/Framework/
|
||
├── Core/ (245 files) - Application bootstrap, routing, DI
|
||
├── Database/ (387 files) - EntityManager, Schema Builder, Migrations
|
||
├── Http/ (156 files) - Request/Response, Middleware, Routing
|
||
├── Queue/ (89 files) - Job processing, Workers, Queue drivers
|
||
├── Cache/ (67 files) - Cache abstraction, Drivers (Redis, File)
|
||
├── ExceptionHandling/ (124 files) - Error handling, Exception types
|
||
├── Discovery/ (78 files) - Attribute discovery, Convention scanning
|
||
├── View/ (201 files) - Template engine, Components
|
||
├── Events/ (95 files) - Event dispatcher, Domain events
|
||
├── Console/ (167 files) - CLI commands, TUI components
|
||
├── Validation/ (54 files) - Input validation, Rules
|
||
├── Security/ (89 files) - Authentication, Authorization, WAF
|
||
├── Logging/ (48 files) - Logger, Handlers
|
||
├── MCP/ (123 files) - Model Context Protocol (AI integration)
|
||
└── [28 more components] (1,960 files)
|
||
|
||
Total: 3,883 files
|
||
```
|
||
|
||
**Duplication Impact**: If pure module approach chosen, each module would duplicate most of these 3,883 files, resulting in:
|
||
- 5 modules × 3,883 files = **19,415 files** (vs current 3,883)
|
||
- Maintenance nightmare: Update framework → update 5 copies
|
||
- Consistency issues: Framework versions drift across modules
|
||
- Lost investment: Years of framework development duplicated
|
||
|
||
**Conclusion**: Framework MUST be shared infrastructure, not duplicated per module.
|
||
|
||
---
|
||
|
||
## Appendix B: PSR-4 Mapping
|
||
|
||
**After Migration**:
|
||
|
||
```json
|
||
{
|
||
"autoload": {
|
||
"psr-4": {
|
||
"App\\Domain\\": "src/Domain/",
|
||
"App\\Application\\": "src/Application/",
|
||
"App\\Framework\\": "src/Framework/",
|
||
"App\\Infrastructure\\": "src/Infrastructure/"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Example Mappings**:
|
||
```
|
||
Namespace → File
|
||
App\Domain\User\Entity\User → src/Domain/User/Entity/User.php
|
||
App\Domain\User\Repository\UserRepositoryInterface → src/Domain/User/Repository/UserRepositoryInterface.php
|
||
App\Application\User\Command\CreateUserCommand → src/Application/User/Command/CreateUserCommand.php
|
||
App\Infrastructure\Persistence\User\DatabaseUserRepository → src/Infrastructure/Persistence/User/DatabaseUserRepository.php
|
||
App\Framework\Database\EntityManager → src/Framework/Database/EntityManager.php
|
||
```
|
||
|
||
**Validation**:
|
||
```bash
|
||
# Verify PSR-4 compliance
|
||
composer dump-autoload --optimize
|
||
composer validate --strict
|
||
|
||
# Check for PSR-4 violations
|
||
./vendor/bin/phpstan analyze --level=8
|
||
```
|
||
|
||
---
|
||
|
||
**End of Document**
|
||
|
||
**Version**: 1.0
|
||
**Last Updated**: 2025-01-28
|
||
**Authors**: Architecture Team
|
||
**Reviewers**: Development Team
|