Skip to main content

Data Caching and Merging Array-Based Data

This recipe demonstrates preparing and supplying product data for something like a highly detailed product comparison page. We are accessing data from multiple sources, and using Conscia to unify arrays of product data in real-time to deliver a richer and more performant web experience.

In this example, we have a hypothetical client who is adopting best-in-breed MACH technologies with a phased approach. They want to maximize the benefit of their new services, but are still being reliant on legacy systems with inadequate or obsolete connectivity. We will be stitching together three data sources:

  • Legacy product metadata from their antiquated PIM, which is uploaded weekly, accessed and cached weekly via DX Engine;
  • Product records from commercetools via REST, cached through Conscia every 5 minutes to improve performance; and
  • Product inventory and pricing data from commercetools, queried via graphQL in real-time.

This allows us to minimize the amount of round-trip traffic to deliver comprehensive product data to the frontend. The same technique can apply to data from a broader selection of sources; the pattern would be the same, with adjustments to ensure the caching is appropriate.

Note: the "PIM data" we're retrieving (AI-generated meta characteristics) could easily be stored in commercetools; it's intended to be illustrative of contributing supplemental data that might be challenging to store in commercetools, for instance traceability details about sustainable fabrics used across multiple products.

Mapping Out DX Engine Elements

Caching and Merging Visualizer

When the frontend calls Conscia's Experience API, it will pass the following Context:

  • The currency value for the currency the customer is shopping in, to serve relevant prices.
  • The locale value to deliver the most relevant localized text content.
  • The categoryid value to select the relevant subset of products.

An example call looks like this:

POST {{engineUrl}}/experience/components/_query
X-Customer-Code: {{customerCode}}
Authorization: Bearer {{dxEngineToken}}

{
"componentCodes": ["product-data-transformation-script"],
"context": {
"currency": "USD",
"locale": "en-US",
"categoryid": "476f69be-e09e-4952-8ae7-61ba6af059ba"
}
}

Based on the context provided in this Experience API call, we will retrieve relevant products from each endpoint and provide the contextually-relevant characteristics to the web experience:

  "response": [
{
"attributes": [
{
"name": "brand_name",
"value": "Winsome Wood Trading"
}
],
"availability": {
"isOnStock": true,
"availableQuantity": 12345,
"version": 1,
"id": "f1fb9f8e-db80-4b50-b20e-71a84828df54"
},
"assets": [],
"images": [
{
"url": "https://item.tscimg.ca/TSC/6/62/623/0x0/623064.jpg?impolicy=M",
"dimensions": {
"w": 200,
"h": 200
}
}
],
"prices": 239.98,
"key": "3326176",
"sku": "1151",
"id": "4f031234-3321-4e37-b67f-127631158ba5",
"name": "Liso Writing Desk with Drawer",
"categories": [
{
"typeId": "category",
"id": "3eb949a9-9a30-42cb-9597-04e1f89aa1a1"
}
],
"slug": "liso-writing-desk-with-drawer-623064",
"inventory": {
"availableQuantity": 12345
},
"PIM Metafields": {
"Dimensions": "48 x 24 x 30 inches",
"Material": "Wood",
"Requires Assembly": "Yes",
"Weight": "40 lbs"
}
},
{...}
]

DX Graph Configuration Details

The topics in this section explain how to create a PIM metafields data model in DX Graph, and populate it with sample data. A variety of alternative endpoints, including git gists, commercetools, or a SaaS PIM, could serve this role if DX Graph is not available.

Data Collection

Data Model for PIM data

  • Navigate to the Data Model page (Settings --> Data Model).
  • Click the + (Create Data Collection) button.
  • Enter the followign and click Submit.
FieldValue
NamePIM Data
Data Collection Codepimdata
Unique ID for Recordid

Schema for PIM Data

  • Right-click the new "PIM Data" Data Model, and select Edit --> Edit Schema. Introduce the following schema:
Field NameField TypeDisplay NameSettings
idTEXT fieldcommercetools Product IDRequired
Not Read-Only
Unique
category-idTEXT fieldcommercetools Category IDRequired
Not Read-Only
Not Unique
name/enTEXT fieldNot Required
Not Read-Only
Not Unique
name/en-USTEXT fieldNot Required
Not Read-Only
Not Unique
pim_metafields/MaterialTEXT fieldNot Required
Not Read-Only
Not Unique
pim_metafields/WeightTEXT fieldNot Required
Not Read-Only
Not Unique
pim_metafields/DimensionsTEXT fieldNot Required
Not Read-Only
Not Unique
pim_metafields/Requires AssemblyTEXT fieldNot Required
Not Read-Only
Not Unique

