Initial commit - Phase 3/4
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
666
docs/archive/REFACTORING_PLAN.md
Normal file
666
docs/archive/REFACTORING_PLAN.md
Normal file
@@ -0,0 +1,666 @@
|
||||
> **ARCHIVED**: This document is superseded by [REFACTORING_ROADMAP.md](REFACTORING_ROADMAP.md).
|
||||
> Kept for historical reference of original service layer design decisions.
|
||||
|
||||
# Dangerous Pi - Service Layer Refactoring Plan
|
||||
|
||||
**Status**: ✅ COMPLETE - Phase 1, 2, & 3 Done!
|
||||
**Priority**: High (Required before BLE feature parity)
|
||||
**Effort**: 2-3 weeks
|
||||
**Last Updated**: 2025-11-26
|
||||
**Progress**: Week 1 Foundation ✅ | Week 2 REST Refactoring ✅ | Week 3 BLE GATT ✅ | Week 4 Workflows (Optional)
|
||||
|
||||
---
|
||||
|
||||
## Problem Statement
|
||||
|
||||
### Current Architecture Issues
|
||||
|
||||
**Code Duplication Risk**: When BLE gets full feature parity, business logic will be duplicated:
|
||||
|
||||
```python
|
||||
# REST API endpoint
|
||||
@router.post("/command")
|
||||
async def execute_command(request):
|
||||
# ❌ Session validation logic
|
||||
if not session_manager.can_execute(request.session_id):
|
||||
raise HTTPException(...)
|
||||
|
||||
# ❌ Command execution logic
|
||||
result = await pm3_worker.execute_command(request.command)
|
||||
|
||||
# ❌ Session update logic
|
||||
session_manager.update_activity(request.session_id)
|
||||
|
||||
return response
|
||||
|
||||
# BLE GATT handler (future)
|
||||
async def handle_command_characteristic(value):
|
||||
# ❌ DUPLICATE session validation
|
||||
# ❌ DUPLICATE command execution
|
||||
# ❌ DUPLICATE session update
|
||||
# Same logic, different interface!
|
||||
```
|
||||
|
||||
**Maintenance Nightmare**: Bug fixes must be applied in 2+ places.
|
||||
|
||||
---
|
||||
|
||||
## Solution: Service Layer Pattern
|
||||
|
||||
### New Architecture
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────┐
|
||||
│ Interface Layer (Multiple Transport Protocols) │
|
||||
│ │
|
||||
│ ├─ REST API (FastAPI) ← Web/Desktop │
|
||||
│ ├─ BLE GATT (BlueZ) ← Mobile App │
|
||||
│ ├─ Plugin System ← Extensions │
|
||||
│ └─ CLI (future) ← Terminal │
|
||||
└─────────────────┬─────────────────────────────────┘
|
||||
│
|
||||
│ All interfaces call services
|
||||
▼
|
||||
┌───────────────────────────────────────────────────┐
|
||||
│ Service Layer (Business Logic) ✨ NEW! │
|
||||
│ │
|
||||
│ ├─ PM3Service ← Command execution │
|
||||
│ │ ├─ execute_command() │
|
||||
│ │ ├─ get_status() │
|
||||
│ │ └─ Session validation │
|
||||
│ │ │
|
||||
│ ├─ SystemService ← System operations │
|
||||
│ │ ├─ get_system_info() │
|
||||
│ │ ├─ shutdown() │
|
||||
│ │ └─ restart() │
|
||||
│ │ │
|
||||
│ ├─ WiFiService ← Network management │
|
||||
│ │ ├─ scan_networks() │
|
||||
│ │ ├─ connect() │
|
||||
│ │ └─ get_status() │
|
||||
│ │ │
|
||||
│ ├─ UpdateService ← Software updates │
|
||||
│ │ ├─ check_updates() │
|
||||
│ │ ├─ download_update() │
|
||||
│ │ └─ install_update() │
|
||||
│ │ │
|
||||
│ └─ WorkflowService ← Guided workflows │
|
||||
│ ├─ tune_antenna() │
|
||||
│ ├─ clone_card() │
|
||||
│ └─ id_tag() │
|
||||
└─────────────────┬─────────────────────────────────┘
|
||||
│
|
||||
│ Services coordinate managers
|
||||
▼
|
||||
┌───────────────────────────────────────────────────┐
|
||||
│ Manager/Worker Layer (Core Implementation) │
|
||||
│ │
|
||||
│ ├─ PM3Worker ← Hardware interface │
|
||||
│ ├─ SessionManager ← Session state │
|
||||
│ ├─ WiFiManager ← Network state │
|
||||
│ ├─ UpdateManager ← Update logic │
|
||||
│ ├─ UPSManager ← Battery monitoring │
|
||||
│ ├─ BLEManager ← BLE advertising │
|
||||
│ └─ PluginManager ← Plugin lifecycle │
|
||||
└───────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Key Benefits
|
||||
|
||||
1. **No Code Duplication** - Business logic written once, used everywhere
|
||||
2. **Easier Testing** - Test services independently of transport layer
|
||||
3. **Consistent Behavior** - REST and BLE behave identically
|
||||
4. **Simpler Maintenance** - Fix bugs in one place
|
||||
5. **Future-Proof** - Easy to add new interfaces (CLI, gRPC, etc.)
|
||||
|
||||
---
|
||||
|
||||
## Implementation Roadmap
|
||||
|
||||
### Phase 1: Create Service Layer (Week 1)
|
||||
|
||||
**Create Service Structure**:
|
||||
```bash
|
||||
app/backend/services/
|
||||
├── __init__.py
|
||||
├── pm3_service.py ✅ CREATED
|
||||
├── system_service.py
|
||||
├── wifi_service.py
|
||||
├── update_service.py
|
||||
└── workflow_service.py # For guided workflows
|
||||
```
|
||||
|
||||
**PM3 Service** (✅ Complete):
|
||||
- [x] Command execution with session validation
|
||||
- [x] Status queries
|
||||
- [x] Connection management
|
||||
- [x] Session lifecycle
|
||||
- [x] Error handling with codes
|
||||
|
||||
**System Service**:
|
||||
```python
|
||||
class SystemService:
|
||||
"""System operations service."""
|
||||
|
||||
async def get_info(self) -> ServiceResult:
|
||||
"""Get system information (CPU, memory, disk)."""
|
||||
|
||||
async def shutdown(self, delay: int = 0) -> ServiceResult:
|
||||
"""Initiate system shutdown."""
|
||||
|
||||
async def restart(self, delay: int = 0) -> ServiceResult:
|
||||
"""Initiate system restart."""
|
||||
|
||||
async def get_logs(self, service: str, lines: int = 100) -> ServiceResult:
|
||||
"""Get service logs."""
|
||||
```
|
||||
|
||||
**WiFi Service**:
|
||||
```python
|
||||
class WiFiService:
|
||||
"""WiFi operations service."""
|
||||
|
||||
async def scan_networks(self) -> ServiceResult:
|
||||
"""Scan for available networks."""
|
||||
|
||||
async def connect(self, ssid: str, password: str) -> ServiceResult:
|
||||
"""Connect to network."""
|
||||
|
||||
async def disconnect(self) -> ServiceResult:
|
||||
"""Disconnect from network."""
|
||||
|
||||
async def get_status(self) -> ServiceResult:
|
||||
"""Get WiFi status and current connection."""
|
||||
```
|
||||
|
||||
### Phase 2: Refactor REST API (Week 1-2)
|
||||
|
||||
**Before** (Current):
|
||||
```python
|
||||
# app/backend/api/pm3.py
|
||||
@router.post("/command")
|
||||
async def execute_command(request: CommandRequest):
|
||||
# Business logic mixed with HTTP concerns
|
||||
if not session_manager.can_execute(request.session_id):
|
||||
raise HTTPException(status_code=423, detail="...")
|
||||
|
||||
result = await pm3_worker.execute_command(request.command)
|
||||
session_manager.update_activity(request.session_id)
|
||||
|
||||
return CommandResponse(...)
|
||||
```
|
||||
|
||||
**After** (Refactored):
|
||||
```python
|
||||
# app/backend/api/pm3.py
|
||||
from ..services import PM3Service
|
||||
|
||||
pm3_service = PM3Service() # Inject singleton
|
||||
|
||||
@router.post("/command")
|
||||
async def execute_command(request: CommandRequest):
|
||||
# Thin adapter: converts HTTP → Service → HTTP
|
||||
result = await pm3_service.execute_command(
|
||||
command=request.command,
|
||||
session_id=request.session_id
|
||||
)
|
||||
|
||||
if result.success:
|
||||
return CommandResponse(
|
||||
success=True,
|
||||
output=result.data["output"]
|
||||
)
|
||||
else:
|
||||
# Convert service error to HTTP error
|
||||
raise HTTPException(
|
||||
status_code=get_status_code(result.error.code),
|
||||
detail=result.error.message
|
||||
)
|
||||
|
||||
def get_status_code(error_code: str) -> int:
|
||||
"""Map service error codes to HTTP status codes."""
|
||||
codes = {
|
||||
"session_locked": 423,
|
||||
"pm3_not_connected": 503,
|
||||
"command_failed": 500,
|
||||
"session_not_found": 404,
|
||||
}
|
||||
return codes.get(error_code, 500)
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- REST endpoints become thin adapters
|
||||
- HTTP concerns separated from business logic
|
||||
- Services are HTTP-agnostic (can be used by BLE)
|
||||
|
||||
### Phase 3: Implement BLE GATT Service (Week 2-3)
|
||||
|
||||
**Create BLE Service Structure**:
|
||||
```bash
|
||||
app/backend/ble/
|
||||
├── __init__.py
|
||||
├── gatt_server.py # BLE GATT server
|
||||
├── characteristics.py # GATT characteristics
|
||||
└── handlers.py # Characteristic handlers
|
||||
```
|
||||
|
||||
**BLE GATT Implementation**:
|
||||
```python
|
||||
# app/backend/ble/gatt_server.py
|
||||
from ..services import PM3Service
|
||||
|
||||
class DangerousPiGATTServer:
|
||||
"""BLE GATT server for Dangerous Pi."""
|
||||
|
||||
# Service UUIDs
|
||||
PM3_SERVICE_UUID = "12345678-1234-5678-1234-56789abcdef0"
|
||||
|
||||
# Characteristic UUIDs
|
||||
COMMAND_CHAR_UUID = "12345678-1234-5678-1234-56789abcdef1"
|
||||
STATUS_CHAR_UUID = "12345678-1234-5678-1234-56789abcdef2"
|
||||
SESSION_CHAR_UUID = "12345678-1234-5678-1234-56789abcdef3"
|
||||
|
||||
def __init__(self):
|
||||
self.pm3_service = PM3Service() # Same service as REST!
|
||||
self.bus = None
|
||||
self.adapter = None
|
||||
|
||||
async def start(self):
|
||||
"""Start GATT server."""
|
||||
await self._register_service()
|
||||
await self._register_characteristics()
|
||||
|
||||
async def _handle_command_write(self, value: bytes):
|
||||
"""Handle command characteristic write.
|
||||
|
||||
Value format: JSON {"command": "hw version", "session_id": "..."}
|
||||
"""
|
||||
import json
|
||||
data = json.loads(value.decode())
|
||||
|
||||
# ✅ REUSE service logic (no duplication!)
|
||||
result = await self.pm3_service.execute_command(
|
||||
command=data["command"],
|
||||
session_id=data.get("session_id")
|
||||
)
|
||||
|
||||
# Return result via BLE notification
|
||||
response = {
|
||||
"success": result.success,
|
||||
"data": result.data if result.success else None,
|
||||
"error": {
|
||||
"code": result.error.code,
|
||||
"message": result.error.message
|
||||
} if result.error else None
|
||||
}
|
||||
|
||||
await self._notify_characteristic(
|
||||
self.COMMAND_CHAR_UUID,
|
||||
json.dumps(response).encode()
|
||||
)
|
||||
|
||||
async def _handle_status_read(self) -> bytes:
|
||||
"""Handle status characteristic read."""
|
||||
# ✅ REUSE service logic
|
||||
result = await self.pm3_service.get_status()
|
||||
|
||||
import json
|
||||
return json.dumps(result.data).encode()
|
||||
```
|
||||
|
||||
**Result**: BLE and REST share identical business logic!
|
||||
|
||||
### Phase 4: Enhanced BLE Manager (Week 3)
|
||||
|
||||
**Upgrade BLE Manager**:
|
||||
```python
|
||||
# app/backend/managers/ble_manager.py
|
||||
|
||||
class BLEManager:
|
||||
"""Enhanced BLE manager with GATT server."""
|
||||
|
||||
def __init__(self):
|
||||
# Existing notification support
|
||||
self._notification_queue = asyncio.Queue()
|
||||
|
||||
# NEW: GATT server for bidirectional communication
|
||||
self._gatt_server = None
|
||||
|
||||
async def initialize(self):
|
||||
"""Initialize BLE with GATT server."""
|
||||
# Existing: notification support
|
||||
await self._setup_notifications()
|
||||
|
||||
# NEW: Start GATT server
|
||||
from ..ble.gatt_server import DangerousPiGATTServer
|
||||
self._gatt_server = DangerousPiGATTServer()
|
||||
await self._gatt_server.start()
|
||||
|
||||
# Existing notification methods remain unchanged
|
||||
async def send_notification(self, ...):
|
||||
"""Send one-way notification (existing)."""
|
||||
...
|
||||
|
||||
# NEW: Bidirectional communication via GATT
|
||||
async def handle_command(self, command: str, session_id: str):
|
||||
"""Handle command via BLE (delegates to GATT server)."""
|
||||
return await self._gatt_server.handle_command(command, session_id)
|
||||
```
|
||||
|
||||
### Phase 5: Workflow Service (Week 3-4)
|
||||
|
||||
**Create Workflow Service**:
|
||||
```python
|
||||
# app/backend/services/workflow_service.py
|
||||
|
||||
class WorkflowService:
|
||||
"""Service for guided PM3 workflows."""
|
||||
|
||||
def __init__(self):
|
||||
self.pm3_service = PM3Service()
|
||||
|
||||
async def tune_antenna(
|
||||
self,
|
||||
frequency_type: str, # "hf", "lf", "hw"
|
||||
session_id: str
|
||||
) -> WorkflowResult:
|
||||
"""Execute antenna tuning workflow.
|
||||
|
||||
Returns real-time tuning data for visualization.
|
||||
"""
|
||||
# 1. Execute tune command
|
||||
command = f"{frequency_type} tune"
|
||||
result = await self.pm3_service.execute_command(command, session_id)
|
||||
|
||||
if not result.success:
|
||||
return WorkflowResult(success=False, error=result.error)
|
||||
|
||||
# 2. Parse output into structured data
|
||||
from ..parsers.pm3_output import parse_antenna_tuning
|
||||
tuning_data = parse_antenna_tuning(result.data["output"])
|
||||
|
||||
# 3. Return workflow result with visualization data
|
||||
return WorkflowResult(
|
||||
success=True,
|
||||
data={
|
||||
"tuning_data": tuning_data,
|
||||
"optimal": tuning_data["voltage"] > 40.0,
|
||||
"recommendation": get_tuning_recommendation(tuning_data)
|
||||
}
|
||||
)
|
||||
|
||||
async def clone_mifare_classic(
|
||||
self,
|
||||
session_id: str,
|
||||
source_uid: Optional[str] = None
|
||||
) -> AsyncGenerator[WorkflowProgress, None]:
|
||||
"""Execute MIFARE Classic cloning workflow.
|
||||
|
||||
Yields progress updates for each step.
|
||||
"""
|
||||
# Step 1: Tune antenna
|
||||
yield WorkflowProgress(step=1, total=4, message="Tuning HF antenna...")
|
||||
tune_result = await self.tune_antenna("hf", session_id)
|
||||
|
||||
if not tune_result.success:
|
||||
yield WorkflowProgress(step=1, error="Tuning failed")
|
||||
return
|
||||
|
||||
# Step 2: Read source card
|
||||
yield WorkflowProgress(step=2, total=4, message="Reading source card...")
|
||||
# ... read logic
|
||||
|
||||
# Step 3: Verify
|
||||
yield WorkflowProgress(step=3, total=4, message="Verifying read...")
|
||||
# ... verify logic
|
||||
|
||||
# Step 4: Write to target
|
||||
yield WorkflowProgress(step=4, total=4, message="Writing to target...")
|
||||
for block in range(64):
|
||||
# ... write each block
|
||||
yield WorkflowProgress(
|
||||
step=4,
|
||||
total=4,
|
||||
message=f"Writing block {block}/64...",
|
||||
progress=block/64
|
||||
)
|
||||
|
||||
yield WorkflowProgress(step=4, total=4, message="Clone complete!", complete=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependency Injection Strategy
|
||||
|
||||
### Service Singletons
|
||||
|
||||
**Create Service Container**:
|
||||
```python
|
||||
# app/backend/services/container.py
|
||||
|
||||
class ServiceContainer:
|
||||
"""Dependency injection container for services."""
|
||||
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
# Create shared worker/manager instances
|
||||
self._pm3_worker = PM3Worker()
|
||||
self._session_manager = SessionManager()
|
||||
self._wifi_manager = WiFiManager()
|
||||
self._update_manager = UpdateManager()
|
||||
|
||||
# Create services with injected dependencies
|
||||
self._pm3_service = PM3Service(
|
||||
pm3_worker=self._pm3_worker,
|
||||
session_manager=self._session_manager
|
||||
)
|
||||
self._wifi_service = WiFiService(
|
||||
wifi_manager=self._wifi_manager
|
||||
)
|
||||
self._workflow_service = WorkflowService(
|
||||
pm3_service=self._pm3_service
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = ServiceContainer()
|
||||
return cls._instance
|
||||
|
||||
@property
|
||||
def pm3_service(self) -> PM3Service:
|
||||
return self._pm3_service
|
||||
|
||||
@property
|
||||
def wifi_service(self) -> WiFiService:
|
||||
return self._wifi_service
|
||||
|
||||
@property
|
||||
def workflow_service(self) -> WorkflowService:
|
||||
return self._workflow_service
|
||||
|
||||
# Global container instance
|
||||
container = ServiceContainer.get_instance()
|
||||
```
|
||||
|
||||
**Usage in REST API**:
|
||||
```python
|
||||
# app/backend/api/pm3.py
|
||||
from ..services.container import container
|
||||
|
||||
@router.post("/command")
|
||||
async def execute_command(request: CommandRequest):
|
||||
result = await container.pm3_service.execute_command(
|
||||
command=request.command,
|
||||
session_id=request.session_id
|
||||
)
|
||||
# ... handle result
|
||||
```
|
||||
|
||||
**Usage in BLE**:
|
||||
```python
|
||||
# app/backend/ble/gatt_server.py
|
||||
from ..services.container import container
|
||||
|
||||
class DangerousPiGATTServer:
|
||||
async def _handle_command_write(self, value: bytes):
|
||||
# Same service instance as REST!
|
||||
result = await container.pm3_service.execute_command(...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Service Unit Tests
|
||||
|
||||
```python
|
||||
# tests/test_pm3_service.py
|
||||
import pytest
|
||||
from app.backend.services import PM3Service
|
||||
from app.backend.workers.pm3_worker import MockPM3Worker
|
||||
from app.backend.managers.session_manager import SessionManager
|
||||
|
||||
@pytest.fixture
|
||||
def pm3_service():
|
||||
"""Create PM3 service with mock worker."""
|
||||
mock_worker = MockPM3Worker()
|
||||
session_manager = SessionManager()
|
||||
return PM3Service(
|
||||
pm3_worker=mock_worker,
|
||||
session_manager=session_manager
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execute_command_success(pm3_service):
|
||||
"""Test successful command execution."""
|
||||
# Create session
|
||||
session_result = pm3_service.create_session()
|
||||
assert session_result.success
|
||||
|
||||
session_id = session_result.data["session_id"]
|
||||
|
||||
# Execute command
|
||||
result = await pm3_service.execute_command(
|
||||
command="hw version",
|
||||
session_id=session_id
|
||||
)
|
||||
|
||||
assert result.success
|
||||
assert "Proxmark3" in result.data["output"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execute_command_session_locked(pm3_service):
|
||||
"""Test command execution with locked session."""
|
||||
# Create session
|
||||
session1 = pm3_service.create_session()
|
||||
|
||||
# Try to execute with different session
|
||||
result = await pm3_service.execute_command(
|
||||
command="hw version",
|
||||
session_id="different-session"
|
||||
)
|
||||
|
||||
assert not result.success
|
||||
assert result.error.code == "session_locked"
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
```python
|
||||
# tests/test_rest_ble_parity.py
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rest_and_ble_return_same_result():
|
||||
"""Verify REST and BLE produce identical results."""
|
||||
|
||||
# Execute via REST
|
||||
rest_response = await rest_client.post("/api/pm3/command",
|
||||
json={"command": "hw version", "session_id": "test"})
|
||||
|
||||
# Execute via BLE
|
||||
ble_response = await ble_client.write_characteristic(
|
||||
COMMAND_CHAR_UUID,
|
||||
json.dumps({"command": "hw version", "session_id": "test"}).encode()
|
||||
)
|
||||
|
||||
# Results should be identical (both use same service)
|
||||
assert rest_response.json()["output"] == ble_response["data"]["output"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
### Week 1: Service Layer Foundation
|
||||
- [x] Create `app/backend/services/` directory (✅ complete)
|
||||
- [x] Implement `PM3Service` (✅ complete)
|
||||
- [x] Implement `SystemService` (✅ complete)
|
||||
- [x] Implement `WiFiService` (✅ complete)
|
||||
- [x] Implement `UpdateService` (✅ complete)
|
||||
- [x] Create `ServiceContainer` for DI (✅ complete)
|
||||
- [x] Write service unit tests (✅ complete - 4 test files, 100+ tests)
|
||||
|
||||
### Week 2: REST API Refactoring
|
||||
- [x] Refactor `api/pm3.py` to use `PM3Service` (✅ complete)
|
||||
- [x] Refactor `api/system.py` to use `SystemService` (✅ complete)
|
||||
- [x] Refactor `api/wifi.py` to use `WiFiService` (✅ complete)
|
||||
- [x] Refactor `api/updates.py` to use `UpdateService` (✅ complete)
|
||||
- [x] Update integration tests (✅ complete - API endpoint tests added)
|
||||
- [x] Create comprehensive test suite (✅ complete - pytest config, fixtures, docs)
|
||||
|
||||
### Week 3: BLE Implementation
|
||||
- [x] Create `app/backend/ble/` directory (✅ complete)
|
||||
- [x] Implement `DangerousPiGATTServer` (✅ complete)
|
||||
- [x] Define GATT service and characteristics (✅ complete)
|
||||
- [x] Implement characteristic handlers using services (✅ complete - PM3, WiFi, System, Update)
|
||||
- [x] Write comprehensive BLE tests (✅ complete - full test coverage)
|
||||
- [x] Create BLE integration guide (✅ complete - BLE_GATT_GUIDE.md)
|
||||
- [ ] Integrate with BlueZ D-Bus (deferred - requires hardware)
|
||||
|
||||
### Week 4: Workflow Service
|
||||
- [ ] Implement `WorkflowService`
|
||||
- [ ] Add antenna tuning workflow
|
||||
- [ ] Add clone workflows
|
||||
- [ ] Add ID tag workflow
|
||||
- [ ] REST endpoints for workflows
|
||||
- [ ] BLE characteristics for workflows
|
||||
- [ ] Integration tests
|
||||
|
||||
---
|
||||
|
||||
## Benefits Summary
|
||||
|
||||
| Aspect | Before (Current) | After (Refactored) |
|
||||
|--------|------------------|-------------------|
|
||||
| **Code Duplication** | High risk (REST + BLE) | None |
|
||||
| **Testing** | Test each interface separately | Test services once |
|
||||
| **Maintenance** | Update 2+ places | Update 1 place |
|
||||
| **New Interfaces** | Copy/paste logic | Reuse services |
|
||||
| **Consistency** | May diverge | Guaranteed identical |
|
||||
| **Testability** | HTTP-coupled | Service isolated |
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] Zero business logic in REST endpoints (thin adapters only)
|
||||
- [ ] BLE GATT handlers reuse 100% of PM3Service logic
|
||||
- [ ] Services have 90%+ test coverage
|
||||
- [ ] REST and BLE produce identical results for same inputs
|
||||
- [ ] Adding new interface (CLI, gRPC) takes < 1 day
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Review this plan** - Approve refactoring approach
|
||||
2. **Start Week 1** - Implement service layer foundation
|
||||
3. **Gradual migration** - Refactor one endpoint at a time
|
||||
4. **BLE implementation** - Add GATT server using services
|
||||
5. **Mobile app** - React Native app can use BLE interface
|
||||
|
||||
---
|
||||
|
||||
**Summary**: Refactor to Service Layer pattern BEFORE expanding BLE to prevent code duplication and enable cross-platform consistency.
|
||||
Reference in New Issue
Block a user