Broadleaf Microservices
  • v1.0.0-latest-prod

Caching

Spring Cache Overview

Out of the box, Broadleaf leverages Apache Ignite to handle caching concerns throughout our services. By default, cache is not distributed, and with a few exceptions, leverages TTL alone for cache eviction. Based on need, caches can be evicted explicitly based on application events by introducing event handlers to interact with the cache API. Apache Ignite can also be configured to operate as a distributed cache for more consistent and/or timely reaction to persistence changes, usually at the cost of performance because of additional blocking at the cache layer. Furthermore, the Apache Ignite support may be swapped out for another Spring Cache compatible caching implementation.

This guide describes:

  1. How to enable this caching.

  2. How to identify the cache instances that are relevant to a given service & override the default configuration if necessary.

  3. How enabling this caching will affect the memory footprint of your deployment.

How to Enable Caching

The out-of-box caching is enabled by simply removing the following property from your Spring application configuration:

spring:
  cache:
    type: none

Adjusting Default TTL

Review the following property classes for default TTL settings. You may declare properties in your own implementation config to override these values.

com.broadleafcommerce.catalog.cache.CatalogCacheProperties
com.broadleafcommerce.asset.cache.AssetCacheProperties
com.broadleafcommerce.auth.cache.AuthCacheProperties
com.broadleafcommerce.data.tracking.core.cache.DataTrackingCacheProperties
com.broadleafcommerce.search.index.core.cache.IndexCacheProperties
com.broadleafcommerce.menu.cache.MenuCacheProperties
com.broadleafcommerce.metadata.cache.MetadataCacheProperties
com.broadleafcommerce.promotion.offer.cache.OfferCacheProperties
com.broadleafcommerce.personalization.cache.PersonalizationCacheProperties
com.broadleafcommerce.pricing.cache.PricingCacheProperties
com.broadleafcommerce.search.core.cache.SearchCacheProperties
com.broadleafcommerce.translation.cache.TranslationCacheProperties

Deeper Configuration Changes

The easiest way to identify the relevant cache configuration for your applications, regardless of how you’ve composed your Broadleaf services, is to download the Broadleaf source code via Maven & use your IDE to search for usages of @Conditional(OnEnabledCacheCondition.class).

Each of these *AutoConfiguration classes registers the relevant cache instances. It’s worth scanning through these configurations to determine whether or not Broadleaf’s defaults meet your needs.

Overriding the default configuration is as simple as defining your own CacheManagerCustomizer<SpringCacheManager> bean that replaces the relevant Broadleaf bean.

The following is an example of the cache configuration for MenuServices:

@Configuration
@Conditional(OnEnabledCacheCondition.class)
@ConditionalOnClass(SpringCacheManager.class)
@ConditionalOnProperty(value = "com.broadleafcommerce.cache.activeCacheManagerImplementation",
        havingValue = "com.broadleafcommerce.common.extension.autoconfigure.IgniteCacheAutoConfiguration",
        matchIfMissing = true)
@EnableConfigurationProperties(MenuCacheProperties.class)
public class MenuCacheAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "menuCacheManagerCustomizer")
    public CacheManagerCustomizer<SpringCacheManager> menuCacheManagerCustomizer(
            IgniteConfigurers.BasicIgniteConfigurer configurer,
            MenuCacheProperties cacheProperties) {
        // This cache is managed directly, but setup a fallback TTL at a reasonable timeframe
        return cacheManager -> cacheManager.getConfiguration()
                .setCacheConfiguration(ArrayUtils.addAll(
                        cacheManager.getConfiguration().getCacheConfiguration(),
                        configurer.basicInitialize(new CacheConfiguration<>(),
                                CACHE_BY_MENU,
                                (int) cacheProperties.getByMenuTtl().getSeconds())));
    }

    @Bean
    @ConditionalOnMissingBean(name = CACHE_BY_MENU)
    public KeyGenerator menuCacheByMenu() {
        return DataTrackingKeyGen.of(APPLICATION_WITH_LOCALE);
    }

}

Memory Footprint Implications

For each application, Ignite is configured to consume up to 2GB of RAM outside of the application heap - i.e. the RAM allocated to the application should remain the same.

