Patrones de Diseño de Bases de Datos para Aplicaciones SaaS
Desde la multi-tenancy hasta la facturación por suscripción y el registro de auditoría, las aplicaciones SaaS tienen necesidades predecibles de bases de datos. Aquí están los patrones que funcionan, con ejemplos de esquemas para cada uno.
Cada aplicación SaaS tiene el mismo conjunto de problemas recurrentes de diseño de bases de datos. ¿Cómo aíslas los datos entre clientes? ¿Cómo modelas suscripciones y facturación? ¿Cómo rastreas quién cambió qué? Estos patrones aparecen con tanta frecuencia que existen soluciones bien establecidas — pero los detalles importan, y elegir el enfoque equivocado al principio es costoso de deshacer.
Esta guía cubre los patrones de diseño de bases de datos más importantes para aplicaciones SaaS. Para cada patrón aprenderás qué resuelve, cómo implementarlo y qué compromisos esperar. ER Flow es la herramienta usada a lo largo de la guía para visualizar y diseñar estos esquemas.
Multi-Tenancy: La Base del SaaS
La multi-tenancy es la capacidad de servir a múltiples clientes (inquilinos) desde una sola instancia de aplicación. Cómo lo manejas a nivel de base de datos determina tus garantías de aislamiento de datos, complejidad operacional y techo de escalabilidad.
Tablas compartidas con `tenant_id` es el enfoque más común. Cada tabla con alcance de inquilino obtiene una columna tenant_id (una clave foránea a una tabla tenants), y cada consulta filtra por tenant_id. El esquema es simple: users(id, tenant_id, email, ...), orders(id, tenant_id, user_id, ...). El riesgo son errores a nivel de aplicación que accidentalmente exponen los datos de un inquilino a otro. Mitiga esto con seguridad a nivel de fila en PostgreSQL o aplicando filtros tenant_id en una clase de consulta base. Esquema por inquilino le da a cada inquilino su propio espacio de nombres dentro de la misma instancia de base de datos. La aplicación selecciona el esquema correcto en el momento de la conexión. Esto proporciona un aislamiento más fuerte pero agrega sobrecarga operacional cuando tienes cientos de inquilinos.
Base de datos por inquilino es el estándar de oro para el aislamiento, usado por productos SaaS empresariales que deben satisfacer estrictos requisitos de cumplimiento. Cada inquilino obtiene una instancia de base de datos completamente separada. La gestión de migraciones se convierte en un problema de sistemas distribuidos y los costos escalan linealmente con el número de inquilinos. Reserva esto para situaciones donde el aislamiento es un requisito estricto, no solo una preferencia.
Esquema de Suscripción y Facturación
El patrón de facturación por suscripción implica cuatro tablas principales. Plans define lo que se ofrece: id, name, price_monthly_cents, price_yearly_cents, features (JSON), trial_days, is_active. Subscriptions rastrea a qué está suscrito un inquilino: id, tenant_id, plan_id, status (un enum de trialing, active, past_due, canceled, paused), trial_ends_at, current_period_start, current_period_end, canceled_at y payment_gateway_subscription_id.
Invoices registra lo que se facturó: id, tenant_id, subscription_id, amount_cents, currency, status (draft, open, paid, void, uncollectible), due_date, paid_at y payment_gateway_invoice_id. Payments registra intentos de pago individuales: id, invoice_id, amount_cents, status, payment_method, gateway_payment_id, failed_reason, created_at. Almacena valores monetarios como enteros en la unidad monetaria más pequeña (centavos para USD) para evitar completamente los problemas de precisión de punto flotante.
Autenticación de Usuario y Roles
Un esquema de autenticación flexible necesita manejar el inicio de sesión social, el restablecimiento de contraseña, la verificación de email y el control de acceso basado en roles. Tablas principales: users(id, email, email_verified_at, password_hash, remember_token, created_at, updated_at). Para autenticación social, agrega oauth_providers(id, user_id, provider, provider_user_id, access_token, refresh_token, expires_at). Para acceso basado en roles, agrega roles(id, tenant_id, name, permissions JSON) y user_roles(user_id, role_id) como tabla de unión.
Para permisos detallados en productos SaaS complejos, considera una tabla de permisos separada en lugar de incrustar permisos en JSON: permissions(id, name, description) y role_permissions(role_id, permission_id). Esto te permite agregar nuevos permisos sin migraciones de esquema. Indexa user_roles.user_id y role_permissions.role_id intensamente — estas columnas se leen en cada verificación de autorización en toda la aplicación.
Registro de Auditoría
Cada aplicación SaaS de producción necesita saber quién cambió qué y cuándo. El patrón estándar usa un registro de auditoría polimórfico: audit_logs(id, tenant_id, user_id, auditable_type, auditable_id, event, old_values JSON, new_values JSON, ip_address, user_agent, created_at). Las columnas auditable_type y auditable_id crean una referencia polimórfica — una tabla de registro de auditoría cubre cambios a cualquier entidad en tu esquema.
Indexa en (tenant_id, auditable_type, auditable_id) para consultar el historial de auditoría de un registro específico, y en (tenant_id, user_id) para consultar todos los cambios realizados por un usuario específico. Almacena old_values y new_values como JSON con solo los campos cambiados, no el registro completo — esto mantiene el tamaño de la tabla manejable incluso para entidades de alta frecuencia. ER Flow te permite modelar esta relación polimórfica visualmente, lo que facilita comunicar el diseño a compañeros de equipo que no están familiarizados con los patrones polimórficos.
Soft Deletes
En SaaS, hacer eliminaciones permanentes de registros suele ser una mala idea. Un cliente elimina algo accidentalmente y espera que lo restaures. Necesitas mantener registros de facturación para cumplimiento. Quieres analizar la cancelación examinando cuentas eliminadas. Los soft deletes resuelven esto con una sola columna deleted_at TIMESTAMP NULL. Un valor NULL significa que el registro está activo; una marca de tiempo significa que fue eliminado.
La compensación es que cada consulta debe incluir WHERE deleted_at IS NULL — o confías en un scope de consulta en tu ORM. Asegúrate de incluir deleted_at como parte de índices compuestos en columnas frecuentemente consultadas, y colócalo después de las columnas de filtro de alta cardinalidad. Si alguna vez necesitas purgar registros para el cumplimiento del RGPD, puedes eliminar permanentemente las filas donde deleted_at cae fuera de tu ventana de retención.
Esquema de Feature Flags
Los feature flags te permiten controlar qué funciones están habilitadas por inquilino o por usuario sin despliegues de código. Un esquema práctico: features(id, key, description, is_enabled_globally, created_at) como el registro de todas las funciones. tenant_features(tenant_id, feature_key, is_enabled, overrides JSON) para anulaciones por inquilino. user_features(user_id, feature_key, is_enabled) para anulaciones por usuario. Al verificar si una función está habilitada, la precedencia es: anulación a nivel de usuario → anulación a nivel de inquilino → valor predeterminado global.
Cachea las búsquedas de feature flags agresivamente — las verificas en cada solicitud. Usa Redis con un TTL corto (60 segundos) e invalida el caché cuando cambia una configuración de función. Este patrón es más flexible que incrustar feature flags en la tabla de planes porque las funciones y los planes pueden evolucionar de forma independiente. En ER Flow, diseñar esto como tres tablas separadas con relaciones claras previene el error común de acoplar la disponibilidad de funciones directamente al nivel de suscripción.
Origen de Eventos para Dominios con Mucha Auditoría
Para dominios donde el historial completo de cambios es crítico — transacciones financieras, registros de cumplimiento, máquinas de estado de pedidos — considera el origen de eventos a nivel de base de datos. En lugar de actualizar registros en el lugar, adjuntas eventos: domain_events(id, tenant_id, aggregate_type, aggregate_id, event_type, payload JSON, metadata JSON, occurred_at, sequence_number). El estado actual de cualquier entidad se deriva reproduciendo sus eventos.
Esto es significativamente más complejo que CRUD estándar, pero te da una pista de auditoría inmutable, la capacidad de reproducir eventos para depuración y una base natural para la arquitectura orientada a eventos. Úsalo selectivamente para las partes de tu esquema donde el historial importa más. ER Flow te permite modelar tanto tu almacén de eventos como tu modelo de lectura (las vistas desnormalizadas construidas desde eventos) en el mismo diagrama, haciendo explícita la relación entre los dos y revisable por todo el equipo.
Diseñar bien los esquemas SaaS requiere pensar varios pasos adelante. Los patrones descritos aquí han sido probados en batalla en cientos de aplicaciones SaaS de producción. Empieza con el enfoque de tabla compartida con multi-tenancy, agrega seguridad a nivel de fila a medida que creces, y añade origen de eventos solo donde realmente resuelva un problema que tienes hoy.