CX Works

A single portal for curated, field-tested and SAP-verified expertise for your SAP Customer Experience solutions. Whether it's a new implementation, adding new features, or getting additional value from an existing deployment, get it here, at CX Works.

Supporting Multiple SAP Commerce Cloud Storefronts

Supporting Multiple SAP Commerce Cloud Storefronts

Many eCommerce solutions require running multiple storefront implementations. Potentially you'll want separate storefronts for different brands, or maybe you want to separate B2C and B2B. We've already covered How to Choose a Storefront for Your SAP Commerce Cloud Solution and we've discussed the impact of multiple storefronts as part of Strategies for Designing your SAP Commerce Cloud Content Catalogs.

In this article we cover the technical considerations for running multiple storefronts. We offer two options: running them with separate webroots under the same DNS name or having them on root using separate DNS names.

Table of Contents

Option #1: Deployment of B2C and B2B Storefronts with different webroots on same DNS name

If you are using the accelerators, you may want to deploy and run your B2C and B2B storefronts in one SAP Commerce Cloud solution. This section describes implementation steps and possible limitations around the proposed solution. It will result in two different webroots that can be used to access your sites via one DNS name:

  • B2C storefront - /yacceleratorstorefront
  • B2B storefront - /yb2bacceleratorstorefront

Though this installation example is based on the out-of-the-box (OOTB) recipe scenario, it can be applied to other custom installations. Typically, when a B2B recipe is executed, it creates a B2B storefront extension named yb2bacceleratorstorefront in /custom folder. This B2B storefront extension is based on the yacceleratorstorefront extension plus b2bacceleratoraddon. If you have named your storefront something else, please replace references to yb2bacceleratorstorefront with the name of your extension.

Please don't forget to initially generate both storefront extensions locally ( recipe or extgen + addoninstall ) and commit them to your GIT repository used for Commerce Cloud. The build process relies on them being present and does not generate them.


The following fragments below demonstrate important sections of the manifest.json necessary to run the CCV2 build and deployment of the B2C and B2B storefronts.

“extensions” section