Note
If needed, the max memory allocation can be tweaked by overriding the IgniteConfiguration bean for the application.
@Configuration
@Conditional(OnEnabledCacheCondition.class)
@ConditionalOnClass(IgniteConfiguration.class)
@ConditionalOnProperty(value = "com.broadleafcommerce.cache.activeCacheManagerImplementation",
        havingValue = "com.broadleafcommerce.common.extension.autoconfigure.IgniteCacheAutoConfiguration",
        matchIfMissing = true)
@EnableCaching
public class IgniteCacheAutoConfiguration extends BaseCacheConfiguration {

    @Bean
    @ConditionalOnMissingBean
    @SuppressWarnings("squid:S3878")
    public CacheManager cacheManager(CacheManagerCustomizers cacheManagerCustomizers) {
        SpringCacheManager cacheManager = new SpringCacheManager();
        cacheManager.setConfiguration(igniteConfiguration());
        cacheManager = cacheManagerCustomizers.customize(cacheManager);
        cacheManager.onApplicationEvent(null);
        return cacheManager;
    }

    @Bean
    @ConditionalOnMissingBean
    public IgniteConfiguration igniteConfiguration() {
        IgniteConfiguration configuration = new IgniteConfiguration();
        configuration.setGridLogger(new Slf4jLogger());
        configuration.setDiscoverySpi(
                new TcpDiscoverySpi().setIpFinder(new TcpDiscoveryVmIpFinder(true)));
        configuration.setMetricsLogFrequency(0L);
        DataStorageConfiguration storageCfg = new DataStorageConfiguration();
        // Setting the size of the default memory region to 2GB (off-heap)
        storageCfg.getDefaultDataRegionConfiguration()
                .setMaxSize(2L * 1024 * 1024 * 1024)
                .setPageEvictionMode(DataPageEvictionMode.RANDOM_2_LRU);
        configuration.setDataStorageConfiguration(storageCfg);
        return configuration;
    }

    @Bean
    @ConditionalOnMissingBean
    public IgniteConfigurers.BasicIgniteConfigurer basicIgniteConfigurer() {
        return new IgniteConfigurers.BasicIgniteConfigurer();
    }
}

Replacing Apache Ignite

Review the code sample above for how Apache Ignite is declared as the Spring Cache implementation. The most important part is the CacheManager instantiation (in this case, Ignite’s SpringCacheManager). To replace, introduce your own configuration class that establishes your desired cache manager. Then, declare the fully qualified name of your new configuration class as the value for the property com.broadleafcommerce.cache.activeCacheManagerImplementation. At this point, all Broadleaf out-of-the-box cache will be disabled. You can implement as many CacheManagerCustomizer instances as you like to support replacing the out-of-the-box customizers that are no longer being loaded by Broadleaf, or introduce new customizers to support new caches that you develop.

ModelMapper Cache Overview

In addition to being able to configure the Spring Cache, Broadleaf also allows you to configure a ModelMapper cache.

Here are a few things to understand about ModelMapper:

  • Broadleaf use ModelMapper to transform data from persistence domain to projection domain.

  • ModelMapper (at runtime) inspects two objects that want to map data between each other and compiles the correct mapping model.

  • The mapping inspection takes some time the very first time it’s performed.

  • Mapping and caching ahead of time removes the need for the lazy cache phase at runtime.

  • This cuts down on the “First request after I start the service instance is slow” phenomenon.

  • To populate the cache, we need to exercise the persistence architecture through JPA, Hibernate, and the mapping pipeline. We simulate runtime via the Spring Boot test (i.e. StartupTest@buildModelMapperCache) and then serialize the cached state for later injection at startup during the spring boot initialization lifecycle when deployed to k8s.

  • The ModelMapper cache is optional. Without it, there is a possible cost of a slower first request to lazy populate the cache at production runtime. The ModelMapper cache we’re talking about here is a cache we can populate ahead of time.

  • ModelMapper cache is not a required concern for local development.

How to Enable ModelMapper Cache

You can enable the ModelMapper cache via property:

broadleaf:
  modelmapper:
    cache:
      load:
        enabled: true

You will need to run and "pre-generate" the cache. You can do this via a startup test. An example startup test is available in your MicroservicesDemo accelerator project.

For example, the Balanced flex packages has the following class available:

com.broadleafdemo.demo.StartupTest#buildModelMapperCache.

Note
you will notice that running the above test will generate the following cache artifacts in the following directory: /flexpackages/balanced/browse/src/main/resources/cache . You may sometimes need to delete/rebuild this cache if entities change/become out of date.