Development
8 min readThe ability to offer flexible, targeted, and powerful promotions is a cornerstone of modern eCommerce. A rigid pricing engine can be a significant bottleneck, forcing businesses to rely on manual processes or simple, inflexible discount codes. Broadleaf Commerce solves this problem with a highly extensible, rules-based engine for managing pricing and promotions. This isn't just about applying a coupon; it’s a sophisticated system designed to handle complex business logic, from multi-tiered discounts to personalized promotions and beyond.
This blog post will provide a technical deep dive into the architecture of Broadleaf's pricing and promotions engine. We’ll explore the key components, the declarative rules-based system that powers it, and—most importantly—how developers can extend and customize it to meet the most demanding business requirements with practical, code-centric guidance.
The Broadleaf pricing and promotions workflow is a pipeline composed of distinct, decoupled microservices. Understanding their specific roles is key to building accurate and extensible solutions.
This architectural separation of concerns means you can manage your pricing rules, offers, and cart logic independently. You could, for instance, deploy a new set of promotions by updating only the offer-service without touching the core pricing-service.
Broadleaf’s promotion system is powered by a declarative rules engine. Instead of writing hard-coded logic for every promotion, you define a set of rules that the engine evaluates at runtime. Each promotion has three key parts:
Here's a simplified view of how an offer for "10% off an order over $100" might be structured:
{
"offerType": "ORDER_PERCENT_DISCOUNT",
"value": 10,
"offerRule": {
"target": "ORDER",
"conditions": [
{
"type": "ORDER_SUBTOTAL_GREATER_THAN",
"value": 100.00
}
]
},
"redeemableCode": "SAVE10",
"startDate": "2024-01-01T00:00:00Z",
"endDate": "2024-01-31T23:59:59Z"
}
This declarative approach allows business users to create a wide variety of promotions without requiring a code change. The power for developers comes from the ability to define new rules and integrate them into this system seamlessly.
Broadleaf’s promotions engine evaluates rules defined in the ItemCriteria domain using Spring Expression Language (SpEL). To add custom logic, a developer’s job is to make new Spring components and their methods available to the SpEL engine. The correct way to do this is by having the component implement the ExpressionVariable interface, which tells the framework how to register it in the SpEL context.
Let's use the example of a custom rule that only applies if the customer has made a purchase in the last 30 days.
First, create a new Spring component that contains the method for your custom business logic. This class must implement com.broadleafcommerce.rulesengine.expression.util.ExpressionVariable to be properly registered by the rules engine.
package com.mycompany.offers.rules;
import com.broadleafcommerce.rulesengine.expression.util.ExpressionVariable;
import com.broadleafcommerce.customer.service.CustomerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
public class PurchaseRuleExpressionVariable implements ExpressionVariable {
public static final String NAME = "purchaseRule";
@Override
public String getName() {
return NAME;
}
private static final Logger LOG = LoggerFactory.getLogger(PurchaseRuleExpressionVariable.class);
private final CustomerService customerService;
@Autowired
public PurchaseRuleExpressionVariable(CustomerService customerService) {
this.customerService = customerService;
}
public boolean hasMadePurchaseSince(String customerId, int days) {
if (customerId == null) {
LOG.info("Skipping purchase check for an unauthenticated customer.");
return false;
}
try {
// Your custom logic to check for recent purchases
// We'll assume a service method exists for this.
return customerService.hasMadePurchaseSince(customerId, LocalDateTime.now().minusDays(days));
} catch (Exception e) {
LOG.error("An error occurred while checking purchase history for customerId: {}", customerId, e);
// Default to false on error to prevent unintended promotion application
return false;
}
}
}
Then override the defaultExpressionVariables bean to add the custom SpEL ExpressionVariable:
@Bean
@ConditionalOnMissingBean(name = "defaultExpressionVariables")
public Map<String, Object> defaultExpressionVariables() {
final Map<String, Object> defaultExpressionVariables = new HashMap<>(1);
defaultExpressionVariables
.put(StringExpressionVariable.NAME, new StringExpressionVariable());
defaultExpressionVariables
.put(DateExpressionVariable.NAME, new DateExpressionVariable());
defaultExpressionVariables
.put(LocaleExpressionVariable.NAME, new LocaleExpressionVariable());
defaultExpressionVariables
.put(CollectionsExpressionVariable.NAME, new CollectionsExpressionVariable());
defaultExpressionVariables
.put(DecimalExpressionVariable.NAME, new DecimalExpressionVariable());
defaultExpressionVariables
.put(MoneyExpressionVariable.NAME, new MoneyExpressionVariable());
defaultExpressionVariables
.put(ObjectExpressionVariable.NAME, new ObjectExpressionVariable());
defaultExpressionVariables
.put(PurchaseRuleExpressionVariableService.NAME, new PurchaseRuleExpressionVariableService(customerService));
return defaultExpressionVariables;
}
Now, a business user can define a rule in the admin console using a SpEL string that calls your new method. The # prefix is required in SpEL to reference a bean from the context.
#purchaseRule.hasMadePurchaseSince(cart.customer.id, 30)
This is how the custom logic is seamlessly integrated into the existing, declarative rules engine. The developer writes the business logic, and the business user configures the promotions without writing any code.
When deploying your custom rule, it's important to remember that it is a part of a Spring Boot microservice. Your custom rule code and configuration will be packaged into a new JAR file and deployed alongside or as part of the offer-service.
Here are some specific scenarios and how to troubleshoot them:
Broadleaf's dynamic pricing and promotions engine is more than just a tool for discounts; it’s a flexible framework that empowers developers to build sophisticated, rules-based commerce logic.
By leveraging Spring, the declarative rules system, and the power of custom extensions, you can create a platform that not only meets today's business needs but can also adapt to new strategies and market demands. The ability to give business users control over powerful, developer-defined logic is a massive competitive advantage, and Broadleaf provides the tools to make it a reality.