Aller au contenu
AEM 7 min de lecture

AEM : bonnes pratiques de développement de composants

Les bonnes pratiques AEM issues du terrain : structure des composants, héritage Sling, policy configuration, édition en contexte et patterns à éviter pour du code maintenable.

AEMComposantsBonnes pratiquesHTLSling

Développer sur AEM, c’est apprendre les règles du framework. Mais ce sont les pratiques non documentées, issues du terrain et des erreurs passées, qui font la différence entre un composant qu’on reprend facilement et un composant que tout le monde évite de toucher. Voici les patterns essentiels.

Principe n°1 : Zero Logic in HTL

HTL (HTML Template Language) est volontairement limité. Si vous vous retrouvez à écrire de la logique complexe en HTL, c’est un signal que quelque chose appartient au Sling Model.

<!-- ❌ À éviter : logique métier dans HTL -->
<sly data-sly-use.component="com.myapp.models.ArticleModel" />
<div data-sly-test="${!component.hideIf || (component.isPublished && component.date > component.threshold)}">
<!-- ✅ Correct : logique dans le model, HTL dédié à l'affichage -->
<sly data-sly-use.model="com.myapp.models.ArticleModel" />
<div data-sly-test="${model.visible}">
  <h1>${model.title}</h1>
</div>

Le Sling Model expose un boolean isVisible() qui encapsule toute la logique. HTL affiche ou cache. C’est tout.

Principe n°2 : Utiliser les Core Components comme base

Les AEM Core Components (v2+) sont les composants officiels Adobe. Ils gèrent l’accessibilité, le responsive, les styles et l’édition auteur de manière standard. Ne recréez pas ce qui existe.

Trois façons d’étendre les Core Components :

Overlay : copier sous /apps pour modifier le template HTL. Simple mais vous perdez les mises à jour Adobe.

Proxy Component (recommandé) : créer un composant proxy qui délègue au Core Component.

<!-- /apps/myapp/components/text/.content.xml -->
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
          jcr:primaryType="cq:Component"
          jcr:title="Texte enrichi"
          jcr:description="Composant texte basé sur WCM Core"
          sling:resourceSuperType="core/wcm/components/text/v2/text"
          componentGroup="MyApp - Contenu" />

Le sling:resourceSuperType hérite tout du Core Component. Vous ne surchargez que ce qui doit changer.

Délégation via Sling Model delegation : pour étendre la logique Java.

