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

  1. Posizionare un Sling Model nell'header della pagina
  2. Al caricamento, il model fa una singola chiamata al backend (via Service)
  3. Salvare l'oggetto prodotto nella sessione della pagina
  4. 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:

  1. Cache a livello Service (TTL 5-10 min)

    • Riduce drasticamente le chiamate al backend
    • Condivisa tra tutti gli utenti
  2. Session Pattern come fallback (opzionale)

    • Utile se la cache è disabilitata (es. ambiente di sviluppo)
    • Garantisce comunque una sola chiamata per request
  3. 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

  1. HTL elabora i componenti sequenzialmente dall'alto verso il basso
  2. La sessione AEM può essere usata per condividere dati intra-request
  3. Una cache ben progettata supera il Session Pattern in performance
  4. 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: