Transitioning from Sitecore Experience Commerce to OrderCloud: Inventory and Pricing

Reading Time: 9 minutes

In this article, we will review and compare Sitecore Experience Commerce inventory and pricing entities against OrderCloud’s inventory and pricing to facilitate developers looking to transition from XC to OrderCloud as well as identify a path for migration of existing XC solutions to OrderCloud.

We will look at a high-level comparison of architecture, functionality, and data models, with greater focus on transitioning from Sitecore Experience Commerce, meaning identifying the closest path to parity as possible in OrderCloud, using the Habitat catalog and SXA Storefront.

Conceptual Architecture and Features

Inventory

Single Inventory Approach

When it comes to inventory management, in its most basic form, a sellable item or item variation will contain inventory information on an associated entity within an inventory set. To be able to resolve the inventory, the sellable item must be associated to a catalog (or category within a catalog) and the catalog must be associated to the inventory set.

Figure 1: The sellable item and item variation relationship to inventory information.

In a more simplified comparison, OrderCloud also has a separate underlying inventory object assigned to a product or variant, however it’s represented and managed as a sub-object of the product/variant. No relationship to a catalog or inventory set equivalent is required.

Figure 2: The product and variant relationship to inventory, equivalent to XC’s standard inventory architecture for single inventory management.

Multi-Inventory Approach

When it comes to dealing with multiple catalogs in XC, the approach has a new layer of complexity; catalogs may have different associated inventory sets, which means that the inventory information entity the commerce engine resolves to is based on catalog-inventory set association.

Figure 3: Sellable item vs item variation inventory management in XC.

In OrderCloud, it is also possible to store multiple sets of inventory data using inventory records, providing two paths for inventory management. For implementations where single sets of inventory data will only ever be required, the traditional inventory management approach in figure 2 can be used. Where multi-inventory management is required, inventory records can be leveraged.

In OrderCloud’s multi-inventory management approach, admin addresses are effectively the inventory set equivalent in creating a logical grouping of inventory records; catalog assignments are still not required to resolve inventory.

Multiple inventory records can be assigned to an address, and although it’s not required in the XC to OrderCloud migration, it’s mentioned simply for a more complete understanding of the OrderCloud architecture.

Figure 4: OrderCloud’s inventory record approach for both single and multi-inventory support.

The most notable difference between the two systems is the Commerce Engine controls the logic to resolve the appropriate inventory information entity, while the middleware application of an OrderCloud implementation would be responsible for resolving applicable inventory records.

Inventory sets can represent the grouping of inventory information entities, typically for source or purpose, e.g. warehouse stock or reserved inventory for online sales. These are used in common customisations, such as click and collect, where more than one inventory set is utilised in a storefront. Using inventory records in OrderCloud can achieve the same result.

Inventory Properties

Apart from the expected quantity property, XC’s inventory information also contains information such as invoice unit price and currency, preorderable and backorderable details. While the commerce engine does perform some logic for decrementing backorder and preorder inventory quantities, the implementation is not complete, therefore we won’t treat this as a functional gap between the two systems and simply adding them to OrderCloud’s extended properties (xp) will suffice from the data migration perspective.

Inventory records have the additional benefit of owning their xp. This means that additional properties such as preorderable and backorderable details better fit the context in the inventory record xp, over the product xp, which would be required when using the traditional product inventory approach.

Pricing

When directly comparing the pricing architecture of XC and OrderCloud, there are some notable differences in how pricing is resolved for at both the sellable item/product level and the item variation/variant level.

List Pricing Approach – Sellable Item Level

Starting with the most basic approach to pricing migration, sellable items have a list price policy that stores a list of multi-currency pricing. Note that item variations do not have list pricing in this example and will inherit from the sellable item. Also leaving the price card architecture out of the equation, the list pricing approach is represented in figure 5.

Figure 5: XC list pricing for sellable items and item variations.

In OrderCloud, pricing is managed via price schedule objects that are assigned to products. In order to replicate the XC architecture, individual price schedules will represent the list price for each currency, then a product assignment is used to create the relationship between the product, the price schedule, and a buyer user group. Locale assignments between locale and buyer user group are used to resolve the currency when creating orders.

To switch between active currencies, the buyer user will need to be moved to the respective buyer group representing the desired currency configuration.

Multi-currency can also be achieved for anonymous/guest shoppers via multiple API clients configured appropriately. See OrderCloud: How to Globalize your eCommerce for more information.

Figure 6: A multi-currency approach for profiled users.

If we only need to support a single currency then we can take advantage of a more simplified approach using just the product and price schedule, using the DefaultPriceScheduleID.

Figure 7: OrderCloud single price approach for products and products with variants.

List Pricing Approach – Item Variation Level