Source Data

Sample Data

A .csv file, linked above, was used for this recipe. It was produced by performing a GET against the relevant Collection in commercetools, removing all fields but id, category, and localized names, and feeding the JSON into an LLM to generate various metafield values. The JSON was then converted into a CSV file.

Data Upload

To set up the ingestion of the csv file, the following tasks were performed:

  1. In a Postman client using the Conscia Postman Collection, run Bucket --> "Upload files to a Bucket" with the following qualities:
    1. dataBucketCode: incoming
    2. Body type: form-data
    3. Key file[], of type File, with value pim.csv
  2. In Postman, run Bucket --> "Analyze a file in a Bucket" with the following qualities:
    1. dataBucketCode: incoming
    2. Body type: JSON
    3. The following body text:
{
"filename": "pim.csv",
"sourceSchema": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"category-id": {
"type": "string"
},
"name/en": {
"type": "string"
},
"name/en-US": {
"type": "string"
},
"pim_metafields/Material": {
"type": "string"
},
"pim_metafields/Weight": {
"type": "string"
},
"pim_metafields/Dimensions": {
"type": "string"
},
"pim_metafields/Requires Assembly": {
"type": "string"
}
},
"required": [
"id", "category-id"
]
},
"recordIdentifierField": "id",
"parseOptions": {
"format": "DELIMITED",
"delimiter": ",",
"quoteChar": "\"",
"escapeChar": "\""
},
"collectionCode": "pimdata",
"transformers": []
}

We should see nbrFinalRecords equal 35 with nbrIssues at 0.

  1. In Postman, run Bucket--> "Import files in a Bucket into a Collection" with the following qualities:
    1. dataBucketCode: incoming
    2. Body type: JSON
    3. The following body text:
{
"skippedBucketCode": "skipped",
"processedBucketCode": "processed",
"invalidBucketCode": "invalid",
"filenamePattern": "pim.csv",
"skipInvalidRecords": false,
"sourceSchema": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"category-id": {
"type": "string"
},
"name/en": {
"type": "string"
},
"name/en-US": {
"type": "string"
},
"pim_metafields/Material": {
"type": "string"
},
"pim_metafields/Weight": {
"type": "string"
},
"pim_metafields/Dimensions": {
"type": "string"
},
"pim_metafields/Requires Assembly": {
"type": "string"
}
},
"required": [
"id", "category-id"
]
},
"recordIdentifierField": "id",
"parseOptions": {
"format": "DELIMITED",
"delimiter": ",",
"quoteChar": "\"",
"escapeChar": "\""
},
"collectionCode": "pimdata",
"transformers": []
}

We should see nbrValidRecords equal 35 with nbrValidationIssues at 0.

  1. Add the new Collection to the navigation menu:

    1. Navigate to the Left Navigation page (Settings --> Left Navigation).
    2. Select Left Nav - Sources.
    3. Select Create Record (button with three boxes and an asterisk, top-right)
    4. Set the following values:
    FieldValue
    Orderany unique integer
    Label"Legacy PIM"
    ActiveChecked
    Content CollectionAdd one item
    Content Collection LabelPIM
    Data RepositoryMaster Content
    Content Data CollectionPIM Data
  2. Visit the PIM Sources page (Collections --> Sources --> Legacy PIM --> PIM) and validate the row count is correct (35) and the csv contents are successfully imported.

We will execute the remainder of the recipe in DX Engine.

DX Engine Configuration Details

The topics in this section explain how to implement the elements involved in this recipe.

Context Fields

We need to create Context Fields to enable our Experience Rules to access Context values. For the purpose of this recipe, we'll be manually entering a few expected values into the Context Field configuration. For a production implementation, you would want to configure a Component to supply the expected values.

Connections

commercetools Connection

To create a Connection used to store the commercetools connection credentials, in the DX Engine UI:

  1. In the top navigation, click Settings, and then click Connections. The Manage Connections page appears.
  2. Click Add Connection. The Create Connection wizard appears.
  3. For Connection Code, enter an identifier for the Conection: conn-conscia-commercetools.
  4. For Connection Name, enter a friendly name for the Connection: Commercetools Connection.
  5. Optionally, enter a Description for the Connection.
  6. For Connector, select Commercetools.
  7. Ener the Project key, Hosting region, Client Id and Client Secret from commercetools.
  8. Click Submit.

