I'm currently in Washington, DC at the excellent SpringOne conference. I had the honor of presenting yesterday at the conference. My presentation was titled "An Introduction to Broadleaf Commerce: A Spring Enabled eCommerce Framework". I discussed the features of Broadleaf Commerce, complete with a demo of the Broadleaf starter application and administrative console.
I also presented a case study that demonstrated how to build flash sales functionality with Broadleaf. In particular, I made the case that it was quite easy to customize Broadleaf with complex functionality such as a shopping cart flow that reserves inventory when adding an item to the cart, and reclaims that inventory if the user does not initiate another action within a period of time. Of course this functionality is important for sites that sell event tickets and for sites that have brief, "flash" sales with limited, perishable inventory.
Flash sales functionality is not currently available as a standard feature in Broadleaf. But it is a very real and somewhat complex requirement for many companies, which is the reason I chose this feature for the case study portion of my presentation. The demo code can be downloaded here. (There are a few small bugs, and it is, by no means, meant to be production ready. In other words, it's meant for educational purposes only. :)
In addition to showcasing the features of Broadleaf Commerce, I was attempting to demonstrate the ease of extending and customizing the framework. Part of this involved discussing how The Spring Framework powers Broadleaf Commerce. I discussed Broadleaf's unique Spring Application Context merge process, which allows a developer to inject their own implementations of Broadleaf components into the framework to override or augment Broadleaf's default functionality at its core. And this brings me to the purpose of this blog.
First, let me describe the basics of Broadleaf's application context merge process:
The Merge Process
Broadleaf provides a number of complex default configurations, bean definitions, and associated dependencies. These are all nicely tucked away in jar files to be used by a developer. But if they're in the jar file, how do you override them? The answer is Broadleaf's Application Context Merge Process. Broadleaf provides a
MergeContextLoaderListener that can be configured in your web.xml file. In order to use it, you simply set a context parameter with the XML files that you define with additional or custom bean definitions:
<context-param> <param-name>patchConfigLocation</param-name> <param-value> classpath:/bl-open-admin-contentClient-applicationContext.xml classpath:/bl-cms-contentClient-applicationContext.xml classpath:/bl-demo-applicationContext.xml /WEB-INF/applicationContext-datasource.xml /WEB-INF/applicationContext-security.xml /WEB-INF/applicationContext-email.xml /WEB-INF/applicationContext.xml </param-value> </context-param>
Broadleaf will merge these XML files with its own, adding any new bean definitions and overriding any default components that have the same bean name with the new implementations defined by you. But a question came up after the presentation: "Why do you use a merge process like this when Spring provides a Standard mechanism to override bean definitions?" On the spot, I have to admit that I was stumped. I thought, "Well that's a good question. I wonder why not."
Needless to say, I didn't have a great answer at the time. It wasn't because this is a new capability in Spring. Spring has allowed this for a long time. So why would we have a unique merge process that requires XML configurations of Spring application context files? Why wouldn't we just use Spring's default capabilities to override bean definitions? After some thought and discussion with my colleagues, I remembered that we are not simply replacing bean definitions. We are overriding partial bean definitions.
Remember that Broadleaf's default configuration and dependency graph is quite complicated, by necessity. We've attempted to provide an exceptionally rich set of features out of the box, wired together with generally accepted best design patterns in mind. We necessarily define 3 different data sources (e.g. to allow for PCI compliance if you choose to store payment card information). We've set up transaction boundaries accordingly. There are beans that depend on lists of other beans. So, what's my point? Simply replacing one bean definition can potentially lead to a lot additional of complicated configurations.
The solution was to provide a unique Spring application context merge process, using XML and XPath. This merge process defines a default strategy for how things get merged. We don't simply replace the entire bean definition in all cases, as Spring's default strategy would do. Instead, we also provide strategies for overriding partial bean definitions. For example, look at the following default Broadleaf configuration defining a list of fulfillment pricing providers that each can process different fulfillment options:
<bean id="blFulfillmentPricingProviders" class="org.springframework.beans.factory.config.ListFactoryBean"> <property name="sourceList"> <list> <ref bean="blFixedPriceFulfillmentPricingProvider" /> <ref bean="blBandedFulfillmentPricingProvider" /> </list> </property> </bean>
This definition is provided inside the Broadleaf libraries and defines the default list of fulfillment pricing providers. Imagine, now, that you want to add additional pricing providers, either in your own application or by adding multiple plugins that also define pricing providers. For example, you include a FedEx integration plugin that has the following snippet in its jar:
<bean id="blFulfillmentPricingProviders" class="org.springframework.beans.factory.config.ListFactoryBean"> <property name="sourceList"> <list> <ref bean="blFedExFulfillmentPricingProvider" /> </list> </property> </bean>
...and you also include the UPS integration plugin that has the following snippet in its jar:
<bean id="blFulfillmentPricingProviders" class="org.springframework.beans.factory.config.ListFactoryBean"> <property name="sourceList"> <list> <ref bean="blUPSFulfillmentPricingProvider" /> </list> </property> </bean>
Rather than knowing that you need to combine all of these together, you simply define the ones that you want to add, as with the FedEx and UPS examples above.
The result after the merge occurs, will look like this:
<bean id="blFulfillmentPricingProviders" class="org.springframework.beans.factory.config.ListFactoryBean"> <property name="sourceList"> <list> <ref bean="blFixedPriceFulfillmentPricingProvider" /> <ref bean="blBandedFulfillmentPricingProvider" /> <ref bean="blFedExFulfillmentPricingProvider" /> <ref bean="blUPSFulfillmentPricingProvider" /> </list> </property> </bean>
In other words, rather than simply replacing one bean with another, the three beans are merged into one in this case. If you mistakenly added one of the defaults, to an additional configuration, it would simply replace it rather than duplicate it.
Here's how it works:
Broadleaf provides a class called
MergeManager that is responsible for actually merging files together. The default strategy for most beans is to simply replace one bean definition with another. However, as described above, there are certain situations where replacing the entire bean is not the default strategy. These special strategies are defined in a file called
default.properties, located in the package
org.broadleafcommerce.common.extensibility.context.merge. This file contains the merge rules for certain special cases. For example, the above case is defined in default.properties like this:
handler.16=org.broadleafcommerce.common.extensibility.context.merge.handlers.NodeReplaceInsert priority.16=16 xpath.16=/beans/bean[@id='blFulfillmentPricingProviders']/* handler.16.1=org.broadleafcommerce.common.extensibility.context.merge.handlers.InsertItems priority.16.1=1 xpath.16.1=/beans/bean[@id='blFulfillmentPricingProviders']/property[@name='sourceList']/list/ref
NodeReplaceInsert class and
InsertItems class are Broadleaf special merge handlers that know how to replace or merge certain XML nodes with others based on XPath expressions.
default.properties file can be overridden by the end user. However, it's extremely rare that one should need to do this.
In addition to merging Spring context files, Broadleaf similarly merges JPA persistence.xml files and EhCache configuration files.
So, it should be clear by now that Broadleaf's Application Context Merge Process makes it much easier to control and override the complex, domain-specific configurations of Broadleaf Commerce without needing to know how to specifically override the entire bean definition. It is still the ability to extend The Spring Framework that makes all this possible.
And of course, SpringSouce continues to innovate in this area as well, providing more and more options, besides XML, for configuration. Broadleaf, no doubt, will continue to embrace SpringSource's innovations, standards, and recommendations. We will continue to innovate in our own right to ensure that users of the Broadleaf Framework have the simplest, most powerful, and most flexible eCommerce framework available.
Finally, I want to shout out a big thanks to SpringSource and No Fluff Just Stuff for putting on a great conference, and for allowing me the opportunity to present on Broadleaf Commerce!