XC also allows for list prices to be set at the item variation level, which includes fallback to the sellable item list pricing if not supplied. Excluding the fallback pricing, we see the following representation of a sellable item with list pricing configured against the item variations in figure 8.

Figure 8: XC list pricing at the variant level.

To replicate XC price resolution at the variant level in OrderCloud, the variant’s xp could be used to host price schedule assignments, while the middleware solution can resolve the price schedule as needed. For example, during the order calculate integration event the order’s line item UnitPrice property can be overridden via LineItemOverrides model.

For further information regarding the order calculate integration event, see Order Checkout Integration Event > Implementation > /OrderCalculate.

Figure 9: An XC list pricing equivalent approach for variants in OrderCloud.
{
  "LineItemOverrides": [
    {
      "LineItemID": "LineItemWithVariantA", // Line Item ID containing variant A
      "UnitPrice": 6.00 // Price resolved from Price Schedule A
    }
  ]
}

Price Card Approach

Associations

In XC, a price book can be associated to multiple catalogs, which represents the price book that the catalog will attempt to resolve price cards from for a given sellable item.

Figure 10: Price Books can be associated to more than one catalog.

Price cards can be resolved, by name or by tag association, to more than one price card across multiple price books. The Commerce Engine contains some additional logic to identify the eligible price cards, prioritise them, and determine the winning price card.

Figure 11: Sellable items are resolved back to a price book based on catalog-price book associations, however multiple price cards may also be resolved. Associations may be resolved by price card name or by tag.
Scheduled Pricing

Taking a step back to look at the minimal configuration of a single price card associated to a sellable item, the price snapshots on the price card represent scheduled pricing. Each snapshot supports multi-currency tiered pricing, and also has approval workflow to prevent new pricing from accidently going live.

Figure 12: Resolving a price from a single price card association in XC.

In OrderCloud, the price schedule contains PriceBreaks, allowing for tier pricing for a single currency. The SaleStart and SaleEnd date/time properties dictate the timeframe when the SalePrice values are active, which is also flagged by the calculated property IsOnSale.

PriceSchedule assignments support price scheduling without needing to differ from the assignment architecture shown in figure 6 and figure 7.

"PriceBreaks": [
  {
    "Quantity": 1,
    "Price": 3.99,
    "SalePrice": null
  },
  {
    "Quantity": 5,
    "Price": 3.49,
    "SalePrice": 2.99
  }
],
"Currency": "USD",
"SaleStart": "2022-03-03T00:00:00+00:00",
"SaleEnd": "2022-04-03T00:00:00+00:00",
"IsOnSale": true
Other Considerations

In migrating from XC’s price card architecture to OrderCloud’s price schedule architecture, the following table highlights additional aspects of XC with high level approaches to implementing them in an OrderCloud solution.

XCOrderCloud
A sellable item can resolve to multiple price cards, which has logic to prioritise and resolve a single price card.OrderCloud supports multiple price schedule assignments to products via assignments allowing for dynamic resolution of a price schedule relevant to the customer. For more complex scenarios, the product’s xp can be utilised to have the middleware resolve the price schedule using custom logic and can override the unit price for calculating orders via the order calculate integration event.
For more information, see OrderCloud: Same Product, Multiple Price Schedules and Order Checkout Integration Event > Implementation > /OrderCalculate
Price cards are grouped into price books, which can be resolved by the price book association of the catalog.Price schedules can be assigned to a specific buyers or buyer groups, which is typically sufficient for resolving price schedules for the majority of business requirements.
For more information, see OrderCloud: Same Product, Multiple Price Schedules.
Price cards may contain multiple price snapshots for scheduled pricing support.Price schedules allow for sale pricing, based on a specific date and time, while future sale pricing can be achieved through creating scheduled tasks, e.g. a timer triggered function app in Azure, to update price schedules accordingly.
Using price cards without list pricing allows standard pricing to be scheduled.Standard pricing can be achieved through creating scheduled tasks, e.g. a timer triggered function app in Azure, to update price schedules accordingly.
XC Price Snapshots have approval workflow.Approval workflow can be implemented via middleware customisation.

Data Mapping

With the conceptual analysis above, we will now review what data mapping would look like for migration and from a comparison standpoint.

In the XC Entity/Component column, components are assumed to live on the primary XC entity being mapped.

OrderCloud IDs do not allow spaces. It is important that the IDs are parsed to remove/replace invalid characters consistently.

Admin Addresses

While admin addresses are a representation of inventory sets, there are a number of mandatory fields for address details that do not exist in XC. These fields can either be filled out with dummy data or enriched with the appropriate values.