DX Graph Connection

To create a Connection used to access DX Graph, in the DX Engine UI:

  1. In the top navigation, click Settings, and then click Connections. The Manage Connections page appears.
  2. Click Add Connection. The Create Connection wizard appears.
  3. For Connection Code, enter an identifier for the Conection: dx-graph.
  4. For Connection Name, enter a friendly name for the Connection: DX Graph.
  5. Optionally, enter a Description for the Connection.
  6. For Connector, select DX Graph.
  7. Ener the Conscia Endpoint, API Key, and Customer Code for your DX Graph instance.
  8. Click Submit.

Product Data Retrieval Components

Component to retrieve PIM data from DX Graph

  • Navigate to the Experience Components page (Manage Experiences --> Components).
  • Click the + Add Component button.
  • Enter the following and click Submit.
  • Note: The example category we are using has 35 products, hence the record limit is set at 35.
FieldValue
Component Codelegacy-pim-endpoint
Component NameLegacy PIM Metadata (DX Engine)
No RulesChecked
Component TypeDx Graph - Dynamic Record List
ConnectionGet value from: Literal
DX Graph
Collection ID"Metadata coming from our legacy PIM system"
Maximum number of records to return35
Query Filter

Get value from: JS Expression
JSON.stringify({<br/>&emsp; $eq: {<br/>&emsp;&emsp;field: 'category-id',<br/>&emsp;&emsp;value: contextField('categoryid')<br/>&emsp;}<br/>})
Response Transform_.map(response, element =><br/>&emsp;_.assign(<br/>&emsp;&emsp;{<br/>&emsp;&emsp;&emsp;"id": element["id"],<br/>&emsp;&emsp;&emsp;"PIM Metafields": {<br/>&emsp;&emsp;&emsp;&emsp;"Dimensions": element["pim_metafields/Dimensions"],<br/>&emsp;&emsp;&emsp;&emsp;"Material": element["pim_metafields/Material"],<br/>&emsp;&emsp;&emsp;&emsp;"Requires Assembly": element["pim_metafields/Requires Assembly"],<br/>&emsp;&emsp;&emsp;&emsp;"Weight": element["pim_metafields/Weight"]<br/>&emsp;&emsp;&emsp;}<br/>&emsp;&emsp;}<br/>&emsp;)<br/>);

Component to retrieve core product data from commercetools

  • Navigate to the Experience Components page (Manage Experiences --> Components).
  • Click the + Add Component button.
  • Enter the following and click Submit.
  • Note: The example category we are using has 35 products, hence the record limit is set at 35.
FieldForm TabValue
Component CodeMainct-products-cached
Component NameMainCached Products (commercetools)
No RulesMainChecked
Component TypeMainCommercetools - Dynamic Product List
Connection

Get value from: Literal
MainCommercetools Connection
IETF language codeMainen-US
Limit

Get value from: Literal
Main35
Filte

Get value from: JS Expression
Main'categories.id:subtree(\"' + contextField('categoryid') + '\")'
Response TransformMain_.map(response, element =><br/>&emsp;_.assign(<br/>&emsp;&emsp;element.masterVariant, {<br/>&emsp;&emsp;&emsp;"id": element.id,<br/>&emsp;&emsp;&emsp;"name": element.name[contextField('locale')],<br/>&emsp;&emsp;&emsp;"categories": element.categories,<br/>&emsp;&emsp;&emsp;"slug": element.slug[contextField('locale')]<br/>&emsp;&emsp;}<br/>&emsp;)<br/>);
CachedCachingChecked
Cache Time-to-liveCaching300

Component to retrieve real-time product specifics from commercetools

  • Navigate to the Experience Components page (Manage Experiences --> Components).
  • Click the + Add Component button.
  • Enter the following and click Submit.
FieldForm TabValue
Component CodeMaincommercetools-graphql-inventory
Component NameMainInventory & Prices (ct GraphQl)
No RulesMainChecked
Component TypeMainConscia - Universal API Connector
Webservice Path

