13 KiB
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:
-
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.deletedevent for other services
-
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
-
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
-
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:
# 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:
# 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:
- Tenant Service - Core deletion logic, memberships, ownership transfer
- Orders Service - Example implementation complete
🔄 In Progress:
- Inventory Service - Template created, needs testing
⏳ Pending:
-
Recipes Service
- Models to delete: Recipe, RecipeIngredient, RecipeStep, RecipeNutrition
-
Production Service
- Models to delete: ProductionBatch, ProductionSchedule, ProductionPlan
-
Sales Service
- Models to delete: Sale, SaleItem, DailySales, SalesReport
-
Suppliers Service
- Models to delete: Supplier, SupplierProduct, PurchaseOrder, PurchaseOrderItem
-
POS Service
- Models to delete: POSConfiguration, POSTransaction, POSSession
-
External Service
- Models to delete: ExternalDataCache, APIKeyUsage
-
Forecasting Service (Already has some deletion logic)
- Models to delete: Forecast, PredictionBatch, ModelArtifact
-
Training Service (Already has some deletion logic)
- Models to delete: TrainingJob, TrainedModel, ModelMetrics
-
Notification Service (Already has some deletion logic)
- Models to delete: Notification, NotificationPreference, NotificationLog
-
Alert Processor Service
- Models to delete: Alert, AlertRule, AlertHistory
-
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:
- Coordinates deletion across all services
- Implements saga pattern for distributed transactions
- Provides rollback/compensation logic for failures
- Tracks deletion job status
Components Needed
1. Deletion Orchestrator Service
# 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
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:
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_attimestamp 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
- Week 1: Complete Phase 2 for critical services (Orders, Inventory, Recipes, Production)
- Week 2: Complete Phase 2 for remaining services
- Week 3: Implement Phase 3 (Orchestration & Saga)
- Week 4: Implement Phase 4 (Enhanced Features)
- Week 5: Testing & Documentation
- Week 6: Production deployment with monitoring
Monitoring & Alerts
Metrics to Track
tenant_deletion_duration_seconds- How long deletions taketenant_deletion_items_deleted- Number of items deleted per servicetenant_deletion_errors_total- Count of deletion failurestenant_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
- Authorization: Only owners, admins, or internal services can delete
- Audit Trail: All deletions must be logged
- No Direct DB Access: All deletions through API endpoints
- Rate Limiting: Prevent abuse of deletion endpoints
- Confirmation Required: User must confirm before deletion
- 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
- Immediate: Complete Phase 2 for remaining 8 services using the template above
- Short-term: Implement orchestration layer in auth service
- Mid-term: Add saga pattern and rollback logic
- 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