component decomposition is done

This commit is contained in:
Oleksandr Bezdieniezhnykh
2025-11-24 14:09:23 +02:00
parent acec83018b
commit f50006d100
34 changed files with 8637 additions and 0 deletions
@@ -0,0 +1,475 @@
# Route Database Layer
## Interface Definition
**Interface Name**: `IRouteDatabase`
### Interface Methods
```python
class IRouteDatabase(ABC):
@abstractmethod
def insert_route(self, route: Route) -> str:
pass
@abstractmethod
def update_route(self, route: Route) -> bool:
pass
@abstractmethod
def query_routes(self, filters: Dict[str, Any], limit: int, offset: int) -> List[Route]:
pass
@abstractmethod
def get_route_by_id(self, route_id: str) -> Optional[Route]:
pass
@abstractmethod
def get_waypoints(self, route_id: str, limit: Optional[int] = None) -> List[Waypoint]:
pass
@abstractmethod
def insert_waypoint(self, route_id: str, waypoint: Waypoint) -> str:
pass
@abstractmethod
def update_waypoint(self, route_id: str, waypoint_id: str, waypoint: Waypoint) -> bool:
pass
@abstractmethod
def delete_route(self, route_id: str) -> bool:
pass
```
## Component Description
### Responsibilities
- Direct database access layer for route data
- Execute SQL queries and commands
- Manage database connections and transactions
- Handle connection pooling and retry logic
- Provide database abstraction for potential migration (PostgreSQL, MySQL, etc.)
### Scope
- CRUD operations on routes table
- CRUD operations on waypoints table
- CRUD operations on geofences table
- Query optimization for large datasets
- Database schema management
- Separate schema from GPS-Denied API database
## API Methods
### `insert_route(route: Route) -> str`
**Description**: Inserts a new route with initial waypoints and geofences into the database.
**Called By**:
- R02 Route Data Manager
**Input**:
```python
Route:
id: str
name: str
description: str
points: List[Waypoint]
geofences: Geofences
created_at: datetime
updated_at: datetime
```
**Output**:
```python
route_id: str # Inserted route ID
```
**Database Operations**:
1. Begin transaction
2. INSERT INTO routes (id, name, description, created_at, updated_at)
3. INSERT INTO waypoints (route_id, ...) for each waypoint
4. INSERT INTO geofences (route_id, ...) for each polygon
5. Commit transaction
**Error Conditions**:
- `IntegrityError`: Duplicate route_id (unique constraint violation)
- `DatabaseError`: Connection error, transaction failure
- Automatic rollback on any error
**Test Cases**:
1. **Insert route with 100 waypoints**: Successful insertion, all waypoints persisted
2. **Duplicate route_id**: Raises IntegrityError
3. **Transaction rollback**: Error on waypoint insertion → route also rolled back
4. **Connection loss**: Mid-transaction error → graceful rollback
---
### `update_route(route: Route) -> bool`
**Description**: Updates route metadata (name, description, updated_at).
**Called By**:
- R02 Route Data Manager
**Input**:
```python
Route with updated fields
```
**Output**:
```python
bool: True if updated, False if route not found
```
**Database Operations**:
```sql
UPDATE routes
SET name = ?, description = ?, updated_at = ?
WHERE id = ?
```
**Error Conditions**:
- `DatabaseError`: Connection or query error
**Test Cases**:
1. **Update existing route**: Returns True
2. **Update non-existent route**: Returns False
3. **Update with same data**: Succeeds, updates timestamp
---
### `query_routes(filters: Dict[str, Any], limit: int, offset: int) -> List[Route]`
**Description**: Queries routes with filtering, pagination for route listing.
**Called By**:
- R02 Route Data Manager
- R01 Route REST API (list endpoints)
**Input**:
```python
filters: Dict[str, Any] # e.g., {"name": "Mission%", "created_after": datetime}
limit: int # Max results
offset: int # For pagination
```
**Output**:
```python
List[Route] # Routes without full waypoint data (metadata only)
```
**Database Operations**:
```sql
SELECT * FROM routes
WHERE name LIKE ? AND created_at > ?
ORDER BY created_at DESC
LIMIT ? OFFSET ?
```
**Error Conditions**:
- `DatabaseError`: Query error
**Test Cases**:
1. **Filter by name**: Returns matching routes
2. **Pagination**: offset=100, limit=50 → returns routes 100-149
3. **Empty result**: No matches → returns []
4. **No filters**: Returns all routes (with limit)
---
### `get_route_by_id(route_id: str) -> Optional[Route]`
**Description**: Retrieves complete route with all waypoints by ID.
**Called By**:
- R02 Route Data Manager
**Input**:
```python
route_id: str
```
**Output**:
```python
Optional[Route] # Complete route with all waypoints, or None
```
**Database Operations**:
1. SELECT FROM routes WHERE id = ?
2. SELECT FROM waypoints WHERE route_id = ? ORDER BY timestamp
3. SELECT FROM geofences WHERE route_id = ?
4. Assemble Route object
**Error Conditions**:
- `DatabaseError`: Query error
- Returns None if route not found
**Test Cases**:
1. **Existing route**: Returns complete Route object
2. **Non-existent route**: Returns None
3. **Large route (3000 waypoints)**: Returns all data within 150ms
4. **Route with no waypoints**: Returns route with empty points list
---
### `get_waypoints(route_id: str, limit: Optional[int] = None) -> List[Waypoint]`
**Description**: Retrieves waypoints for a route, optionally limited.
**Called By**:
- R02 Route Data Manager
**Input**:
```python
route_id: str
limit: Optional[int] # For pagination or preview
```
**Output**:
```python
List[Waypoint]
```
**Database Operations**:
```sql
SELECT * FROM waypoints
WHERE route_id = ?
ORDER BY timestamp ASC
LIMIT ? -- if limit provided
```
**Error Conditions**:
- `DatabaseError`: Query error
**Test Cases**:
1. **All waypoints**: limit=None → returns all
2. **Limited waypoints**: limit=100 → returns first 100
3. **No waypoints**: Empty list
---
### `insert_waypoint(route_id: str, waypoint: Waypoint) -> str`
**Description**: Inserts a new waypoint into a route.
**Called By**:
- R02 Route Data Manager
**Input**:
```python
route_id: str
waypoint: Waypoint
```
**Output**:
```python
waypoint_id: str
```
**Database Operations**:
```sql
INSERT INTO waypoints (id, route_id, lat, lon, altitude, confidence, timestamp, refined)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
```
**Error Conditions**:
- `ForeignKeyError`: route_id doesn't exist
- `IntegrityError`: Duplicate waypoint_id
**Test Cases**:
1. **Valid insertion**: Returns waypoint_id
2. **Non-existent route**: Raises ForeignKeyError
---
### `update_waypoint(route_id: str, waypoint_id: str, waypoint: Waypoint) -> bool`
**Description**: Updates a waypoint. Critical path for GPS-Denied per-frame updates.
**Called By**:
- R02 Route Data Manager
**Input**:
```python
route_id: str
waypoint_id: str
waypoint: Waypoint
```
**Output**:
```python
bool: True if updated, False if not found
```
**Database Operations**:
```sql
UPDATE waypoints
SET lat = ?, lon = ?, altitude = ?, confidence = ?, refined = ?
WHERE id = ? AND route_id = ?
```
**Optimization**:
- Prepared statement caching
- Connection pooling for high throughput
- Indexed on (route_id, id) for fast lookups
**Error Conditions**:
- `DatabaseError`: Query error
**Test Cases**:
1. **Update existing waypoint**: Returns True, updates data
2. **Non-existent waypoint**: Returns False
3. **High-frequency updates**: 100 updates/sec sustained for 20 seconds
---
### `delete_route(route_id: str) -> bool`
**Description**: Deletes a route and cascades to waypoints and geofences.
**Called By**:
- R02 Route Data Manager
**Input**:
```python
route_id: str
```
**Output**:
```python
bool: True if deleted, False if not found
```
**Database Operations**:
```sql
DELETE FROM routes WHERE id = ?
-- Cascade deletes from waypoints and geofences via FK constraints
```
**Error Conditions**:
- `DatabaseError`: Query error
**Test Cases**:
1. **Delete route with waypoints**: Deletes route and all waypoints
2. **Verify cascade**: Check waypoints table empty for route_id
3. **Non-existent route**: Returns False
## Integration Tests
### Test 1: Complete Database Lifecycle
1. insert_route() with 500 waypoints
2. get_route_by_id() and verify all data
3. update_waypoint() × 100
4. query_routes() with filters
5. delete_route() and verify removal
### Test 2: High-Frequency Update Pattern (GPS-Denied Simulation)
1. insert_route() with 2000 waypoints
2. update_waypoint() × 2000 sequentially
3. Measure total time and throughput
4. Verify all updates persisted correctly
### Test 3: Concurrent Access
1. Insert 10 routes concurrently
2. Update waypoints in parallel (100 concurrent connections)
3. Query routes while updates occurring
4. Verify no deadlocks or data corruption
### Test 4: Transaction Integrity
1. Begin insert_route() transaction
2. Simulate error mid-waypoint insertion
3. Verify complete rollback (no partial data)
## Non-Functional Requirements
### Performance
- **insert_route**: < 200ms for 100 waypoints
- **update_waypoint**: < 30ms (critical path)
- **get_route_by_id**: < 100ms for 2000 waypoints
- **query_routes**: < 150ms for pagination queries
- **Throughput**: 200+ waypoint updates per second
### Scalability
- Connection pool: 50-100 connections
- Support 1000+ concurrent operations
- Handle tables with millions of waypoints
### Reliability
- ACID transaction guarantees
- Automatic retry on transient errors (3 attempts with exponential backoff)
- Connection health checks
- Graceful degradation on connection pool exhaustion
### Security
- SQL injection prevention (parameterized queries only)
- Principle of least privilege (database user permissions)
- Connection string encryption
## Dependencies
### Internal Components
- None (lowest layer)
### External Dependencies
- **PostgreSQL** or **MySQL**: Relational database
- **SQLAlchemy** or **psycopg2**: Database driver
- **Alembic**: Schema migration tool
## Data Models
### Database Schema
```sql
-- Routes table
CREATE TABLE routes (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_created_at (created_at),
INDEX idx_name (name)
);
-- Waypoints table
CREATE TABLE waypoints (
id VARCHAR(36) PRIMARY KEY,
route_id VARCHAR(36) NOT NULL,
lat DECIMAL(10, 7) NOT NULL,
lon DECIMAL(11, 7) NOT NULL,
altitude DECIMAL(7, 2),
confidence DECIMAL(3, 2) NOT NULL,
timestamp TIMESTAMP NOT NULL,
refined BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY (route_id) REFERENCES routes(id) ON DELETE CASCADE,
INDEX idx_route_timestamp (route_id, timestamp),
INDEX idx_route_id (route_id, id)
);
-- Geofences table
CREATE TABLE geofences (
id VARCHAR(36) PRIMARY KEY,
route_id VARCHAR(36) NOT NULL,
nw_lat DECIMAL(10, 7) NOT NULL,
nw_lon DECIMAL(11, 7) NOT NULL,
se_lat DECIMAL(10, 7) NOT NULL,
se_lon DECIMAL(11, 7) NOT NULL,
FOREIGN KEY (route_id) REFERENCES routes(id) ON DELETE CASCADE,
INDEX idx_route_id (route_id)
);
```
### Connection Configuration
```python
class DatabaseConfig(BaseModel):
host: str
port: int
database: str
username: str
password: str
pool_size: int = 50
max_overflow: int = 50
pool_timeout: int = 30
pool_recycle: int = 3600
```