OC PropertyData TypeRequiredXC Entity/ComponentXC PropertyData TypeNotes
IDstringNoInventorySetFriendlyIdstring
CompanyNamestringNoN/AN/AN/A
FirstNamestringYesN/AN/AN/A
LastNamestringNoN/AN/AN/A
Street1stringYesN/AN/AN/ASample data will need to be supplied.
Street2stringNoN/AN/AN/A
CitystringYesN/AN/AN/ASample data will need to be supplied.
StatestringYesN/AN/AN/ASample data will need to be supplied.
ZipstringYesN/AN/AN/ASample data will need to be supplied.
CountrystringYesN/AN/AN/ASample data will need to be supplied.
PhonestringNoN/AN/AN/A
AddressNamestringNoInventorySetDisplayNamestring
xpobjectNoN/AN/AN/A
xp.DescriptionstringNoInventorySetDescriptionstring

Inventory Records

The xp in inventory records should only be mapped if they are being used in the XC implementation to avoid unnecessary bloating of the xp. The xp mapping can also be used on the product for the traditional inventory management approach.

OC PropertyData TypeRequiredXC Entity/ComponentXC PropertyData TypeNotes
productIDstringYesSellableItemFriendlyIdstring
IDstringNoInventoryInformationFriendlyIdstring
OwnerIDstringNoN/AN/AN/A
AddressIDstringNoInventorySetFriendlyIdstring
OrderCanExceedbooleanNoN/AN/AN/A
QuantityAvailableintegerNoInventoryInformationQuantityinteger
xpobjectNoN/AN/AN/A
xp.InvoiceUnitAmountdecimalNoInventoryInformationInvoiceUnitPrice.Amountdecimal
xp.InvoiceUnitCurrencystringNoInventoryInformationInvoiceUnitPrice.CurrencyCodestring
xp.PreorderablebooleanNo[PreorderableComponent]Preorderableboolean
xp.PreorderAvailabilityDatedatetimeoffsetNo[PreorderableComponent]PreorderAvailabilityDatedatetimeoffset
xp.PreorderedQuantityintegerNo[PreorderableComponent]PreorderedQuantityinteger
xp.PreorderLimitintegerNo[PreorderableComponent]PreorderLimitinteger
xp.BackorderablebooleanNo[BackorderableComponent]Backorderableboolean
xp.BackorderAvailabilityDatedatetimeoffsetNo[BackorderableComponent]BackorderAvailabilityDatedatetimeoffset
xp.BackorderedQuantityintegerNo[BackorderableComponent]BackorderedQuantityinteger
xp.BackorderLimitintegerNo[BackorderableComponent]BackorderLimitinteger

Price Schedules

As price schedules only support the equivalent of static list pricing of XC’s sellable items, this data mapping only covers this scenario.

OC PropertyData TypeRequiredXC Entity/ComponentXC PropertyData TypeNotes
OwnerIDstringNoN/AN/AN/A
IDstringNoSellableItem
[ListPricePolicy].Prices
FriendlyId
CurrencyCode
string
string
NamestringYesSellableItem
[ListPricePolicy].Prices
FriendlyId
CurrencyCode
string
string
ApplyTaxstringNoN/AN/AN/A
ApplyShippingbooleanNoN/AN/AN/A
MinQuantityintegerNoN/AN/AN/A
MaxQuantityintegerNo~[LineQuantityPolicy]MaximumdecimalXC stores this in the environment policies.
UseCumulativeQuantitybooleanNo~[RollupCartLinesPolicy]RollupbooleanXC stores this in the environment policies.
RestrictedQuantitybooleanNoN/AN/AN/A
PriceBreaksarrayNoN/AN/AN/A
PriceBreaks.QuantityintegerNoN/AN/AN/ASet to 1.
PriceBreaks.PricefloatNo[ListPricePolicy].PricesAmountdecimalFor a given price in the [ListPricePolicy].Prices array.
CurrencystringNo[ListPricePolicy].PricesCurrencyCodestringFor a given price in the [ListPricePolicy].Prices array.
xpobjectNoN/AN/AN/A

References

Continue the Series

  1. Transitioning from Sitecore Experience Commerce to OrderCloud: Customers to Buyer Users
  2. Transitioning from Sitecore Experience Commerce to OrderCloud: Customers and Buyers – API Access
  3. Transitioning from Sitecore Experience Commerce to OrderCloud: Catalogs and Categories
  4. Transitioning from Sitecore Experience Commerce to OrderCloud: Sellable Items To Products
  5. Transitioning from Sitecore Experience Commerce to OrderCloud: Inventory and Pricing
  6. Transitioning from Sitecore Experience Commerce to OrderCloud: Carts to Unsubmitted Orders and Carts
  7. Transitioning from Sitecore Experience Commerce to OrderCloud: Fulfillments to Shipping
  8. Transitioning from Sitecore Experience Commerce to OrderCloud: Tax and Payments
  9. Transitioning from Sitecore Experience Commerce to OrderCloud: Orders
  10. Transitioning from Sitecore Experience Commerce to OrderCloud: Order Workflow and Minions
  11. Transitioning from Sitecore Experience Commerce to OrderCloud: Promotions

Transitioning from Sitecore Experience Commerce to OrderCloud: Customers to Buyer Users

Reading Time: 5 minutes

In this article, we will review and compare Sitecore Experience Commerce customers and OrderCloud buyer users to facilitate developers looking to transition from XC to OrderCloud as well as identify a path for migration of existing XC solutions to OrderCloud.

We will look at a high-level comparison of architecture, functionality, and data models, with greater focus on transitioning from Sitecore Experience Commerce, meaning identifying a close path to parity in OrderCloud, using the Habitat catalog and SXA Storefront.

Customers

The Customer to Storefront Association

In an XC SXA implementation, customer accounts are registered under a security domain, which can be configured in the Content Editor on the /sitecore/Content/<tenant>/<site>/Settings/Site Grouping/<storefront> item.

Figure 1: An example domain configuration for a storefront.

Customers can only be registered to a single security domain and will not be accessible in storefronts configured to a different domain. A separate customer account would need to be created under the other security domains, but it would have no knowledge or relationship to accounts across domains. However, a security domain can be registered to multiple storefronts allowing a customer to share an account across storefronts.

Figure 2: XC Security Domains have a zero to many relationship with Storefronts.

In OrderCloud, customers can be represented as buyer users, which are registered under buyer companies, and can only belong to a single buyer company. This can be compared directly to the relationship between security domains and customers.

The relationship of a buyer to an OrderCloud storefront is not tightly coupled like the SXA storefront. Access to an OrderCloud storefront is managed through API clients, security profiles and their assignments to buyers. See Getting Started > Establishing API Access for more information.

Figure 3: The basic structure comparison between customers to users.

The Customer to Catalog Association

Buyers also contain a DefaultCatalogID property, which will create an associated catalog with an ID matching the buyer ID if not specified during the creation of a buyer. While it may seem like this limits Buyer A to a single catalog (Catalog A), multiple catalogs can be assigned via OrderCloud’s catalog assignments, which matches XC’s equivalent in supporting both Catalog A and Catalog C associations to Security Domain A via storefronts (figure 4).

The DefaultCatalogID property will be utilised in certain APIs when a catalogID is not supplied in their request, however this can be overridden. For now, it is only important to understand that this is not a limiting factor in OrderCloud functionality for migration of data or functionality.

Figure 4: XC relationship between customers and catalogs vs. OrderCloud’s relationship between buyer users and catalogs.

OrderCloud User Groups

OrderCloud also allows buyer users to be assigned to one or more buyer user groups, which can be used to supersede assignments to buyer users in bulk, e.g. provide access to special pricing for VIP buyer users.

Figure 5: OrderCloud can group buyer users in buyer user groups.

Over in XC, a common customisation is an implementation of customer groups to assign customers in order to provide customer-group specific pricing, promotions, etc. While the implementation details may have the customer group represented as a separate custom entity or a property on a customer, conceptually both approaches achieve the same outcome (figure 6) and are on par with OrderCloud’s architecture (figure 5), allowing a seamless transition path to OrderCloud.

Figure 6: Customer groups are a common customisation in XC, which can be thought of in a simliar respect to OrderCloud’s buyer user groups, however it is not part of the default ecosystem.

Customer Addresses

In XC, customers have explicit addresses associations as the address component lives on the customer entity. The IsPrimary flag is used to allow a default address selection in implementations.

Figure 7: Customer addresses in XC.

To bring across customer addresses into OrderCloud, figure 8 shows that we can create buyer user addresses, via the /me/addresses resource, creating a custom IsPrimary flag in the buyer address xp.

Figure 8: Buyer user address architecture in OrderCloud.

Buyer user addresses, also contain Shipping and Billing properties, which can facilitate filtering addresses for the purpose of being retrieving and setting for an order’s shipping address or billing address respectively. As the SXA Storefront allows customer addresses to be used for both of these addresses, these flags should also be set to true.

Data Mapping

With the conceptual analysis above, we will now review what data mapping would look like for migration and from a comparison standpoint.

In the XC Entity/Component column, components are assumed to live on the primary XC entity being mapped.

OrderCloud IDs do not allow spaces. It is important that the IDs are parsed to remove/replace invalid characters consistently.

Buyers

The DefaultCatalogID property will be reviewed further during the catalog analysis article.

OC PropertyData TypeRequiredXC Entity/ComponentXC PropertyData TypeNotes
IDstringNoCustomerDomainstring
ActivestringYesN/AN/AN/ASet to true by default.
NamestringNoCustomerDomainstring
DefaultCatalogIDstringYesN/AN/AN/AAssign to an existing catalog or PATCH later.
xpobjectNoN/AN/AN/AAny custom fields can be mapped to xp.

