--- id: ADR-0001 type: ADR title: "Arquitectura Multi-Tenant" status: Accepted decision_date: 2026-01-04 updated_at: 2026-01-10 simco_version: "3.8.0" stakeholders: - "Equipo MiChangarrito" tags: - multi-tenant - postgresql - rls - database - arquitectura - seguridad --- # ADR-0001: Arquitectura Multi-Tenant ## Metadata | Campo | Valor | |-------|-------| | **ID** | ADR-0001 | | **Estado** | Accepted | | **Fecha** | 2026-01-06 | | **Autor** | Architecture Team | | **Supersede** | - | --- ## Contexto MiChangarrito es un SaaS para multiples tiendas independientes. Cada tienda (tenant) debe tener sus datos completamente aislados de otras tiendas por razones de: - **Seguridad:** Un tenant no puede ver datos de otro - **Privacidad:** Informacion financiera sensible - **Regulacion:** Cumplimiento con leyes de proteccion de datos - **Escalabilidad:** Soporte para miles de tiendas Se necesita decidir la estrategia de multi-tenancy para la base de datos. --- ## Decision **Adoptamos el modelo de multi-tenancy por columna (tenant_id) con Row Level Security (RLS) de PostgreSQL.** Cada tabla tiene una columna `tenant_id` y politicas RLS que filtran automaticamente los datos segun el tenant actual. ```sql -- Ejemplo de RLS ALTER TABLE catalog.products ENABLE ROW LEVEL SECURITY; CREATE POLICY products_tenant_isolation ON catalog.products USING (tenant_id = current_setting('app.current_tenant')::UUID); ``` --- ## Alternativas Consideradas ### Opcion 1: Base de datos por tenant - **Pros:** - Aislamiento total - Facil backup/restore individual - Sin riesgo de data leaks - **Cons:** - Costoso en recursos - Complejo de mantener (miles de DBs) - Migraciones complicadas ### Opcion 2: Schema por tenant - **Pros:** - Buen aislamiento - Un solo servidor - Backup conjunto - **Cons:** - Limite de schemas en PostgreSQL - Migraciones complicadas - Connection pooling complejo ### Opcion 3: Tenant ID con RLS (Elegida) - **Pros:** - Simple de implementar - Escalable - Migraciones faciles - RLS garantiza aislamiento - Eficiente en recursos - **Cons:** - Requiere disciplina (no olvidar tenant_id) - Dependencia de RLS - Query planning puede ser afectado --- ## Consecuencias ### Positivas 1. **Escalabilidad:** Una sola base de datos maneja todos los tenants 2. **Simplicidad:** Migraciones aplican a todos 3. **Eficiencia:** Pool de conexiones compartido 4. **Seguridad:** RLS es enforced a nivel de DB ### Negativas 1. **Disciplina:** Todas las queries deben considerar tenant_id 2. **Testing:** Necesitamos tests de aislamiento 3. **Performance:** Indices deben incluir tenant_id ### Neutrales 1. **Backup:** Todos los tenants juntos 2. **Monitoreo:** Una base de datos que monitorear --- ## Implementacion ### Estructura de Tablas ```sql CREATE TABLE catalog.products ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id), name VARCHAR(255) NOT NULL, price DECIMAL(10,2), -- ... CONSTRAINT idx_products_tenant UNIQUE (tenant_id, id) ); ``` ### Middleware de Contexto ```typescript // Interceptor NestJS @Injectable() export class TenantInterceptor implements NestInterceptor { async intercept(context: ExecutionContext, next: CallHandler) { const request = context.switchToHttp().getRequest(); const tenantId = request.user?.tenantId; // Setear tenant en conexion await this.dataSource.query( `SET app.current_tenant = '${tenantId}'` ); return next.handle(); } } ``` --- ## Validacion ### Criterios de Exito - [ ] Todas las tablas tienen tenant_id - [ ] RLS habilitado en todas las tablas - [ ] Tests de aislamiento pasan - [ ] No hay queries sin filtro de tenant ### Tests de Aislamiento ```typescript it('should not allow tenant A to see tenant B data', async () => { // Setup tenant A await setTenant(tenantAId); const productA = await createProduct({ name: 'Product A' }); // Switch to tenant B await setTenant(tenantBId); const products = await productService.findAll(); // Tenant B should not see product A expect(products).not.toContainEqual( expect.objectContaining({ id: productA.id }) ); }); ``` --- ## Referencias - [PostgreSQL RLS Documentation](https://www.postgresql.org/docs/current/ddl-rowsecurity.html) - [Multi-tenant SaaS patterns](https://docs.microsoft.com/en-us/azure/sql-database/sql-database-design-patterns-multi-tenancy) - [DATABASE_INVENTORY.yml](../../orchestration/inventarios/DATABASE_INVENTORY.yml) --- **Fecha decision:** 2026-01-06 **Autores:** Architecture Team