Make sure that all storefronts as well as addons are added to the extensions section (or are included in your localextensions.xml if you're using Configuration Reuse):

manifest.json
...
	"extensions":[
        "yacceleratorstorefront",
        "yb2bacceleratorstorefront",
 "commerceorgsamplesaddon",
 "promotionenginesamplesaddon",
 "textfieldconfiguratortemplateaddon",
 "assistedservicestorefront",
 "assistedservicepromotionaddon",
 "customerticketingaddon",
 "orderselfserviceaddon",
 "adaptivesearchsamplesaddon",
 "pcmbackofficesamplesaddon",
 "personalizationsearchsamplesaddon",
 "smarteditaddon",
 "b2bacceleratoraddon",
 "commerceorgaddon"
]
.....

“addons” section

Make sure the “storefrontAddons” section contains the B2B-related addons with their corresponding extensions the addons are being installed into. As per a paragraph above, the addons should be installed into both yacceleratorstorefront and yb2bacceleratorstorefront.

manifest.json
...
		{
            "addon": "commerceorgsamplesaddon",
            "storefront": "yb2bacceleratorstorefront",
            "template": "yacceleratorstorefront"
        },
        {
            "addon": "promotionenginesamplesaddon",
            "storefront": "yb2bacceleratorstorefront",
            "template": "yacceleratorstorefront"
        },
        {
            "addon": "textfieldconfiguratortemplateaddon",
            "storefront": "yb2bacceleratorstorefront",
            "template": "yacceleratorstorefront"
        },
        {
            "addon": "assistedservicestorefront",
            "storefront": "yb2bacceleratorstorefront",
            "template": "yacceleratorstorefront"
        },
        {
            "addon": "assistedservicepromotionaddon",
            "storefront": "yb2bacceleratorstorefront",
            "template": "yacceleratorstorefront"
        },
        {
            "addon": "customerticketingaddon",
            "storefront": "yb2bacceleratorstorefront",
            "template": "yacceleratorstorefront"
        },
        {
            "addon": "orderselfserviceaddon",
            "storefront": "yb2bacceleratorstorefront",
            "template": "yacceleratorstorefront"
        },
        {
            "addon": "adaptivesearchsamplesaddon",
            "storefront": "yb2bacceleratorstorefront",
            "template": "yacceleratorstorefront"
        },
        {
            "addon": "pcmbackofficesamplesaddon",
            "storefront": "yb2bacceleratorstorefront",
            "template": "yacceleratorstorefront"
        },
        {
            "addon": "personalizationsearchsamplesaddon",
            "storefront": "yb2bacceleratorstorefront",
            "template": "yacceleratorstorefront"
        },
        {
            "addon": "smarteditaddon",
            "storefront": "yb2bacceleratorstorefront",
            "template": "yacceleratorstorefront"
        },
        {
            "addon":"b2bacceleratoraddon",
            "storefront":"yb2bacceleratorstorefront",
            "template": "yacceleratorstorefront"
        },
        {
            "addon":"commerceorgaddon",
            "storefront":"yb2bacceleratorstorefront",
            "template": "yacceleratorstorefront"
        },
.....

“properties” section

Specify separate context root for both B2B and B2C storefronts as per JSON fragment provided below:

manifest.json
"properties": [
        {
            "key": "yacceleratorstorefront.webroot",
            "value": "/yacceleratorstorefront"
        },
        {
            "key": "yb2bacceleratorstorefront.webroot",
            "value": "/yb2bacceleratorstorefront"
        },
    ],

“accstorefront” aspect section

manifest.json
"properties": [
                {
                    "key": "spring.session.enabled",
                    "value": "true"
                },
                {
                    "key": "spring.session.yacceleratorstorefront.save",
                    "value":"async"
                },
                {
                    "key": "spring.session.yacceleratorstorefront.cookie.name",
                    "value": "JSESSIONID"
                },
                {
                    "key": "spring.session.yacceleratorstorefront.cookie.path",
                    "value": "/yacceleratorstorefront"
                },
                {
                    "key": "spring.session.yb2bacceleratorstorefront.save",
                    "value":"async"
                },
                {
                    "key": "spring.session.yb2bacceleratorstorefront.cookie.name",
                    "value": "JSESSIONID"
                },
                {
                    "key": "spring.session.yb2bacceleratorstorefront.cookie.path",
                    "value": "/yb2bacceleratorstorefront"
                }
            ],

Each storefront should have its own JSESSIONID cookie specified as well as the cookie path.

“webapps” section

Another important setting is a ContextPath for each storefront. The ContextPath goes for each storefront application specified in the “webapps” section. The JSON fragment that demonstrates this setting is provided below:

manifest.json
"webapps": [
                {
                    "name": "hac",
                    "contextPath": "/hac"
                },
                {
                    "name": "mediaweb",
                    "contextPath": "/medias"
                },
                {
                    "name": "yb2bacceleratorstorefront",
                    "contextPath": "/yb2bacceleratorstorefront"
                },
                {
                    "name": "yacceleratorstorefront",
                    "contextPath": "/yacceleratorstorefront"
                },
                {
                    "name": "acceleratorservices",
                    "contextPath": "/acceleratorservices"
                }
            ]

Root context limitations and mitigation approaches

With this setup you're able to run both frontends on the same DNS name (e.g. accstorefront.super-project-p1-public.model-t.myhybris.cloud/yacceleratorstorefront and accstorefront.super-project-p1-public.model-t.myhybris.cloud/yb2bacceleratorstorefront ) as independent web applications. However, if customers just enter the server name without any context path, there will be no response.

From here you'd need to implement some central 'landing' web application on root context which is somehow redirecting the customer to either one or the other storefront.

At this stage you'll start creating independent DNS names and map them to separate endpoints in Commerce Cloud. With these, now the 'landing' web app could inspect the incoming DNS name and redirect to the matching context root ( e.g. b2b.myshop.com → b2b.myshop.com/yacceleratorstorefront and b2c.myshop.com → b2c.myshop.com/yb2bacceleratorstorefront ). 

Option #2: Multiple Storefronts on root context with different DNS names

Often there is a requirement that all storefronts be served up from root context. To achieve this you can take advantage of Tomcat's Virtual Hosting capabilities and configure your SAP Commerce Cloud solution as follows.

  1. Download the attached code which consists of the following:
    1. README.txt - outlines the same steps covered here
    2. 'template' folder - contains templates of your tomcat web contexts and server.xml that will be used to override the standard ones provided with SAP Commerce Cloud
    3. 'firstweb' & 'secondweb' folders - two web extensions which will serve up your two storefronts. If you already have existing web extensions you can use those instead. ( e.g. yacceleratorstorefront and yb2bacceleratorstorefront as described above )

      Note that in order to successfully build these extensions their extensioninfo.xml will require to have a unique webroot parameter. As part of the configuration below these will be ignored by your SAP Commerce Cloud solution.

  2. Add these extensions to your manifest.json file (if you're taking advantage of SAP Configuration Reuse then add them to your localextensions.xml)
  3. Ensure in the general "properties" section of your manifest.json that you are not declaring any "*.webroot" key/value pairs. This is different than the example above where we defined yacceleratorstorefront.webroot and yb2bacceleratorstorefront.webroot. 
  4. Ensure that in the "webapps" section of your manifest.json you are not mapping them as web applications. Also this is different the example in option #1 above!
  5. Override the template/server.xml.tmpl file with one defining all your separate DNS names for the storefronts. In our template file we defined 2 hosts:

    1. b2c.customer-d1-public.model-t.myhybris.cloud

    2. b2b.customer-d1-public.model-t.myhybris.cloud

    These hostnames should reflect the names of your Commerce Cloud endpoints created for each storefront DNS name:

    <Host name="b2c.customer-d1-public.model-t.myhybris.cloud"
          unpackWARs="true"
          autoDeploy="true">
    ...
    </Host>
    <Host name="b2b.customer-d1-public.model-t.myhybris.cloud"
          unpackWARs="true"
          autoDeploy="true">
    ...
    </Host>
    
  6. Next, override the standard server.xml.tmpl with the one you created by encoding it as base64 and including the following in your environment's or aspect's properties file:

    ccv2.file.override.66.path=/opt/startup/server.xml.tmpl
    ccv2.file.override.66.content=<base64 content of your local server.xml.tmpl>

    Please note that the .66. above needs to be a unique number identifying exactly this file you want to inject. If you accidentally use that number for another file only one of them will be injected!

  7. Edit the example template/firstweb-context.xml to reflect your specific storefront extensions. You should have two of such files now:

    <!-- 1) path always root 2) docBase points to frontend extension, *alway* within '${PLATFORM_HOME}/../custom/' on CCv2!  -->
    <Context path="" docBase="${PLATFORM_HOME}/../custom/<your storefront extension, e.g. first web or yacceleratorstorefront>" >
    <!-- leave the rest unchanged unless you know exactly what you're doing -->
      <Manager pathname="" />
      <Loader platformHome="${PLATFORM_HOME}" className="de.hybris.tomcat.HybrisWebappLoader" deployName="default" />
      <JarScanner>
        <JarScanFilter defaultTldScan="true" defaultPluggabilityScan="true" />
      </JarScanner>
    </Context>
  8. Inject these files into the host location for the given Commerce Cloud aspect. Override the standard context paths by including the following in your environment's or aspect's properties file:

    ccv2.file.override.69.path=/opt/aspects/<aspect, e.g. accstorefront>/tomcat/conf/Catalina/<host name, see (5)>/ROOT.xml
    ccv2.file.override.69.content=<base64 content of your *-context.xml file>

    Note that the server-side file name always needs to be ROOT.xml.

    Please note that the .69. above needs to be a unique number identifying exactly this file you want to inject. If you accidentally use that number for another file only one of them will be injected!

  9. Finally also override the webroot property for all your storefront extension to become root within the aspect which you injected the server.xml.tmpl and ROOT.xml files to ( e.g. accstorefront ):

    # for the template example
    firstweb.webroot=
    secondweb.webroot=
    # or for the above example
    yacceleratorstorefront.webroot=
    yb2bacceleratorstorefront.webroot=

