Skip to content

Commit 6a4b18e

Browse files
committed
Add b2c-custom-caches skill for custom cache usage
Covers: - When to use custom caches (expensive calculations, external responses, config) - Defining caches in caches.json with package.json reference - CacheMgr and Cache Script API classes - get(key, loader) pattern for automatic population - Scoped cache keys for site/catalog/locale separation - Limitations (20MB memory, 128KB entry size, no cross-server sync) - Common patterns for external APIs, calculations, and configuration - Troubleshooting guide Content validated against official documentation and Script API reference.
1 parent e34f596 commit 6a4b18e

1 file changed

Lines changed: 279 additions & 0 deletions

File tree

  • plugins/b2c/skills/b2c-custom-caches
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
---
2+
name: b2c-custom-caches
3+
description: Custom cache definition and usage with CacheMgr and Cache classes
4+
---
5+
6+
# B2C Custom Caches
7+
8+
Custom caches improve code performance by storing data that is expensive to calculate, takes a long time to retrieve, or is accessed frequently. Caches are defined in JSON files within cartridges and accessed via the Script API.
9+
10+
## When to Use Custom Caches
11+
12+
| Use Case | Example |
13+
|----------|---------|
14+
| Expensive calculations | Check if any variation product is on sale for base product display |
15+
| External system responses | Cache in-store availability or prices from external APIs |
16+
| Configuration settings | Store configuration data from JSON files or external sources |
17+
| Frequently accessed data | Product attributes, category data, site preferences |
18+
19+
## Limitations
20+
21+
| Constraint | Value |
22+
|------------|-------|
23+
| Total memory per app server | ~20 MB for all custom caches |
24+
| Max caches per code version | 100 |
25+
| Max entry size | 128 KB |
26+
| Supported value types | Primitives, arrays, plain objects, `null` (not `undefined`) |
27+
| Cross-server sync | None (caches are per-application-server) |
28+
29+
## Defining a Custom Cache
30+
31+
### File Structure
32+
33+
```
34+
my_cartridge/
35+
├── package.json # References caches.json
36+
└── caches.json # Cache definitions
37+
```
38+
39+
### package.json
40+
41+
Add a `caches` entry pointing to the cache definition file:
42+
43+
```json
44+
{
45+
"name": "my_cartridge",
46+
"caches": "./caches.json"
47+
}
48+
```
49+
50+
### caches.json
51+
52+
Define caches with unique IDs and optional expiration:
53+
54+
```json
55+
{
56+
"caches": [
57+
{
58+
"id": "ProductAttributeCache"
59+
},
60+
{
61+
"id": "ExternalPriceCache",
62+
"expireAfterSeconds": 300
63+
},
64+
{
65+
"id": "SiteConfigCache",
66+
"expireAfterSeconds": 60
67+
}
68+
]
69+
}
70+
```
71+
72+
| Property | Required | Description |
73+
|----------|----------|-------------|
74+
| `id` | Yes | Unique ID across all cartridges in code version |
75+
| `expireAfterSeconds` | No | Maximum seconds an entry is retained |
76+
77+
## Using Custom Caches
78+
79+
### Script API Classes
80+
81+
| Class | Description |
82+
|-------|-------------|
83+
| `dw.system.CacheMgr` | Entry point for accessing defined caches |
84+
| `dw.system.Cache` | Cache instance for storing and retrieving entries |
85+
86+
### Basic Usage
87+
88+
```javascript
89+
var CacheMgr = require('dw/system/CacheMgr');
90+
91+
// Get a defined cache
92+
var cache = CacheMgr.getCache('ProductAttributeCache');
93+
94+
// Get value (returns undefined if not found)
95+
var value = cache.get('myKey');
96+
97+
// Store value directly
98+
cache.put('myKey', { data: 'value' });
99+
100+
// Remove entry
101+
cache.invalidate('myKey');
102+
```
103+
104+
### Recommended Pattern: get with Loader
105+
106+
Use `get(key, loader)` to automatically populate the cache on miss:
107+
108+
```javascript
109+
var CacheMgr = require('dw/system/CacheMgr');
110+
var Site = require('dw/system/Site');
111+
112+
var cache = CacheMgr.getCache('SiteConfigCache');
113+
114+
// Loader function called only on cache miss
115+
var config = cache.get(Site.current.ID + '_config', function() {
116+
// Expensive operation - only runs if not cached
117+
return loadConfigurationFromFile(Site.current);
118+
});
119+
```
120+
121+
### Scoped Cache Keys
122+
123+
Include scope identifiers in keys to separate entries by context:
124+
125+
```javascript
126+
var CacheMgr = require('dw/system/CacheMgr');
127+
var Site = require('dw/system/Site');
128+
129+
var cache = CacheMgr.getCache('ProductCache');
130+
131+
// Site-scoped key
132+
var siteKey = Site.current.ID + '_' + productID;
133+
var productData = cache.get(siteKey, loadProductData);
134+
135+
// Catalog-scoped key
136+
var catalogKey = 'catalog_' + catalogID + '_' + productID;
137+
var catalogData = cache.get(catalogKey, loadCatalogData);
138+
139+
// Locale-scoped key
140+
var localeKey = request.locale + '_' + contentID;
141+
var content = cache.get(localeKey, loadLocalizedContent);
142+
```
143+
144+
## Cache Methods
145+
146+
| Method | Description |
147+
|--------|-------------|
148+
| `get(key)` | Returns cached value or `undefined` |
149+
| `get(key, loader)` | Returns cached value or calls loader, stores result |
150+
| `put(key, value)` | Stores value directly (overwrites existing) |
151+
| `invalidate(key)` | Removes entry for key |
152+
153+
## Best Practices
154+
155+
### Do
156+
157+
- Use `get(key, loader)` pattern for automatic population
158+
- Include scope (site, catalog, locale) in cache keys
159+
- Set appropriate `expireAfterSeconds` for time-sensitive data
160+
- Handle cache misses gracefully (data may be evicted anytime)
161+
- Use descriptive cache IDs
162+
163+
### Don't
164+
165+
- Include personal user data in cache keys (keys may appear in logs)
166+
- Store Script API objects (only primitives and plain objects)
167+
- Rely on cache entries existing (no persistence guarantee)
168+
- Expect cross-server cache synchronization
169+
- Store `undefined` values (use `null` instead)
170+
171+
## Cache Invalidation
172+
173+
Caches are automatically cleared when:
174+
- Any file in the active code version changes
175+
- A new code version is activated
176+
- Data replication completes
177+
- Code replication completes
178+
179+
Manual invalidation only affects the current application server:
180+
181+
```javascript
182+
var cache = CacheMgr.getCache('MyCache');
183+
184+
// Invalidate single entry (current app server only)
185+
cache.invalidate('myKey');
186+
187+
// Storing undefined has same effect as invalidate
188+
cache.put('myKey', undefined);
189+
```
190+
191+
## Common Patterns
192+
193+
### Caching External API Responses
194+
195+
```javascript
196+
var CacheMgr = require('dw/system/CacheMgr');
197+
var LocalServiceRegistry = require('dw/svc/LocalServiceRegistry');
198+
199+
var priceCache = CacheMgr.getCache('ExternalPriceCache');
200+
201+
function getExternalPrice(productID) {
202+
return priceCache.get('price_' + productID, function() {
203+
var service = LocalServiceRegistry.createService('PriceService', {
204+
createRequest: function(svc, args) {
205+
svc.setRequestMethod('GET');
206+
svc.addParam('productId', args.productID);
207+
return null;
208+
},
209+
parseResponse: function(svc, response) {
210+
return JSON.parse(response.text);
211+
}
212+
});
213+
214+
var result = service.call({ productID: productID });
215+
return result.ok ? result.object : null;
216+
});
217+
}
218+
```
219+
220+
### Caching Expensive Calculations
221+
222+
```javascript
223+
var CacheMgr = require('dw/system/CacheMgr');
224+
225+
var saleCache = CacheMgr.getCache('ProductSaleCache');
226+
227+
function isProductOnSale(masterProduct) {
228+
return saleCache.get('sale_' + masterProduct.ID, function() {
229+
var variants = masterProduct.variants.iterator();
230+
while (variants.hasNext()) {
231+
var variant = variants.next();
232+
if (isInPromotion(variant)) {
233+
return true;
234+
}
235+
}
236+
return false;
237+
});
238+
}
239+
```
240+
241+
### Configuration Cache with Site Scope
242+
243+
```javascript
244+
var CacheMgr = require('dw/system/CacheMgr');
245+
var Site = require('dw/system/Site');
246+
var File = require('dw/io/File');
247+
var FileReader = require('dw/io/FileReader');
248+
249+
var configCache = CacheMgr.getCache('SiteConfigCache');
250+
251+
function getSiteConfig() {
252+
var siteID = Site.current.ID;
253+
254+
return configCache.get(siteID + '_config', function() {
255+
var configFile = new File(File.IMPEX + '/src/config/' + siteID + '.json');
256+
if (!configFile.exists()) {
257+
return null;
258+
}
259+
260+
var reader = new FileReader(configFile);
261+
var content = reader.getString();
262+
reader.close();
263+
264+
return JSON.parse(content);
265+
});
266+
}
267+
```
268+
269+
## Troubleshooting
270+
271+
| Issue | Cause | Solution |
272+
|-------|-------|----------|
273+
| Cache not found exception | Cache ID not defined in any caches.json | Add cache definition to caches.json |
274+
| Duplicate cache ID error | Same ID used in multiple cartridges | Use unique IDs across all cartridges |
275+
| Entry not stored | Value exceeds 128 KB limit | Reduce data size or cache subsets |
276+
| Entry not stored | Value contains Script API objects | Use only primitives and plain objects |
277+
| Unexpected cache misses | Different app server or cache cleared | Always handle misses gracefully |
278+
279+
Check the custom error log and custom warn log for cache-related messages.

0 commit comments

Comments
 (0)