Consolidar un modelo de contenido sin migrar
Consolidar un modelo de contenido en un sitio en vivo es sobre todo un ejercicio de lo que usted se niega a romper. Así es como consolidamos doce tipos de contenido en seis sin tiempo de inactividad, y la disciplina de depuración que mantuvo la migración honesta cuando el frontend quedó en blanco.
Por qué un modelo de contenido se expande
Los modelos de contenido crecen como crecen las bases de código: una decisión razonable a la vez. Un sitio se lanza con un puñado de tipos de contenido, y luego cada nuevo tipo de página parece merecer su propio tipo. Artículos, casos de estudio y noticias. Plataformas, capacidades, soluciones e industrias. Cada adición es defendible por sí sola; la suma es un modelo con doce tipos de contenido, muchos de ellos estructuralmente casi idénticos, cada uno con su propio conjunto de campos ligeramente distinto.
El costo se paga después, y lo pagan todos. Los editores enfrentan una docena de opciones de "Crear" donde bastarían tres. Los campos que significan lo mismo tienen nombres de máquina distintos en cada tipo, así que nada puede consultarse de manera uniforme. El frontend desacoplado tiene que tratar cada tipo como caso especial. Y cada nuevo campo o cambio de plantilla hay que hacerlo, y probarlo, una docena de veces en lugar de una.
Nuestro modelo había llegado a ese punto. Nos propusimos consolidar doce tipos de contenido en seis: un único tipo paraguas solution (con Plataforma, Capacidad, Oferta e Industria como discriminador), un único tipo paraguas resource (que absorbe artículos y casos de estudio), un page consolidado y el resto genuinamente distinto. Lo difícil no era el modelo objetivo. Lo difícil era que el sitio estaba en vivo, y todo lo relacionado con esos doce tipos (las URL, los posicionamientos de búsqueda, el historial editorial) tenía que sobrevivir al cambio.
La restricción: cambiar el modelo, preservar todo lo demás
Una consolidación de tipos de contenido en un sitio en vivo se define por sus invariantes. Antes de escribir una línea de código de migración, enumeramos lo que no podía cambiar:
- Identidad del nodo. Cada nodo conserva su id y su UUID. Cualquier cosa que haga referencia a un nodo por id (enlaces internos, integraciones, analítica) debe seguir funcionando.
- URL. Cada alias publicado sobrevive. Una consolidación que cambia las URL es un evento de SEO, no una refactorización. Los alias existentes persisten intactos.
- Revisiones y moderación. El historial editorial completo y el estado de moderación de cada nodo se mueven con él. Ningún nodo vuelve silenciosamente a borrador.
- Metadatos SEO. Los títulos y las meta descripciones por nodo se conservan, incluso cuando cambia el almacenamiento subyacente de los campos.
- Disponibilidad. La migración se ejecuta como parte de un despliegue normal, no en una ventana de mantenimiento.
Estos invariantes descartaron el enfoque obvio (crear nuevos tipos de contenido y copiar los nodos en ellos), porque copiar genera nuevos id de nodo y rompe cada una de las garantías anteriores. El cambio tenía que ocurrir sin migrar los datos.
La técnica: reescribir el bundle, conservar el nodo
El tipo de contenido de un nodo Drupal, su "bundle", se almacena como dato, no como identidad. El id del nodo es la identidad; el bundle es una columna. Esa es la costura sobre la que gira toda la migración: si cambia solo las columnas de clave de bundle, el nodo en sí (id, UUID, revisiones, estado de moderación, alias, marcas de tiempo) queda intacto, porque ninguno de esos datos vive en la columna de bundle.
Así que la migración reescribe el bundle en el mismo lugar. Para cada nodo que pasa de un tipo de origen a su tipo paraguas de destino, actualiza el valor de bundle en la tabla base, en la tabla de datos de campos y en la columna bundle de cada tabla de campo dedicada, y nada más. El nodo conserva todo lo que lo hace ser él mismo y simplemente responde de otra forma a "¿qué tipo eres?" después.
Dos detalles hacen que esto sea seguro en lugar de temerario:
- Orden respecto a la importación de configuración. La reescritura del bundle se ejecuta durante las actualizaciones de base de datos, antes de importar la configuración. Ese orden es decisivo: cuando la importación de configuración elimina después los tipos de contenido de origen ya vacíos, no encuentra nodos adjuntos a ellos, así que reconcilia limpiamente en lugar de eliminar contenido en cascada. Invierta el orden y la misma importación elimina precisamente los nodos que estaba migrando.
- Campos discriminadores, aplicados después. Fusionar cuatro tipos en un solo
solutionperdería la distinción entre una Plataforma y una Capacidad, así que antes de la reescritura se registra el tipo original de cada nodo y, después de que la importación de configuración crea el nuevo campo discriminador, un script complementario marca cada nodo con su tipo (Plataforma, Capacidad, Oferta, Industria). La información no se pierde; se mueve del bundle a un campo, que es donde corresponde.
Cada paso es idempotente: un nodo ya migrado se omite, de modo que una nueva ejecución (en staging, en producción, después de una reversión) no produce ningún cambio. Esa propiedad es lo que permite que una operación que suena destructiva se ejecute con confianza sobre datos en vivo.
Cuando el frontend quedó en blanco: una lección sobre premisas
Con la migración validada en staging, el frontend desacoplado empezó a devolver páginas en blanco. La coincidencia temporal era condenatoria: ocurrió al mismo tiempo que una actualización del framework Next.js, y el error apuntaba a una configuración de imágenes a nivel de framework. La historia obvia se escribía sola: la actualización rompió el renderizado. Esa historia era falsa, y perseguirla habría costado días.
La disciplina que ahorró ese tiempo fue negarse a aceptar la premisa obvia sin evidencia. En lugar de ajustar el framework, reprodujimos la falla y la rastreamos hasta su origen: cada renderizado de página lanzaba una excepción al obtener sus datos. Lo que fallaba era la consulta de datos, no el renderizador.
La causa raíz era un único campo muerto. La consulta del frontend seguía seleccionando un campo que la consolidación había eliminado del esquema GraphQL. GraphQL rechaza una consulta que hace referencia a un campo que el esquema no expone, así que toda la consulta era inválida, la obtención de datos fallaba en cada renderizado y la página se renderizaba en blanco. El "error de Next.js" era un nombre de campo obsoleto en nuestra propia consulta. La solución fue eliminarlo.
La lección se generaliza mucho más allá de este incidente. Cuando un síntoma aparece junto a un cambio sospechoso, la correlación es una hipótesis, no un diagnóstico. Reproduzca la falla, rástrela hasta donde se origina el valor incorrecto y verifique la premisa contra el sistema real; en una arquitectura desacoplada, eso significa introspeccionar el esquema en vivo y comparar su consulta con él en lugar de confiar en los supuestos de cualquiera de las dos partes. Culpar al framework es la respuesta cómoda; rara vez es la correcta.
Cómo un campo vacío vació un listado completo
De la misma consolidación surgió una falla más sutil. Una página de listado (todas las Soluciones de un tipo dado) a veces volvía completamente vacía, aunque el contenido claramente existía. La causa estaba en la intersección del modelo de datos y la capa GraphQL, y vale la pena entenderla porque el modo de falla es muy silencioso.
El nuevo tipo solution llevaba una referencia de entidad de valor único a su tipo. En la mayoría de los nodos estaba definida. En un nodo estaba vacía. Cuando la capa GraphQL resolvió ese nodo, la referencia vacía de valor único se resolvió a null, y debido a cómo el esquema compone una lista, un null en un elemento se propagó hacia arriba y anuló la respuesta de la lista entera. Un campo sin definir en un nodo dejó en blanco un listado de docenas.
El instinto es parchar el frontend para que tolere el null. Eso trata el síntoma. La solución correcta está en la capa de datos: garantizar que el campo esté poblado (un valor de respaldo en la migración que asigne un valor predeterminado razonable, y una restricción que lo mantenga poblado en adelante), de modo que la consulta no tenga con qué atragantarse. La robustez en la fuente vale más que la defensa en cada consumidor, porque solo hay una fuente y hay muchos consumidores.
La reconciliación como higiene
Una consolidación es también una oportunidad, y una obligación, de reconciliar los campos que se expandieron junto con los tipos. Fusionar tipos saca a la luz la duplicación acumulada entre ellos: dos campos que significan "resumen", un campo que ningún tipo usa ya, visualizaciones de formulario que se distanciaron hasta que el mismo tipo de párrafo se veía distinto en dos tipos de contenido.
Tratamos esa reconciliación como parte del trabajo, no como un seguimiento: los campos de resumen duplicados se consolidaron en uno, los campos sin uso se eliminaron en la capa de almacenamiento y el desvío de las visualizaciones de formulario se normalizó para que un componente se vea y se comporte igual en todos los lugares donde aparece. El objetivo de consolidar el modelo era la consistencia; dejar los campos inconsistentes lo habría socavado.
Principios que vale la pena conservar
- Defina la migración por sus invariantes. En un sitio en vivo, lo que usted se niega a romper (id, URL, revisiones, SEO, disponibilidad) determina la técnica antes de escribir cualquier código.
- Cambie los datos en el mismo lugar cuando la identidad deba sobrevivir. El bundle de un nodo es una columna, no su identidad. Reescriba la columna y el nodo conserva todo lo demás.
- Ordene las operaciones en torno al paso destructivo. Ejecutar la reescritura en el mismo lugar antes de la importación de configuración convierte una eliminación en cascada en una reconciliación limpia. La secuencia es parte de la corrección.
- Haga idempotente cada paso de la migración. La idempotencia es lo que permite que una operación que suena destructiva se ejecute con confianza en producción y sobreviva a una nueva ejecución.
- Desconfíe del diagnóstico conveniente. Un síntoma junto a un cambio sospechoso es una hipótesis. Reproduzca, rastree hasta el origen y verifique la premisa contra el sistema real antes de corregir nada.
- Corrija la robustez en la fuente. Un campo sin definir puede anular una respuesta completa. Garantice los datos en la capa que los posee en lugar de defenderse de ellos en cada consumidor.
Consolidar un modelo de contenido es un trabajo poco vistoso con una recompensa desproporcionada: un modelo mental más simple para los editores, una superficie de datos uniforme para cada consumidor y una plataforma más barata de cambiar durante años. Hecho en el mismo lugar, con los invariantes nombrados desde el principio y la depuración mantenida honesta, es también un trabajo que puede hacerse en un sitio en vivo sin que nadie lo note, que es exactamente el punto.