Qué pertenece realmente al módulo ui.apps (y qué no)
Una guía práctica de qué debe contener el módulo Maven ui.apps de un proyecto AEM —componentes, clientlibs, diccionarios i18n— y por qué el contenido autorado nunca debería vivir ahí, junto con las reglas de filter.xml de FileVault que deciden qué se sobrescribe o se borra en cada despliegue.
ui.apps es el módulo Maven de un proyecto AEM que construye un paquete
de contenido FileVault que se instala en /apps. Eso lo sabe cualquier
desarrollador de AEM. Lo que realmente causa incidentes es menos obvio:
qué nodos exactamente pueden vivir ahí, y qué hace el filter.xml del
paquete con cualquier otra cosa que encuentre bajo esas rutas en el
momento del despliegue. Si el scope está mal definido, un despliegue
rutinario de ui.apps puede borrar silenciosamente contenido que no
tiene nada que ver con tu cambio.
Este artículo asume que ya conoces la estructura multimódulo estándar
(core, ui.apps, ui.content, ui.frontend…) —se centra
exclusivamente en qué debe contener ui.apps y cómo su filtro FileVault
controla lo que ocurre al instalar.
Qué entrega realmente ui.apps
ui.apps es un paquete de contenido con packageType=application. En
AEM as a Cloud Service, un paquete de tipo application solo puede tocar
/apps —nunca /content, /conf ni ninguna otra área editable en
tiempo de ejecución. Lo que pertenece bajo /apps es, por definición,
código y configuración creados por desarrolladores y desplegados vía
CI/CD, no contenido autorado por editores en la interfaz de AEM:
- Componentes —la estructura de nodos
cq:Component: el.content.xml(diálogo,sling:resourceType,sling:resourceSuperType), el script HTL y cualquier registro de Sling Model que viva junto a él bajo/apps/mysite/components/.... - Definiciones de carpetas de clientlib —el propio nodo
cq:ClientLibraryFolder:categories,embed,dependencies,jsProcessor/cssProcessor, y los manifiestosjs.txt/css.txtbajo/apps/mysite/clientlibs/.... Esto es distinto del propio resultado JS/CSS minificado: eso normalmente lo construye el móduloui.frontendpor separado y se copia dentro de esa misma estructura de carpetas como parte del build de Maven, pero la definición de la carpeta —su nombre de categoría, su grafo de dependencias— es contenido deui.apps, no deui.frontend. - Diccionarios i18n bajo
/apps—nodossling:MessageEntry/mix:Languagepara cadenas de UI propiedad del desarrollador (etiquetas de campos de diálogo, textos de componente que no son contenido editable) bajo/apps/mysite/i18n/....
Algo que sorprende a los equipos que piensan todo en términos de
/apps: las definiciones de plantillas editables y sus políticas no
se despliegan desde ui.apps, aunque se sientan como “código”. Un
cq:Template y sus nodos cq:Policy viven bajo
/conf/mysite/settings/wcm/..., y /conf es una ruta mutable, editable
por autores —las políticas en particular se editan habitualmente desde
la UI “Edit Template” en producción. Como un paquete application solo
puede tocar /apps, las plantillas y políticas tienen que desplegarse
desde un paquete de tipo content (normalmente ui.content, o un módulo
dedicado de configuración/estructura), típicamente con mode="merge"
para que un redespliegue no pise los ajustes de política que un autor
hizo después del go-live. Lo que realmente pertenece a ui.apps del
mundo de las plantillas es el código del componente de estructura —el
HTL/Java que hay detrás del layout container— no el nodo de plantilla ni
el de política.
Por qué el contenido autorado nunca debe vivir en ui.apps
Las páginas bajo /content, los assets del DAM bajo /content/dam y las
tags usadas para clasificar ese contenido son responsabilidad de
ui.content, nunca de ui.apps —y esto no es solo una cuestión de
estilo, lo impone AEM as a Cloud Service: un mismo paquete de contenido
no puede desplegar a la vez en /apps y en un área editable en tiempo de
ejecución como /content. Pero incluso sin esa regla estricta, mezclar
ambos causa daño real:
- Las promociones en Cloud Manager se vuelven más difíciles de
razonar. Los builds full-stack empaquetan
ui.appsjunto con todo lo demás; si un revisor no puede asumir “este paquete es código puro”, cada promoción exige comprobar de nuevo si se ha colado contenido. - El contenido se sobrescribe o se borra silenciosamente en el
siguiente despliegue.
ui.appsse trata como completamente reemplazable —cada ejecución del pipeline lo reinstala desde cero. El modo de importación por defecto de FileVault esreplace: todo lo cubierto por el filtro del paquete pero no presente en el archivo del paquete se elimina del repositorio al importar. Una página o un asset que haya acabado bajo un filter root deui.appstiene una esperanza de vida muy corta. - El nodo equivocado en el paquete equivocado destruye trabajo de
autor. Suele pasar por accidente: alguien ejecuta
vlt checkouto exporta un paquete con un root demasiado amplio, arrastra un nodo que un autor creó bajo una ruta que el paquete ahora reclama como propia, y el siguiente despliegue deui.appslo borra sin que nadie haya tocado Package Manager directamente.
filter.xml: qué controla realmente un filter root
Todo paquete de contenido —ui.apps incluido— declara su alcance en
META-INF/vault/filter.xml mediante elementos <filter root="...">. Un
filter root no es una pista sobre “dónde suele poner cosas este
paquete”; según la
documentación de Apache Jackrabbit FileVault,
define el subárbol que el paquete posee a efectos de la importación:
- El modo de importación por defecto es
replace: el contenido existente bajo un root cubierto se reemplaza por lo que traiga el paquete —sobrescribiéndolo o borrándolo según haga falta para igualar exactamente el paquete. - Un detalle crítico: “los nodos/propiedades cubiertos por alguna regla
de filtro pero no contenidos en el contenido que se va a importar se
eliminan del repositorio”. Si tu filter root es
/apps/mysitepero eljcr_rootde tu paquete no contiene realmente un nodo que ya existe en destino bajo esa ruta, FileVault lo borra al instalar —es el filter root, no el contenido del paquete, el que decide qué está en scope para ser eliminado. - El contenido fuera de cualquier filter root declarado se deja intacto, sea lo que sea.
- Los elementos
<include>/<exclude>dentro de un<filter>acotan aún más ese root. Se evalúan en orden contra la ruta JCR completa, y gana la última regla que coincida —así que ordenarlos mal cambia silenciosamente lo que un filtro cubre en realidad.
Ese único hecho —las rutas declaradas pero no cubiertas por el contenido del paquete se borran, no se ignoran— es el mecanismo detrás de casi todo incidente del tipo “un despliegue borró contenido que no tenía nada que ver con mi cambio”.
Un filter.xml realista para ui.apps
<?xml version="1.0" encoding="UTF-8"?>
<workspaceFilter version="1.0">
<filter root="/apps/mysite/components"/>
<filter root="/apps/mysite/clientlibs"/>
<filter root="/apps/mysite/i18n"/>
</workspaceFilter>
Tres roots estrechos y explícitos —cada uno coincide exactamente con el
subárbol del que este módulo es responsable. Nada aquí reclama /apps
en bloque, y nada llega hasta /conf o /content.
El error de ser demasiado amplio
<filter root="/apps"/>
Parece un atajo inofensivo —“somos dueños de /apps/mysite, y
/apps/mysite está bajo /apps, así que por qué no”. Pero el filter
root es /apps en sí mismo. Al instalar, FileVault considera ahora que
todo el árbol /apps —incluyendo los propios Core Components de AEM
bajo /apps/core, el paquete de otro equipo bajo /apps/othersite,
cualquier cosa— está cubierto por este paquete. Como el jcr_root de tu
paquete solo contiene realmente apps/mysite/..., todo lo demás bajo
/apps está “cubierto pero no contenido”, y se borra al importar. Así es
exactamente como un despliegue rutinario de ui.apps se carga los Core
Components o una aplicación hermana en una instancia AEM compartida.
El error de ser demasiado estrecho
<filter root="/apps/mysite/components"/>
<!-- alguien añade /apps/mysite/templates/structure localmente
y se olvida de añadir un filter root para esa ruta -->
El content-package-maven-plugin construye el paquete estrictamente a
partir de lo que cubre filter.xml. Una carpeta añadida bajo jcr_root
que no está bajo ningún filter root declarado simplemente se excluye del
paquete construido —sin aviso, sin fallo de build. Está en git, existe en
disco, y nunca llega a la instancia de destino. Este es el modo de fallo
más silencioso: nada se rompe de forma visible, una funcionalidad
simplemente no aparece nunca, y el arreglo suele ser “a alguien se le
olvidó añadir una línea <filter root>”.
Dónde aparece esto en la práctica
- Fallos de despliegue en Cloud Manager por filter roots
solapados. La propia documentación de Adobe es explícita en que el
filtro de un container package nunca debería solaparse con el filtro
de un application package, y lo mismo aplica entre dos application
packages. Si
ui.appsy un paquete de estructura de repositorio o de un vendor declaran ambos un root que cubre la misma ruta, el pipeline puede fallar en la validación o, peor, instalarse correctamente pero dejar a los dos paquetes disputándose los mismos nodos en cada despliegue posterior. Cuando un pipeline de Cloud Manager falla en el paso de deploy con errores relacionados con paquetes, comparar elfilter.xmlde todos los content packages de la entrega entre sí es de lo primero que merece la pena revisar. - Contenido que desaparece justo después de un despliegue de
ui.apps. Si algo bajo
/appsdesaparece en el instante en que se instala un paqueteui.apps, casi siempre la respuesta es el filter root: o bien el root es más amplio de lo previsto y arrastró algo que no debería poseer, o un nodo que antes estaba cubierto por un filtro más antiguo quedó huérfano al estrechar el filtro sin migrar antes el contenido. Comparar elfilter.xmlentre la versión anterior y la actual del paquete —no solo el diff deljcr_root— es la forma más rápida de encontrarlo.