Limitations of this approach

Now you'll be able to directly access both storefronts directly using their DNS names without having to worry about the context path.

However, there are also limits to the solution. For Commerce internal reasons it's currently impossible to map a single web app extension ( e.g. yacceleratorstorefront) onto multiple context paths ( e.g. foo.com/ and bar.com/somepath ). However, you can add multiple DNS names to the <host> entry as alias, allowing to expose the frontend under multiple names.

Also there's currently a bug affecting the admin console (HAC) on the same aspect where Tomcat Virtual Hosts are used ( e.g. accstorefront). On all other aspects HAC will work fine!

HAC issue workaround

To work around the issue you will need to put the attached DefaultStatisticsGateway.java and DefaultStatisticsGatewayFactory.java files in an extension and then add the following to that extensions buildcallbacks.xml (replacing YOUREXTENSIONHERE with the name of the extension):

buildcallbacks.xml
    <macrodef name="patchDefaultStatisticsGateway">
        <sequential>
            <if>
                <available file="${ext.core.path}/bin/coreserver.jar" />
                <then>
                    <echo> PATCHING coreserver.jar to include CCv2 statistics gateway fix </echo>
                    <jar update="true" destfile="${ext.core.path}/bin/coreserver.jar">
                        <fileset dir="${ext.YOUREXTENSIONHERE.path}/classes" includes="**/DefaultStatisticsGateway*.class"/>
                    </jar>
                </then>
                <else>
                    <echo> ${ext.core.path}/bin/coreserver.jar doesn't exists - no need to patch CCv2 statistics gateway</echo>
                </else>
            </if>
            <if>
                <available file="${ext.core.path}/classes/de/hybris/platform/masterserver/impl/DefaultStatisticsGateway.class" />
                <then>
                    <echo> PATCHING ${ext.core.path}/classes/de/hybris/platform/masterserver/impl/DefaultStatisticsGateway.class to fix Commerce Cloud multi - web app issues </echo>
                    <copy overwrite="true" todir="${ext.core.path}/classes" verbose="true">
                        <fileset dir="${ext.YOUREXTENSIONHERE.path}/classes" includes="**/DefaultStatisticsGateway*.class"/>
                    </copy>
                </then>
                <else>
                    <echo> ${ext.core.path}/classes/de/hybris/platform/masterserver/impl/DefaultStatisticsGateway.class doesn't exists - no need to patch Commerce Cloud statistics gateway </echo>
                </else>
            </if>
        </sequential>
    </macrodef>

Conclusion

You should now have a clear understanding of how to configure and deploy multiple storefronts. For more information, consider reviewing the following articles: