PRD-001: Service Architecture Refinement
Status: Implemented Date: 2026-05-20 Updated: 2026-07-14
Problem Statement
The services layer (micboard/services/) has grown without architectural discipline. Two files exceed healthy module size (discovery_service.py at 898 lines, hardware_lifecycle.py at 640 lines), model save() overrides contain orchestration logic that belongs in services, and error handling is inconsistent across domains. Dead code (unused BaseCRUDService, empty utils.py) adds clutter.
Goals
- Every service file ≤400 lines with a single documented responsibility.
- All model
save()overrides are idempotent and side-effect-free; business logic lives in services. - Consistent exception boundaries through the canonical hierarchy in
micboard/exceptions.py, while retaining Django and Pydantic exceptions where their frameworks require them.
Non-Goals
- Adding new features.
- Changing public API behavior of existing services.
- Rewriting or deprecating the service layer wholesale.
Scope
| Area | ADR | Issues |
|---|---|---|
| Decompose discovery_service.py and hardware_lifecycle.py | ADR-001 | #51, #53, #54 |
| Extract business logic from model save()/clean() | ADR-002 | #52, #56 |
| Standardize service-layer exception handling | — | #55 |
Design
- Split strategy: Each oversized module splits by operational concern (not arbitrary chunking). Discovery splits into scanner, queue manager, and mapper submodules. Hardware lifecycle splits into events and validation submodules.
- Extraction strategy: For each model with embedded logic, (a) introduce a service method, (b) update all call sites, (c) remove the
save()override. - Error strategy: Put public operational errors under
MicboardError, preserve typed transport metadata, translate only unexpected failures at named external seams, and retain native authorization, validation, DTO, and programmer errors at their intended boundaries. - Import migration: Update every caller to the owning module in the same change. Do not retain aliases, compatibility modules, or package re-exports.
Success Metrics
- discovery_service.py ≤400 lines
- hardware_lifecycle.py ≤400 lines
- No model
save()overrides contain DB writes, external calls, or signal emission uv run ruff check .passes- No service module defines an alternate exception root
- All existing tests pass
Risks
- Import churn: ~15-25 call sites across tasks, admin, and management commands need import path updates.
- Missed callers: Post-save signal handlers may duplicate service logic — audit required before removal.
- No test safety net: 33 of 38 service files have zero tests. PRD-004 addresses this before risky splits.
Issues
- #51 — chore: remove dead code from services layer
- #52 — refactor: extract business logic from model save()/validate() overrides
- #53 — refactor: decompose discovery_service.py
- #54 — refactor: decompose hardware_lifecycle.py
- #55 — refactor: standardize service-layer error handling
- #56 — refactor: audit and migrate post-save signal handlers
References
- ADR-001: Service Layer Decomposition
- ADR-002: Extract Business Logic from Models to Services
