Sharing Product Data Between AEM Components: From Session Pattern to Cache
Sharing Product Data Between AEM Components: From Session Pattern to Cache
The Context
In an Adobe Experience Manager e-commerce project, we faced a significant architectural evolution: the product page evolved from a single monolithic component to N specialized components.
The Problem
- Before: A single component loaded all product information from the backend
- After: Multiple components (header, gallery, specs, reviews, cross-sell, etc.) needed the same product information
- Critical issues: Calling the backend N times for each component would have caused:
- Degraded performance
- Rendering timeouts
- High costs on backend APIs
Key question: How can all components access the same product data with only one backend call?
Solution 1: Session Pattern
Our first solution leveraged a fundamental AEM characteristic: every page has a session and HTL processing is sequential, not parallel.
Strategy
- Place a Sling Model in the page header
- On load, the model makes a single backend call (via Service)
- Save the product object in the page session
- Other page components read from the session
Why does it work?
HTL processing in AEM is top-down and sequential:
Header (executes first)
↓ saves to session
Body Component 1 (reads from session)
↓
Body Component 2 (reads from session)
↓
Body Component N (reads from session)Technical Implementation
1. Header Model - Loading and saving to session
@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() {
// Extract product ID from URL or page properties
String productId = getProductIdFromPage();
if (productId != null) {
// Call backend ONCE
Product product = productService.getProduct(productId);
// Save to session to share with other components
request.getSession().setAttribute(SESSION_PRODUCT_KEY, product);
}
}
public Product getProduct() {
return (Product) request.getSession().getAttribute(SESSION_PRODUCT_KEY);
}
private String getProductIdFromPage() {
// Logic to extract product ID
// (from URL selectors, page properties, etc.)
return request.getRequestPathInfo().getSelectorString();
}
}2. Service - Backend call
The ProductService is a simple OSGi service that handles calling the e-commerce backend (REST/GraphQL) and returning the parsed Product object.
@Component(service = ProductService.class)
public class ProductServiceImpl implements ProductService {
@Override
public Product getProduct(String productId) {
// Call e-commerce backend and parse response
// (implementation omitted for brevity)
}
}3. Body Components - Reading from session
@Model(adaptables = SlingHttpServletRequest.class)
public class ProductSpecsModel {
private static final String SESSION_PRODUCT_KEY = "current.product.data";
@SlingObject
private SlingHttpServletRequest request;
public Product getProduct() {
// Read from session (already populated by header)
return (Product) request.getSession().getAttribute(SESSION_PRODUCT_KEY);
}
public List<Specification> getSpecs() {
Product product = getProduct();
return product != null ? product.getSpecifications() : Collections.emptyList();
}
}Session Pattern Advantages
✅ Single backend call per page ✅ Simple to implement - uses standard Sling APIs ✅ Guaranteed sequentiality - HTL processes top-down ✅ No external dependencies - only native AEM session
Disadvantages
❌ Session bound to request - doesn't persist between requests ❌ Memory overhead (minimal, but present) ❌ Doesn't work with async rendering (if introduced in the future)
Evolution: Cache Layer
Subsequently, to further optimize performance, we introduced a cache layer at the Service level.
Cache Implementation
@Component(service = ProductService.class)
public class ProductServiceImpl implements ProductService {
@Reference
private CacheManager cacheManager;
@Override
public Product getProduct(String productId) {
// 1. Check cache
Product cached = cacheManager.get("products", productId);
if (cached != null) {
log.debug("Product {} served from cache", productId);
return cached;
}
// 2. Cache miss - call backend
Product product = fetchFromBackend(productId);
// 3. Save to cache (TTL: 5 minutes)
if (product != null) {
cacheManager.put("products", productId, product, 300);
}
return product;
}
}Cache Impact on Session Pattern
With cache enabled, the Session Pattern becomes almost superfluous:
| Scenario | Without Cache | With Cache |
|---|---|---|
| First page request | Backend call → Session | Backend call → Cache + Session |
| Second request (same user) | Backend call → Session | Cache hit (no backend) |
| Different user request | Backend call → Session | Cache hit (no backend) |
Result: The session becomes just an "intermediate step" because cache solves the problem at the root.
Lessons Learned
When to use the Session Pattern?
✅ Good for:
- Sharing data between components in the same request
- Projects without advanced cache
- Frequently changing data (no cache)
- Rapid prototyping
❌ Avoid if:
- You already have an efficient cache layer (becomes redundant)
- You use client-side async rendering
- Components can be reused on other pages
Final Best Practice
In our case, the optimal solution was:
Cache at Service level (TTL 5-10 min)
- Drastically reduces backend calls
- Shared among all users
Session Pattern as fallback (optional)
- Useful if cache is disabled (e.g., dev environment)
- Still guarantees one call per request
Smart cache invalidation
- Invalidate cache when product is updated in backend
- JCR/Workflow events for automatic invalidation
Conclusions
The Session Pattern is an elegant and simple solution for sharing data between AEM components on the same page, leveraging the sequential nature of HTL rendering.
However, in high-performance scenarios like e-commerce, adding a cache layer becomes the true scalable solution, making the session a secondary or fallback mechanism.
Key Takeaways
- HTL processes components sequentially from top to bottom
- AEM session can be used to share intra-request data
- A well-designed cache outperforms Session Pattern in performance
- The best solution is often a mix of both (cache + session fallback)
Have you implemented similar patterns in your AEM projects? Share your experience in the comments or contact me to discuss!
Related articles: