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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user