Buyer Users

There are a few properties in OrderCloud are required while not mandatory in the SXA Storefront, so considerations for handling these properties will need to be made in these instances, e.g. applying fallback values.

OC PropertyData TypeRequiredXC Entity/ComponentXC PropertyData TypeNotes
buyerIDstringYesCustomerDomainstringIs a resource parameter, not body property.
IDstringNoCustomerFriendlyIdstring
UsernamestringYesCustomerLoginNamestring
PasswordstringNoN/AN/AN/APassword should not be migrated.
FirstNamestringYesCustomerFirstNamestringNot a mandatory field in SXA, so a fallback value will need to be populated.
LastNamestringYesCustomerLastNamestringNot a mandatory field in SXA, so a fallback value will need to be populated.
EmailstringYesCustomerEmailstring
PhonestringNoCustomerDetailsComponentPhoneNumberstringThe XC PhoneNumber exists under an entity view of the CustomerDetailsComponent, so consider this a rough mapping.
TermsAcceptedstringNoN/AN/AN/A
ActivebooleanYesCustomerAccountStatusstringSet to true where AccountStatus == "ActiveAccount"
xpobjectNoN/AN/AN/AAny custom fields can be mapped to xp.

Buyer User Addresses

OrderCloud only stores the ISO 3166-1 alpha-2 2-letter country code. Retrieving additional country details, such as country name, would best be handled by custom middleware, outside of OrderCloud.

OC PropertyData TypeRequiredXC Entity/ComponentXC PropertyData TypeNotes
ShippingbooleanNoN/AN/AN/ASet to true
BillingbooleanNoN/AN/AN/ASet to true
FirstNamestringNoAddressComponentFirstNamestringSXA Storefront does not capture FirstName. May wish to use Customer.FirstName instead.
LastNamestringYesAddressComponentLastNamestringSXA Storefront does not capture LastName. May wish to use Customer.LastName instead.
Street1stringYesAddressComponentParty.Address1string
Street2stringNoAddressComponentParty.Address2string
CitystringYesAddressComponentParty.Citystring
StatestringYesAddressComponentParty.StateCodestringCan also use Party.State for full state name, depending on requirement.
ZipstringYesAddressComponentParty.ZipPostalCodestring
CountrystringYesAddressComponentParty.CountryCodestring
PhonestringNoAddressComponentParty.PhoneNumberstringWill be empty in SXA Storefront default implementation. May wish to use Customer.[CustomerDetailsComponent].PhoneNumber instead.
AddressNamestringNoAddressComponentParty.AddressNamestring
xpobjectNoN/AN/AN/AAny custom fields can be mapped to xp.
xp.IsPrimaryobjectNoAddressComponentParty.IsPrimaryboolean

References

Continue the Series

  1. Transitioning from Sitecore Experience Commerce to OrderCloud: Customers to Buyer Users
  2. Transitioning from Sitecore Experience Commerce to OrderCloud: Customers and Buyers – API Access
  3. Transitioning from Sitecore Experience Commerce to OrderCloud: Catalogs and Categories
  4. Transitioning from Sitecore Experience Commerce to OrderCloud: Sellable Items To Products
  5. Transitioning from Sitecore Experience Commerce to OrderCloud: Inventory and Pricing
  6. Transitioning from Sitecore Experience Commerce to OrderCloud: Carts to Unsubmitted Orders and Carts
  7. Transitioning from Sitecore Experience Commerce to OrderCloud: Fulfillments to Shipping
  8. Transitioning from Sitecore Experience Commerce to OrderCloud: Tax and Payments
  9. Transitioning from Sitecore Experience Commerce to OrderCloud: Orders
  10. Transitioning from Sitecore Experience Commerce to OrderCloud: Order Workflow and Minions
  11. Transitioning from Sitecore Experience Commerce to OrderCloud: Promotions

Sitecore Experience Commerce: Working with Environment Variables

Reading Time: 3 minutes

In this article, we will review how environment variables are utilised in the Commerce Engine solution. These are findings that provide some more context on top of Commerce Developer Reference: Configuring the Commerce Engine using environment variables, which will allow us to work with them more effectively.

Introduction

Environment variables are primarily utilised in 3 areas of the Commerce Engine and each area has nuances that we need to be aware of. The first area is in the Commerce Engine configuration file, config.json. The second is the global environment configuration, global.json. The third are is made up of the role environment and policy set configurations.

Environment variables will only be consumed into the Commerce Engine’s configuration builder if they are prefixed with "COMMERCEENGINE_".

Environment Variable Usage in Configuration Files

The Commerce Engine Configuration File (config.json)

Using the environment variable configuration provider, the config.json is updated without the need for placeholders or data type specification.

Updating a property value simply requires adding an environment variable that represents the path of the property to be overridden.

