Ce qui appartient vraiment au module ui.apps (et ce qui n'y appartient pas)
Un guide pratique de ce que le module Maven ui.apps d'un projet AEM doit contenir — composants, clientlibs, dictionnaires i18n — et pourquoi le contenu rédigé par les auteurs n'y a jamais sa place, avec les règles filter.xml de FileVault qui décident ce qui est écrasé ou supprimé à chaque déploiement.
ui.apps est le module Maven d’un projet AEM qui construit un package de
contenu FileVault s’installant dans /apps. Ça, tout développeur AEM le
sait. Ce qui provoque de vrais incidents est moins évident : quels nœuds
exactement ont le droit d’y vivre, et ce que fait le filter.xml du
package avec tout ce qu’il trouve d’autre sous ces chemins au moment du
déploiement. Si le périmètre est mal défini, un déploiement ui.apps de
routine peut supprimer silencieusement du contenu qui n’a rien à voir
avec votre changement.
Cet article suppose que vous connaissez déjà la structure multimodule
standard (core, ui.apps, ui.content, ui.frontend…) — il se
concentre uniquement sur ce que ui.apps doit contenir et sur la façon
dont son filtre FileVault contrôle ce qui se passe à l’installation.
Ce que ui.apps livre réellement
ui.apps est un package de contenu de type packageType=application.
Sur AEM as a Cloud Service, un package de type application ne peut
toucher qu’à /apps — jamais /content, /conf, ni aucune autre zone
modifiable au runtime. Ce qui appartient sous /apps est, par
définition, du code et de la configuration écrits par des développeurs et
livrés via CI/CD, pas du contenu rédigé par des éditeurs dans l’interface
AEM :
- Les composants — la structure de nœuds
cq:Component: le.content.xml(dialogue,sling:resourceType,sling:resourceSuperType), le script HTL et tout enregistrement de Sling Model qui vit à côté sous/apps/mysite/components/.... - Les définitions de dossiers clientlib — le nœud
cq:ClientLibraryFolderlui-même :categories,embed,dependencies,jsProcessor/cssProcessor, et les manifestesjs.txt/css.txtsous/apps/mysite/clientlibs/.... Ceci est distinct du résultat JS/CSS minifié lui-même : celui-ci est en général construit par le moduleui.frontendséparé et copié dans cette même structure de dossiers pendant le build Maven, mais la définition du dossier — son nom de catégorie, son graphe de dépendances — est du contenuui.apps, pasui.frontend. - Les dictionnaires i18n sous
/apps— les nœudssling:MessageEntry/mix:Languagepour les chaînes d’interface détenues par le développeur (libellés de champs de dialogue, textes de composant qui ne sont pas du contenu éditable) sous/apps/mysite/i18n/....
Une chose qui surprend les équipes qui pensent tout en termes de
/apps : les définitions de templates éditables et leurs policies ne
sont pas livrées depuis ui.apps, même si elles ressemblent à du
“code”. Un cq:Template et ses nœuds cq:Policy vivent sous
/conf/mysite/settings/wcm/..., et /conf est un chemin mutable,
modifiable par les auteurs — les policies en particulier sont
régulièrement modifiées par les auteurs via l’interface “Edit Template”
en production. Comme un package application ne peut toucher qu’à
/apps, les templates et les policies doivent être livrés depuis un
package de type content (généralement ui.content, ou un module dédié
de configuration/structure), typiquement avec mode="merge" pour qu’un
redéploiement n’écrase pas les réglages de policy qu’un auteur a faits
après la mise en production. Ce qui appartient réellement à ui.apps
dans le monde des templates, c’est le code du composant de structure —
le HTL/Java derrière le layout container — pas le nœud de template ni
celui de la policy.
Pourquoi le contenu rédigé ne doit jamais vivre dans ui.apps
Les pages sous /content, les assets DAM sous /content/dam, et les
tags utilisés pour classer ce contenu relèvent de ui.content, jamais de
ui.apps — et ce n’est pas qu’une question de style, c’est imposé par
AEM as a Cloud Service : un même package de contenu ne peut pas déployer
à la fois vers /apps et vers une zone modifiable au runtime comme
/content. Mais même en dehors de cette règle stricte, mélanger les deux
cause de vrais dégâts :
- Les promotions Cloud Manager deviennent plus difficiles à
raisonner. Les builds full-stack empaquettent
ui.appsavec tout le reste ; si un relecteur ne peut pas supposer que “ce package est du code pur”, chaque promotion exige de revérifier si du contenu s’y est glissé. - Le contenu est écrasé ou supprimé silencieusement au déploiement
suivant.
ui.appsest traité comme entièrement remplaçable — chaque exécution du pipeline le réinstalle depuis zéro. Le mode d’import par défaut de FileVault estreplace: tout ce qui est couvert par le filtre du package mais absent de l’archive du package est supprimé du dépôt à l’import. Une page ou un asset qui a fini sous un filter root deui.appsa une espérance de vie très courte. - Le mauvais nœud dans le mauvais package détruit le travail d’un
auteur. Cela arrive généralement par accident : quelqu’un exécute
vlt checkoutou exporte un package avec un root trop large, embarque un nœud qu’un auteur a créé sous un chemin que le package revendique désormais, et le déploiementui.appssuivant le supprime sans que personne n’ait touché à Package Manager directement.
filter.xml : ce qu’un filter root contrôle réellement
Chaque package de contenu — ui.apps compris — déclare son périmètre
dans META-INF/vault/filter.xml via des éléments <filter root="...">.
Un filter root n’est pas une indication de “là où ce package met
généralement ses fichiers” ; selon la
documentation d’Apache Jackrabbit FileVault,
il définit le sous-arbre que le package possède aux fins de
l’import :
- Le mode d’import par défaut est
replace: le contenu existant sous un root couvert est remplacé par ce qu’apporte le package — écrasé ou supprimé selon ce qu’il faut pour correspondre exactement au package. - Un point crucial : « les nœuds/propriétés couverts par une règle de
filtre mais non contenus dans le contenu à importer sont supprimés du
dépôt ». Si votre filter root est
/apps/mysitemais que lejcr_rootde votre package ne contient pas réellement un nœud qui existe déjà sur la cible sous ce chemin, FileVault le supprime à l’installation — c’est le filter root, pas le contenu du package, qui décide de ce qui est dans le périmètre de suppression. - Le contenu hors de tout filter root déclaré reste intact, quel qu’il soit.
- Les éléments
<include>/<exclude>à l’intérieur d’un<filter>affinent encore ce root. Ils sont évalués dans l’ordre par rapport au chemin JCR complet, et c’est la dernière règle qui correspond qui l’emporte — donc les ordonner incorrectement change silencieusement ce qu’un filtre couvre réellement.
Ce seul fait — les chemins déclarés mais non couverts par le contenu du package sont supprimés, pas ignorés — est le mécanisme derrière presque tous les incidents du type « un déploiement a effacé du contenu qui n’avait rien à voir avec mon changement ».
Un filter.xml réaliste pour 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>
Trois roots étroits et explicites — chacun correspondant exactement au
sous-arbre dont ce module est responsable. Rien ici ne revendique
/apps en bloc, et rien ici n’atteint /conf ou /content.
L’erreur du root trop large
<filter root="/apps"/>
Ça ressemble à un raccourci inoffensif — « on possède /apps/mysite, et
/apps/mysite est sous /apps, alors pourquoi pas ». Mais le filter
root est /apps lui-même. À l’installation, FileVault considère
désormais que tout l’arbre /apps — y compris les Core Components
d’AEM eux-mêmes sous /apps/core, le package d’une autre équipe sous
/apps/othersite, n’importe quoi — est couvert par ce package. Comme le
jcr_root de votre package ne contient réellement que
apps/mysite/..., tout le reste sous /apps est « couvert mais non
contenu », et est supprimé à l’import. C’est exactement comme ça qu’un
déploiement ui.apps de routine met à terre les Core Components ou une
application voisine sur une instance AEM partagée.
L’erreur du root trop étroit
<filter root="/apps/mysite/components"/>
<!-- quelqu'un ajoute /apps/mysite/templates/structure localement
et oublie d'ajouter un filter root pour ce chemin -->
Le content-package-maven-plugin construit le package strictement à
partir de ce que couvre filter.xml. Un dossier ajouté sous jcr_root
qui n’est sous aucun filter root déclaré est simplement exclu du package
construit — sans avertissement, sans échec de build. Il est committé
dans git, il existe sur disque, et il n’atteint jamais l’instance cible.
C’est le mode d’échec le plus silencieux : rien ne casse visiblement, une
fonctionnalité n’apparaît tout simplement jamais, et la correction est
généralement « quelqu’un a oublié d’ajouter une ligne <filter root> ».
Où cela se manifeste en pratique
- Échecs de déploiement Cloud Manager dus à des filter roots
qui se chevauchent. La documentation d’Adobe elle-même est explicite :
le filtre d’un container package ne devrait jamais chevaucher celui
d’un application package, et la même règle s’applique entre deux
application packages. Si
ui.appset un package de structure de dépôt ou d’un vendor déclarent tous deux un root couvrant le même chemin, le pipeline peut échouer à la validation ou, pire, s’installer avec succès mais laisser les deux packages se disputer les mêmes nœuds à chaque déploiement suivant. Quand un pipeline Cloud Manager échoue à l’étape de déploiement avec des erreurs liées aux packages, comparer lefilter.xmlde tous les content packages de la livraison entre eux est l’une des premières choses à vérifier. - Du contenu qui disparaît juste après un déploiement ui.apps. Si
quelque chose sous
/appsdisparaît au moment même où un packageui.appss’installe, le filter root est presque toujours la réponse : soit le root est plus large que prévu et a embarqué quelque chose qu’il ne devrait pas posséder, soit un nœud auparavant couvert par un filtre plus ancien s’est retrouvé orphelin quand le filtre a été resserré sans migrer le contenu au préalable. Comparer lefilter.xmlentre la version précédente et la version actuelle du package — pas seulement le diff dujcr_root— est le moyen le plus rapide de le trouver.