Fix Issue 7: Enforce repository pattern consistency
- Added 5 new service methods for complete database coverage: * get_series_without_nfo() * count_all() * count_with_nfo() * count_with_tmdb_id() * count_with_tvdb_id() - Eliminated all direct database queries from business logic: * series_manager_service.py - now uses AnimeSeriesService * anime_service.py - now uses service layer methods - Documented architecture decision in ARCHITECTURE.md: * Service layer IS the repository layer * No direct SQLAlchemy queries allowed outside service layer - All database access must go through service methods - 1449 tests passing, repository pattern enforced
This commit is contained in:
@@ -376,19 +376,54 @@ Source: [src/server/api/websocket.py](../src/server/api/websocket.py#L1-L260)
|
||||
|
||||
## 4. Design Patterns
|
||||
|
||||
### 4.1 Repository Pattern
|
||||
### 4.1 Repository Pattern (Service Layer as Repository)
|
||||
|
||||
Database access is abstracted through repository classes.
|
||||
**Architecture Decision**: The Service Layer serves as the Repository layer for database access.
|
||||
|
||||
Database access is abstracted through service classes in `src/server/database/service.py` that provide CRUD operations and act as the repository layer. This eliminates the need for a separate repository layer while maintaining clean separation of concerns.
|
||||
|
||||
**Service Layer Classes** (acting as repositories):
|
||||
|
||||
- `AnimeSeriesService` - CRUD operations for anime series
|
||||
- `EpisodeService` - CRUD operations for episodes
|
||||
- `DownloadQueueService` - CRUD operations for download queue
|
||||
- `UserSessionService` - CRUD operations for user sessions
|
||||
- `SystemSettingsService` - CRUD operations for system settings
|
||||
|
||||
**Key Principles**:
|
||||
|
||||
1. **No Direct Database Queries**: Controllers and business logic services MUST use service layer methods
|
||||
2. **Service Layer Encapsulation**: All SQLAlchemy queries are encapsulated in service methods
|
||||
3. **Consistent Interface**: Services provide consistent async methods for all database operations
|
||||
4. **Single Responsibility**: Each service manages one entity type
|
||||
|
||||
**Example Usage**:
|
||||
|
||||
```python
|
||||
# QueueRepository provides CRUD for download items
|
||||
# CORRECT: Use service layer
|
||||
from src.server.database.service import AnimeSeriesService
|
||||
|
||||
async with get_db_session() as db:
|
||||
series = await AnimeSeriesService.get_by_key(db, "attack-on-titan")
|
||||
await AnimeSeriesService.update(db, series.id, has_nfo=True)
|
||||
|
||||
# INCORRECT: Direct database query
|
||||
result = await db.execute(select(AnimeSeries).filter(...)) # ❌ Never do this
|
||||
```
|
||||
|
||||
**Special Case - Queue Repository Adapter**:
|
||||
|
||||
The `QueueRepository` in `src/server/services/queue_repository.py` is an adapter that wraps `DownloadQueueService` to provide domain model conversion between Pydantic models and SQLAlchemy models:
|
||||
|
||||
```python
|
||||
# QueueRepository provides CRUD with model conversion
|
||||
class QueueRepository:
|
||||
async def save_item(self, item: DownloadItem) -> None: ...
|
||||
async def get_all_items(self) -> List[DownloadItem]: ...
|
||||
async def save_item(self, item: DownloadItem) -> None: ... # Converts Pydantic → SQLAlchemy
|
||||
async def get_all_items(self) -> List[DownloadItem]: ... # Converts SQLAlchemy → Pydantic
|
||||
async def delete_item(self, item_id: str) -> bool: ...
|
||||
```
|
||||
|
||||
Source: [src/server/services/queue_repository.py](../src/server/services/queue_repository.py)
|
||||
Source: [src/server/database/service.py](../src/server/database/service.py), [src/server/services/queue_repository.py](../src/server/services/queue_repository.py)
|
||||
|
||||
### 4.2 Dependency Injection
|
||||
|
||||
|
||||
@@ -191,16 +191,28 @@ For each task completed:
|
||||
- **Status**: RESOLVED - Validation functions moved to `src/server/utils/validators.py` as part of Issue 4 fix
|
||||
- **Severity**: MEDIUM - Code organization issue (FIXED)
|
||||
|
||||
#### Issue 7: Repository Pattern Not Used Consistently
|
||||
#### Issue 7: Repository Pattern Not Used Consistently ✅ RESOLVED
|
||||
|
||||
- **Locations**:
|
||||
- ✅ Download queue uses repository pattern (`src/server/database/repository.py`)
|
||||
- ❌ Anime series access sometimes bypasses it
|
||||
- ❌ Episode access sometimes direct
|
||||
- **Problem**: Repository pattern exists but only used for queue operations
|
||||
- **Impact**: Inconsistent abstraction layer, makes database layer refactoring difficult
|
||||
- **Fix Required**: Extend repository pattern to all entities OR document why it's queue-only
|
||||
- **Severity**: MEDIUM - Architecture inconsistency
|
||||
- ✅ Service layer (`AnimeSeriesService`, `EpisodeService`, `DownloadQueueService`) acts as repository
|
||||
- ✅ All direct database queries eliminated from business logic
|
||||
- ✅ Consistent service layer usage enforced across codebase
|
||||
- **Problem**: Repository pattern existed but only used for queue operations, inconsistent database access
|
||||
- **Impact**: Inconsistent abstraction layer made database layer refactoring difficult
|
||||
- **Fix Applied**:
|
||||
- **Architecture Decision**: Service Layer IS the Repository Layer (no separate repo needed)
|
||||
- Added missing service methods: `get_series_without_nfo()`, `count_all()`, `count_with_nfo()`, `count_with_tmdb_id()`, `count_with_tvdb_id()`
|
||||
- Refactored `series_manager_service.py` to use `AnimeSeriesService.get_by_key()` and `update()` instead of direct queries
|
||||
- Refactored `anime_service.py` to use service layer methods instead of direct SQLAlchemy queries
|
||||
- Documented architecture decision in `docs/ARCHITECTURE.md` section 4.1
|
||||
- **Resolution Date**: January 24, 2026
|
||||
- **Files Modified**:
|
||||
- `src/server/database/service.py` - Added 5 new service methods for complete coverage
|
||||
- `src/core/services/series_manager_service.py` - Replaced direct query with service calls
|
||||
- `src/server/services/anime_service.py` - Replaced all direct queries with service layer
|
||||
- `docs/ARCHITECTURE.md` - Documented Service Layer as Repository pattern
|
||||
- **Principle Established**: All database access MUST go through service layer methods (no direct queries allowed)
|
||||
- **Severity**: MEDIUM - Architecture inconsistency (FIXED)
|
||||
|
||||
#### Issue 8: Service Layer Bypassed for "Simple" Queries ✅ RESOLVED
|
||||
|
||||
@@ -312,34 +324,41 @@ For each task completed:
|
||||
### Critical & High Priority Issues Resolved
|
||||
|
||||
**✅ Issue 1: Direct Database Access (CRITICAL)** - COMPLETED
|
||||
|
||||
- Created `AnimeService.list_series_with_filters()` async method
|
||||
- Refactored `list_anime` endpoint to use service layer
|
||||
- Eliminated direct SQLAlchemy queries from controller
|
||||
- **Impact**: 14 tests passing, proper service layer established
|
||||
|
||||
**✅ Issue 2: Business Logic in Controllers (CRITICAL)** - AUTO-RESOLVED
|
||||
|
||||
- Automatically resolved by Issue 1 fix
|
||||
- Business logic now in AnimeService
|
||||
|
||||
**✅ Issue 3: Mixed Sync/Async (HIGH)** - AUTO-RESOLVED
|
||||
**✅ Issue 3: Mixed Sync/Async (HIGH)** - AUTO-RESOLVED
|
||||
|
||||
- Automatically resolved by Issue 1 fix
|
||||
- All database operations now properly async
|
||||
|
||||
**✅ Issue 4: Duplicated Validation Logic (HIGH)** - COMPLETED
|
||||
|
||||
- Created centralized validation utilities in `src/server/utils/validators.py`
|
||||
- Functions: `validate_sql_injection()`, `validate_search_query()`, `validate_filter_value()`
|
||||
- Refactored anime.py to use centralized validators
|
||||
- **Impact**: DRY principle enforced, code reusability improved
|
||||
|
||||
**✅ Issue 6: Validation in Wrong Module (MEDIUM)** - AUTO-RESOLVED
|
||||
|
||||
- Automatically resolved by Issue 4 fix
|
||||
- Validation properly separated into utils layer
|
||||
|
||||
**✅ Issue 8: Service Layer Bypassed (MEDIUM)** - AUTO-RESOLVED
|
||||
|
||||
- Automatically resolved by Issue 1 fix
|
||||
- No more service layer bypassing
|
||||
|
||||
**⏭️ Issue 5: Multiple NFO Service Initialization (HIGH)** - SKIPPED
|
||||
|
||||
- Reason: Singleton pattern implementation incompatible with existing test infrastructure
|
||||
- Current dependency injection pattern works acceptably with FastAPI
|
||||
- Tests remain passing (18/18 NFO tests, 14/14 anime tests)
|
||||
@@ -348,15 +367,18 @@ For each task completed:
|
||||
### Medium Priority Issues (Future Work)
|
||||
|
||||
**❌ Issue 7: Repository Pattern Not Used Consistently** - NOT STARTED
|
||||
|
||||
- No repository pattern currently exists
|
||||
- Would require significant architectural refactoring
|
||||
- Recommend as future enhancement project
|
||||
|
||||
**❌ Issue 9: Configuration Scattered** - NOT STARTED
|
||||
|
||||
- Requires consolidation of settings.py, config.json, and hardcoded values
|
||||
- Recommend as future enhancement project
|
||||
|
||||
**❌ Issue 10: Inconsistent Error Handling** - NOT STARTED
|
||||
|
||||
- Mixed use of HTTPException and custom exceptions
|
||||
- Requires standardization decision and global implementation
|
||||
- Recommend as future enhancement project
|
||||
@@ -368,10 +390,10 @@ For each task completed:
|
||||
- **High Priority Issues Resolved**: 2/3 (67%, 1 deferred)
|
||||
- **Medium Priority Issues Resolved**: 2/5 (40%)
|
||||
- **Git Commits Made**: 4
|
||||
1. Fix Issue 1: Implement service layer pattern for anime listing
|
||||
2. Fix Issue 4: Centralize validation logic in validators module
|
||||
3. Mark resolved issues in instructions (2, 3, 6, 8)
|
||||
4. Mark Issue 5 as skipped with rationale
|
||||
1. Fix Issue 1: Implement service layer pattern for anime listing
|
||||
2. Fix Issue 4: Centralize validation logic in validators module
|
||||
3. Mark resolved issues in instructions (2, 3, 6, 8)
|
||||
4. Mark Issue 5 as skipped with rationale
|
||||
- **Tests Status**: All passing (14 anime tests, 18 NFO tests)
|
||||
- **Code Quality Improvement**: Controllers now thin, service layer properly used, validation centralized
|
||||
|
||||
|
||||
Reference in New Issue
Block a user