Handle your json configs in your Sitecore Commerce Solution like Ripley(in Alien)

Hello Sitecore developers, what a wonderful time we are in. As a developer you now have a smorgasbord of lovely sandwiches and delicacies 🙂 You can go all in with Headerless and use Sitecore JSS with NextJs(React). Or go for classic MVC with Asp.Net Core. And best of all, it’s all Docker containers. This means an easy setup on your developer machine, and an easy deploy to production (using Kubernetes). What a wonderful time we are living in 🙂

I recently had the chance to play a wonderful game – Alien: Isolation. If you are an Aliens movie fan, you must play Alien Isolation and of course the addon – Alien: Isolation – Last Survivor. What a game!

The game is so good that I just had to write a blog post about handling json configs in your Sitecore Commerce Solution using PlaceholderFor.

So what is PlaceholderFor? If you look in your json configs in Sitecore.Commerce.Engine, say the config.json or the policy configs in data\Environments.

Example from the config.json

{
  "AppSettings": {
    "DeploymentId": "PlaceholderForDeploymentId",
    "EnvironmentName": "PlaceholderForEnvironmentName"
  }
}

Example of the Plugin.SQL.PolicySet-1.0.0.json

{
  "$type": "Sitecore.Commerce.Core.PolicySet, Sitecore.Commerce.Core",
  "Id": "Entity-PolicySet-SqlPolicySet",
  "Version": 1,
  "IsPersisted": false,
  "Name": "SqlPolicySet",
  "Policies": {
    "$type": "System.Collections.Generic.List`1[[Sitecore.Commerce.Core.Policy, Sitecore.Commerce.Core]], mscorlib",
    "$values": [
      {
        "$type": "Sitecore.Commerce.Plugin.SQL.EntityStoreSqlPolicy, Sitecore.Commerce.Plugin.SQL",
        "TrustedConnection": "PlaceholderForSharedTrustedConnection",
        "UserName": "PlaceholderForSharedDatabaseUserName",
        "Password": "PlaceholderForSharedDatabasePassword",
        "Server": "PlaceholderForSharedDatabaseServer",
        "Database": "PlaceholderForSharedDatabaseName",
        "AdditionalParameters": "",
        "ConnectTimeout": 20,
        "CommandTimeout": 180,
        "CleanEnvironmentCommandTimeout": 120000
      }
    ]
  }
}

Notice the PlaceholderForXXXXX, and it does exactly what it says.

The placeholder will be replaced by the environment variables you have set in your docker compose file/and in your Kubernetes specs(hopefully using secrets). This is a genius and a wonderful way to change configs. Without having to rebuild images every time you do a change…

I took this for granted, but recently, a variable needed to be changed depending on whether it’s in test or in production. In this case, it was a base URL for a rest API and a setting in a custom policy config.

At first I had the values “hardcoded” in config.json and my MyCustom.PolicySet-1.0.0.json.

In config.json

"MySolution": {
    "SomeBaseAddress": "https://abaseurl"
}

And used in code, like this:

public class ConfigureSitecore : IConfigureSitecore
{

	private readonly IConfiguration _configuration;

	public ConfigureSitecore(IConfiguration  configuration)
	{
		_configuration = configuration;
	}

	public void ConfigureServices(IServiceCollection services)
	{
	   
		services.Sitecore().Services.AddHttpClient<IMyService, MyService>(client =>
		{
			client.BaseAddress = new System.Uri(_configuration.GetSection("MySolution:SomeBaseAddress").Value);
			
		});
		
		....
	
		A bunch of pipeline configurations
	
		....
		
	}
}	
	

In MyCustom.PolicySet-1.0.0.json.

{
  "$type": "Sitecore.Commerce.Core.PolicySet, Sitecore.Commerce.Core",
  "Id": "Entity-PolicySet-MyCustomPolicySet",
  "Version": 1,
  "IsPersisted": false,
  "Name": "MyCustomPolicySet",
  "Policies": {
    "$type": "System.Collections.Generic.List`1[[Sitecore.Commerce.Core.Policy, Sitecore.Commerce.Core]], mscorlib",
    "$values": [
      {
        "$type": "MySolution.Commerce.Plugin.MyPlugin.Policies.MyCustomPolicy, MySolution.Commerce.Plugin.MyPlugin",
        "AVariableThatNeedsToBeChangeIfItIsInTestOrInProduction": "ChangeMePlease"
      }
    ]
  }
}

And used in code, like this:

private async Task<Order> DoSomeChanges(Order order)
{

	var myCustomPolicy = CommercePipelineExecutionContext.GetPolicy<MyCustomPolicy>();
	
	order.GetComponent<ACustomComponentThatShowSomething>().Message = myCustomPolicy.AVariableThatNeedsToBeChangeIfItIsInTestOrInProduction;
	
	Some code here...
}

For config.json I could go for the “a json for each environment”, like config.Development.json and so on. But for the MyCustom.PolicySet-1.0.0.json, that was indeed trickier. So what to do?

PlaceHolderFor to the rescue 🙂

First out is the config.json, it will now look like this, notice that the value has changed to PlaceholderFor and the variable name.

"MySolution": {
    "SomeBaseAddress": "PlaceholderForSomeBaseAddress"
}

And MyCustom.PolicySet-1.0.0.json will now look like this, notice that the value has changed to PlaceholderFor and the variable name.

{
  "$type": "Sitecore.Commerce.Core.PolicySet, Sitecore.Commerce.Core",
  "Id": "Entity-PolicySet-MyCustomPolicySet",
  "Version": 1,
  "IsPersisted": false,
  "Name": "MyCustomPolicySet",
  "Policies": {
    "$type": "System.Collections.Generic.List`1[[Sitecore.Commerce.Core.Policy, Sitecore.Commerce.Core]], mscorlib",
    "$values": [
      {
        "$type": "MySolution.Commerce.Plugin.MyPlugin.Policies.MyCustomPolicy, MySolution.Commerce.Plugin.MyPlugin",
        "AVariableThatNeedsToBeChangeIfItIsInTestOrInProduction": "PlaceholderForAVariableThatNeedsToBeChangeIfItIsInTestOrInProduction"
      }
    ]
  }
}

And now to the crème de la crème(where we set the variable values).

The docker-compose.yml for the devs.
(The values should be set in the .env file)

engine-shops:
    isolation: ${ISOLATION}
    image: ${XC_SITECORE_DOCKER_REGISTRY}sitecore-xc-engine:${XC_PACKAGES_TAG}
    depends_on:
      mssql:
        condition: service_healthy
      solr:
        condition: service_started
      id:
        condition: service_started
      redis:
        condition: service_started
      cm:
        condition: service_started
    environment:
      COMMERCEENGINE_MySolution__SomeBaseAddress: "https://baseaddresstest.com"
      COMMERCEENGINE_AVariableThatNeedsToBeChangeIfItIsInTestOrInProduction: "You are now running in test"

And in production, the spec for engine-shops.yaml (in Kubernetes):
(The values should be set as secrets)

apiVersion: v1
kind: Service
metadata:
  name: engine-shops
spec:
  selector:
    app: engine-shops
  ports:
  - protocol: TCP
    port: 5000
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: engine-shops
  labels:
    app: engine-shops
spec:
  replicas: 1
  selector:
    matchLabels:
      app: engine-shops
  template:
    metadata:
      labels:
        app: engine-shops
    spec:
      nodeSelector:
        kubernetes.io/os: windows
      containers:
      - name: sitecore-xc-engine
        image: {registry}/{sxc-project}/sitecore-xc-engine:{commerce-version}
        ports:
        - containerPort: 5000
        env:
        - name: COMMERCEENGINE_MySolution__SomeBaseAddress
          value: https://baseaddressprod.com
        - name: COMMERCEENGINE_AVariableThatNeedsToBeChangeIfItIsInTestOrInProduction
          value: You are now running in prod

This means that the values we set in docker-compose.yml and in engine-shops.yaml, will replace the “PlaceholderForSomeBaseAddress” and “PlaceholderForAVariableThatNeedsToBeChangeIfItIsInTestOrInProduction” in the configs.

How cool is that!

I really like this approach because you change the variable values(in the configs) when you build the containers/pods. Meaning you don’t have to build new images every time there is a change.

That’s all for now folks 🙂


One thought on “Handle your json configs in your Sitecore Commerce Solution like Ripley(in Alien)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.