379 lines
13 KiB
Markdown
379 lines
13 KiB
Markdown
|
|
# Tenant Deletion Implementation Guide
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
This guide documents the standardized approach for implementing tenant data deletion across all microservices in the Bakery-IA platform.
|
||
|
|
|
||
|
|
## Architecture
|
||
|
|
|
||
|
|
### Phase 1: Tenant Service Core (✅ COMPLETED)
|
||
|
|
|
||
|
|
The tenant service now provides three critical endpoints:
|
||
|
|
|
||
|
|
1. **DELETE `/api/v1/tenants/{tenant_id}`** - Delete a tenant and all associated data
|
||
|
|
- Verifies caller permissions (owner/admin or internal service)
|
||
|
|
- Checks for other admins before allowing deletion
|
||
|
|
- Cascades deletion to local tenant data (members, subscriptions)
|
||
|
|
- Publishes `tenant.deleted` event for other services
|
||
|
|
|
||
|
|
2. **DELETE `/api/v1/tenants/user/{user_id}/memberships`** - Delete all memberships for a user
|
||
|
|
- Only accessible by internal services
|
||
|
|
- Removes user from all tenant memberships
|
||
|
|
- Used during user account deletion
|
||
|
|
|
||
|
|
3. **POST `/api/v1/tenants/{tenant_id}/transfer-ownership`** - Transfer tenant ownership
|
||
|
|
- Atomic operation to change owner and update member roles
|
||
|
|
- Requires current owner permission or internal service call
|
||
|
|
|
||
|
|
4. **GET `/api/v1/tenants/{tenant_id}/admins`** - Get all tenant admins
|
||
|
|
- Returns list of users with owner/admin roles
|
||
|
|
- Used by auth service to check before tenant deletion
|
||
|
|
|
||
|
|
### Phase 2: Service-Level Deletion (IN PROGRESS)
|
||
|
|
|
||
|
|
Each microservice must implement tenant data deletion using the standardized pattern.
|
||
|
|
|
||
|
|
## Implementation Pattern
|
||
|
|
|
||
|
|
### Step 1: Create Deletion Service
|
||
|
|
|
||
|
|
Each service should create a `tenant_deletion_service.py` that implements `BaseTenantDataDeletionService`:
|
||
|
|
|
||
|
|
```python
|
||
|
|
# services/{service}/app/services/tenant_deletion_service.py
|
||
|
|
|
||
|
|
from typing import Dict
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
from sqlalchemy import select, delete, func
|
||
|
|
import structlog
|
||
|
|
|
||
|
|
from shared.services.tenant_deletion import (
|
||
|
|
BaseTenantDataDeletionService,
|
||
|
|
TenantDataDeletionResult
|
||
|
|
)
|
||
|
|
|
||
|
|
class {Service}TenantDeletionService(BaseTenantDataDeletionService):
|
||
|
|
"""Service for deleting all {service}-related data for a tenant"""
|
||
|
|
|
||
|
|
def __init__(self, db_session: AsyncSession):
|
||
|
|
super().__init__("{service}-service")
|
||
|
|
self.db = db_session
|
||
|
|
|
||
|
|
async def get_tenant_data_preview(self, tenant_id: str) -> Dict[str, int]:
|
||
|
|
"""Get counts of what would be deleted"""
|
||
|
|
preview = {}
|
||
|
|
|
||
|
|
# Count each entity type
|
||
|
|
# Example:
|
||
|
|
# count = await self.db.scalar(
|
||
|
|
# select(func.count(Model.id)).where(Model.tenant_id == tenant_id)
|
||
|
|
# )
|
||
|
|
# preview["model_name"] = count or 0
|
||
|
|
|
||
|
|
return preview
|
||
|
|
|
||
|
|
async def delete_tenant_data(self, tenant_id: str) -> TenantDataDeletionResult:
|
||
|
|
"""Delete all data for a tenant"""
|
||
|
|
result = TenantDataDeletionResult(tenant_id, self.service_name)
|
||
|
|
|
||
|
|
try:
|
||
|
|
# Delete each entity type
|
||
|
|
# 1. Delete child records first (respect foreign keys)
|
||
|
|
# 2. Then delete parent records
|
||
|
|
# 3. Use try-except for each delete operation
|
||
|
|
|
||
|
|
# Example:
|
||
|
|
# try:
|
||
|
|
# delete_stmt = delete(Model).where(Model.tenant_id == tenant_id)
|
||
|
|
# result_proxy = await self.db.execute(delete_stmt)
|
||
|
|
# result.add_deleted_items("model_name", result_proxy.rowcount)
|
||
|
|
# except Exception as e:
|
||
|
|
# result.add_error(f"Model deletion: {str(e)}")
|
||
|
|
|
||
|
|
await self.db.commit()
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
await self.db.rollback()
|
||
|
|
result.add_error(f"Fatal error: {str(e)}")
|
||
|
|
|
||
|
|
return result
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 2: Add API Endpoints
|
||
|
|
|
||
|
|
Add two endpoints to the service's API router:
|
||
|
|
|
||
|
|
```python
|
||
|
|
# services/{service}/app/api/{main_router}.py
|
||
|
|
|
||
|
|
@router.delete("/tenant/{tenant_id}")
|
||
|
|
async def delete_tenant_data(
|
||
|
|
tenant_id: str,
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
db = Depends(get_db)
|
||
|
|
):
|
||
|
|
"""Delete all {service} data for a tenant (internal only)"""
|
||
|
|
|
||
|
|
# Only allow internal service calls
|
||
|
|
if current_user.get("type") != "service":
|
||
|
|
raise HTTPException(status_code=403, detail="Internal services only")
|
||
|
|
|
||
|
|
from app.services.tenant_deletion_service import {Service}TenantDeletionService
|
||
|
|
|
||
|
|
deletion_service = {Service}TenantDeletionService(db)
|
||
|
|
result = await deletion_service.safe_delete_tenant_data(tenant_id)
|
||
|
|
|
||
|
|
return {
|
||
|
|
"message": "Tenant data deletion completed",
|
||
|
|
"summary": result.to_dict()
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/tenant/{tenant_id}/deletion-preview")
|
||
|
|
async def preview_tenant_deletion(
|
||
|
|
tenant_id: str,
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
db = Depends(get_db)
|
||
|
|
):
|
||
|
|
"""Preview what would be deleted (dry-run)"""
|
||
|
|
|
||
|
|
# Allow internal services and admins
|
||
|
|
if not (current_user.get("type") == "service" or
|
||
|
|
current_user.get("role") in ["owner", "admin"]):
|
||
|
|
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||
|
|
|
||
|
|
from app.services.tenant_deletion_service import {Service}TenantDeletionService
|
||
|
|
|
||
|
|
deletion_service = {Service}TenantDeletionService(db)
|
||
|
|
preview = await deletion_service.get_tenant_data_preview(tenant_id)
|
||
|
|
|
||
|
|
return {
|
||
|
|
"tenant_id": tenant_id,
|
||
|
|
"service": "{service}-service",
|
||
|
|
"data_counts": preview,
|
||
|
|
"total_items": sum(preview.values())
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Services Requiring Implementation
|
||
|
|
|
||
|
|
### ✅ Completed:
|
||
|
|
1. **Tenant Service** - Core deletion logic, memberships, ownership transfer
|
||
|
|
2. **Orders Service** - Example implementation complete
|
||
|
|
|
||
|
|
### 🔄 In Progress:
|
||
|
|
3. **Inventory Service** - Template created, needs testing
|
||
|
|
|
||
|
|
### ⏳ Pending:
|
||
|
|
4. **Recipes Service**
|
||
|
|
- Models to delete: Recipe, RecipeIngredient, RecipeStep, RecipeNutrition
|
||
|
|
|
||
|
|
5. **Production Service**
|
||
|
|
- Models to delete: ProductionBatch, ProductionSchedule, ProductionPlan
|
||
|
|
|
||
|
|
6. **Sales Service**
|
||
|
|
- Models to delete: Sale, SaleItem, DailySales, SalesReport
|
||
|
|
|
||
|
|
7. **Suppliers Service**
|
||
|
|
- Models to delete: Supplier, SupplierProduct, PurchaseOrder, PurchaseOrderItem
|
||
|
|
|
||
|
|
8. **POS Service**
|
||
|
|
- Models to delete: POSConfiguration, POSTransaction, POSSession
|
||
|
|
|
||
|
|
9. **External Service**
|
||
|
|
- Models to delete: ExternalDataCache, APIKeyUsage
|
||
|
|
|
||
|
|
10. **Forecasting Service** (Already has some deletion logic)
|
||
|
|
- Models to delete: Forecast, PredictionBatch, ModelArtifact
|
||
|
|
|
||
|
|
11. **Training Service** (Already has some deletion logic)
|
||
|
|
- Models to delete: TrainingJob, TrainedModel, ModelMetrics
|
||
|
|
|
||
|
|
12. **Notification Service** (Already has some deletion logic)
|
||
|
|
- Models to delete: Notification, NotificationPreference, NotificationLog
|
||
|
|
|
||
|
|
13. **Alert Processor Service**
|
||
|
|
- Models to delete: Alert, AlertRule, AlertHistory
|
||
|
|
|
||
|
|
14. **Demo Session Service**
|
||
|
|
- May not need tenant deletion (demo data is transient)
|
||
|
|
|
||
|
|
## Phase 3: Orchestration & Saga Pattern (PENDING)
|
||
|
|
|
||
|
|
### Goal
|
||
|
|
Create a centralized deletion orchestrator in the auth service that:
|
||
|
|
1. Coordinates deletion across all services
|
||
|
|
2. Implements saga pattern for distributed transactions
|
||
|
|
3. Provides rollback/compensation logic for failures
|
||
|
|
4. Tracks deletion job status
|
||
|
|
|
||
|
|
### Components Needed
|
||
|
|
|
||
|
|
#### 1. Deletion Orchestrator Service
|
||
|
|
```python
|
||
|
|
# services/auth/app/services/deletion_orchestrator.py
|
||
|
|
|
||
|
|
class DeletionOrchestrator:
|
||
|
|
"""Coordinates tenant deletion across all services"""
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
self.service_registry = {
|
||
|
|
"orders": OrdersServiceClient(),
|
||
|
|
"inventory": InventoryServiceClient(),
|
||
|
|
"recipes": RecipesServiceClient(),
|
||
|
|
# ... etc
|
||
|
|
}
|
||
|
|
|
||
|
|
async def orchestrate_tenant_deletion(
|
||
|
|
self,
|
||
|
|
tenant_id: str,
|
||
|
|
deletion_job_id: str
|
||
|
|
) -> DeletionResult:
|
||
|
|
"""
|
||
|
|
Execute deletion saga across all services
|
||
|
|
Returns comprehensive result with per-service status
|
||
|
|
"""
|
||
|
|
pass
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 2. Deletion Job Status Tracking
|
||
|
|
```sql
|
||
|
|
CREATE TABLE deletion_jobs (
|
||
|
|
id UUID PRIMARY KEY,
|
||
|
|
tenant_id UUID NOT NULL,
|
||
|
|
initiated_by UUID NOT NULL,
|
||
|
|
status VARCHAR(50), -- pending, in_progress, completed, failed, rolled_back
|
||
|
|
services_completed JSONB,
|
||
|
|
services_failed JSONB,
|
||
|
|
total_items_deleted INTEGER,
|
||
|
|
error_log TEXT,
|
||
|
|
created_at TIMESTAMP,
|
||
|
|
completed_at TIMESTAMP
|
||
|
|
);
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 3. Service Registry
|
||
|
|
Track all services that need to be called for deletion:
|
||
|
|
|
||
|
|
```python
|
||
|
|
SERVICE_DELETION_ENDPOINTS = {
|
||
|
|
"orders": "http://orders-service:8000/api/v1/orders/tenant/{tenant_id}",
|
||
|
|
"inventory": "http://inventory-service:8000/api/v1/inventory/tenant/{tenant_id}",
|
||
|
|
"recipes": "http://recipes-service:8000/api/v1/recipes/tenant/{tenant_id}",
|
||
|
|
"production": "http://production-service:8000/api/v1/production/tenant/{tenant_id}",
|
||
|
|
"sales": "http://sales-service:8000/api/v1/sales/tenant/{tenant_id}",
|
||
|
|
"suppliers": "http://suppliers-service:8000/api/v1/suppliers/tenant/{tenant_id}",
|
||
|
|
"pos": "http://pos-service:8000/api/v1/pos/tenant/{tenant_id}",
|
||
|
|
"external": "http://external-service:8000/api/v1/external/tenant/{tenant_id}",
|
||
|
|
"forecasting": "http://forecasting-service:8000/api/v1/forecasts/tenant/{tenant_id}",
|
||
|
|
"training": "http://training-service:8000/api/v1/models/tenant/{tenant_id}",
|
||
|
|
"notification": "http://notification-service:8000/api/v1/notifications/tenant/{tenant_id}",
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Phase 4: Enhanced Features (PENDING)
|
||
|
|
|
||
|
|
### 1. Soft Delete with Retention Period
|
||
|
|
- Add `deleted_at` timestamp to tenants table
|
||
|
|
- Implement 30-day retention before permanent deletion
|
||
|
|
- Allow restoration during retention period
|
||
|
|
|
||
|
|
### 2. Audit Logging
|
||
|
|
- Log all deletion operations with details
|
||
|
|
- Track who initiated deletion and when
|
||
|
|
- Store deletion summaries for compliance
|
||
|
|
|
||
|
|
### 3. Deletion Preview for All Services
|
||
|
|
- Aggregate preview from all services
|
||
|
|
- Show comprehensive impact analysis
|
||
|
|
- Allow download of deletion report
|
||
|
|
|
||
|
|
### 4. Async Job Status Check
|
||
|
|
- Add endpoint to check deletion job progress
|
||
|
|
- WebSocket support for real-time updates
|
||
|
|
- Email notification on completion
|
||
|
|
|
||
|
|
## Testing Strategy
|
||
|
|
|
||
|
|
### Unit Tests
|
||
|
|
- Test each service's deletion service independently
|
||
|
|
- Mock database operations
|
||
|
|
- Verify correct SQL generation
|
||
|
|
|
||
|
|
### Integration Tests
|
||
|
|
- Test deletion across multiple services
|
||
|
|
- Verify CASCADE deletes work correctly
|
||
|
|
- Test rollback scenarios
|
||
|
|
|
||
|
|
### End-to-End Tests
|
||
|
|
- Full tenant deletion from API call to completion
|
||
|
|
- Verify all data is actually deleted
|
||
|
|
- Test with production-like data volumes
|
||
|
|
|
||
|
|
## Rollout Plan
|
||
|
|
|
||
|
|
1. **Week 1**: Complete Phase 2 for critical services (Orders, Inventory, Recipes, Production)
|
||
|
|
2. **Week 2**: Complete Phase 2 for remaining services
|
||
|
|
3. **Week 3**: Implement Phase 3 (Orchestration & Saga)
|
||
|
|
4. **Week 4**: Implement Phase 4 (Enhanced Features)
|
||
|
|
5. **Week 5**: Testing & Documentation
|
||
|
|
6. **Week 6**: Production deployment with monitoring
|
||
|
|
|
||
|
|
## Monitoring & Alerts
|
||
|
|
|
||
|
|
### Metrics to Track
|
||
|
|
- `tenant_deletion_duration_seconds` - How long deletions take
|
||
|
|
- `tenant_deletion_items_deleted` - Number of items deleted per service
|
||
|
|
- `tenant_deletion_errors_total` - Count of deletion failures
|
||
|
|
- `tenant_deletion_jobs_status` - Current status of deletion jobs
|
||
|
|
|
||
|
|
### Alerts
|
||
|
|
- Alert if deletion takes longer than 5 minutes
|
||
|
|
- Alert if any service fails to delete data
|
||
|
|
- Alert if CASCADE deletes don't work as expected
|
||
|
|
|
||
|
|
## Security Considerations
|
||
|
|
|
||
|
|
1. **Authorization**: Only owners, admins, or internal services can delete
|
||
|
|
2. **Audit Trail**: All deletions must be logged
|
||
|
|
3. **No Direct DB Access**: All deletions through API endpoints
|
||
|
|
4. **Rate Limiting**: Prevent abuse of deletion endpoints
|
||
|
|
5. **Confirmation Required**: User must confirm before deletion
|
||
|
|
6. **GDPR Compliance**: Support right to be forgotten
|
||
|
|
|
||
|
|
## Current Status Summary
|
||
|
|
|
||
|
|
| Phase | Status | Completion |
|
||
|
|
|-------|--------|------------|
|
||
|
|
| Phase 1: Tenant Service Core | ✅ Complete | 100% |
|
||
|
|
| Phase 2: Service Deletions | 🔄 In Progress | 20% (2/10 services) |
|
||
|
|
| Phase 3: Orchestration | ⏳ Pending | 0% |
|
||
|
|
| Phase 4: Enhanced Features | ⏳ Pending | 0% |
|
||
|
|
|
||
|
|
## Next Steps
|
||
|
|
|
||
|
|
1. **Immediate**: Complete Phase 2 for remaining 8 services using the template above
|
||
|
|
2. **Short-term**: Implement orchestration layer in auth service
|
||
|
|
3. **Mid-term**: Add saga pattern and rollback logic
|
||
|
|
4. **Long-term**: Implement soft delete and enhanced features
|
||
|
|
|
||
|
|
## Files Created/Modified
|
||
|
|
|
||
|
|
### New Files:
|
||
|
|
- `/services/shared/services/tenant_deletion.py` - Base classes and utilities
|
||
|
|
- `/services/orders/app/services/tenant_deletion_service.py` - Orders implementation
|
||
|
|
- `/services/inventory/app/services/tenant_deletion_service.py` - Inventory template
|
||
|
|
- `/TENANT_DELETION_IMPLEMENTATION_GUIDE.md` - This document
|
||
|
|
|
||
|
|
### Modified Files:
|
||
|
|
- `/services/tenant/app/services/tenant_service.py` - Added deletion methods
|
||
|
|
- `/services/tenant/app/services/messaging.py` - Added deletion event
|
||
|
|
- `/services/tenant/app/api/tenants.py` - Added DELETE endpoint
|
||
|
|
- `/services/tenant/app/api/tenant_members.py` - Added membership deletion & transfer endpoints
|
||
|
|
- `/services/orders/app/api/orders.py` - Added tenant deletion endpoints
|
||
|
|
|
||
|
|
## References
|
||
|
|
|
||
|
|
- [Saga Pattern](https://microservices.io/patterns/data/saga.html)
|
||
|
|
- [GDPR Right to Erasure](https://gdpr-info.eu/art-17-gdpr/)
|
||
|
|
- [Distributed Transactions in Microservices](https://www.nginx.com/blog/microservices-pattern-distributed-transactions-saga/)
|