@Model(adaptables = SlingHttpServletRequest.class,
       adapters = TextModel.class,
       defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ExtendedTextModel implements Text {

    @Self
    @Via(type = ResourceSuperType.class)
    private Text delegate;

    @ValueMapValue
    private String customField;

    @Override
    public String getText() {
        // Enrichir la sortie du Core Component
        return delegate.getText() + (customField != null ? " " + customField : "");
    }
    // ... déléguer toutes les autres méthodes
}

Principe n°3 : Policy vs Dialog

En AEM, les auteurs configurent les composants via la dialog (propriétés de l’instance). Les développeurs/administrateurs configurent les comportements autorisés via les policies (template editor).

Règle : si une option doit être contrôlable par template (activer/désactiver des features), c’est une policy. Si c’est propre à une instance de composant, c’est une dialog.

@Model(adaptables = SlingHttpServletRequest.class)
public class ButtonModel {

    @Self
    private SlingHttpServletRequest request;

    @ValueMapValue
    private String label;

    @ValueMapValue
    private String link;

    // Récupérer la policy du composant
    public boolean isOpenInNewTab() {
        ComponentPolicyManager policyManager =
            request.adaptTo(ComponentPolicyManager.class);
        ComponentPolicy policy = policyManager.getPolicy(
            request.getResource().getResourceResolver(), request);

        if (policy == null) return false;
        return policy.getProperties().get("openInNewTab", false);
    }
}

Principe n°4 : Rendre les composants éditables en contexte

L’expérience auteur compte. Un composant qui s’affiche proprement en mode édition (placeholder quand vide, zones d’édition claires) est adopté. Un composant invisible en mode édition frustre les auteurs.

<!-- Afficher un placeholder si le composant est vide -->
<div class="myapp-card ${wcmmode.edit && !model.hasContent ? 'cq-placeholder' : ''}">
  <sly data-sly-test="${model.hasContent}">
    <!-- Contenu réel -->
    <h2>${model.title}</h2>
    <p>${model.description}</p>
  </sly>
  <sly data-sly-test="${wcmmode.edit && !model.hasContent}">
    <p>Cliquez pour configurer la carte.</p>
  </sly>
</div>

Le style .cq-placeholder d’AEM affiche un cadre pointillé indiquant que le composant est vide.

Principe n°5 : Nommer correctement les nœuds et propriétés

Les noms de propriétés JCR deviennent des noms de variables dans les Sling Models. Une nomenclature cohérente évite les erreurs.

<!-- ❌ Incohérent -->
<myComponent jcr:primaryType="nt:unstructured"
             Title="Mon titre"
             linkHref="https://..."
             show_image="true" />

<!-- ✅ camelCase, cohérent -->
<myComponent jcr:primaryType="nt:unstructured"
             title="Mon titre"
             linkHref="https://..."
             showImage="true" />

Conventions à respecter :

  • camelCase pour les propriétés JCR
  • Préfixe ./ dans les dialogs pour les propriétés du nœud courant
  • Nœuds enfants en lowercase avec tirets pour les listes (items, link-items)

Principe n°6 : Tester les Sling Models avec Mockito + AEM Mocks

@ExtendWith(AemContextExtension.class)
class ButtonModelTest {

    private final AemContext ctx = new AemContext();

    @Test
    void label_returnsValueFromJcr() {
        ctx.create().resource("/content/button",
            "sling:resourceType", "myapp/components/button",
            "label", "Cliquer ici",
            "linkHref", "https://exemple.com"
        );
        ctx.currentResource("/content/button");

        ButtonModel model = ctx.request().adaptTo(ButtonModel.class);

        assertThat(model).isNotNull();
        assertThat(model.getLabel()).isEqualTo("Cliquer ici");
        assertThat(model.getLinkHref()).isEqualTo("https://exemple.com");
    }

    @Test
    void isOpenInNewTab_returnsFalse_byDefault() {
        ctx.create().resource("/content/button",
            "sling:resourceType", "myapp/components/button"
        );
        ctx.currentResource("/content/button");

        ButtonModel model = ctx.request().adaptTo(ButtonModel.class);
        assertThat(model.isOpenInNewTab()).isFalse();
    }
}

La dépendance io.wcm.testing.mock.aem:io.wcm.testing.mock.aem fournit AemContext qui simule l’environnement AEM en JUnit, sans démarrer l’instance complète.

Patterns à éviter

Écrire dans le JCR au runtime : modifier le contenu via code Java lors d’une requête HTTP est fragile et crée des problèmes en cluster. Pour les formulaires et contenus dynamiques, utilisez des services dédiés.

Sessions JCR non fermées : toujours fermer les sessions administratives dans un finally ou utiliser try-with-resources.

Requêtes JCR non optimisées : les requêtes SELECT * FROM [nt:base] sans index Oak créent des scans complets. Analysez avec le Query Performance dans la console AEM.

Composants qui connaissent leurs parents : un composant ne doit pas dépendre de la structure de page qui le contient. La communication parent-enfant passe par les Sling Models et les services OSGi.

Conclusion

Les bonnes pratiques AEM convergent toutes vers le même objectif : du code que la prochaine équipe peut reprendre. Sling Models testables, Core Components étendus par proxy, dialogs et policies bien séparées, templates HTL lisibles. Ces pratiques réduisent la dette technique et les bugs au déploiement.

Amine MEGDICHE

Amine MEGDICHE

Développeur AEM & Java Full Stack — Freelance depuis 2013

Vous avez un projet sur ces sujets ?

Envoyez-moi un message pour qualifier votre besoin, vos contraintes techniques et le bon format d'intervention.

Cadrer mon besoin