401 lines
16 KiB
Markdown
401 lines
16 KiB
Markdown
# Clean Architecture Violation Fix Plan
|
|
|
|
## Executive Summary
|
|
|
|
**Problem**: The codebase violates Clean Architecture by having use cases call presenters directly (`this.output.present()`), creating tight coupling and causing "Presenter not presented" errors.
|
|
|
|
**Root Cause**: Use cases are doing the presenter's job instead of returning data and letting controllers handle the wiring.
|
|
|
|
**Solution**: Remove ALL `.present()` calls from use cases. Use cases return Results. Controllers wire Results to Presenters.
|
|
|
|
---
|
|
|
|
## The Violation Pattern
|
|
|
|
### ❌ Current Wrong Pattern (Violates Clean Architecture)
|
|
```typescript
|
|
// core/racing/application/use-cases/GetRaceDetailUseCase.ts
|
|
class GetRaceDetailUseCase {
|
|
constructor(
|
|
private repositories: any,
|
|
private output: UseCaseOutputPort<GetRaceDetailResult> // ❌ Wrong
|
|
) {}
|
|
|
|
async execute(input: GetRaceDetailInput): Promise<Result<void, ApplicationError>> {
|
|
const race = await this.raceRepository.findById(input.raceId);
|
|
|
|
if (!race) {
|
|
const result = Result.err({ code: 'RACE_NOT_FOUND', details: {...} });
|
|
this.output.present(result); // ❌ WRONG: Use case calling presenter
|
|
return result;
|
|
}
|
|
|
|
const result = Result.ok({ race, league, registrations, drivers, userResult, isUserRegistered, canRegister });
|
|
this.output.present(result); // ❌ WRONG: Use case calling presenter
|
|
return result;
|
|
}
|
|
}
|
|
```
|
|
|
|
### ✅ Correct Pattern (Clean Architecture)
|
|
```typescript
|
|
// core/racing/application/use-cases/GetRaceDetailUseCase.ts
|
|
class GetRaceDetailUseCase {
|
|
constructor(
|
|
private repositories: any,
|
|
// NO output port - removed
|
|
) {}
|
|
|
|
async execute(input: GetRaceDetailInput): Promise<Result<GetRaceDetailResult, ApplicationError>> {
|
|
const race = await this.raceRepository.findById(input.raceId);
|
|
|
|
if (!race) {
|
|
return Result.err({ code: 'RACE_NOT_FOUND', details: {...} });
|
|
// ✅ No .present() call
|
|
}
|
|
|
|
return Result.ok({ race, league, registrations, drivers, userResult, isUserRegistered, canRegister });
|
|
// ✅ No .present() call
|
|
}
|
|
}
|
|
|
|
// apps/api/src/domain/race/RaceService.ts (Controller layer)
|
|
class RaceService {
|
|
constructor(
|
|
private getRaceDetailUseCase: GetRaceDetailUseCase,
|
|
private raceDetailPresenter: RaceDetailPresenter,
|
|
) {}
|
|
|
|
async getRaceDetail(params: GetRaceDetailParamsDTO): Promise<RaceDetailPresenter> {
|
|
const result = await this.getRaceDetailUseCase.execute(params);
|
|
|
|
if (result.isErr()) {
|
|
throw new NotFoundException(result.error.details.message);
|
|
}
|
|
|
|
this.raceDetailPresenter.present(result.value); // ✅ Controller wires to presenter
|
|
return this.raceDetailPresenter;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## What Needs To Be Done
|
|
|
|
### Phase 1: Fix Use Cases (Remove Output Ports)
|
|
|
|
**Files to modify in `core/racing/application/use-cases/`:**
|
|
|
|
1. **GetRaceDetailUseCase.ts** (lines 35-44, 46-115)
|
|
- Remove `output: UseCaseOutputPort<GetRaceDetailResult>` from constructor
|
|
- Change return type from `Promise<Result<void, ApplicationError>>` to `Promise<Result<GetRaceDetailResult, ApplicationError>>`
|
|
- Remove all `this.output.present()` calls (lines 100, 109-112)
|
|
|
|
2. **GetRaceRegistrationsUseCase.ts** (lines 27-29, 31-70)
|
|
- Remove output port from constructor
|
|
- Change return type
|
|
- Remove `this.output.present()` calls (lines 40-43, 66-69)
|
|
|
|
3. **GetLeagueFullConfigUseCase.ts** (lines 35-37, 39-92)
|
|
- Remove output port from constructor
|
|
- Change return type
|
|
- Remove `this.output.present()` calls (lines 47-50, 88-91)
|
|
|
|
4. **GetRaceWithSOFUseCase.ts** (lines 43-45, 47-118)
|
|
- Remove output port from constructor
|
|
- Change return type
|
|
- Remove `this.output.present()` calls (lines 58-61, 114-117)
|
|
|
|
5. **GetRaceResultsDetailUseCase.ts** (lines 41-43, 45-100)
|
|
- Remove output port from constructor
|
|
- Change return type
|
|
- Remove `this.output.present()` calls (lines 56-59, 95-98)
|
|
|
|
**Continue this pattern for ALL 150+ use cases listed in your original analysis.**
|
|
|
|
### Phase 2: Fix Controllers/Services (Add Wiring Logic)
|
|
|
|
**Files to modify in `apps/api/src/domain/`:**
|
|
|
|
1. **RaceService.ts** (lines 135-139)
|
|
- Update `getRaceDetail()` to wire use case result to presenter
|
|
- Add error handling for Result.Err cases
|
|
|
|
2. **RaceProviders.ts** (lines 138-144, 407-437)
|
|
- Remove adapter classes that wrap presenters
|
|
- Update provider factories to inject presenters directly to controllers
|
|
- Remove `RaceDetailOutputAdapter` and similar classes
|
|
|
|
3. **All other service files** that use use cases
|
|
- Update method signatures to handle Results
|
|
- Add proper error mapping
|
|
- Wire results to presenters
|
|
|
|
### Phase 3: Update Module Wiring
|
|
|
|
**Files to modify:**
|
|
|
|
1. **RaceProviders.ts** (lines 287-779)
|
|
- Remove all adapter classes (lines 111-285)
|
|
- Update provider definitions to not use adapters
|
|
- Simplify dependency injection
|
|
|
|
2. **All other provider files** in `apps/api/src/domain/*/`
|
|
- Remove adapter patterns
|
|
- Update DI containers
|
|
|
|
### Phase 4: Fix Presenters (If Needed)
|
|
|
|
**Some presenters may need updates:**
|
|
|
|
1. **RaceDetailPresenter.ts** (lines 15-26, 28-114)
|
|
- Ensure `present()` method accepts `GetRaceDetailResult` directly
|
|
- No changes needed if already correct
|
|
|
|
2. **CommandResultPresenter.ts** and similar
|
|
- Ensure they work with Results from controllers, not use cases
|
|
|
|
---
|
|
|
|
## Implementation Checklist
|
|
|
|
### For Each Use Case File:
|
|
- [ ] Remove `output: UseCaseOutputPort<T>` from constructor
|
|
- [ ] Change return type from `Promise<Result<void, E>>` to `Promise<Result<T, E>>`
|
|
- [ ] Remove all `this.output.present()` calls
|
|
- [ ] Return Result directly
|
|
- [ ] Update imports if needed
|
|
|
|
### For Each Controller/Service File:
|
|
- [ ] Update methods to call use case and get Result
|
|
- [ ] Add `if (result.isErr())` error handling
|
|
- [ ] Call `presenter.present(result.value)` after success
|
|
- [ ] Return presenter or ViewModel
|
|
- [ ] Remove adapter usage
|
|
|
|
### For Each Provider File:
|
|
- [ ] Remove adapter classes
|
|
- [ ] Update DI to inject presenters to controllers
|
|
- [ ] Simplify provider definitions
|
|
|
|
---
|
|
|
|
## Files That Need Immediate Attention
|
|
|
|
### High Priority (Core Racing Domain):
|
|
```
|
|
core/racing/application/use-cases/GetRaceDetailUseCase.ts
|
|
core/racing/application/use-cases/GetRaceRegistrationsUseCase.ts
|
|
core/racing/application/use-cases/GetLeagueFullConfigUseCase.ts
|
|
core/racing/application/use-cases/GetRaceWithSOFUseCase.ts
|
|
core/racing/application/use-cases/GetRaceResultsDetailUseCase.ts
|
|
core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.ts
|
|
core/racing/application/use-cases/CompleteRaceUseCase.ts
|
|
core/racing/application/use-cases/ApplyPenaltyUseCase.ts
|
|
core/racing/application/use-cases/JoinLeagueUseCase.ts
|
|
core/racing/application/use-cases/JoinTeamUseCase.ts
|
|
core/racing/application/use-cases/RegisterForRaceUseCase.ts
|
|
core/racing/application/use-cases/WithdrawFromRaceUseCase.ts
|
|
core/racing/application/use-cases/CancelRaceUseCase.ts
|
|
core/racing/application/use-cases/ReopenRaceUseCase.ts
|
|
core/racing/application/use-cases/CompleteRaceUseCaseWithRatings.ts
|
|
core/racing/application/use-cases/ImportRaceResultsUseCase.ts
|
|
core/racing/application/use-cases/ImportRaceResultsApiUseCase.ts
|
|
core/racing/application/use-cases/FileProtestUseCase.ts
|
|
core/racing/application/use-cases/ReviewProtestUseCase.ts
|
|
core/racing/application/use-cases/QuickPenaltyUseCase.ts
|
|
core/racing/application/use-cases/ApplyForSponsorshipUseCase.ts
|
|
core/racing/application/use-cases/AcceptSponsorshipRequestUseCase.ts
|
|
core/racing/application/use-cases/RejectSponsorshipRequestUseCase.ts
|
|
core/racing/application/use-cases/GetSponsorDashboardUseCase.ts
|
|
core/racing/application/use-cases/GetSponsorSponsorshipsUseCase.ts
|
|
core/racing/application/use-cases/GetSponsorshipPricingUseCase.ts
|
|
core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase.ts
|
|
core/racing/application/use-cases/GetSeasonSponsorshipsUseCase.ts
|
|
core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase.ts
|
|
core/racing/application/use-cases/GetLeagueWalletUseCase.ts
|
|
core/racing/application/use-cases/WithdrawFromLeagueWalletUseCase.ts
|
|
core/racing/application/use-cases/GetLeagueStatsUseCase.ts
|
|
core/racing/application/use-cases/GetLeagueMembershipsUseCase.ts
|
|
core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.ts
|
|
core/racing/application/use-cases/GetTeamJoinRequestsUseCase.ts
|
|
core/racing/application/use-cases/GetLeagueRosterMembersUseCase.ts
|
|
core/racing/application/use-cases/GetLeagueRosterJoinRequestsUseCase.ts
|
|
core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.ts
|
|
core/racing/application/use-cases/RejectLeagueJoinRequestUseCase.ts
|
|
core/racing/application/use-cases/UpdateLeagueMemberRoleUseCase.ts
|
|
core/racing/application/use-cases/RemoveLeagueMemberUseCase.ts
|
|
core/racing/application/use-cases/TransferLeagueOwnershipUseCase.ts
|
|
core/racing/application/use-cases/GetLeagueAdminUseCase.ts
|
|
core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase.ts
|
|
core/racing/application/use-cases/GetLeagueScoringConfigUseCase.ts
|
|
core/racing/application/use-cases/ListLeagueScoringPresetsUseCase.ts
|
|
core/racing/application/use-cases/GetLeagueScheduleUseCase.ts
|
|
core/racing/application/use-cases/CreateLeagueSeasonScheduleRaceUseCase.ts
|
|
core/racing/application/use-cases/UpdateLeagueSeasonScheduleRaceUseCase.ts
|
|
core/racing/application/use-cases/DeleteLeagueSeasonScheduleRaceUseCase.ts
|
|
core/racing/application/use-cases/PublishLeagueSeasonScheduleUseCase.ts
|
|
core/racing/application/use-cases/UnpublishLeagueSeasonScheduleUseCase.ts
|
|
core/racing/application/use-cases/PreviewLeagueScheduleUseCase.ts
|
|
core/racing/application/use-cases/GetSeasonDetailsUseCase.ts
|
|
core/racing/application/use-cases/ListSeasonsForLeagueUseCase.ts
|
|
core/racing/application/use-cases/CreateSeasonForLeagueUseCase.ts
|
|
core/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase.ts
|
|
core/racing/application/use-cases/ManageSeasonLifecycleUseCase.ts
|
|
core/racing/application/use-cases/RecalculateChampionshipStandingsUseCase.ts
|
|
core/racing/application/use-cases/GetLeagueStandingsUseCase.ts
|
|
core/racing/application/use-cases/GetDriversLeaderboardUseCase.ts
|
|
core/racing/application/use-cases/GetTeamsLeaderboardUseCase.ts
|
|
core/racing/application/use-cases/GetTotalDriversUseCase.ts
|
|
core/racing/application/use-cases/GetTotalLeaguesUseCase.ts
|
|
core/racing/application/use-cases/GetTotalRacesUseCase.ts
|
|
core/racing/application/use-cases/GetAllRacesUseCase.ts
|
|
core/racing/application/use-cases/GetAllRacesPageDataUseCase.ts
|
|
core/racing/application/use-cases/GetRacesPageDataUseCase.ts
|
|
core/racing/application/use-cases/GetAllLeaguesWithCapacityAndScoringUseCase.ts
|
|
core/racing/application/use-cases/GetAllTeamsUseCase.ts
|
|
core/racing/application/use-cases/GetTeamDetailsUseCase.ts
|
|
core/racing/application/use-cases/GetTeamMembersUseCase.ts
|
|
core/racing/application/use-cases/UpdateTeamUseCase.ts
|
|
core/racing/application/use-cases/CreateTeamUseCase.ts
|
|
core/racing/application/use-cases/LeaveTeamUseCase.ts
|
|
core/racing/application/use-cases/ApproveTeamJoinRequestUseCase.ts
|
|
core/racing/application/use-cases/RejectTeamJoinRequestUseCase.ts
|
|
core/racing/application/use-cases/GetDriverTeamUseCase.ts
|
|
core/racing/application/use-cases/GetProfileOverviewUseCase.ts
|
|
core/racing/application/use-cases/CompleteDriverOnboardingUseCase.ts
|
|
core/racing/application/use-cases/UpdateDriverProfileUseCase.ts
|
|
core/racing/application/use-cases/SendFinalResultsUseCase.ts
|
|
core/racing/application/use-cases/SendPerformanceSummaryUseCase.ts
|
|
core/racing/application/use-cases/RequestProtestDefenseUseCase.ts
|
|
core/racing/application/use-cases/SubmitProtestDefenseUseCase.ts
|
|
core/racing/application/use-cases/GetRaceProtestsUseCase.ts
|
|
core/racing/application/use-cases/GetLeagueProtestsUseCase.ts
|
|
core/racing/application/use-cases/GetRacePenaltiesUseCase.ts
|
|
core/racing/application/use-cases/GetSponsorsUseCase.ts
|
|
core/racing/application/use-cases/GetSponsorUseCase.ts
|
|
core/racing/application/use-cases/CreateSponsorUseCase.ts
|
|
core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase.ts
|
|
core/racing/application/use-cases/GetLeagueStatsUseCase.ts
|
|
```
|
|
|
|
### Medium Priority (Media Domain):
|
|
```
|
|
core/media/application/use-cases/GetAvatarUseCase.ts
|
|
core/media/application/use-cases/GetMediaUseCase.ts
|
|
core/media/application/use-cases/DeleteMediaUseCase.ts
|
|
core/media/application/use-cases/UploadMediaUseCase.ts
|
|
core/media/application/use-cases/UpdateAvatarUseCase.ts
|
|
core/media/application/use-cases/RequestAvatarGenerationUseCase.ts
|
|
core/media/application/use-cases/SelectAvatarUseCase.ts
|
|
```
|
|
|
|
### Medium Priority (Identity Domain):
|
|
```
|
|
core/identity/application/use-cases/SignupUseCase.ts
|
|
core/identity/application/use-cases/SignupWithEmailUseCase.ts
|
|
core/identity/application/use-cases/LoginUseCase.ts
|
|
core/identity/application/use-cases/LoginWithEmailUseCase.ts
|
|
core/identity/application/use-cases/ForgotPasswordUseCase.ts
|
|
core/identity/application/use-cases/ResetPasswordUseCase.ts
|
|
core/identity/application/use-cases/GetCurrentSessionUseCase.ts
|
|
core/identity/application/use-cases/GetCurrentUserSessionUseCase.ts
|
|
core/identity/application/use-cases/LogoutUseCase.ts
|
|
core/identity/application/use-cases/StartAuthUseCase.ts
|
|
core/identity/application/use-cases/HandleAuthCallbackUseCase.ts
|
|
core/identity/application/use-cases/SignupSponsorUseCase.ts
|
|
core/identity/application/use-cases/CreateAchievementUseCase.ts
|
|
```
|
|
|
|
### Medium Priority (Notifications Domain):
|
|
```
|
|
core/notifications/application/use-cases/GetUnreadNotificationsUseCase.ts
|
|
core/notifications/application/use-cases/MarkNotificationReadUseCase.ts
|
|
core/notifications/application/use-cases/NotificationPreferencesUseCases.ts
|
|
core/notifications/application/use-cases/SendNotificationUseCase.ts
|
|
```
|
|
|
|
### Medium Priority (Analytics Domain):
|
|
```
|
|
core/analytics/application/use-cases/GetAnalyticsMetricsUseCase.ts
|
|
core/analytics/application/use-cases/GetDashboardDataUseCase.ts
|
|
core/analytics/application/use-cases/RecordPageViewUseCase.ts
|
|
core/analytics/application/use-cases/RecordEngagementUseCase.ts
|
|
```
|
|
|
|
### Medium Priority (Admin Domain):
|
|
```
|
|
core/admin/application/use-cases/ListUsersUseCase.ts
|
|
```
|
|
|
|
### Medium Priority (Social Domain):
|
|
```
|
|
core/social/application/use-cases/GetUserFeedUseCase.ts
|
|
core/social/application/use-cases/GetCurrentUserSocialUseCase.ts
|
|
```
|
|
|
|
### Medium Priority (Payments Domain):
|
|
```
|
|
core/payments/application/use-cases/GetWalletUseCase.ts
|
|
core/payments/application/use-cases/GetMembershipFeesUseCase.ts
|
|
core/payments/application/use-cases/UpdatePaymentStatusUseCase.ts
|
|
core/payments/application/use-cases/AwardPrizeUseCase.ts
|
|
core/payments/application/use-cases/DeletePrizeUseCase.ts
|
|
core/payments/application/use-cases/CreatePrizeUseCase.ts
|
|
core/payments/application/use-cases/CreatePaymentUseCase.ts
|
|
core/payments/application/use-cases/ProcessWalletTransactionUseCase.ts
|
|
core/payments/application/use-cases/UpdateMemberPaymentUseCase.ts
|
|
core/payments/application/use-cases/GetPaymentsUseCase.ts
|
|
core/payments/application/use-cases/UpsertMembershipFeeUseCase.ts
|
|
```
|
|
|
|
### Controller/Service Files:
|
|
```
|
|
apps/api/src/domain/race/RaceService.ts
|
|
apps/api/src/domain/race/RaceProviders.ts
|
|
apps/api/src/domain/sponsor/SponsorService.ts
|
|
apps/api/src/domain/league/LeagueService.ts
|
|
apps/api/src/domain/driver/DriverService.ts
|
|
apps/api/src/domain/auth/AuthService.ts
|
|
apps/api/src/domain/analytics/AnalyticsService.ts
|
|
apps/api/src/domain/notifications/NotificationsService.ts
|
|
apps/api/src/domain/payments/PaymentsService.ts
|
|
apps/api/src/domain/admin/AdminService.ts
|
|
apps/api/src/domain/social/SocialService.ts
|
|
apps/api/src/domain/media/MediaService.ts
|
|
```
|
|
|
|
---
|
|
|
|
## Success Criteria
|
|
|
|
✅ **All use cases return `Result<T, E>` directly**
|
|
✅ **No use case calls `.present()`**
|
|
✅ **All controllers wire Results to Presenters**
|
|
✅ **All adapter classes removed**
|
|
✅ **Module wiring simplified**
|
|
✅ **"Presenter not presented" errors eliminated**
|
|
✅ **Tests updated and passing**
|
|
|
|
---
|
|
|
|
## Estimated Effort
|
|
|
|
- **150+ use cases** to fix
|
|
- **20+ controller/service files** to update
|
|
- **10+ provider files** to simplify
|
|
- **Estimated time**: 2-3 days of focused work
|
|
- **Risk**: Medium (requires careful testing)
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
1. **Start with Phase 1**: Fix the core racing use cases first (highest impact)
|
|
2. **Test each change**: Run existing tests to ensure no regressions
|
|
3. **Update controllers**: Wire Results to Presenters
|
|
4. **Simplify providers**: Remove adapter classes
|
|
5. **Run full test suite**: Verify everything works
|
|
|
|
**This plan provides the roadmap to achieve 100% Clean Architecture compliance.** |