Black box view of the promotion engine's architecture
At the core of the promotion component is the promotion engine. The promotion engine is surrounded by three sets of APIs: the persistence management APIs, the dependency management APIs, and the Runtime APIs.
The following diagram shows the promotion engine as a black box.
The central block in the diagram represents the promotion engine. The promotion engine is well insulated by the three sets of APIs, as shown in the diagram.
The first set of APIs are the persistence management APIs. These define the interactions between the promotion engine and a persistent storage system. This persistent storage allows the promotion engine to store persistent objects such as promotions, promotion policies, the results of applying promotions to an order, and statistics.
The second set of APIs is the dependency definition layer. This layer defines how the promotion engine views HCL Commerce by defining a set of interfaces such as order, customer, customer profile, and catalog entry. It defines the object dependencies that the promotion engine has on HCL Commerce. HCL Commerce has a very complex data model. Not every detail in the data model is of interest to the promotion engine. By defining the dependency API, the promotion engine explicitly declares how it uses the HCL Commerce data model, and insulates itself from changes in the HCL Commerce data model.
The third set of APIs is the Runtime APIs. These APIs perform two tasks. First, they provide a system initialization API. Currently, this is called by the HCL Commerce Server. When the Commerce Server initializes, it calls this API set, passing the promotion engine configuration information, and instructs the promotion engine to initialize itself. Second, it provides an invocation API with which the order subsystem in HCL Commerce calls the promotion engine when it prepares an order. It defines the input and output parameters of the call method, as well as any possible exception cases and error codes.
HCL Commerce is depicted as the outermost block. It contains the traditional HCL Commerce data model, business logic, and the persistence management mechanism. HCL Commerce provides the promotion engine with persistence management services using the defined persistence management API, and implements the dependency layer required by the promotion engine using the traditional HCL Commerce data model.
The API details are covered in more detail in the following sections.
Runtime API and order subsystem integration
The set of runtime APIs consists of two calls. The first method deals with system initialization. It is very unlikely this API will ever require customization, therefore, this section looks at the invocation APIs. The promotion engine's invocation APIs contains a single method on the PromotionEngine interface and a single input and output parameter, called PromotionArgument. The only defined exception is the PromotionRuntimeException, which is an unchecked exception.
The method definition is below:
PromotionArgument invoke(
String template,
Long orderId,
boolean discardPreviousResult,
boolean saveCurrentResult,
Hashtable NVP)
throws PromotionRuntimeException;
The first parameter of this method, called template, is a string. This defines the name of the template that is used to invoke the engine. Templates are prescribed ways of calling the promotion engine. A template contains an ordered list of promotions. Templates are configured in the promotion configuration XML file. This configuration file is examined later in this document. The following XML fragment shows a typical template configuration. The comments in the XML fragment explain the purpose of each section in the fragment.
<Template>
<!-- A template has a key, comprised of a reference to the owning
store, and string name unique within the store. If the store reference
refers to the "NullEntity" store of the "root organization", this
template is considered a "wildcard" template. If the promotion engine
is called in a store for which no template with the given name can be
found. The promotion engine searches wildcard templates and attempts
to find a name match. If a match is found, this wildcard template is
used with all the references to "NullEntity" store replaced with
references to the store within which the promotion engine is called.
This includes all of the store references in the group key definition
in the template.
-->
<TemplateKey>
<!--A template is identified by a key which is made up of a reference
to a store and a string identifier unique within the store. -->
<StoreKey>
<DN>o=root organization</DN>
<Identifier>NullEntity</Identifier>
</StoreKey>
<!--This is the string identifier. Currently, the only template used
by HCL Commerce is named "All Promotions".
-->
<Name>All Promotions</Name>
</TemplateKey>
<!-- This section defines for the template, which of the following four
monetary values associated with an order and order items are present:
Price
Tax
Shipping Charge
Tax on Shipping Charge
-->
<MonetaryValuePresence>
<Price>true</Price>
<ShippingCharge>false</ShippingCharge>
<Tax>false</Tax>
<ShippingTax>false</ShippingTax>
</MonetaryValuePresence>
<!-- list of promotion groups will be given -->
<PromotionGroupKey>
<StoreKey>
<DN>o=root organization</DN>
<Identifier>NullEntity</Identifier>
</StoreKey>
<GroupName>ProductLevelPromotion</GroupName>
</PromotionGroupKey>
<PromotionGroupKey>
<StoreKey>
<DN>o=root organization</DN>
<Identifier>NullEntity</Identifier>
</StoreKey>
<GroupName>OrderLevelPromotion</GroupName>
</PromotionGroupKey>
<PromotionGroupKey>
<StoreKey>
<DN>o=root organization</DN>
<Identifier>NullEntity</Identifier>
</StoreKey>
<GroupName>ShippingPromotion</GroupName>
</PromotionGroupKey>
</Template>
Currently, the only template used by HCL Commerce is a template that is called "All Promotions". It contains the "ProductLevelPromotion", "OrderLevelPromotion" and "ShippingPromotion" promotion groups, in this order. When this template is called, the only monetary value that is available for an order or order item is the price information. Taxes, shipping charges, and shipping taxes are not calculated at the time the promotion engine is called.
The second parameter of this method, called orderId, is a Long type. It is the primary key of the order to which any promotions should be applied.
The third and fourth parameters of this method are of Boolean type. The third parameter is called "discardPreviousResult". This is a directive to the promotion engine. It instructs the promotion engine to eliminate any previous record about how promotions are applied to the current order, that is, the order whose primary key is specified in the call. When the third parameter is set to true, previous results are discarded. When this value is set to false, previous results are loaded into memory, and promotion evaluation continues from the point at which it left off previously, thus effectively making calls to the promotion engine stateful. (Due to a resource allocation issue, this stateful invocation of the promotion engine feature has not been subject to the same vigorous testing as the stateless invocation of promotion engine. It is recommended that you use the stateless invocation for any potential customization.) The fourth parameter, called saveCurrentResult, is another directive that tells the promotion engine whether the current results should be persisted in the database. This is usually set to true.
This last parameter is reserved to permit any potential customization that requires additional parameters. It is of Hashtable type, which means that it is simply a carrier for name-value pairs. One thing to note here is that CommandContext, a common HCL Commerce object, is saved on this Hashtable using the name "cmdContext".
The promotion engine throws a PromotionRuntimeException if it encounters an error from which it cannot recover. This is usually a rare event. User errors like improperly entered promotion codes will not trigger such an exception. This exception is a subclass of RuntimeException, which is an unchecked exception.
The input parameters are simple. Conversely, the output parameter of the method is quite rich. This output parameter is PromotionArgument.
Note: PromotionArgument can also serve as an input parameter. A new method on the default promotion engine implementation takes a PromotionArgument as the input. It carries all of the individual input parameters as well as some additional information.
The following diagram shows the structure of this class. The detailed signatures of these interfaces and classes can be found in the API information for these entities.
PromotionArguments returns a list of PromotionErrorReport. Currently, only problems associated with improper use of promotion codes and coupons, such as incorrect or expired codes and coupons are returned as errors. Promotion policy violations cause promotions, which are applicable to the current order, to not be applied. This situation is not considered erroneous. No PromotionErrorReport will be generated as a result.
In additional to error reports, PromotionArgument carries a list of PromotionExecutionRecords. A PromotionExecutionRecord is the evidence that a promotion is applied to an order once. If a promotion is applied to an order N times, N PromotionExecutionRecords are returned. Each PromotionExecutionRecord carries a large variety of information.
First, it carries a reference to the promotion which is applied to the current order. Also, it carries a corresponding promotion code, if one is entered and used to qualify for this promotion.
Optionally, it maintains a reference to a Coupon if one is redeemed for this promotion.
More importantly, a PromotionExecutionRecord returns a LineItemSet which contains all of the order items (or portions of order items, if an order item's quantity is more than 1) that are targeted by this promotion. If an order item is targeted, that means that it is used to qualify for a promotion. A targeted order item is not necessarily discounted as a result of applying the promotion. The order items that are actually discounted are called affected order items.
Affected order items are always a subset of targeted order items. The PromotionExecutionRecord maintains a list of affected order items in the form of an array of LineItemSet. Associated with each LineItemSet in the array, there is a corresponding Adjustment. The Adjustment captures changes made to order items in the affected LineItemSet as the result of applying this promotion. There are three ways an Adjustment can be associated with a LineItemSet:
- The Adjustment is applied to each unit of items in the LineItemSet. For example, if a FixedAmountOffAdjustment offers a $5 discount, and is associated with the LineItemSet this way, each unit of order items in the LineItemSet is discounted by $5. Note that, the $5 is associated with each unit of order items, not each order item. This is significant when an order item contains quantities larger than 1.
- The Adjustment is applied to all of the items in the LineItemSet. For example, if a FixedAmountOffAdjustment offers a $5 discount, and is associated with the LineItemSet this way, a total $5 discount will be applied to each item, regardless of how many items are in the LineItemSet.
- The Adjustment is applied to the entire order as a whole. In this case, the LineItemSet itself is irrelevant. This is the only exception that the affected items (the entire order) could potentially not to a subset of the targeted items.
Adjustment is defined as an interface. The different implementations are listed below:
- FreePurchasableGiftAdjustment: Offers a free gift as the reward of a promotion.
- VoucherAdjustment: Offers a coupon that can be used towards a future purchase.
- FixedAmountOffAdjustment: Offers a discount of a fixed amount.
- FixedAmountOffVolumeDiscountAdjustment: Offers different fixed amount discounts based on the total purchase amount.
- FixedCostAdjustment: Offers a discounted price regardless of the price.
- FixedAmountOffShipping: Offers fixed amount off the shipping charges.
- FixedCostShippingAdjustment: Offers a discounted shipping charge.
- PercentOffAdjustment: Offers a percentage discount.
- PercentOffShippingAdjustment: Offers a percentage off the shipping charges.
- ItemUpgradeAtExtraCostAdjustment: Offers a replacement of an order item at an optional additional fixed charge.
- ShippingUpgradeAtExtraCostAdjustment: Offers a shipping mode upgrade at an optional fixed additional charge.
- FreeNonPurchasableGiftAdjustment: Offers a gift that is not otherwise purchasable.
Currently, the order subsystem calls the promotion engine through this API. The two commands that call the promotion engine are:
- PromotionEngineOrderCalculateCmdImpl
- com.ibm.commerce.order.calculation.PromotionEngineDiscountCalculationCodeCombineCmdImpl
When the promotion engine is called, it processes any promotions for the current order, and returns a PromotionArgument. The order subsystem is then responsible for interpreting the results (PromotionArgument) returned by the promotion engine and storing the results the respective order and order item adjustment tables. Due to limitations in the order subsystem, not all Adjustments are supported by the order subsystem. Here is a list of the Adjustments fully supported by the order subsystem:
- FreePurchasableGiftAdjustment
- FixedAmountOffAdjustment
- FixedAmountOffPriceAdjustment
- FixedAmountOffVolumeDiscountAdjustment
- FixedCostAdjustment
- FixedCostShippingAdjustment
- PercentOffAdjustment
- PercentOffPriceAdjustment
- VoucherAdjustment
If an Adjustment type that is unsupported by the order subsystem is used, the order subsystem will simply ignore the Adjustment returned by the promotion engine. The author of this new adjustment type is then responsible for applying this adjustment when the promotion is finalized.
Dependency model and current dependencies
The second set of APIs that helps insulate the PromotionEngine from the rest of HCL Commerce is the dependency APIs. They describe all of the external entities on which the PromotionEngine depends. Currently, this set of dependencies includes:
- Campaign
- CatalogEntry
- Category
- Customer
- CustomerProfile
- Order
- OrderItem
- Store
All of these entities follow the same implementation pattern in the promotion engine. This pattern is illustrated in the diagram below, which illustrates order as an example:
For each of these entities, there is a key (OrderKey) defined. This is how this entity is identified externally. The dependency is declared as an interface (Order). An abstract factory (OrderFactory) is defined. This factory looks up and instantiates external entities. The PromotionEngine owns an ExternalEntityFactoryRegistry, where the concrete factory (WCSOrderFactory) is registered. At runtime, the promotion engine interacts with the dependency interface (Order) and the abstract factory (OrderFactory), which are implemented by WCSOrder and WCSOrderFactory.
The following XML fragment is used to configure ExternalEntityFactoryRegistry which is part of the promotion engine configuration. (Lines are split for presentation purposes.)
<ExternalEntityFactoryRegistry
impl="com.ibm.commerce.marketing.promotion.dependency.ExternalEntityFactoryRegistry">
<OrderFactory
impl="com.ibm.commerce.marketing.promotion.integration.dependency.WCSOrderFactory" />
<OrderItemFactory
impl="com.ibm.commerce.marketing.promotion.integration.dependency.WCSOrderItemFactory" />
<CustomerFactory
impl="com.ibm.commerce.marketing.promotion.integration.dependency.WCSCustomerFactory" />
<CustomerProfileFactory
impl="com.ibm.commerce.marketing.promotion.integration.dependency.WCSCustomerProfileFactory" />
<CatalogEntryFactory
impl="com.ibm.commerce.marketing.promotion.integration.dependency.WCSCatalogEntryFactory" />
<StoreFactory
impl="com.ibm.commerce.marketing.promotion.integration.dependency.WCSStoreFactory" />
<CampaignFactory
impl="com.ibm.commerce.marketing.promotion.integration.dependency.WCSCampaignFactory" />
</ExternalEntityFactoryRegistry>
All eight external entities on which the promotion engine depends follow the same implementation pattern. Note that, if additional attributes of an order need to be exposed, you can extend the two green classes in the diagram to add the attribute, and register the new concrete factory in the ExternalEntityFactoryRegistry. Also, if the promotion engine has to be integrated with system other than HCL Commerce, a different set of external dependencies are required. In the case of order, two new classes need to be implemented to replace the two green classes in the diagram.
Persistence
The last set of APIs that closes the firewall around the promotion engine is the persistence management API. The following objects in the promotion engine need to be persisted:
- Promotion
- PromotionGroup
- PromotionPolicy
- Coupon
- PromotionArgument
- Promotion statistics
- DynamicAttribute
The persistence of these objects is managed in an identical fashion. The following diagram shows how their persistence is managed by the promotion engine using promotion as an example.
The persistence service that the promotion engine requires is defined as an interface ( PromotionPersistenceManager). Concrete implementations can then be provided. The concrete implementation is then registered with the PersistenceManagerRegistry owned by the promotion engine. The details of all these classes and interfaces can be found in the API information.
The service provided by the persistence management APIs includes the C.R.U.D operations for objects that need persistence. This set of APIs is the basis of any authoring tools for promotions, including the HCL Commerce Accelerator.
The following XML fragment is used to configure the PersistenceManagerRegistry. This is part of the promotion engine configuration. (Lines may be split for presentation purposes.)
<PersistenceManagerRegistry
impl="com.ibm.commerce.marketing.promotion.persistence.PersistenceManagerRegistry">
<PromotionPersistenceManager
impl="com.ibm.commerce.marketing.promotion.PromotionSessionBeanPersistenceManager">
<InitialCacheSize>1024</InitialCacheSize>
<MaxCacheSize>8192</MaxCacheSize>
</PromotionPersistenceManager>
<PromotionPolicyPersistenceManager
impl="com.ibm.commerce.marketing.promotion.policy.PromotionPolicySessionBeanPersistenceManager">
<InitialCacheSize>32</InitialCacheSize>
<MaxCacheSize>1024</MaxCacheSize>
</PromotionPolicyPersistenceManager>
<PromotionGroupPersistenceManager
impl="com.ibm.commerce.marketing.promotion.group.PromotionGroupSessionBeanPersistenceManager">
<InitialCacheSize>32</InitialCacheSize>
<MaxCacheSize>1024</MaxCacheSize>
</PromotionGroupPersistenceManager>
<DynamicAttributePersistenceManager
impl="com.ibm.commerce.marketing.promotion.dynattr.DynamicAttributeSessionBeanPersistenceManager>
<InitialCacheSize>32</InitialCacheSize>
<MaxCacheSize>1024</MaxCacheSize>
</DynamicAttributePersistenceManager>
<PromotionArgumentPersistenceManager
impl="com.ibm.commerce.marketing.promotion.runtime.PromotionArgumentSessionBeanPersistenceManager" />
<CouponPersistenceManager
impl="com.ibm.commerce.marketing.promotion.coupon.CouponSessionBeanPersistenceManager" />
<StatsPersistenceManager
impl="com.ibm.commerce.marketing.promotion.stats.StatsSessionBeanPersistenceManager" />
</PersistenceManagerRegistry>