Best practice PLP / PDP: Sales price list items, products, assortments and product tags

A customer wishes to develop an attractive webshop page that displays products and allows for filtering options.

Given this scenario, what is the best way to configure the Product Detail Page (PDP) and Product Listing Page (PLP) to enhance user experience and efficiency?"

1 Like

PLP: Product lister page - context

PLP’s show all products in a category and can be found on any website.
It’s a selection of products that are in this category. Typically, a product can exist in multiple categories (tags).

In multi-brand, multi-language, multi-country website, we often see that sites use the same categories, but that the products that are shown can differ because of local availability or pricing.

Also, PLP’s are often filtered/faceted based on product properties.

This all together means that much querying and filtering on the database must be done.

In ‘meta’ query:
“Display all products, that are linked to category A, that are available for website B, and which have product property C and D, and a price below € 100”

To make this performing in high-load, high-volume environments with the end points, it’s needed to non-normalise the data to avoid sub-queries.

Best practice: end point set-up

We recommend the following set-up for end points for PDP (Product Detail Page) and PLP (Product Lister Page). This is based on the record types:

  1. Product Tags (for the categories)
  2. Sales Channel Products: records that exist for every combination of products that can be sold for a specific sales channel.

Below in this post, the Sales Channel Product concept is explained in more detail.

This leads to an and point set-up with the following two end points for the PLP:

/rest/hcms/producttags/{producttag}?wt=1 for the information about the Product tag (name, facets/filters), for a specific Website Translation
/rest/hcms/producttags/{producttag}/products?wt=1&page=1 for the products on the PLP. This end point typically supports pagination

For the PDP’s we use the SCP
/rest/hcms/saleschannelproducts/{scp}?wt=1 for the information about the product

Best practice: record types and components

In order to make fast response times, we introduced two concepts and record types which are non-functional (users don’t see them and they are created automatically) but they need to be there for the end points.

  1. SalesChannelProducts
  2. SalesChannelProductProductTags

These are available through Novulo Components M9092 and M9329.

The data structure follows the structure of Products, OMS and Website

Initial configuration

  1. It starts with a Sales price list. A price list defines the currency and validity of the price list and can have multiple products on it
  2. Then, you need a Sales Channel. Sales Channels are defined as any channel that can be used for sales. It’s used to identify websites, but also physical stores, events or market places
  3. You also need a Product Assortment. An assortment is made to group products together. It’s typically used to define which products can be sold in a certain country, region or market place. Product Assortments are in M6383.
  4. You link to Assortment and Price List to the Sales Channel
  5. And you link to Sales Channel to the Website

The result is that when you know the website, you also know the Sales Channel, and thereby you know the Assortment and the Price List.

In order for a product to be shown on the website, it must be both in the Assortment and on the Sales Price List.

For each product

  1. Create a product
  2. Add a Sales Price List Item for this Product. Result: the product is on a price list. The Price list item stores the sales price, in the currency of the price list. It also has an active toggle;
  3. Add the product to an assortment. Result: the product is linked to the assortment

Now, you have enough information to know if the product is on the assortment.

On the site, you sell the Sales Price List Item, as this contains the price. Now, you could retrieve products like this

{
 salespricelistitems,  
 this,
 boolean:and([
  sales_price_list.equals(%sales_channel.sales_price_list),
  is_active.equals(yesno:load(1)),
  product.in({
   productassortmentproducts,
   product,
   product_assortment.equals(%sales_channel.product_assortment)
  })
 ])
}

Introducing Sales Channel Products

It works, but the subquery gives a performance penalty. That’s the reason the SalesChannelProducts exist. They are created automatically using M9092, after adding the process to Ensure Sales Channel Products to the scheduler.

To add it to the Scheduler, you need to add ensure_sales_channel_productscontrollercomponent_ and schedule it frequently. Typical settings are:

  1. Batch size: 500
  2. Run every 3 minutes
  3. Remove orphans = true

Then you can do

{
 saleschannelproducts,
 sales_price_list_item,
 sales_channel.equals(%sales_channel)
}

Avoiding the sub query and making an expression that can be ran on a simple index in SQL-server.

Introducing the producttags
A similar set-up exists when going to the Product Tags for the PLP.

Set-up

  1. Create a Product Tag
  2. Go to the product and link it to the tag. Result: ProductTagProduct

Now, if you want to show the products for a certain tag, you would do:

{
 saleschannelproducts,
 sales_price_list_item,
 boolean:and([
  sales_channel.equals(%sales_channel)
  product.in({
   producttagproducts, 
   product, 
   product_tag.equals(%product_tag)
 ])
}

Giving you, again, a sub query. Now one sub query is not too bad, but when you want to filter on Properties, you get more sub queries and you get inefficient queries.

This is why M9329 introuces SalesChannelProductProductTags, applying the same non-normalization again.

It creates SalesChannelProductProductTags. So for each combination of SalesChannelProducts and ProductTags, it creates a record any time a product is linked to (or unlinked from) a tag. Given the fact that you typically have way more requests on PLP’s, than you have new products, it makes sense in the balance of server load,

And this means you can do (typically, you include publish_product)

{
 saleschannelproductsproducttags,  
 sales_price_list_item,
 boolean:and([
  sales_channel.equals(%sales_channel), 
  product_tag.equals(%product_tag),
  publish_product().equals(yesno:load(1))
 ])
}

Making again that you only need one table to search, on two columns that can be in an index.

This makes for such efficient queries, that you have enough ‘space’ in your SQL server to use a sub query for faceting.

By this pre-filtering on SalesChannelProductsProductTags, you reduce the amount of products from millions to dozens, so you can have fast end points in faceting.

Interaction with Stock Classification

Sales Channel Products (SCP), Sales Channel Products - Product Tags (SCPPT) and Stock Classifications are closely related.

The Stock Classification determines if a product (in a webshop environment) can be published and/or sold.

The result (Can be published, can be ordered) is then registered at the SCP and SCPPT-level, to allow for fast indexing and searching.

Stock Classifications are tied to the Product Assortments and are calculated in a background process. To get this running, please add this process to your scheduled tasks.

productassortmentproducts_update_stock_classificcontrollercomponent

image

Typical settings are:

  1. Run every 15 minutes (during webshop opening hours, so typically 24/7, as products can run out of stock during the night).
  2. Batch size: 5000
  3. Log verbose: true when you start it, false when your process runs as desired. When set to false, only errors and warnings are logged.
  4. Batch size classification bulk: 500
1 Like