For example, adding the environment variable, COMMERCEENGINE_Serilog__MinimumLevel__Default: Information, will override the corresponding configuration property by resolving it to the structure of the configuration.

{
  "AppSettings": { ... },
  "Serilog": {
    ...
    "MinimumLevel": {
      "Default": "Warning",
      ...
    }
  },
  ...
}

While the placeholder values aren’t relevant to the overrides themselves, the Commerce Engine is distributed with ‘PlaceholderFor<property>’ values in config.json. Consider these flags to ensure that environment variables are not omitted. When replacing other properties in the Commerce Engine configuration, placeholders are not required.

The Global Environment Configuration File (global.json)

Similar to the config.json, the global environment configuration file, global.json, is loaded during the start up process of the Commerce Engine. However, while the Commerce Engine leverages the environment variables for this configuration, the replacement strategy differs from the environment variable configuration provider in that instead of resolving variables to the structure of the configuration file it instead resolves via placedholder matches.

For example, the environment variable COMMERCEENGINE_GlobalDatabaseUserName: sa will override the corresponding configuration property by resolving it to the placeholder.

{
  ...
  "Name": "GlobalEnvironment",
  "Policies: {
    "$type": "System.Collections.ObjectModel.ReadOnlyCollection`1[[Sitecore.Commerce.Core.Policy, Sitecore.Commerce.Core]], mscorlib",
    "$values": [
      ...
      {
        "$type": "Sitecore.Commerce.Plugin.SQL.EntityStoreSqlPolicy, Sitecore.Commerce.Plugin.SQL",
        ...
        "UserName": "PlaceholderForGlobalDatabaseUserName",
        ...
      },
      ...
    ]
  }
}

Role Environment and Policy Set Configuration Files

For environment variables used in role environment and policy set configurations, the Commerce Engine also uses the same strategy as per the global.json (placeholder matching over structure resolution), however the replacement is performed at the time of bootstrapping the Commerce Engine, not during the start up process of the Commerce Engine.

This means that while the role environment and policy set configurations will retain the ‘PlaceholderFor…’ values on disk, the engine startup will still load configurations from the commerce global database. Therefore, the Commerce Engine role will not resolve changes during the start up process with updated variables (e.g. running docker-compose up for containers).

Changing environment variables used in role environment and policy set configuration files, should be thought the same as making changes to the configuration files themselves, where bootstrapping the Commerce Engine is required to consume the changes.

Supported Data Types in Placeholders

In Commerce Developer Reference: Configuring the Commerce Engine using environment variables, it 3 mentions supported data types for placeholders, where string is the implicit (default) type, and boolean and integer values can be represented by appending ‘|bool’ and ‘|int’ to placeholder names respectively.

The reality is that under the hood all that is happening is identifying whether the value should be wrapped with quotation marks or not. This means that we can actually support other data types, such as decimals by appending either ‘|bool’ or ‘|int’ to the placeholder.

Data types apply to placeholders in the global environment, role environment and policy set configuration files. They do not apply to placeholders in the Commerce Engine configuration file (config.json).

PlaceholderValuesResults
“PlaceholderForMyVariable”dev.sitecore.com
true
“dev.sitecore.com”
“true”
“PlaceholderForMyVariable|bool” or
“PlaceholderForMyVariable|int”
dev.sitecore.com
true
3
1.2 (decimal)
dev.sitecore.com
true
3
1.2

Summary

The following table summarises how environment variables are utilised in the various configuration files of the Commerce Engine.

Configuration File(s)Property ResolverAllows Fallback / Default Value*AppliesApplied ByRequires Bootstrap
config.jsonStructureYesDuring start upEnvironment variable configuration provider (.Net Core)No
global.jsonPlaceholderNoDuring start upCommerce EngineNo
Environment/Policy Set ConfigurationsPlaceholderNoDuring BootstrapCommerce EngineYes
*Where environment variable not provided.

References

Sitecore Experience Commerce: Customising Commerce Content in the Sitecore Indexes for Storefront Search Results

Reading Time: 4 minutes

In this article, we will look at how to control the storefront search results by customising the commerce content that is indexed in the Sitecore master and web indexes.

If you are looking for how to add new fields for faceting and other purposes, see Adding a property field to an index.

How Storefront Search Works

The SXA Search component creates a solr query based on a number of criteria, however focusing on how the search term is involved in the query, we see that the sxacontent field is our culprit for this customisation. This can be seen highlighted below in our storefront and in our search log.

From XC 9.3 onwards, the commerce portion of the Sitecore indexes were migrated over to the Commerce Engine for indexing performance improvements. There are a number of handlers utilised for populating the indexes with catalog data, however for managing the content related to the storefront search component we will look at the SxaContentFieldHandler.

