# 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/)