|
| 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