See Catalog Search Field Handlers for a full list of handlers provided with XC, which are utilised when registering index fields in Plugin.Search.Solr.PolicySet-1.0.0.json.

The SxaContentFieldHandler Class

By default, the sellable item properties that are added to the sxacontent field consist of the following entries:

  • Tags
  • ProductId
  • Name
  • DisplayName
  • ItemDefinition
  • Brand

As we can see from the sitecore_web_index in Solr, a query for a specific ‘Spectra’ product, productid_t:6042260, shows that these properties are indexed against the sellable item.

{
  "sxacontent_txm":["39inch|4k|uhd|television|spectra",
    "6042260",
    "Habitat Spectra 39” 4K LED Ultra HD Television",
    "Habitat Spectra 39” 4K LED Ultra HD Television",
    "Product",
    "Spectra Televisions"],
  "productid_t":"6042260",
  ...
}

A common property that I have seen often requested to be included for searching is the sellable item’s description, so we will use this property as well as a generic property from a custom component for demonstration purposes.

In the sample code below, we see that we inherit from the default SxaContentFieldHandler and execute the base ComposeValue method, which will populate the default fields listed above. We then simply add the properties to the sxaContent reponse object, which in this case is a list of strings.

You may have noticed that we are only adding the properties to sxacontent if a value is present. This is to minimise the bloat of empty entries and may lead to performance improvement however insignificant it may be. You may prefer to leave empty entries in the index to simplfy troubleshooting in mapping each entry back to its source property, so choose an approach that best suits you.

public class MySxaContentFieldHandler : Sitecore.Commerce.Plugin.Catalog.SxaContentFieldHandler
{
    public override object ComposeValue(object source, ConcurrentDictionary<string, object> context)
    {
        var sxaContent= base.ComposeValue(source, context) as List<string>;
        if (!(source is SellableItem) || sxaContent == null || sxaContent.Count() == 0)
        {
            return sxaContent;
        }

        var sellableItem = source as SellableItem;

        if (!string.IsNullOrWhiteSpace(sellableItem.Description))
        {
            sxaContent.Add(sellableItem.Description);
        }

        var myProperty = sellableItem.GetComponent<MyCustomComponent>().MyCustomProperty;
        if (!string.IsNullOrWhiteSpace(myProperty))
        {
            sxaContent.Add(myProperty);
        }

        return sxaContent;
    }
}

Now that we have created our modified SxaContentFieldHandler class, we register it in Plugin.Search.Solr.PolicySet-1.0.0.json for both the master and web indexes.

{
  "$type": "Sitecore.Commerce.Plugin.Search.ItemIndexablePolicy, Sitecore.Commerce.Plugin.Search",
  "IndexName": "sitecore_web_index",
  "FieldTypeMappers": [ ... ],
  "Fields": [
    {
      "$type": "Sitecore.Commerce.Plugin.Search.Solr.SolrIndexFieldConfiguration, Sitecore.Commerce.Plugin.Search.Solr",
      "Name": "sxacontent",
      "Type": "System.Collections.Generic.List`1[System.String]",
      "TypeHint": "textCollection",
      "Handler": {
        "$type": "Sitecore.Commerce.Engine.Search.MySxaContentFieldHandler, Sitecore.Commerce.Engine"
      }
    },
    ...
  ]
}

After deploying our customisation, boostrapping, and reindexing, we can see our custom entries in the sxacontent field.

{
  "sxacontent_txm":["39inch|4k|uhd|television|spectra",
    "6042260",
    "Habitat Spectra 39” 4K LED Ultra HD Television",
    "Habitat Spectra 39” 4K LED Ultra HD Television",
    "Product",
    "Spectra Televisions",
    "Enjoy incredible picture and dramatic detail with 4X the resolution of full HD. Enjoy incredible hues of color with technology that blends digital color for near-perfect representation. Experience Ultra HD picture quality with enhanced detail in every way.",
    "mycustomproperty"],
  "productid_t":"6042260",
  ...
}

And by performing a search in the storefront our search results now return our product from our custom search entries.

Development Considerations

Field Handlers are Synchronous

At this time, field handlers have been implemented synchronously and expect the information you require to be available on the provided source entity, e.g. sellable item in this case, therefore if you are looking at sourcing data externally it may be worth further consideration.

Following Helix Priniciples

Whether upgrading from a pre XC 9.3 solution or introducing existing component properties into the search index, by implementing the custom SxaContentFieldHandler you may be faced with having to break helix principles, i.e. housing your custom SxaContentFieldHandler in a feature project while referencing another feature project to retrieve the desired properties/content. As feature projects should not reference each other, I reluctantly chose to add MySxaContentFieldHandler to the Commerce Engine project in this instance.

References

Sitecore Experience Commerce: Pipeline Block Types

Reading Time: 3 minutes