Get value from: Literal
Mainhttps://api.us-central1.gcp.commercetools.com/omni-channel-experience/graphql
MethodMainPOST
HeadersMainHeader: content-type, Get value from: Literal, Value: application/json
Header: Authorization, Get value from: Literal, Value: Bearer REDACTED
URL encode the keys and valuesMainChecked
Body

Get value from: JS Expression
MainJSON.stringify({<br/>&emsp;query: "query getRealTime($filter: String, $currency: Currency!, $locale: Locale) {\n productProjectionSearch( \n queryFilters: [{ string: $filter }],\n limit: 35\n ) {\n results {\n id\n name(locale: $locale)\n \n masterVariant {\n availability{\n noChannel {\n&emsp;availableQuantity\n }\n }\n price(currency:$currency) {\n value {\n&emsp;centAmount\n }\n } \n \n }\n }\n }\n}",<br/>&emsp;variables: {<br/>&emsp;&emsp;"filter": 'categories.id:subtree(\"' + contextField('categoryid') + '\")' ,<br/>&emsp;&emsp;"currency": contextField('currency') ,<br/>&emsp;&emsp;"locale": contextField('locale')<br/>}<br/>})
Response TransformMain_.map(response.data.productProjectionSearch.results, element =><br/>&emsp;_.assign(<br/>&emsp;&emsp;{<br/>&emsp;&emsp;&emsp;"id": element.id,<br/>&emsp;&emsp;&emsp;"inventory": element.masterVariant.availability.noChannel,<br/>&emsp;&emsp;&emsp;"prices": element.masterVariant.price.value.centAmount / 100<br/>&emsp;&emsp;}<br/>&emsp;)<br/>);

Mapping and Transforming Components

Because each of our sources are providing an array of 35 partial product records in indeterminate order, we must iterate across each array and map them together by a shared field (id). Conscia provides the capability to execute this complex logic quickly and efficiently in two Components: A Mapper, and a Transformation Script running against the mapped object.

Mapper Component to represent all product data on one object

  • Navigate to the Experience Components page (Manage Experiences --> Components).
  • Click the + Add Component button.
  • Enter the following and click Submit.
  • Note: the ordering is deliberate here; we wish to override the cached pricing, which may be outdated, with up-to-the-second and currency-specific pricing from the graphQL query. This ordering ensures the freshest data overwrites the stalest.
FieldForm TabValue
Component CodeMainproduct-data-property-mapper
Component NameMainProduct Data Property Mapper
Component TypeMainConscia - Property Mapper
No RulesMainChecked
Default Expression TypeMainjavascript
  • Create three Property Maps as follows and click Submit.

PIM Metadata Map:

FieldValue
Source DataGet value from: Component Response Legacy PIM Metadata (DX Engine)
Expression TypeJavascript
MappingsTarget property: pim
Source expression: data

commercetools Map:

FieldValue
Source DataGet value from: Component Response Cached Products (commercetools)
Expression TypeJavascript
MappingsTarget property: cached
Source expression: data

GraphQL Map:

FieldValue
Source DataGet value from: Component Response Inventory & Prices (ct GraphQL)
Expression TypeJavascript
MappingsTarget property: graphQL
Source expression: data

Transformation Script Component to consolidate product data arrays

  • Navigate to the Experience Components page (Manage Experiences --> Components).
  • Click the + Add Component button.
  • Enter the following and click Submit.
FieldForm TabValue
Component CodeMainproduct-data-transformation-script
Component NameMainProduct Data Transformation Script
No RulesMainChecked
Component TypeMainConscia - Data Transformation Script
Data to modifyMainGet value from: Component Response
Product Data Property Mapper
ScriptMainconst map = new Map();<br/><br/>// Function to add an array to the map.<br/>function addToMap(arr) {<br/>&emsp;&emsp;for (const item of arr) {<br/>&emsp;&emsp;&emsp;&emsp;if (!map.has(item.id)) {<br/>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;map.set(item.id, {});<br/>&emsp;&emsp;&emsp;&emsp;}<br/>&emsp;&emsp;&emsp;&emsp;Object.assign(map.get(item.id), item);<br/>&emsp;&emsp;}<br/>}<br/><br/>// Add all arrays to the map, stalest to freshest.<br/>addToMap(data["pim"]);<br/>addToMap(data["cached"]);<br/>addToMap(data["graphQL"]);<br/><br/>// Convert map values to array. Assigning to a variable returns that variable.<br/>v = Array.from(map.values());

References