Volver a la wiki

Module Gating · SaaSModule + Plan en lugar de feature flags genéricos

ADR — Module Gating con SaaSModule + Plan

Contexto

CreaRack Pro es una plataforma SaaS multi-tenant cuyas funcionalidades se agrupan en módulos discretos: Rack Editor, Network Maps, Network Management, Network Observatory, Wireless Monitor, UPS Monitor y Digital Signage. Cada organización contrata un plan (starter, pro, custom) y debe poder usar únicamente los módulos contemplados por su suscripción, más eventuales extras à la carte. La habilitación afecta a URLs de páginas, endpoints de la API REST (~509), navegación lateral y CTAs en plantillas.

Hasta v1.0.48 (2026-04-01, commit 806214ce) no existía un mecanismo formal: las funcionalidades se asumían disponibles para toda organización autenticada. La salida comercial del producto requería un sistema explícito que permitiese vender planes diferenciados sin desplegar ramas distintas ni mantener forks por cliente.

Problema

Hace falta un mecanismo que cumpla simultáneamente:

  1. Bloqueo en runtime sin cambiar código entre clientes. La org no debe poder llegar a una vista o endpoint cuyo módulo no tiene activo.
  2. Granularidad por módulo, no por permission individual. El producto se vende por bloques funcionales completos, no por acción granular (eso lo cubre ModulePermission aparte para roles dentro de la org).
  3. Composición plan + à la carte. El plan define un set base; la org puede tener extras concedidos comercialmente sin saltar de tier.
  4. Auditable y editable desde Django Admin sin redeploy ni intervención de ingeniería para cada cambio comercial.
  5. Compatible con la arquitectura existente (Django 6 + Ninja + Channels, middleware stack), sin introducir un servicio externo de feature flags ni un SDK adicional que complique despliegue, secrets y observabilidad.

Opciones consideradas

1. Feature flags genéricos (django-waffle, LaunchDarkly, Unleash) — biblioteca o SaaS externo pensados para flags booleanos por usuario, porcentaje o segmento. Soporte rico para A/B testing y rollouts graduales. Descartada: el caso de uso no es experimentación, es derecho de uso comercial vinculado a contrato. Añade dependencia externa (en el caso SaaS) o tablas y panel paralelos al Admin (en el caso self-hosted), sin aportar el modelado plan↔módulo que necesitamos. Para LaunchDarkly/Unleash añadiría además latencia de red en cada request o complejidad de SDK con cache local.

2. RBAC granular por permission individual — usar el sistema de permissions de Django (auth.Permission) o django-guardian para gobernar acceso a cada vista. Descartada: granularidad equivocada. El producto se vende en bloques (network-observatory entero, no “puede ver targets pero no editarlos”); modelar esto a nivel permission obliga a definir y mantener decenas de permisos sintéticos por módulo y a una lógica de checks compleja en cada view. La granularidad fina dentro de un módulo ya la resuelve ModulePermission para roles internos de la org, que es problema distinto.

3. Plan tiers como enum hardcoded en settings — STARTER_FEATURES = [...], comprobaciones tipo if org.plan == "pro" en cada vista. Descartada: cada nuevo plan o cada extra à la carte requiere edición de código y redeploy. Imposibilita configuración comercial dinámica desde Admin. Genera duplicación entre frontend (mostrar/ocultar) y backend (autorizar).

4. Tabla M:N Plan ↔ SaaSModule + middleware con registry URL → módulo — modelar módulos como filas en BD (SaaSModule con slug, is_core, url_prefixes), planes como filas (Plan) con M2M a módulos, y Organization con FK a Plan más M2M extra_modules para resolver à la carte. Un ModuleGatingMiddleware resuelve el módulo de cada path vía un registry estático (URL_TO_MODULE con longest-prefix-match) y bloquea en 403 si la org no lo tiene activo. Template tag {% ifmodule "slug" %} para gating en navegación. Seleccionada.

Decisión

Adoptamos la opción 4. El sistema queda definido por:

Consecuencias

Status

Accepted — implementado en commit 806214ce (2026-04-01, v1.0.48). En producción desde entonces. Migrations posteriores 0014_fix_custom_plan_modules y 0015_add_core_to_extra_modules ajustan el seed sin alterar la decisión arquitectural.

Véase también

Subir