In this article, we will look at the foundational pipeline block types that are used to construct pipelines within the Commerce Engine plugins and custom solutions.

Asynchronous vs Synchronous Pipeline Blocks

From XC 10.0, the PipelineBlock class has been split into AsyncPipelineBlock and SyncPipelineBlock classes for improved performance and stability, with the primary implementation difference being whether to call the pipeline block asynchronously or not during pipeline execution.

A notable change to the AsyncPipelineBlock is that it uses the RunAsync method instead of the Run method to enforce that the return type is a Task<TOutput> and as an added reminder that the pipeline block is in fact asynchronously executed.

How Async and Sync Pipeline Blocks Work in the Same Pipeline

The initial questions I had when seeing the pipeline split were “how does this affect registering pipeline blocks to pipelines?” and “how does this affect the way in which pipelines are executed?”. The good news here is that is doesn’t affect either from an implementation standpoint.

From the registration perspective, both async and sync pipelines are registered as per XC 9.X registration mechanisms.

.AddPipeline<IMyPipeline, MyPipeline>(pipeline => pipeline
    .Add<MySyncPipelineBlock>()
    .Add<MyAsyncPipelineBlock>()
    .Add<MyAsyncConditionalPipelineBlock>()
    .Add<MyAsyncPolicyTriggerConditionalPipelineBlock>()
    .Add<MyConditionalPipelineBlock>()
    .Add<MyAsyncConditionalPipelineBlock>()
)

From the async/sync execution perspective, async pipeline blocks are awaited before continuing on to the following pipeline block, which ensures the output of a pipeline block is the input of the following pipeline block.

Specialised Pipeline Blocks

You may have seen a few types of enhanced pipeline blocks in XC 9.X+, being ConditionalPipelineBlock and PolicyTriggerConditionalPipelineBlock, which may have been better suited to your custom implementations over adding conditional statements to the begining of your Run/RunAsync method.

Conditional Pipeline Blocks

As indicated by their names, the ConditionalPipelineBlock and AsyncConditionalPipelineBlock classes will be conditionally executed. This is achieved by defining the BlockCondition predicate to be evaluated to determine if the Run/RunAsync method should be called.

Where the evaluated BlockCondition returns false, an alternate method called ContinueTask is executed instead of Run/RunAsync and is intended to simply return the arg or null.

Returning arg from ContinueTask considers the pipeline block as optional whereas returning null may consider the pipeline block as mandatory depending on how this output is handled in subsequent pipeline blocks or the pipeline itself.

Warning: Using the ContinueTask method to run alternate code logic may be a sign of a bad code smell, such as breaking the separation of concerns rule. Having 2 separate pipeline blocks with inverted predicates is the recommended approach.

public class MyConditionalPipelineBlock : ConditionalPipelineBlock<MyArgument, MyArgument, CommercePipelineExecutionContext>
{
    public MyConditionalPipelineBlock()
    {
        BlockCondition = obj => ((CommercePipelineExecutionContext)obj).CommerceContext.HasPolicy<MyCustomPolicy>();
    }

    public override MyArgument Run(MyArgument arg, CommercePipelineExecutionContext context)
    {
        Condition.Requires(arg, nameof(arg)).IsNotNull();

        // My code logic

        return arg;
    }

    public override MyArgument ContinueTask(MyArgument arg, CommercePipelineExecutionContext context)
    {
        return arg;
    }
}

Policy Trigger Conditional Pipeline Blocks

Another more specialised form of conditional pipeline blocks are the PolicyTriggerConditionalPipelineBlock and AsyncPolicyTriggerConditionalPipelineBlock. These blocks actually have somewhat inverted logic to their name as you will need to specify the name of a policy in the ShouldNotRunPolicyTrigger property which will ensure the pipeline is not executed.

When the policy name is present in the request header’s PolicyKeys value the pipeline block will execute the ContinueTask method over the Run/RunAsync method (See Conditional Pipeline Blocks for more details).

A full set of existing policy keys and their usages can be found in Commerce Developer Reference: Policy keys. It is recommended that the Commerce Engine policy keys be avoided when creating custom policy trigger conditional pipeline blocks.

The BlockCondition predicate is already implemented to manage the conditional logic as mentioned above and the ContinueTask method will return the arg as policy trigger conditional pipeline blocks should be considered optional and not mandatory.

public class MyPolicyTriggerConditionalPipelineBlock : PolicyTriggerConditionalPipelineBlock<MyArgument, MyArgument, CommercePipelineExecutionContext>
{
    public override string ShouldNotRunPolicyTrigger => "IgnoreMyPipelineBlock";

    public override MyArgument Run(MyArgument arg, CommercePipelineExecutionContext context)
    {
        Condition.Requires(arg, nameof(arg)).IsNotNull();

        // My code logic

        return arg;
    }
}

References