Condivisione dati prodotto tra componenti AEM: dal Session Pattern alla Cache
Condivisione dati prodotto tra componenti AEM: dal Session Pattern alla Cache
Il Contesto
In un progetto e-commerce su Adobe Experience Manager, ci siamo trovati di fronte a un'evoluzione architetturale importante: la pagina prodotto è passata da un singolo componente monolitico a N componenti specializzati.
Il Problema
- Prima: Un solo componente caricava tutte le informazioni del prodotto dal backend
- Dopo: Multipli componenti (header, gallery, specifiche, reviews, cross-sell, ecc.) necessitavano delle stesse informazioni prodotto
- Criticità: Chiamare il backend N volte per ogni componente avrebbe causato:
- Performance degradate
- Timeout di rendering
- Costi elevati sulle API del backend
Domanda chiave: Come far accedere tutti i componenti agli stessi dati prodotto con una sola chiamata al backend?
Soluzione 1: Session Pattern
La nostra prima soluzione ha sfruttato una caratteristica fondamentale di AEM: ogni pagina ha una sessione e l'elaborazione HTL è sequenziale, non parallela.
Strategia
- Posizionare un Sling Model nell'header della pagina
- Al caricamento, il model fa una singola chiamata al backend (via Service)
- Salvare l'oggetto prodotto nella sessione della pagina
- Gli altri componenti della pagina leggono dalla sessione
Perché funziona?
L'elaborazione HTL in AEM è top-down e sequenziale:
Header (esegue per primo)
↓ salva in sessione
Body Component 1 (legge da sessione)
↓
Body Component 2 (legge da sessione)
↓
Body Component N (legge da sessione)Implementazione Tecnica
1. Header Model - Caricamento e salvataggio in sessione
@Model(adaptables = SlingHttpServletRequest.class)
public class ProductPageHeaderModel {
private static final String SESSION_PRODUCT_KEY = "current.product.data";
@SlingObject
private SlingHttpServletRequest request;
@OSGiService
private ProductService productService;
@PostConstruct
protected void init() {
// Estrai product ID dall'URL o dalle page properties
String productId = getProductIdFromPage();
if (productId != null) {
// Chiama il backend UNA SOLA VOLTA
Product product = productService.getProduct(productId);
// Salva in sessione per condividerlo con altri componenti
request.getSession().setAttribute(SESSION_PRODUCT_KEY, product);
}
}
public Product getProduct() {
return (Product) request.getSession().getAttribute(SESSION_PRODUCT_KEY);
}
private String getProductIdFromPage() {
// Logica per estrarre il product ID
// (da selettori URL, page properties, ecc.)
return request.getRequestPathInfo().getSelectorString();
}
}2. Service - Chiamata al backend
Il ProductService è un semplice OSGi service che si occupa di chiamare il backend e-commerce (REST/GraphQL) e restituire l'oggetto Product parsato.
@Component(service = ProductService.class)
public class ProductServiceImpl implements ProductService {
@Override
public Product getProduct(String productId) {
// Chiamata al backend e-commerce e parse della risposta
// (implementazione omessa per brevità)
}
}3. Componenti Body - Lettura dalla sessione
@Model(adaptables = SlingHttpServletRequest.class)
public class ProductSpecsModel {
private static final String SESSION_PRODUCT_KEY = "current.product.data";
@SlingObject
private SlingHttpServletRequest request;
public Product getProduct() {
// Legge dalla sessione (già popolata dall'header)
return (Product) request.getSession().getAttribute(SESSION_PRODUCT_KEY);
}
public List<Specification> getSpecs() {
Product product = getProduct();
return product != null ? product.getSpecifications() : Collections.emptyList();
}
}Vantaggi del Session Pattern
✅ Una sola chiamata al backend per pagina ✅ Semplice da implementare - usa API standard di Sling ✅ Sequenzialità garantita - HTL processa dall'alto verso il basso ✅ Nessuna dipendenza esterna - solo sessione AEM nativa
Svantaggi
❌ Sessione bound alla request - non persiste tra richieste ❌ Overhead di memoria (minimo, ma presente) ❌ Non funziona con rendering asincrono (se introdotto in futuro)
Evoluzione: Cache Layer
Successivamente, per ottimizzare ulteriormente le performance, abbiamo introdotto un layer di cache a livello di Service.
Implementazione Cache
@Component(service = ProductService.class)
public class ProductServiceImpl implements ProductService {
@Reference
private CacheManager cacheManager;
@Override
public Product getProduct(String productId) {
// 1. Controlla cache
Product cached = cacheManager.get("products", productId);
if (cached != null) {
log.debug("Product {} served from cache", productId);
return cached;
}
// 2. Cache miss - chiama backend
Product product = fetchFromBackend(productId);
// 3. Salva in cache (TTL: 5 minuti)
if (product != null) {
cacheManager.put("products", productId, product, 300);
}
return product;
}
}Impatto della Cache sul Session Pattern
Con la cache attiva, il Session Pattern diventa quasi superfluo:
| Scenario | Senza Cache | Con Cache |
|---|---|---|
| Prima request pagina | Backend call → Session | Backend call → Cache + Session |
| Seconda request (stesso utente) | Backend call → Session | Cache hit (no backend) |
| Request utente diverso | Backend call → Session | Cache hit (no backend) |
Risultato: La sessione diventa solo un "passaggio intermedio" perché la cache risolve il problema alla radice.
Lesson Learned
Quando usare il Session Pattern?
✅ Buono per:
- Condividere dati tra componenti nella stessa request
- Progetti senza cache avanzata
- Dati che cambiano frequentemente (no cache)
- Prototipazione rapida
❌ Evitare se:
- Hai già una cache layer efficiente (diventa ridondante)
- Usi rendering client-side asincrono
- I componenti possono essere riutilizzati in altre pagine
Best Practice Finale
Nel nostro caso, la soluzione ottimale è stata:
Cache a livello Service (TTL 5-10 min)
- Riduce drasticamente le chiamate al backend
- Condivisa tra tutti gli utenti
Session Pattern come fallback (opzionale)
- Utile se la cache è disabilitata (es. ambiente di sviluppo)
- Garantisce comunque una sola chiamata per request
Invalidazione cache smart
- Invalida cache quando il prodotto viene aggiornato nel backend
- Eventi JCR/Workflow per invalidazione automatica
Conclusioni
Il Session Pattern è una soluzione elegante e semplice per condividere dati tra componenti AEM nella stessa pagina, sfruttando la natura sequenziale del rendering HTL.
Tuttavia, in scenari ad alte performance come un e-commerce, l'aggiunta di un cache layer diventa la vera soluzione scalabile, rendendo la sessione un meccanismo secondario o di fallback.
Key Takeaways
- HTL elabora i componenti sequenzialmente dall'alto verso il basso
- La sessione AEM può essere usata per condividere dati intra-request
- Una cache ben progettata supera il Session Pattern in performance
- La soluzione migliore è spesso un mix di entrambi (cache + session fallback)
Hai implementato pattern simili nei tuoi progetti AEM? Condividi la tua esperienza nei commenti o contattami per discuterne!
Articoli correlati: