Develop a New Commerce Connector
Info: This feature in Bloomreach Content requires a standard or premium license. Contact Bloomreach for details.
Introduction
Goal
This guide explains how to develop and test a commerce connector module for Bloomreach Content.
Overview
This tutorial demonstrates how to create a basic commerce connector module that reads product and category data from a static JSON resource file. It also covers basic customer and cart operations using in-memory data storage.
You will implement both the connector logic and unit tests to validate your module. Finally, you will learn how to perform integration testing by registering the connector in a Bloomreach B2C or B2B Commerce Accelerator project.
Demo Commerce Connector Module Source
All source files referenced in this guide are available in the Demo Commerce Connector Module project at https://github.com/onehippo/demo-commerce-connector. Clone, build, and test the examples using the following commands:
$ git clone https://github.com/onehippo/demo-commerce-connector.git
$ cd demo-commerce-connector
$ mvn clean install
The mvn clean install command compiles the code, runs unit tests, packages the JAR, and installs the module to your local Maven repository.
Commerce Connector Module Project Structure
A Commerce Connector Module must be packaged as a JAR and structured as an HST Addon Module. This enables the Bloomreach Commerce Accelerator Application to discover the module and its CommerceRepository components. The module descriptor, located at META-INF/hst-assembly/addon/module.xml, defines the module and Spring bean assembly locations.
The project includes two submodules:
- B2C Demo Commerce Connector Module (in the
b2cfolder) - B2B Demo Commerce Connector Module (in the
b2bfolder)
Module Descriptor
Assign a unique, reverse-DNS name to your Commerce Connector Module, such as com.foo.connectors.foocommerce or com.bloomreach.commercedxp.demo.connectors.mydemoconnector. The Commerce Accelerator discovers modules by this name.
Example descriptor for the B2C Demo Commerce Connector Module:
<?xml version="1.0" encoding="UTF-8"?> <module xmlns="http://www.onehippo.org/schema/hst/hst-addon-module_1_0.xsd"> <name>com.bloomreach.commercedxp.demo.connectors.mydemoconnector</name> <config-locations> <config-location>classpath*:META-INF/spring-assembly/addon/com/bloomreach/commercedxp/demo/connectors/mydemoconnector/*.xml</config-location> <config-location>classpath*:META-INF/hst-assembly/addon/com/bloomreach/commercedxp/demo/connectors/mydemoconnector/overrides/*.xml</config-location> </config-locations> </module>
Descriptor for the B2B Demo Commerce Connector Module:
<?xml version="1.0" encoding="UTF-8"?> <module xmlns="http://www.onehippo.org/schema/hst/hst-addon-module_1_0.xsd"> <name>com.bloomreach.commercedxp.demo.connectors.myb2bdemoconnector</name> <config-locations> <config-location>classpath*:META-INF/spring-assembly/addon/com/bloomreach/commercedxp/demo/connectors/myb2bdemoconnector/connector-*.xml</config-location> <config-location>classpath*:META-INF/hst-assembly/addon/com/bloomreach/commercedxp/demo/connectors/myb2bdemoconnector/overrides/connector-*.xml</config-location> </config-locations> </module>
An HST Addon Module uses <config-locations> and <config-location> elements to specify Spring bean assembly XML files for your connector's components.
CommerceRepository Beans Assembly
Example Spring bean assembly for the B2C Demo Commerce Connector Module:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- Entrypoint bean of type c.b.c.api.v2.connector.provider.ConnectorRepositoryProvider. The Commerce Accelerator retrieves CommerceRepository components through this bean. --> <bean class="com.bloomreach.commercedxp.demo.connectors.mydemoconnector.repository.MyDemoConnectorRepositoryProviderImpl"> <property name="commerceRepositoryMap"> <map> <entry key="com.bloomreach.commercedxp.api.v2.connector.repository.CategoryRepository" value-ref="categoryRepository" /> <entry key="com.bloomreach.commercedxp.api.v2.connector.repository.ProductRepository" value-ref="productRepository" /> <entry key="com.bloomreach.commercedxp.api.v2.connector.repository.CustomerRepository" value-ref="customerRepository" /> <entry key="com.bloomreach.commercedxp.api.v2.connector.repository.AddressRepository" value-ref="addressRepository" /> <entry key="com.bloomreach.commercedxp.api.v2.connector.repository.CartRepository" value-ref="cartRepository" /> <entry key="com.bloomreach.commercedxp.api.v2.connector.repository.OrderRepository" value-ref="orderRepository" /> </map> </property> </bean> <bean id="categoryRepository" class="com.bloomreach.commercedxp.demo.connectors.mydemoconnector.repository.MyDemoCategoryRepositoryImpl" /> <bean id="productRepository" class="com.bloomreach.commercedxp.demo.connectors.mydemoconnector.repository.MyDemoProductRepositoryImpl" /> <bean id="customerRepository" class="com.bloomreach.commercedxp.demo.connectors.mydemoconnector.repository.MyDemoCustomerRepositoryImpl" /> <bean id="cartRepository" class="com.bloomreach.commercedxp.demo.connectors.mydemoconnector.repository.MyDemoCartRepositoryImpl" /> <bean id="orderRepository" class="com.bloomreach.commercedxp.demo.connectors.mydemoconnector.repository.MyDemoOrderRepositoryImpl" /> <bean id="addressRepository" class="com.bloomreach.commercedxp.demo.connectors.mydemoconnector.repository.MyDemoAddressRepositoryImpl" /> </beans>
Example Spring bean assembly for the B2B Demo Commerce Connector Module:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- Entrypoint bean of type c.b.c.api.v2.connector.provider.ConnectorRepositoryProvider. StarterStore retrieves CommerceRepository components through this bean. --> <bean class="com.bloomreach.commercedxp.demo.connectors.mydemoconnector.repository.MyDemoConnectorRepositoryProviderImpl"> <property name="commerceRepositoryMap"> <map> <entry key="com.bloomreach.commercedxp.api.v2.connector.repository.CategoryRepository" value-ref="bizCategoryRepository" /> <entry key="com.bloomreach.commercedxp.b2b.api.v2.connector.repository.BizCategoryRepository" value-ref="bizCategoryRepository" /> <entry key="com.bloomreach.commercedxp.api.v2.connector.repository.ProductRepository" value-ref="bizProductRepository" /> <entry key="com.bloomreach.commercedxp.b2b.api.v2.connector.repository.BizProductRepository" value-ref="bizProductRepository" /> <entry key="com.bloomreach.commercedxp.api.v2.connector.repository.AccountRepository" value-ref="bizAccountRepository" /> <entry key="com.bloomreach.commercedxp.api.v2.connector.repository.CustomerRepository" value-ref="bizCustomerRepository" /> <entry key="com.bloomreach.commercedxp.b2b.api.v2.connector.repository.BizCustomerRepository" value-ref="bizCustomerRepository" /> <entry key="com.bloomreach.commercedxp.api.v2.connector.repository.AddressRepository" value-ref="bizAddressRepository" /> <entry key="com.bloomreach.commercedxp.b2b.api.v2.connector.repository.BizAddressRepository" value-ref="bizAddressRepository" /> <entry key="com.bloomreach.commercedxp.api.v2.connector.repository.CartRepository" value-ref="bizCartRepository" /> <entry key="com.bloomreach.commercedxp.b2b.api.v2.connector.repository.BizCartRepository" value-ref="bizCartRepository" /> <entry key="com.bloomreach.commercedxp.api.v2.connector.repository.OrderRepository" value-ref="bizOrderRepository" /> <entry key="com.bloomreach.commercedxp.b2b.api.v2.connector.repository.BizOrderRepository" value-ref="bizOrderRepository" /> <entry key="com.bloomreach.commercedxp.api.v2.connector.repository.WishListRepository" value-ref="bizWishListRepository" /> <entry key="com.bloomreach.commercedxp.b2b.api.v2.connector.repository.BizWishListRepository" value-ref="bizWishListRepository" /> <entry key="com.bloomreach.commercedxp.b2b.api.v2.connector.repository.BizStoredPaymentRepository" value-ref="bizStoredPaymentRepository" /> <entry key="com.bloomreach.commercedxp.b2b.api.v2.connector.repository.BizInvoiceRepository" value-ref="bizInvoiceRepository" /> </map> </property> </bean> <bean id="bizCategoryRepository" class="com.bloomreach.commercedxp.demo.connectors.myb2bdemoconnector.repository.MyDemoBizCategoryRepositoryImpl" /> <bean id="bizProductRepository" class="com.bloomreach.commercedxp.demo.connectors.myb2bdemoconnector.repository.MyDemoBizProductRepositoryImpl" /> <bean id="bizAccountRepository" class="com.bloomreach.commercedxp.demo.connectors.myb2bdemoconnector.repository.MyDemoBizAccountRepositoryImpl" /> <bean id="bizCustomerRepository" class="com.bloomreach.commercedxp.demo.connectors.myb2bdemoconnector.repository.MyDemoBizCustomerRepositoryImpl"> <property name="accountRepository" ref="bizAccountRepository" /> </bean> <bean id="bizAddressRepository" class="com.bloomreach.commercedxp.demo.connectors.myb2bdemoconnector.repository.MyDemoBizAddressRepositoryImpl" /> <bean id="bizCartRepository" class="com.bloomreach.commercedxp.demo.connectors.myb2bdemoconnector.repository.MyDemoBizCartRepositoryImpl" /> <bean id="bizOrderRepository" class="com.bloomreach.commercedxp.demo.connectors.myb2bdemoconnector.repository.MyDemoBizOrderRepositoryImpl" /> <bean id="bizWishListRepository" class="com.bloomreach.commercedxp.demo.connectors.myb2bdemoconnector.repository.MyDemoBizWishListRepositoryImpl" /> <bean id="bizStoredPaymentRepository" class="com.bloomreach.commercedxp.demo.connectors.myb2bdemoconnector.repository.MyDemoBizStoredPaymentRepositoryImpl" /> <bean id="bizInvoiceRepository" class="com.bloomreach.commercedxp.demo.connectors.myb2bdemoconnector.repository.MyDemoBizInvoiceRepositoryImpl" /> </beans>
After the Commerce Accelerator discovers the Commerce Connector Module as an HST Addon Module, it retrieves the bean of type com.bloomreach.commercedxp.api.v2.connector.provider.ConnectorRepositoryProvider and accesses all available CommerceRepository components through it.
A Commerce Connector Module must:
- Be packaged as a JAR and structured as an HST Addon Module.
- Provide an entry point bean of type
com.bloomreach.commercedxp.api.v2.connector.provider.ConnectorRepositoryProviderin the Spring bean assembly XML. The Commerce Accelerator accesses the module through this bean. - The
ConnectorRepositoryProviderbean supplies all availableCommerceRepositorybeans. Applications retrieve repositories through the provider, not by accessing beans directly.
You can implement a custom ConnectorRepositoryProvider as needed, or extend AbstractConnectorRepositoryProvider for basic getter and setter support:
package com.bloomreach.commercedxp.demo.connectors.mydemoconnector.repository; import com.bloomreach.commercedxp.api.v2.connector.provider.AbstractConnectorRepositoryProvider; /** * Simple ConnectorRepositoryProvider extending AbstractConnectorRepositoryProvider. * Inherits getter and setter methods for CommerceRepository beans. */ public class MyDemoConnectorRepositoryProviderImpl extends AbstractConnectorRepositoryProvider { }
Implement and Test CommerceRepository
This tutorial uses a static JSON resource file for product and category data to avoid runtime dependencies on external systems. The example file is located at src/main/resources/com/bloomreach/commercedxp/demo/connectors/mydemoconnector/demoproducts.json. Example structure:
{ "response":{ "numFound":10, "start":0, "docs":[ { "sale_price":49.99, "price":49.99, "description":"Vestri M-Class logo anchors the signature web stripes racing around the collar of a women's polo cut for comfort from knit stretch cotton.", "title":"Women's M-Class Tee", "url":"www.elasticpath.com", "brand":"", "pid":"WOMENS_M-Class_TEE", "default_sku": "97115", "categories": [ "VPA_T_MCLASS" ], "thumb_image":"https://s3-us-west-2.amazonaws.com/elasticpath-demo-images/VESTRI_VIRTUAL/97115.png", "sale_price_range":[ 49.99, 49.99 ], "price_range":[ 49.99, 49.99 ] }, { "sale_price":35900.0, "price":35900.0, "description":"Our full size electric/hybrid primum four door sedan gives you the performance of a sports car with the function of a sedan. Perfect for family needing something bigger, but still wanting to feel young at heart. The performance and reliability is such that it's been selected as the de factor electric police car by many governments. It's extended range, options give everything needed for the environmentally conscious service.", "title":"X-Class Full Size Premium Sedan", "url":"www.elasticpath.com", "brand":"", "pid":"X-Class-S", "default_sku": "10002", "categories": [ "VPA_T_MCLASS" ], "thumb_image":"https://s3-us-west-2.amazonaws.com/elasticpath-demo-images/VESTRI_VIRTUAL/10002.png", "sale_price_range":[ 35900.0, 35900.0 ], "price_range":[ 35900.0, 35900.0 ] } // SNIP ] }, "category_map":{ "VESTRI_BM_APPAREL":"Apparel", "VPA_CHARGING_AND_ADAPTERS":"Charging and Adapters", "VPA_TA_T50":"T50", "VPA_VA_MCLASS":"M-Class", "VPA_VA_T50":"T50", "VPA_CA_XCLASS":"X-Class", "VPA_T_MCLASS":"M-Class", "VESTRI_BM_ACCESSORIES":"Accessories", "VESTRI_APPAREL_WOMENS":"Womens", "VPA_CA_MCLASS":"M-Class", "VPA_T_T50":"T50", "VESTRI_APPAREL_MENS":"Mens", "VPA_CHARING_AND_ADAPTERS":"Charging and Adapters", "VPA_TIRES":"Tires", "VPA_VEHICLE_ADDONS":"Addons", "VPA_T_XCLASS":"X-Class" } }
The response/docs array contains product items. The category_map node provides the navigational category mapping. Demo CommerceRepository implementations use this data via the following utility class:
package com.bloomreach.commercedxp.demo.connectors.mydemoconnector.repository; // SNIP /** * Demo Data Loader Utility. */ public class MyDemoDataLoader { /** * Name of the optional system property specifying demo products data in CSV format. */ public static final String SYS_PROP_DEMO_PRODUCTS_CSV = "demo.products.csv"; /** * Name of the optional system property specifying demo categories data in CSV format. */ public static final String SYS_PROP_DEMO_CATEGORIES_CSV = "demo.categories.csv"; /** * Default demo product data JSON resource path. */ private static final String DEFAULT_PRODUCT_DATA_JSON_RESOURCE = "com/bloomreach/commercedxp/demo/connectors/mydemoconnector/demoproducts.json"; private static MyDemoData loadMyDemoData() { MyDemoData demoData = null; // Load product/catalog data from the default JSON file. try (InputStream input = MyDemoDataLoader.class.getClassLoader() .getResourceAsStream(DEFAULT_PRODUCT_DATA_JSON_RESOURCE)) { final ObjectMapper objectMapper = new ObjectMapper(); demoData = objectMapper.readValue(input, MyDemoData.class); } catch (Exception e) { e.printStackTrace(); } if (demoData != null) { // If a custom product data file (CSV) is provided, replace product data. String productsCsvProp = System.getProperty(SYS_PROP_DEMO_PRODUCTS_CSV); if (StringUtils.isNotBlank(productsCsvProp)) { productsCsvProp = StrSubstitutor.replaceSystemProperties(productsCsvProp); final URL productsCsvUrl = MyDemoDataResourceUtils.getResource(productsCsvProp); if (productsCsvUrl != null) { try (InputStream input = productsCsvUrl.openStream()) { final CsvSchema csvSchema = CsvSchema.emptySchema().withHeader(); final ObjectMapper csvMapper = new CsvMapper(); final MappingIterator<MyDemoProductItem> mappingIt = csvMapper .readerFor(MyDemoProductItem.class).with(csvSchema).readValues(input); demoData.getResponse().setProductItems(mappingIt.readAll()); } catch (Exception e) { e.printStackTrace(); } } } // If a custom category data file (CSV) is provided, replace category data. String categoriesCsvProp = System.getProperty(SYS_PROP_DEMO_CATEGORIES_CSV); if (StringUtils.isNotBlank(categoriesCsvProp)) { categoriesCsvProp = StrSubstitutor.replaceSystemProperties(categoriesCsvProp); final URL categoriesCsvUrl = MyDemoDataResourceUtils.getResource(categoriesCsvProp); if (categoriesCsvUrl != null) { try (InputStream input = categoriesCsvUrl.openStream()) { final CsvSchema csvSchema = CsvSchema.emptySchema().withHeader(); final ObjectMapper csvMapper = new CsvMapper(); final MappingIterator<MyDemoCategoryModel> mappingIt = csvMapper .readerFor(MyDemoCategoryModel.class).with(csvSchema).readValues(input); demoData.setCategoryModels(mappingIt.readAll()); demoData.resetCategoryModelHierarchy(); } catch (Exception e) { e.printStackTrace(); } } } } return demoData; } static MyDemoData myDemoData; protected MyDemoDataLoader() { } /** * Return the static MyDemoData instance loaded from the JSON resource. */ public static synchronized MyDemoData getMyDemoData() { MyDemoData data = myDemoData; if (data == null) { data = loadMyDemoData(); myDemoData = data; } return data; } /** * Clear the static MyDemoData instance. Useful for resetting during unit tests. */ protected static synchronized void clearMyDemoData() { myDemoData = null; } }
CommerceRepository implementations access internal data using MyDemoDataLoader.getMyDemoData().
Implement ProductRepository
To implement ProductRepository, provide methods to retrieve a single product, query all products, and list products by category.
package com.bloomreach.commercedxp.demo.connectors.mydemoconnector.repository; //... /** * Demo ProductRepository Implementation. */ public class MyDemoProductRepositoryImpl extends AbstractProductRepository { @Override public ItemModel findOne(CommerceConnector connector, String id, QuerySpec querySpec) throws ConnectorException { // Retrieve internal data. final MyDemoData data = MyDemoDataLoader.getMyDemoData(); final List<MyDemoProductItem> productItems = data.getResponse().getProductItems(); // Find and return the product by code. for (MyDemoProductItem item : productItems) { if (id.equals(item.getCode())) { return item; } } return null; } @Override public PageResult<ItemModel> findAll(CommerceConnector connector, QuerySpec querySpec) throws ConnectorException { // Read pagination parameters from querySpec. final long offset = querySpec.getOffset(); final long limit = (querySpec.getLimit() != null) ? querySpec.getLimit().longValue() : MyDemoConstants.DEFAULT_PAGE_LIMIT; // Prepare the collection for the result. final List<ItemModel> itemModels = new LinkedList<>(); final MyDemoData data = MyDemoDataLoader.getMyDemoData(); final List<My
(Content truncated. Continue implementing repository methods as needed.)
Next Steps:
- Implement additional repository interfaces as required.
- Write unit tests for your connector logic.
- Register the connector module in your Bloomreach Commerce Accelerator project for integration testing.
For further details, refer to the Demo Commerce Connector Module source code.