Développement AEM : ce que ça implique vraiment en projet
Ce que recouvre concrètement une mission de développement Adobe Experience Manager : composants HTL, Sling Models, Dispatcher, intégration front/back, pipeline CI/CD et points de vigilance récurrents.
Quand un client parle de “développement AEM”, il peut vouloir dire des choses très différentes : créer un nouveau composant, migrer depuis une ancienne version, intégrer une maquette graphique, optimiser les performances ou mettre en place un pipeline de déploiement. Cette page explique ce que recouvre concrètement chaque type d’intervention.
Ce qu’est AEM (et ce que ce n’est pas)
Adobe Experience Manager est une plateforme de gestion de contenu entreprise construite sur Apache Sling, Apache Felix (OSGi) et Apache Jackrabbit (JCR). Ce n’est pas un CMS comme WordPress : c’est un système Java complet avec son propre modèle de déploiement, son référentiel de contenu, son moteur de workflow et son écosystème de composants.
Pour travailler efficacement sur AEM, il faut maîtriser simultanément :
- Java (backend : Sling Models, services OSGi, Servlets, Event Handlers)
- HTL (Sling Template Language, le langage de template AEM)
- JavaScript/CSS (frontend : Client Libraries)
- JCR/Oak (modèle de contenu, requêtes, indexation)
- Apache Felix (configuration OSGi, bundles)
- Maven (build et déploiement via le Content Package Plugin)
Développement de composants : le cœur du travail
Un composant AEM est composé de plusieurs fichiers qui coexistent dans le référentiel JCR :
/apps/monprojet/components/hero/
├── .content.xml # Définition du composant (type, groupe, icône)
├── hero.html # Template HTL
├── _cq_dialog/ # Dialog d'édition (Coral UI 3)
│ └── .content.xml
├── _cq_editConfig.xml # Comportement dans l'éditeur
└── HeroModel.java # Sling Model (logique métier)
Le template HTL — logique minimale, appel au Sling Model :
<sly data-sly-use.model="com.monprojet.core.models.HeroModel">
<section class="hero ${model.cssClass @ context='attribute'}"
data-hero-variant="${model.variant @ context='attribute'}">
<sly data-sly-test="${model.hasImage}">
<img src="${model.imageUrl}" alt="${model.imageAlt}" loading="eager" />
</sly>
<div class="hero__content">
<h1 class="hero__title">${model.title}</h1>
<sly data-sly-test="${model.subtitle}">
<p class="hero__subtitle">${model.subtitle}</p>
</sly>
<sly data-sly-test="${model.ctaLabel}">
<a href="${model.ctaUrl}" class="hero__cta btn">
${model.ctaLabel}
</a>
</sly>
</div>
</section>
</sly>
Le Sling Model — logique de récupération et transformation des données :
@Model(
adaptables = {SlingHttpServletRequest.class},
adapters = {HeroModel.class},
resourceType = HeroModel.RESOURCE_TYPE,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class HeroModelImpl implements HeroModel {
static final String RESOURCE_TYPE = "monprojet/components/hero";
@Inject
private Resource resource;
@Inject @Via(type = ResourceSuperType.class)
private ImageModel image;
@ValueMapValue
private String title;
@ValueMapValue
private String subtitle;
@ValueMapValue
private String ctaLabel;
@ValueMapValue
private String ctaUrl;
@ValueMapValue
@Default(values = "default")
private String variant;
@Override
public boolean hasImage() {
return image != null && image.getSrc() != null;
}
@Override
public String getImageUrl() {
return image != null ? image.getSrc() : null;
}
@Override
public String getCssClass() {
return "hero hero--" + variant;
}
// Autres getters...
}
Services OSGi : la logique applicative
Pour les traitements transverses (intégration API, calculs complexes, cache applicatif), on utilise des services OSGi :
@Component(service = PrixService.class, immediate = true)
@Designate(ocd = PrixService.Config.class)
public class PrixServiceImpl implements PrixService {
@ObjectClassDefinition(name = "Configuration du service de prix")
@interface Config {
@AttributeDefinition(name = "URL API ERP")
String erp_url() default "https://erp.monentreprise.com/api";
@AttributeDefinition(name = "Timeout (ms)")
int timeout() default 5000;
}
private String erpUrl;
private int timeout;
@Activate
protected void activate(Config config) {
this.erpUrl = config.erp_url();
this.timeout = config.timeout();
}
@Override
public Optional<Prix> getPrixProduit(String sku) {
// Appel REST vers l'ERP avec timeout configuré
// Cache avec Guava ou EhCache si nécessaire
}
}
Intégration frontend : les Client Libraries
Les assets CSS/JS dans AEM sont organisés en Client Libraries (clientlibs) :
<!-- /apps/monprojet/clientlibs/hero/.content.xml -->
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0"
jcr:primaryType="cq:ClientLibraryFolder"
categories="[monprojet.components.hero]"
dependencies="[core.wcm.components.commons.react.base]" />
// clientlibs/hero/js/hero.js
(function() {
"use strict";
const HeroComponent = {
selectors: {
root: "[data-hero-variant]"
},
init(root) {
// Logique JavaScript du composant
this.handleIntersection(root);
},
handleIntersection(root) {
const observer = new IntersectionObserver(
entries => entries.forEach(e => {
if (e.isIntersecting) root.classList.add("hero--visible");
}),
{ threshold: 0.1 }
);
observer.observe(root);
}
};
// Initialisation au chargement + après Edit Mode refresh
document.querySelectorAll(HeroComponent.selectors.root)
.forEach(el => HeroComponent.init(el));
document.addEventListener("cq-editor-loaded", () => {
document.querySelectorAll(HeroComponent.selectors.root)
.forEach(el => HeroComponent.init(el));
});
})();
Pipeline de déploiement AEM
Le déploiement AEM en production suit toujours la même chaîne :
Git push
→ Jenkins/Cloud Manager
→ mvn clean package (build des bundles OSGi)
→ Tests unitaires (JUnit + Mockito/AEM Mocks)
→ Tests d'intégration (AEM IT tests avec Docker)
→ Déploiement Author
→ Smoke tests Author
→ Déploiement Publisher(s) (rolling)
→ Invalidation Dispatcher
→ Vérification cache
Points critiques du pipeline :
- L’ordre de déploiement doit être : bundles OSGi → contenu JCR. Inverser cause des erreurs 500.
- L’invalidation du Dispatcher après déploiement est obligatoire (sans elle, les pages en cache servent l’ancien code)
- Un rollback sur AEM est complexe : prévoir une stratégie (package CRX de sauvegarde ou immutable content)
Ce qui prend du temps et pourquoi
Développement initial d’un composant complexe (3-5 jours) Un composant avec dialog multi-onglet, variantes de style, comportement JavaScript interactif, export vers Experience Fragment, et tests unitaires complets : comptez 3 jours minimum.
Reprise d’un composant Foundation existant (2-3 jours) Migrer un composant Foundation (déprécié) vers un composant Core Components custom implique de retravailler le dialog, les templates, et potentiellement le contenu en base.
Intégration d’une API externe (2-4 jours) Connexion à un ERP, PIM ou CRM depuis AEM : configuration OSGi, service Java, gestion des erreurs/timeouts, cache des données, tests d’intégration.
Configuration du Dispatcher (1-2 jours) La configuration du Dispatcher est un sujet à part entière : règles de cache, headers de sécurité, gestion des sessions, regles de réécriture URL.
Ce qu’on me demande le plus souvent
En mission AEM, les demandes les plus fréquentes sont :
- Créer de nouveaux composants pour enrichir le système de design éditeur
- Optimiser les performances (cache Dispatcher, clientlibs, lazy loading images DAM)
- Intégrer des APIs externes (catalogue produits, stock, prix personnalisés)
- Mettre en place un pipeline CI/CD qui n’existait pas
- Former l’équipe aux bonnes pratiques HTL et Sling Models
Si votre besoin correspond à l’un de ces sujets, décrivez-le en quelques lignes — je reviens sous 24h avec une première qualification.
Amine MEGDICHE
Développeur AEM & Java Full Stack — Freelance depuis 2013