Enforce repository boundary: Remove DbDep from routers
This commit enforces the repository boundary by eliminating direct database connection dependencies (DbDep) from all routers. Routers now depend on service context dependencies that combine the database connection with the related repositories. Changes: - Add 5 service context dependencies in dependencies.py: * SessionServiceContext: db + session_repo * BlocklistServiceContext: db + blocklist_repo + import_log_repo + settings_repo * SettingsServiceContext: db + settings_repo * BanServiceContext: db + fail2ban_db_repo * HistoryServiceContext: db + fail2ban_db_repo + history_archive_repo - Refactor all 9 routers (auth, bans, blocklist, config_misc, dashboard, geo, history, jails, setup) to use service contexts instead of DbDep. - Update Backend-Development.md with clear examples of the new pattern and documentation of available service contexts. Rationale: - Enforces the repository boundary through the dependency system - Makes database operations explicit and auditable - Improves testability by allowing service contexts to be mocked - Prevents accidental direct database access from routers The deprecated DbDep remains available for backward compatibility with services that have not yet been refactored, but routers can no longer import it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -253,44 +253,60 @@ The **repository boundary** separates database-aware code from application logic
|
||||
|
||||
| Layer | Responsibilities | Dependencies |
|
||||
|---|---|---|
|
||||
| **Routers** | Receive requests, validate input, return responses. | Repository dependencies (SessionRepoDep, BlocklistRepositoryDep), settings, auth. Never raw database connections. |
|
||||
| **Routers** | Receive requests, validate input, return responses. | Service context dependencies (SessionServiceContextDep, BlocklistServiceContextDep), settings, auth. Never raw database connections. |
|
||||
| **Services** | Contain business logic, orchestrate operations. | Other services, repositories. May receive `aiosqlite.Connection` for repository operations. |
|
||||
| **Repositories** | Execute all SQL queries. All database knowledge lives here. | `aiosqlite.Connection` (from callers). |
|
||||
|
||||
**Rule: Routers must NOT depend on `DbDep` (raw database connections).**
|
||||
|
||||
Instead, routers should:
|
||||
1. Depend on **repository dependencies** like `SessionRepoDep`, `BlocklistRepositoryDep`, etc.
|
||||
2. Pass repositories to services, not raw database connections.
|
||||
3. Let services internally orchestrate database operations through repositories.
|
||||
1. Depend on **service context dependencies** like `SessionServiceContextDep`, `BlocklistServiceContextDep`, etc.
|
||||
2. These context dependencies combine the database connection and related repositories.
|
||||
3. Pass the context to services, which internally orchestrate database operations.
|
||||
|
||||
**Service Context Dependencies Available:**
|
||||
- `SessionServiceContextDep` — Contains `db` and `session_repo` for session operations.
|
||||
- `BlocklistServiceContextDep` — Contains `db`, `blocklist_repo`, `import_log_repo`, `settings_repo`.
|
||||
- `SettingsServiceContextDep` — Contains `db` and `settings_repo`.
|
||||
- `BanServiceContextDep` — Contains `db` and `fail2ban_db_repo`.
|
||||
- `HistoryServiceContextDep` — Contains `db`, `fail2ban_db_repo`, `history_archive_repo`.
|
||||
|
||||
**Why:**
|
||||
- **Enforcement**: Not exporting `DbDep` from the dependencies module makes it impossible for routers to accidentally bypass repositories.
|
||||
- **Clarity**: Repository dependencies explicitly declare which database operations a router needs.
|
||||
- **Clarity**: Service context dependencies explicitly declare which database operations a router needs.
|
||||
- **Testability**: Services and routers are easier to test when they depend on repositories (which can be mocked) rather than raw connections.
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
# ✅ GOOD — router depends on repository
|
||||
@router.get("/blocklists")
|
||||
async def list_blocklists(
|
||||
blocklist_repo: BlocklistRepositoryDep,
|
||||
# ✅ GOOD — router depends on service context
|
||||
@router.post("/login")
|
||||
async def login(
|
||||
body: LoginRequest,
|
||||
response: Response,
|
||||
session_ctx: SessionServiceContextDep, # Contains db + session_repo
|
||||
_auth: AuthDep,
|
||||
) -> BlocklistListResponse:
|
||||
sources = await blocklist_repo.list_sources(???) # db comes from where?
|
||||
return BlocklistListResponse(sources=sources)
|
||||
) -> LoginResponse:
|
||||
return await auth_service.login(
|
||||
session_ctx.db,
|
||||
password=body.password,
|
||||
session_repo=session_ctx.session_repo,
|
||||
...
|
||||
)
|
||||
|
||||
# ❌ BAD — router depends on raw db (DbDep is not exported for this reason)
|
||||
@router.get("/blocklists")
|
||||
async def list_blocklists(
|
||||
@router.post("/login")
|
||||
async def login(
|
||||
body: LoginRequest,
|
||||
db: DbDep, # ← Cannot import DbDep in routers
|
||||
_auth: AuthDep,
|
||||
) -> BlocklistListResponse:
|
||||
sources = await blocklist_service.list_sources(db)
|
||||
return BlocklistListResponse(sources=sources)
|
||||
) -> LoginResponse:
|
||||
return await auth_service.login(db, password=body.password, ...)
|
||||
```
|
||||
|
||||
**Migration Path**: Services are gradually being refactored to accept repositories instead of raw database connections. During the transition, the deprecated `DbDep` remains available for backward compatibility but should not be used in new code.
|
||||
**DEPRECATED: DbDep**
|
||||
- The `DbDep` type alias is provided for backward compatibility only.
|
||||
- DO NOT use in new code. Use service context dependencies instead.
|
||||
- See `backend/app/dependencies.py` for available service contexts.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user